diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index fea1a3672..d8781bb0c 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -1,11 +1,12 @@
name: 🐞 Bug report
description: Thanks for taking the time to fill out this bug report! Please fill the form in **English**
+labels: ["bug"]
body:
- type: textarea
id: desc
attributes:
label: Bug Description
- description: A clear and concise description of what the bug is
+ description: A clear and concise description of what the bug is (if it's a keyboard issue, provide the keyboard mode you're using. e.g. legacy, map, translate)
validations:
required: true
- type: textarea
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
index 29b0d0e0f..ae2d0aa4c 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yaml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -1,5 +1,6 @@
name: 🛠️ Feature request
description: Suggest an idea for RustDesk
+labels: ["enhancement"]
body:
- type: textarea
id: desc
diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml
index 74e4efa99..a4ddb15b5 100644
--- a/.github/workflows/flutter-ci.yml
+++ b/.github/workflows/flutter-ci.yml
@@ -18,11 +18,12 @@ on:
env:
LLVM_VERSION: "15.0.6"
- FLUTTER_VERSION: "3.7.0"
+ FLUTTER_VERSION: "3.7.5"
# vcpkg version: 2022.05.10
# for multiarch gcc compatibility
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
VERSION: "1.2.0"
+ NDK_VERSION: "r23"
jobs:
build-for-windows:
@@ -62,7 +63,7 @@ jobs:
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
- toolchain: "1.62"
+ toolchain: stable
target: ${{ matrix.job.target }}
override: true
components: rustfmt
@@ -260,7 +261,7 @@ jobs:
job:
- {
target: x86_64-unknown-linux-gnu,
- os: ubuntu-18.04,
+ os: ubuntu-20.04,
extra-build-args: "",
}
steps:
@@ -319,7 +320,7 @@ jobs:
./flutter/lib/generated_bridge.dart
./flutter/lib/generated_bridge.freezed.dart
- build-rustdesk-android-arm64:
+ build-rustdesk-android:
needs: [generate-bridge-linux]
name: build rustdesk android apk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
runs-on: ${{ matrix.job.os }}
@@ -330,15 +331,17 @@ jobs:
- {
arch: x86_64,
target: aarch64-linux-android,
+ os: ubuntu-20.04,
+ extra-build-features: "",
+ openssl-arch: android-arm64
+ }
+ - {
+ arch: x86_64,
+ target: armv7-linux-androideabi,
os: ubuntu-18.04,
extra-build-features: "",
+ openssl-arch: android-arm
}
- # - {
- # arch: x86_64,
- # target: armv7-linux-androideabi,
- # os: ubuntu-18.04,
- # extra-build-features: "",
- # }
steps:
- name: Install dependencies
run: |
@@ -354,15 +357,14 @@ jobs:
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
- ndk-version: r22b
+ ndk-version: ${{ env.NDK_VERSION }}
add-to-path: true
- - name: Download deps
+ - name: Clone deps
shell: bash
run: |
pushd /opt
- wget https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/dep.tar.gz
- tar xzf dep.tar.gz
+ git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
- name: Restore bridge files
uses: actions/download-artifact@master
@@ -390,7 +392,7 @@ jobs:
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
- VCPKG_ROOT: /opt/vcpkg
+ VCPKG_ROOT: /opt/rustdesk_thirdparty_lib/vcpkg
run: |
rustup target add ${{ matrix.job.target }}
cargo install cargo-ndk
@@ -413,16 +415,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
run: |
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
- # download so
- pushd flutter
- wget -O so.tar.gz https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/so.tar.gz
- tar xzvf so.tar.gz
- popd
# temporary use debug sign config
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
case ${{ matrix.job.target }} in
aarch64-linux-android)
mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a
+ cp /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/arm64-v8a/*.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
# build flutter
pushd flutter
@@ -431,6 +429,7 @@ jobs:
;;
armv7-linux-androideabi)
mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a
+ cp /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/armeabi-v7a/*.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so
# build flutter
pushd flutter
@@ -907,19 +906,19 @@ jobs:
- {
arch: x86_64,
target: x86_64-unknown-linux-gnu,
- os: ubuntu-18.04,
+ os: ubuntu-20.04,
extra-build-features: "",
}
- {
arch: x86_64,
target: x86_64-unknown-linux-gnu,
- os: ubuntu-18.04,
+ os: ubuntu-20.04,
extra-build-features: "flatpak",
}
- {
arch: x86_64,
target: x86_64-unknown-linux-gnu,
- os: ubuntu-18.04,
+ os: ubuntu-20.04,
extra-build-features: "appimage",
}
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
diff --git a/.github/workflows/flutter-nightly.yml b/.github/workflows/flutter-nightly.yml
index b08193971..e2093afc1 100644
--- a/.github/workflows/flutter-nightly.yml
+++ b/.github/workflows/flutter-nightly.yml
@@ -14,6 +14,7 @@ env:
# for multiarch gcc compatibility
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
VERSION: "1.2.0"
+ NDK_VERSION: "r23"
#signing keys env variable checks
ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}'
MACOS_P12_BASE64: '${{ secrets.MACOS_P12_BASE64 }}'
@@ -86,7 +87,7 @@ jobs:
shell: bash
- name: Build rustdesk
- run: python3 .\build.py --portable --hwcodec --flutter
+ run: python3 .\build.py --portable --hwcodec --flutter --feature IddDriver
- name: Sign rustdesk files
uses: GermanBluefox/code-sign-action@v7
@@ -419,7 +420,7 @@ jobs:
./flutter/lib/generated_bridge.dart
./flutter/lib/generated_bridge.freezed.dart
- build-rustdesk-android-arm64:
+ build-rustdesk-android:
needs: [generate-bridge-linux]
name: build rustdesk android apk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
runs-on: ${{ matrix.job.os }}
@@ -430,15 +431,17 @@ jobs:
- {
arch: x86_64,
target: aarch64-linux-android,
+ os: ubuntu-20.04,
+ extra-build-features: "",
+ openssl-arch: android-arm64
+ }
+ - {
+ arch: x86_64,
+ target: armv7-linux-androideabi,
os: ubuntu-18.04,
extra-build-features: "",
+ openssl-arch: android-arm
}
- # - {
- # arch: x86_64,
- # target: armv7-linux-androideabi,
- # os: ubuntu-18.04,
- # extra-build-features: "",
- # }
steps:
- name: Install dependencies
run: |
@@ -454,15 +457,14 @@ jobs:
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
- ndk-version: r22b
+ ndk-version: ${{ env.NDK_VERSION }}
add-to-path: true
- - name: Download deps
+ - name: Clone deps
shell: bash
run: |
pushd /opt
- wget https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/dep.tar.gz
- tar xzf dep.tar.gz
+ git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
- name: Restore bridge files
uses: actions/download-artifact@master
@@ -490,7 +492,7 @@ jobs:
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
- VCPKG_ROOT: /opt/vcpkg
+ VCPKG_ROOT: /opt/rustdesk_thirdparty_lib/vcpkg
run: |
rustup target add ${{ matrix.job.target }}
cargo install cargo-ndk
@@ -513,16 +515,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
run: |
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
- # download so
- pushd flutter
- wget -O so.tar.gz https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/so.tar.gz
- tar xzvf so.tar.gz
- popd
# temporary use debug sign config
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
case ${{ matrix.job.target }} in
aarch64-linux-android)
mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a
+ cp /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/arm64-v8a/*.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
# build flutter
pushd flutter
@@ -531,6 +529,7 @@ jobs:
;;
armv7-linux-androideabi)
mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a
+ cp /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/armeabi-v7a/*.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so
# build flutter
pushd flutter
@@ -732,7 +731,7 @@ jobs:
x86_64)
# no need mock on x86_64
export VCPKG_ROOT=/opt/artifacts/vcpkg
- cargo build --lib --features hwcodec,flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
+ cargo build --lib --features hwcodec,flutter,${{ matrix.job.extra-build-features }} --release
;;
esac
@@ -900,7 +899,7 @@ jobs:
ln -s /usr/include /vcpkg/installed/arm64-linux/include
export VCPKG_ROOT=/vcpkg
# disable hwcodec for compilation
- cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
+ cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release
;;
armv7)
cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/arm-linux-gnueabihf/
@@ -910,7 +909,7 @@ jobs:
ln -s /usr/include /vcpkg/installed/arm-linux/include
export VCPKG_ROOT=/vcpkg
# disable hwcodec for compilation
- cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
+ cargo build --lib --features flutter,${{ matrix.job.extra-build-features }} --release
;;
esac
@@ -1511,7 +1510,7 @@ jobs:
pushd flatpak
git clone https://github.com/flathub/shared-modules.git --depth=1
flatpak-builder --user --force-clean --repo=repo ./build ./rustdesk.json
- flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}.flatpak org.rustdesk.rustdesk
+ flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}.flatpak com.rustdesk.RustDesk
- name: Publish flatpak package
uses: softprops/action-gh-release@v1
diff --git a/Cargo.lock b/Cargo.lock
index a2cdf91a4..b80b4d924 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "addr2line"
-version = "0.17.0"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
@@ -17,12 +17,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-[[package]]
-name = "adler32"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
-
[[package]]
name = "ahash"
version = "0.7.6"
@@ -111,12 +105,12 @@ dependencies = [
[[package]]
name = "android_logger"
-version = "0.11.1"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e9dd62f37dea550caf48c77591dc50bd1a378ce08855be1a0c42a97b7550fb"
+checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a"
dependencies = [
"android_log-sys",
- "env_logger 0.9.3",
+ "env_logger 0.10.0",
"log",
"once_cell",
]
@@ -141,23 +135,24 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.66"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
+checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "arboard"
-version = "2.1.1"
+version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc120354d1b5ec6d7aaf4876b602def75595937b5e15d356eb554ab5177e08bb"
+checksum = "d6041616acea41d67c4a984709ddab1587fd0b10efe5cc563fee954d2f011854"
dependencies = [
"clipboard-win",
"core-graphics 0.22.3",
- "image 0.23.14",
+ "image",
"log",
"objc",
"objc-foundation",
"objc_id",
+ "once_cell",
"parking_lot 0.12.1",
"thiserror",
"winapi 0.3.9",
@@ -166,13 +161,12 @@ dependencies = [
[[package]]
name = "async-broadcast"
-version = "0.4.1"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
dependencies = [
"event-listener",
"futures-core",
- "parking_lot 0.12.1",
]
[[package]]
@@ -200,6 +194,18 @@ dependencies = [
"slab",
]
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock",
+ "autocfg 1.1.0",
+ "blocking",
+ "futures-lite",
+]
+
[[package]]
name = "async-io"
version = "1.12.0"
@@ -215,19 +221,18 @@ dependencies = [
"parking",
"polling",
"slab",
- "socket2 0.4.7",
+ "socket2 0.4.9",
"waker-fn",
"windows-sys 0.42.0",
]
[[package]]
name = "async-lock"
-version = "2.6.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
+checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
"event-listener",
- "futures-lite",
]
[[package]]
@@ -250,13 +255,13 @@ dependencies = [
[[package]]
name = "async-recursion"
-version = "1.0.0"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea"
+checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -267,13 +272,13 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "async-trait"
-version = "0.1.59"
+version = "0.1.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
+checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -284,7 +289,7 @@ checksum = "39991bc421ddf72f70159011b323ff49b0f783cc676a7287c59453da2e2531cf"
dependencies = [
"atk-sys",
"bitflags",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
]
@@ -311,9 +316,9 @@ dependencies = [
[[package]]
name = "atomic-waker"
-version = "1.0.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
+checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
[[package]]
name = "atty"
@@ -321,7 +326,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
"libc",
"winapi 0.3.9",
]
@@ -343,24 +348,30 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
-version = "0.3.66"
+version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
- "miniz_oxide 0.5.4",
+ "miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
-name = "base64"
-version = "0.13.1"
+name = "base-x"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bindgen"
@@ -377,12 +388,12 @@ dependencies = [
"lazycell",
"log",
"peeking_take_while",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"regex",
"rustc-hash",
"shlex",
- "which 4.3.0",
+ "which",
]
[[package]]
@@ -397,19 +408,41 @@ dependencies = [
"lazy_static",
"lazycell",
"peeking_take_while",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"regex",
"rustc-hash",
"shlex",
- "syn 1.0.105",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.64.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 1.0.109",
+ "which",
]
[[package]]
name = "bit_field"
-version = "0.10.1"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
+checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
@@ -437,9 +470,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
-version = "0.10.3"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
@@ -471,9 +504,9 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "2.3.2"
+version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -487,15 +520,15 @@ checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b"
[[package]]
name = "bumpalo"
-version = "3.11.1"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytemuck"
-version = "1.12.3"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f"
+checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
@@ -505,11 +538,11 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -520,7 +553,7 @@ checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d"
dependencies = [
"bitflags",
"cairo-sys-rs",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
"once_cell",
"thiserror",
@@ -549,11 +582,11 @@ dependencies = [
[[package]]
name = "camino"
-version = "1.1.1"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e"
+checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -562,7 +595,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -573,9 +606,9 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
dependencies = [
"camino",
"cargo-platform",
- "semver 1.0.14",
- "serde 1.0.149",
- "serde_json 1.0.89",
+ "semver 1.0.16",
+ "serde 1.0.154",
+ "serde_json 1.0.94",
]
[[package]]
@@ -585,23 +618,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6358dedf60f4d9b8db43ad187391afe959746101346fe51bb978126bec61dfb"
dependencies = [
"clap 3.2.23",
- "heck 0.4.0",
+ "heck 0.4.1",
"indexmap",
"log",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "serde 1.0.149",
- "serde_json 1.0.89",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "serde 1.0.154",
+ "serde_json 1.0.94",
+ "syn 1.0.109",
"tempfile",
- "toml",
+ "toml 0.5.11",
]
[[package]]
name = "cc"
-version = "1.0.77"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
@@ -659,9 +692,9 @@ dependencies = [
[[package]]
name = "cidr-utils"
-version = "0.5.9"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "355d5b5df67e58b523953d0c1a8d3d2c05f5af51f1332b0199b9c92263614ed0"
+checksum = "fdfa36f04861d39453affe1cf084ce2d6554021a84eb6f31ebdeafb6fb92a01c"
dependencies = [
"debug-helper",
"num-bigint",
@@ -672,9 +705,9 @@ dependencies = [
[[package]]
name = "clang-sys"
-version = "1.4.0"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3"
+checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a"
dependencies = [
"glob",
"libc",
@@ -705,7 +738,7 @@ dependencies = [
"atty",
"bitflags",
"clap_derive",
- "clap_lex",
+ "clap_lex 0.2.4",
"indexmap",
"once_cell",
"strsim 0.10.0",
@@ -713,17 +746,30 @@ dependencies = [
"textwrap 0.16.0",
]
+[[package]]
+name = "clap"
+version = "4.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
+dependencies = [
+ "bitflags",
+ "clap_lex 0.3.2",
+ "is-terminal",
+ "strsim 0.10.0",
+ "termcolor",
+]
+
[[package]]
name = "clap_derive"
version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [
- "heck 0.4.0",
+ "heck 0.4.1",
"proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -735,6 +781,15 @@ dependencies = [
"os_str_bytes",
]
+[[package]]
+name = "clap_lex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
+dependencies = [
+ "os_str_bytes",
+]
+
[[package]]
name = "clipboard"
version = "0.1.0"
@@ -742,16 +797,16 @@ dependencies = [
"cc",
"hbb_common",
"lazy_static",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
"thiserror",
]
[[package]]
name = "clipboard-win"
-version = "4.4.2"
+version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
@@ -823,6 +878,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+[[package]]
+name = "colored"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
+dependencies = [
+ "atty",
+ "lazy_static",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "combine"
version = "4.6.6"
@@ -835,9 +901,9 @@ dependencies = [
[[package]]
name = "concurrent-queue"
-version = "2.0.0"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
+checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
dependencies = [
"crossbeam-utils",
]
@@ -848,9 +914,9 @@ version = "0.4.0"
source = "git+https://github.com/open-trade/confy#630cc28a396cb7d01eefdd9f3824486fe4d8554b"
dependencies = [
"directories-next",
- "serde 1.0.149",
+ "serde 1.0.154",
"thiserror",
- "toml",
+ "toml 0.5.11",
]
[[package]]
@@ -972,27 +1038,26 @@ dependencies = [
[[package]]
name = "cpal"
-version = "0.13.5"
+version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116"
+checksum = "f342c1b63e185e9953584ff2199726bf53850d96610a310e3aca09e9405a2d0b"
dependencies = [
"alsa",
"core-foundation-sys 0.8.3",
"coreaudio-rs",
"jni 0.19.0",
"js-sys",
- "lazy_static",
"libc",
"mach",
- "ndk 0.6.0",
- "ndk-glue 0.6.2",
- "nix 0.23.2",
+ "ndk 0.7.0",
+ "ndk-context",
"oboe",
- "parking_lot 0.11.2",
+ "once_cell",
+ "parking_lot 0.12.1",
"stdweb",
"thiserror",
"web-sys",
- "winapi 0.3.9",
+ "windows 0.37.0",
]
[[package]]
@@ -1015,9 +1080,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.6"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@@ -1025,9 +1090,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
-version = "0.8.2"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
@@ -1036,14 +1101,14 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.13"
+version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg 1.1.0",
"cfg-if 1.0.0",
"crossbeam-utils",
- "memoffset 0.7.1",
+ "memoffset 0.8.0",
"scopeguard",
]
@@ -1059,9 +1124,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.14"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1084,12 +1149,12 @@ dependencies = [
[[package]]
name = "ctrlc"
-version = "3.2.4"
+version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
+checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
dependencies = [
- "nix 0.26.1",
- "windows-sys 0.42.0",
+ "nix 0.26.2",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -1100,9 +1165,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "cxx"
-version = "1.0.83"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
+checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -1112,34 +1177,34 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.83"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
+checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"scratch",
- "syn 1.0.105",
+ "syn 1.0.109",
]
[[package]]
name = "cxxbridge-flags"
-version = "1.0.83"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
+checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.83"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
+checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1187,10 +1252,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"strsim 0.9.3",
- "syn 1.0.105",
+ "syn 1.0.109",
]
[[package]]
@@ -1201,10 +1266,10 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"strsim 0.10.0",
- "syn 1.0.105",
+ "syn 1.0.109",
]
[[package]]
@@ -1214,8 +1279,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core 0.10.2",
- "quote 1.0.21",
- "syn 1.0.105",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1225,8 +1290,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core 0.13.4",
- "quote 1.0.21",
- "syn 1.0.105",
+ "quote 1.0.23",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "dart-sys"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d8b5680b5c2cc52f50acb2457d9b3a3b58adcca785db13a0e3655626f601de6"
+dependencies = [
+ "cc",
]
[[package]]
@@ -1350,9 +1424,9 @@ dependencies = [
[[package]]
name = "dbus"
-version = "0.9.6"
+version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
+checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
dependencies = [
"libc",
"libdbus-sys",
@@ -1361,9 +1435,9 @@ dependencies = [
[[package]]
name = "dbus-crossroads"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "554114296d012b33fdaf362a733db6dc5f73c4c9348b8b620ddd42e61b406e30"
+checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0"
dependencies = [
"dbus",
]
@@ -1392,25 +1466,15 @@ dependencies = [
"windows 0.30.0",
]
-[[package]]
-name = "deflate"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
-dependencies = [
- "adler32",
- "byteorder",
-]
-
[[package]]
name = "delegate"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "082a24a9967533dc5d743c602157637116fc1b52806d694a5a45e6f32567fcdd"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1419,9 +1483,9 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1431,9 +1495,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1cf41b4580a37cca5ef2ada2cc43cf5d6be3983f4522e83010d67ab6925e84b"
dependencies = [
"darling 0.10.2",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1513,6 +1577,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
[[package]]
name = "dispatch"
version = "0.2.0"
@@ -1565,7 +1635,7 @@ checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f"
dependencies = [
"lazy_static",
"regex",
- "serde 1.0.149",
+ "serde 1.0.154",
"strsim 0.10.0",
]
@@ -1588,25 +1658,25 @@ dependencies = [
"cc",
"hbb_common",
"lazy_static",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
"thiserror",
]
[[package]]
name = "ed25519"
-version = "1.5.2"
+version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369"
+checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
dependencies = [
"signature",
]
[[package]]
name = "either"
-version = "1.8.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "embed-resource"
@@ -1616,16 +1686,16 @@ checksum = "e62abb876c07e4754fae5c14cafa77937841f01740637e17d78dc04352f32a5e"
dependencies = [
"cc",
"rustc_version 0.4.0",
- "toml",
+ "toml 0.5.11",
"vswhom",
"winreg 0.10.1",
]
[[package]]
name = "encoding_rs"
-version = "0.8.31"
+version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1640,7 +1710,7 @@ dependencies = [
"objc",
"pkg-config",
"rdev",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
"tfc",
"unicode-segmentation",
@@ -1649,34 +1719,34 @@ dependencies = [
[[package]]
name = "enum-map"
-version = "2.4.1"
+version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1"
+checksum = "50c25992259941eb7e57b936157961b217a4fc8597829ddef0596d6c3cd86e1a"
dependencies = [
"enum-map-derive",
]
[[package]]
name = "enum-map-derive"
-version = "0.10.0"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27"
+checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
name = "enum_dispatch"
-version = "0.3.8"
+version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eb359f1476bf611266ac1f5355bc14aeca37b299d0ebccc038ee7058891c9cb"
+checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2"
dependencies = [
"once_cell",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1686,7 +1756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb"
dependencies = [
"enumflags2_derive",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -1695,9 +1765,9 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -1723,6 +1793,19 @@ dependencies = [
"termcolor",
]
+[[package]]
+name = "env_logger"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
[[package]]
name = "epoll"
version = "4.3.1"
@@ -1740,10 +1823,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"
dependencies = [
"proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"rustversion",
- "syn 1.0.105",
+ "syn 1.0.109",
"synstructure",
]
@@ -1758,6 +1841,17 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "errno"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
@@ -1804,62 +1898,52 @@ dependencies = [
"flume",
"half",
"lebe",
- "miniz_oxide 0.6.2",
+ "miniz_oxide",
"smallvec",
"threadpool",
"zune-inflate",
]
-[[package]]
-name = "extend"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5216e387a76eebaaf11f6d871ec8a4aae0b25f05456ee21f228e024b1b3610"
-dependencies = [
- "proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
-]
-
-[[package]]
-name = "failure"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
-dependencies = [
- "backtrace",
-]
-
[[package]]
name = "fastrand"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
-name = "field-offset"
-version = "0.3.4"
+name = "fern"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
+checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
dependencies = [
- "memoffset 0.6.5",
- "rustc_version 0.3.3",
+ "chrono",
+ "colored",
+ "log",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535"
+dependencies = [
+ "memoffset 0.8.0",
+ "rustc_version 0.4.0",
]
[[package]]
name = "filetime"
-version = "0.2.19"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
+checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -1869,27 +1953,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
- "miniz_oxide 0.6.2",
+ "miniz_oxide",
]
[[package]]
name = "flexi_logger"
-version = "0.22.6"
+version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c76a80dd14a27fc3d8bc696502132cb52b3f227256fd8601166c3a35e45f409"
+checksum = "6eae57842a8221ef13f1f207632d786a175dd13bd8fbdc8be9d852f7c9cf1046"
dependencies = [
- "ansi_term",
- "atty",
"chrono",
"crossbeam-channel",
"crossbeam-queue",
"glob",
+ "is-terminal",
"lazy_static",
"log",
+ "nu-ansi-term",
"regex",
- "rustversion",
"thiserror",
- "time 0.3.9",
]
[[package]]
@@ -1907,9 +1989,9 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge"
-version = "1.61.1"
+version = "1.68.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8079119bbe8fb63d7ebb731fa2aa68c6c8375f4ac95ca26d5868e64c0f4b9244"
+checksum = "54f0d71ff30fc2ae7c18508b517488a89051d81e3bfc4d48d4a6cf54b5dab789"
dependencies = [
"allo-isolate",
"anyhow",
@@ -1918,6 +2000,7 @@ dependencies = [
"cc",
"chrono",
"console_error_panic_hook",
+ "dart-sys",
"flutter_rust_bridge_macros",
"js-sys",
"lazy_static",
@@ -1931,39 +2014,41 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge_codegen"
-version = "1.61.1"
+version = "1.68.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd7396bc479eae8aa24243e4c0e3d3dbda1909134f8de6bde4f080d262c9a0d"
+checksum = "2a2a75a72411f0c5b480e4671417f52780172053128cf87d5614a9757d7680a0"
dependencies = [
"anyhow",
+ "atty",
"cargo_metadata",
"cbindgen",
+ "chrono",
"clap 3.2.23",
"convert_case",
"delegate",
"enum_dispatch",
- "env_logger 0.9.3",
- "extend",
+ "fern",
"itertools 0.10.5",
"lazy_static",
"log",
"pathdiff",
- "quote 1.0.21",
+ "quote 1.0.23",
"regex",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_yaml",
- "syn 1.0.105",
+ "strum_macros 0.24.3",
+ "syn 1.0.109",
"tempfile",
"thiserror",
- "toml",
+ "toml 0.5.11",
"topological-sort",
]
[[package]]
name = "flutter_rust_bridge_macros"
-version = "1.61.1"
+version = "1.68.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d5cd827645690ef378be57a890d0581e17c28d07b712872af7d744f454fd27d"
+checksum = "f6187d1635afede47c23a9979f85c3dc57e3391eb9c690b7fe95715bf945da72"
[[package]]
name = "fnv"
@@ -2038,9 +2123,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -2053,9 +2138,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -2063,15 +2148,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -2080,9 +2165,9 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-lite"
@@ -2101,32 +2186,32 @@ dependencies = [
[[package]]
name = "futures-macro"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
name = "futures-sink"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -2140,15 +2225,6 @@ dependencies = [
"slab",
]
-[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
[[package]]
name = "gdk"
version = "0.16.2"
@@ -2160,7 +2236,7 @@ dependencies = [
"gdk-pixbuf",
"gdk-sys",
"gio",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
"pango",
]
@@ -2174,7 +2250,7 @@ dependencies = [
"bitflags",
"gdk-pixbuf-sys",
"gio",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
]
@@ -2232,7 +2308,7 @@ dependencies = [
"glib-sys 0.16.3",
"libc",
"system-deps 6.0.3",
- "x11 2.20.1",
+ "x11 2.21.0",
]
[[package]]
@@ -2280,9 +2356,9 @@ dependencies = [
[[package]]
name = "gimli"
-version = "0.26.2"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
+checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "gio"
@@ -2296,7 +2372,7 @@ dependencies = [
"futures-io",
"futures-util",
"gio-sys",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
"once_cell",
"pin-project-lite",
@@ -2338,9 +2414,9 @@ dependencies = [
[[package]]
name = "glib"
-version = "0.16.5"
+version = "0.16.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cd04d150a2c63e6779f43aec7e04f5374252479b7bed5f45146d9c0e821f161"
+checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f"
dependencies = [
"bitflags",
"futures-channel",
@@ -2369,9 +2445,9 @@ dependencies = [
"itertools 0.9.0",
"proc-macro-crate 0.1.5",
"proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -2381,12 +2457,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf"
dependencies = [
"anyhow",
- "heck 0.4.0",
- "proc-macro-crate 1.2.1",
+ "heck 0.4.1",
+ "proc-macro-crate 1.3.1",
"proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -2411,9 +2487,9 @@ dependencies = [
[[package]]
name = "glob"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gobject-sys"
@@ -2584,7 +2660,7 @@ dependencies = [
"gdk",
"gdk-pixbuf",
"gio",
- "glib 0.16.5",
+ "glib 0.16.7",
"gtk-sys",
"gtk3-macros",
"libc",
@@ -2618,18 +2694,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cfd6557b1018b773e43c8de9d0d13581d6b36190d0501916cbec4731db5ccff"
dependencies = [
"anyhow",
- "proc-macro-crate 1.2.1",
+ "proc-macro-crate 1.3.1",
"proc-macro-error",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
name = "h2"
-version = "0.3.15"
+version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
dependencies = [
"bytes",
"fnv",
@@ -2673,8 +2749,9 @@ dependencies = [
"confy",
"directories-next",
"dirs-next",
- "env_logger 0.9.3",
+ "env_logger 0.10.0",
"filetime",
+ "flexi_logger",
"futures",
"futures-util",
"lazy_static",
@@ -2688,16 +2765,16 @@ dependencies = [
"quinn",
"rand 0.8.5",
"regex",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
- "serde_json 1.0.89",
+ "serde_json 1.0.94",
"socket2 0.3.19",
"sodiumoxide",
"sysinfo",
"tokio",
"tokio-socks",
"tokio-util",
- "toml",
+ "toml 0.7.2",
"winapi 0.3.9",
"zstd",
]
@@ -2713,9 +2790,9 @@ dependencies = [
[[package]]
name = "heck"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
@@ -2726,6 +2803,21 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
[[package]]
name = "hex"
version = "0.4.3"
@@ -2740,13 +2832,13 @@ checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1"
[[package]]
name = "http"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
- "itoa 1.0.4",
+ "itoa 1.0.6",
]
[[package]]
@@ -2781,21 +2873,21 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hwcodec"
version = "0.1.0"
-source = "git+https://github.com/21pages/hwcodec#64f885b3787694b16dfcff08256750b0376b2eba"
+source = "git+https://github.com/21pages/hwcodec#d55f7761ef692fae738259d8c14506d901eb824c"
dependencies = [
"bindgen 0.59.2",
"cc",
"log",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
- "serde_json 1.0.89",
+ "serde_json 1.0.94",
]
[[package]]
name = "hyper"
-version = "0.14.23"
+version = "0.14.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
dependencies = [
"bytes",
"futures-channel",
@@ -2806,9 +2898,9 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
- "itoa 1.0.4",
+ "itoa 1.0.6",
"pin-project-lite",
- "socket2 0.4.7",
+ "socket2 0.4.9",
"tokio",
"tower-service",
"tracing",
@@ -2868,22 +2960,6 @@ dependencies = [
"unicode-normalization",
]
-[[package]]
-name = "image"
-version = "0.23.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
-dependencies = [
- "bytemuck",
- "byteorder",
- "color_quant",
- "num-iter",
- "num-rational 0.3.2",
- "num-traits 0.2.15",
- "png 0.16.8",
- "tiff 0.6.1",
-]
-
[[package]]
name = "image"
version = "0.24.5"
@@ -2895,12 +2971,12 @@ dependencies = [
"color_quant",
"exr",
"gif",
- "jpeg-decoder 0.3.0",
+ "jpeg-decoder",
"num-rational 0.4.1",
"num-traits 0.2.15",
- "png 0.17.7",
+ "png",
"scoped_threadpool",
- "tiff 0.8.1",
+ "tiff",
]
[[package]]
@@ -2926,8 +3002,8 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
]
[[package]]
@@ -2972,6 +3048,16 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "io-lifetimes"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+dependencies = [
+ "libc",
+ "windows-sys 0.45.0",
+]
+
[[package]]
name = "iovec"
version = "0.1.4"
@@ -2983,9 +3069,21 @@ dependencies = [
[[package]]
name = "ipnet"
-version = "2.6.0"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded"
+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.45.0",
+]
[[package]]
name = "itertools"
@@ -3013,9 +3111,9 @@ checksum = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
[[package]]
name = "itoa"
-version = "1.0.4"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "jni"
@@ -3053,19 +3151,13 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
-version = "0.1.25"
+version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
-[[package]]
-name = "jpeg-decoder"
-version = "0.1.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
-
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
@@ -3077,9 +3169,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.60"
+version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
@@ -3101,7 +3193,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68"
dependencies = [
"bitflags",
- "serde 1.0.149",
+ "serde 1.0.154",
"unicode-segmentation",
]
@@ -3129,7 +3221,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f"
dependencies = [
- "glib 0.16.5",
+ "glib 0.16.7",
"gtk",
"gtk-sys",
"libappindicator-sys",
@@ -3149,15 +3241,15 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.138"
+version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libdbus-sys"
-version = "0.2.2"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
+checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0"
dependencies = [
"pkg-config",
]
@@ -3174,9 +3266,9 @@ dependencies = [
[[package]]
name = "libpulse-binding"
-version = "2.26.0"
+version = "2.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17be42160017e0ae993c03bfdab4ecb6f82ce3f8d515bd8da8fdf18d10703663"
+checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890"
dependencies = [
"bitflags",
"libc",
@@ -3188,9 +3280,9 @@ dependencies = [
[[package]]
name = "libpulse-simple-binding"
-version = "2.25.0"
+version = "2.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cbf1a1dfd69a48cb60906399fa1d17f1b75029ef51c0789597be792dfd0bcd5"
+checksum = "5ced94199e6e44133431374e4043f34e1f0697ebfb7b7d6c244a65bfaedf0e31"
dependencies = [
"libpulse-binding",
"libpulse-simple-sys",
@@ -3199,9 +3291,9 @@ dependencies = [
[[package]]
name = "libpulse-simple-sys"
-version = "1.19.2"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c73f96f9ca34809692c4760cfe421225860aa000de50edab68a16221fd27cc1"
+checksum = "84e423d9c619c908ce9b4916080e65ab586ca55b8c4939379f15e6e72fb43842"
dependencies = [
"libpulse-sys",
"pkg-config",
@@ -3209,9 +3301,9 @@ dependencies = [
[[package]]
name = "libpulse-sys"
-version = "1.19.3"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "991e6bd0efe2a36e6534e136e7996925e4c1a8e35b7807fe533f2beffff27c30"
+checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb"
dependencies = [
"libc",
"num-derive",
@@ -3257,14 +3349,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212"
dependencies = [
"libc",
- "x11 2.20.1",
+ "x11 2.21.0",
+]
+
+[[package]]
+name = "line-wrap"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9"
+dependencies = [
+ "safemem",
]
[[package]]
name = "link-cplusplus"
-version = "1.0.7"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
@@ -3275,6 +3376,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+
[[package]]
name = "lock_api"
version = "0.4.9"
@@ -3385,6 +3492,15 @@ dependencies = [
"autocfg 1.1.0",
]
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg 1.1.0",
+]
+
[[package]]
name = "mime"
version = "0.3.16"
@@ -3397,34 +3513,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "miniz_oxide"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
-dependencies = [
- "adler32",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
-dependencies = [
- "adler",
- "autocfg 1.1.0",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
-dependencies = [
- "adler",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.6.2"
@@ -3455,14 +3543,14 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -3517,9 +3605,9 @@ dependencies = [
[[package]]
name = "muda"
-version = "0.4.1"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c66365a21dc5e322c6b6ba25c735d00153c57dd2eb377926aa50e3caf547b6f6"
+checksum = "0ee091608fe840d80c6b25e8f838964b264ee8e02e82ae0a38b2d900464d1582"
dependencies = [
"cocoa",
"crossbeam-channel",
@@ -3530,7 +3618,7 @@ dependencies = [
"libxdo",
"objc",
"once_cell",
- "png 0.17.7",
+ "png",
"thiserror",
"windows-sys 0.45.0",
]
@@ -3586,7 +3674,7 @@ dependencies = [
"jni-sys",
"ndk-sys 0.4.1+23.1.7779620",
"num_enum",
- "raw-window-handle 0.5.0",
+ "raw-window-handle 0.5.1",
"thiserror",
]
@@ -3611,21 +3699,6 @@ dependencies = [
"ndk-sys 0.2.2",
]
-[[package]]
-name = "ndk-glue"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f"
-dependencies = [
- "lazy_static",
- "libc",
- "log",
- "ndk 0.6.0",
- "ndk-context",
- "ndk-macro",
- "ndk-sys 0.3.0",
-]
-
[[package]]
name = "ndk-macro"
version = "0.3.0"
@@ -3633,10 +3706,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
dependencies = [
"darling 0.13.4",
- "proc-macro-crate 1.2.1",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro-crate 1.3.1",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -3714,35 +3787,23 @@ dependencies = [
[[package]]
name = "nix"
-version = "0.25.1"
+version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
- "autocfg 1.1.0",
"bitflags",
"cfg-if 1.0.0",
"libc",
- "memoffset 0.6.5",
+ "memoffset 0.7.1",
"pin-utils",
-]
-
-[[package]]
-name = "nix"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
-dependencies = [
- "bitflags",
- "cfg-if 1.0.0",
- "libc",
"static_assertions",
]
[[package]]
name = "nom"
-version = "7.1.1"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
@@ -3750,13 +3811,23 @@ dependencies = [
[[package]]
name = "ntapi"
-version = "0.3.7"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
+checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc"
dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "num-bigint"
version = "0.4.3"
@@ -3770,9 +3841,9 @@ dependencies = [
[[package]]
name = "num-complex"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
+checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits 0.2.15",
]
@@ -3783,9 +3854,9 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -3798,17 +3869,6 @@ dependencies = [
"num-traits 0.2.15",
]
-[[package]]
-name = "num-iter"
-version = "0.1.43"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
-dependencies = [
- "autocfg 1.1.0",
- "num-integer",
- "num-traits 0.2.15",
-]
-
[[package]]
name = "num-rational"
version = "0.3.2"
@@ -3851,42 +3911,33 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "num_enum"
-version = "0.5.7"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
-version = "0.5.7"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
- "proc-macro-crate 1.2.1",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
-]
-
-[[package]]
-name = "num_threads"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
-dependencies = [
- "libc",
+ "proc-macro-crate 1.3.1",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -3930,9 +3981,9 @@ dependencies = [
[[package]]
name = "object"
-version = "0.29.0"
+version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
+checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
dependencies = [
"memchr",
]
@@ -3962,9 +4013,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.17.0"
+version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "openssl-probe"
@@ -3984,14 +4035,26 @@ dependencies = [
[[package]]
name = "ordered-stream"
-version = "0.1.2"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01ca8c99d73c6e92ac1358f9f692c22c0bfd9c4701fa086f5d365c0d4ea818ea"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
dependencies = [
"futures-core",
"pin-project-lite",
]
+[[package]]
+name = "os-version"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a8a1fed76ac765e39058ca106b6229a93c5a60292a1bd4b602ce2be11e1c020"
+dependencies = [
+ "anyhow",
+ "plist",
+ "uname",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "os_str_bytes"
version = "6.4.1"
@@ -4004,11 +4067,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
- "serde_json 1.0.89",
+ "serde_json 1.0.94",
]
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
[[package]]
name = "pango"
version = "0.16.5"
@@ -4017,7 +4086,7 @@ checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94"
dependencies = [
"bitflags",
"gio",
- "glib 0.16.5",
+ "glib 0.16.7",
"libc",
"once_cell",
"pango-sys",
@@ -4064,7 +4133,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
- "parking_lot_core 0.8.5",
+ "parking_lot_core 0.8.6",
]
[[package]]
@@ -4074,14 +4143,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.5",
+ "parking_lot_core 0.9.7",
]
[[package]]
name = "parking_lot_core"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if 1.0.0",
"instant",
@@ -4093,22 +4162,22 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.5"
+version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
name = "paste"
-version = "1.0.9"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
+checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pathdiff"
@@ -4128,16 +4197,6 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
-[[package]]
-name = "pest"
-version = "2.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0"
-dependencies = [
- "thiserror",
- "ucd-trie",
-]
-
[[package]]
name = "phf"
version = "0.7.24"
@@ -4191,9 +4250,9 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -4215,15 +4274,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
-name = "png"
-version = "0.16.8"
+name = "plist"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
+checksum = "ffac6a51110e97610dd3ac73e34a65b27e56a1e305df41bad1f616d8e1cb22f4"
dependencies = [
- "bitflags",
- "crc32fast",
- "deflate",
- "miniz_oxide 0.3.7",
+ "base64",
+ "indexmap",
+ "line-wrap",
+ "quick-xml",
+ "serde 1.0.154",
+ "time 0.3.20",
]
[[package]]
@@ -4235,21 +4296,23 @@ dependencies = [
"bitflags",
"crc32fast",
"flate2",
- "miniz_oxide 0.6.2",
+ "miniz_oxide",
]
[[package]]
name = "polling"
-version = "2.5.1"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "166ca89eb77fd403230b9c156612965a81e094ec6ec3aa13663d4c8b113fa748"
+checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa"
dependencies = [
"autocfg 1.1.0",
+ "bitflags",
"cfg-if 1.0.0",
+ "concurrent-queue",
"libc",
"log",
- "wepoll-ffi",
- "windows-sys 0.42.0",
+ "pin-project-lite",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -4279,18 +4342,17 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
- "toml",
+ "toml 0.5.11",
]
[[package]]
name = "proc-macro-crate"
-version = "1.2.1"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
- "thiserror",
- "toml",
+ "toml_edit",
]
[[package]]
@@ -4300,9 +4362,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
"version_check",
]
@@ -4312,8 +4374,8 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"version_check",
]
@@ -4328,9 +4390,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.47"
+version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
@@ -4375,7 +4437,7 @@ dependencies = [
"protobuf-support",
"tempfile",
"thiserror",
- "which 4.3.0",
+ "which",
]
[[package]]
@@ -4401,17 +4463,25 @@ dependencies = [
]
[[package]]
-name = "quinn"
-version = "0.8.5"
+name = "quick-xml"
+version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b435e71d9bfa0d8889927231970c51fb89c58fa63bffcab117c9c7a41e5ef8f"
+checksum = "ffc053f057dd768a56f62cd7e434c42c831d296968997e9ac1f76ea7c2d14c41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quinn"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1"
dependencies = [
"bytes",
- "futures-channel",
- "futures-util",
- "fxhash",
+ "pin-project-lite",
"quinn-proto",
"quinn-udp",
+ "rustc-hash",
"rustls",
"thiserror",
"tokio",
@@ -4421,17 +4491,16 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.8.4"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fce546b9688f767a57530652488420d419a8b1f44a478b451c3d1ab6d992a55"
+checksum = "72ef4ced82a24bb281af338b9e8f94429b6eca01b4e66d899f40031f074e74c9"
dependencies = [
"bytes",
- "fxhash",
"rand 0.8.5",
"ring",
+ "rustc-hash",
"rustls",
"rustls-native-certs",
- "rustls-pemfile 0.2.1",
"slab",
"thiserror",
"tinyvec",
@@ -4441,16 +4510,15 @@ dependencies = [
[[package]]
name = "quinn-udp"
-version = "0.1.4"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b07946277141531aea269befd949ed16b2c85a780ba1043244eda0969e538e54"
+checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4"
dependencies = [
- "futures-util",
"libc",
"quinn-proto",
- "socket2 0.4.7",
- "tokio",
+ "socket2 0.4.9",
"tracing",
+ "windows-sys 0.42.0",
]
[[package]]
@@ -4464,11 +4532,11 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.21"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
- "proc-macro2 1.0.47",
+ "proc-macro2 1.0.51",
]
[[package]]
@@ -4624,18 +4692,15 @@ dependencies = [
[[package]]
name = "raw-window-handle"
-version = "0.5.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
-dependencies = [
- "cty",
-]
+checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559"
[[package]]
name = "rayon"
-version = "1.6.1"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
@@ -4643,9 +4708,9 @@ dependencies = [
[[package]]
name = "rayon-core"
-version = "1.10.1"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -4656,7 +4721,7 @@ dependencies = [
[[package]]
name = "rdev"
version = "0.5.0-2"
-source = "git+https://github.com/fufesou/rdev#5b9fb5e42117f44e0ce0fe7cf2bddf270c75f1dc"
+source = "git+https://github.com/fufesou/rdev#25a99ce71ab42843ad253dd51e6a35e83e87a8a4"
dependencies = [
"cocoa",
"core-foundation 0.9.3",
@@ -4669,12 +4734,12 @@ dependencies = [
"lazy_static",
"libc",
"log",
- "mio 0.8.5",
+ "mio 0.8.6",
"strum 0.24.1",
"strum_macros 0.24.3",
"widestring 1.0.2",
"winapi 0.3.9",
- "x11 2.20.1",
+ "x11 2.21.0",
]
[[package]]
@@ -4717,9 +4782,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.7.0"
+version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@@ -4732,15 +4797,6 @@ version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
-[[package]]
-name = "remove_dir_all"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
-dependencies = [
- "winapi 0.3.9",
-]
-
[[package]]
name = "repng"
version = "0.2.2"
@@ -4753,9 +4809,9 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.11.13"
+version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
"base64",
"bytes",
@@ -4775,9 +4831,9 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"rustls",
- "rustls-pemfile 1.0.1",
- "serde 1.0.149",
- "serde_json 1.0.89",
+ "rustls-pemfile",
+ "serde 1.0.154",
+ "serde_json 1.0.94",
"serde_urlencoded",
"tokio",
"tokio-rustls",
@@ -4851,12 +4907,12 @@ dependencies = [
[[package]]
name = "runas"
-version = "0.2.1"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a620b0994a180cdfa25c0439e6d58c0628272571501880d626ffff58e96a0799"
+checksum = "ed87390fefd18965ff20baae5aeb9913bcf82d2b59dc04c0f6d8f17f7be56ff2"
dependencies = [
"cc",
- "which 3.1.1",
+ "which",
]
[[package]]
@@ -4891,11 +4947,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
-version = "0.3.3"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
- "semver 0.11.0",
+ "semver 0.9.0",
]
[[package]]
@@ -4904,14 +4960,14 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
- "semver 1.0.14",
+ "semver 1.0.16",
]
[[package]]
name = "rustdesk"
version = "1.2.0"
dependencies = [
- "android_logger 0.11.1",
+ "android_logger 0.11.3",
"arboard",
"async-process",
"async-trait",
@@ -4921,7 +4977,7 @@ dependencies = [
"cfg-if 1.0.0",
"chrono",
"cidr-utils",
- "clap 3.2.23",
+ "clap 4.1.8",
"clipboard",
"cocoa",
"core-foundation 0.9.3",
@@ -4936,15 +4992,15 @@ dependencies = [
"dispatch",
"dlopen",
"enigo",
- "errno",
+ "errno 0.3.0",
"evdev",
- "flexi_logger",
"flutter_rust_bridge",
"flutter_rust_bridge_codegen",
"fruitbasket",
"hbb_common",
+ "hex",
"hound",
- "image 0.24.5",
+ "image",
"impersonate_system",
"include_dir",
"jni 0.19.0",
@@ -4958,6 +5014,7 @@ dependencies = [
"num_cpus",
"objc",
"objc_id",
+ "os-version",
"parity-tokio-ipc",
"rdev",
"repng",
@@ -4969,9 +5026,9 @@ dependencies = [
"samplerate",
"sciter-rs",
"scrap",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
- "serde_json 1.0.89",
+ "serde_json 1.0.94",
"sha2",
"shared_memory",
"shutdown_hooks",
@@ -5020,10 +5077,24 @@ dependencies = [
]
[[package]]
-name = "rustls"
-version = "0.20.7"
+name = "rustix"
+version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
+checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
+dependencies = [
+ "bitflags",
+ "errno 0.2.8",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
@@ -5038,40 +5109,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
dependencies = [
"openssl-probe",
- "rustls-pemfile 1.0.1",
+ "rustls-pemfile",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-pemfile"
-version = "0.2.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9"
-dependencies = [
- "base64",
-]
-
-[[package]]
-name = "rustls-pemfile"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64",
]
[[package]]
name = "rustversion"
-version = "1.0.9"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
+checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
-version = "1.0.11"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "same-file"
@@ -5093,12 +5161,11 @@ dependencies = [
[[package]]
name = "schannel"
-version = "0.1.20"
+version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
dependencies = [
- "lazy_static",
- "windows-sys 0.36.1",
+ "windows-sys 0.42.0",
]
[[package]]
@@ -5135,7 +5202,7 @@ name = "scrap"
version = "0.5.0"
dependencies = [
"android_logger 0.10.1",
- "bindgen 0.59.2",
+ "bindgen 0.64.0",
"block",
"cfg-if 1.0.0",
"dbus",
@@ -5152,8 +5219,8 @@ dependencies = [
"num_cpus",
"quest",
"repng",
- "serde 1.0.149",
- "serde_json 1.0.89",
+ "serde 1.0.154",
+ "serde_json 1.0.94",
"target_build_utils",
"tracing",
"webm",
@@ -5162,9 +5229,9 @@ dependencies = [
[[package]]
name = "scratch"
-version = "1.0.2"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "sct"
@@ -5178,9 +5245,9 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "2.7.0"
+version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [
"bitflags",
"core-foundation 0.9.3",
@@ -5191,9 +5258,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.6.1"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
dependencies = [
"core-foundation-sys 0.8.3",
"libc",
@@ -5201,30 +5268,27 @@ dependencies = [
[[package]]
name = "semver"
-version = "0.11.0"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
-version = "1.0.14"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
+checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
name = "semver-parser"
-version = "0.10.2"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
-dependencies = [
- "pest",
-]
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
@@ -5234,22 +5298,22 @@ checksum = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af"
[[package]]
name = "serde"
-version = "1.0.149"
+version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
+checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.149"
+version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4"
+checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -5266,24 +5330,33 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.89"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
+checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
- "itoa 1.0.4",
+ "itoa 1.0.6",
"ryu",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
name = "serde_repr"
-version = "0.1.9"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
+checksum = "395627de918015623b32e7669714206363a7fc00382bf477e72c1f7533e8eafc"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
+dependencies = [
+ "serde 1.0.154",
]
[[package]]
@@ -5293,9 +5366,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
- "itoa 1.0.4",
+ "itoa 1.0.6",
"ryu",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -5306,10 +5379,19 @@ checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
dependencies = [
"indexmap",
"ryu",
- "serde 1.0.149",
+ "serde 1.0.154",
"yaml-rust",
]
+[[package]]
+name = "sha1"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
+dependencies = [
+ "sha1_smol",
+]
+
[[package]]
name = "sha1"
version = "0.10.5"
@@ -5321,6 +5403,12 @@ dependencies = [
"digest",
]
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
[[package]]
name = "sha2"
version = "0.10.6"
@@ -5359,9 +5447,9 @@ checksum = "6057adedbec913419c92996f395ba69931acbd50b7d56955394cd3f7bedbfa45"
[[package]]
name = "signal-hook"
-version = "0.3.14"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
@@ -5369,9 +5457,9 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
@@ -5394,7 +5482,7 @@ version = "0.1.0"
dependencies = [
"confy",
"hbb_common",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_derive",
"walkdir",
]
@@ -5407,9 +5495,9 @@ checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
[[package]]
name = "slab"
-version = "0.4.7"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg 1.1.0",
]
@@ -5452,9 +5540,9 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.4.7"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi 0.3.9",
@@ -5469,7 +5557,7 @@ dependencies = [
"ed25519",
"libc",
"libsodium-sys",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -5495,9 +5583,52 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
-version = "0.1.3"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version 0.2.3",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "serde 1.0.154",
+ "serde_derive",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "serde 1.0.154",
+ "serde_derive",
+ "serde_json 1.0.94",
+ "sha1 0.6.1",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "str-buf"
@@ -5548,9 +5679,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c"
dependencies = [
"heck 0.3.3",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -5559,11 +5690,11 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
- "heck 0.4.0",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "heck 0.4.1",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"rustversion",
- "syn 1.0.105",
+ "syn 1.0.109",
]
[[package]]
@@ -5579,12 +5710,12 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.105"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"unicode-ident",
]
@@ -5594,30 +5725,30 @@ version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
"unicode-xid 0.2.4",
]
[[package]]
name = "sys-locale"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3358acbb4acd4146138b9bda219e904a6bb5aaaa237f8eed06f4d6bc1580ecee"
+checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
dependencies = [
"js-sys",
"libc",
"wasm-bindgen",
"web-sys",
- "winapi 0.3.9",
+ "windows-sys 0.45.0",
]
[[package]]
name = "sysinfo"
-version = "0.24.7"
+version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54cb4ebf3d49308b99e6e9dc95e989e2fdbdc210e4f67c39db0bb89ba927001c"
+checksum = "d3e847e2de7a137c8c2cede5095872dbb00f4f9bf34d061347e36b43322acd56"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys 0.8.3",
@@ -5660,7 +5791,7 @@ dependencies = [
"strum 0.18.0",
"strum_macros 0.18.0",
"thiserror",
- "toml",
+ "toml 0.5.11",
"version-compare 0.0.10",
]
@@ -5671,19 +5802,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff"
dependencies = [
"cfg-expr",
- "heck 0.4.0",
+ "heck 0.4.1",
"pkg-config",
- "toml",
+ "toml 0.5.11",
"version-compare 0.1.1",
]
[[package]]
name = "system_shutdown"
-version = "3.0.0"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "035e081d603551d8d78db27d2232913269c749ea67648c369100049820406a14"
+checksum = "7567f71160af5e9abfb4f5a21532cf2174cefe91ac5c336419295685a695cc66"
dependencies = [
- "winapi 0.3.9",
+ "windows 0.44.0",
+ "zbus",
]
[[package]]
@@ -5705,10 +5837,10 @@ dependencies = [
"gdkwayland-sys",
"gdkx11-sys",
"gio",
- "glib 0.16.5",
+ "glib 0.16.7",
"glib-sys 0.16.3",
"gtk",
- "image 0.24.5",
+ "image",
"instant",
"jni 0.20.0",
"lazy_static",
@@ -5720,8 +5852,8 @@ dependencies = [
"objc",
"once_cell",
"parking_lot 0.12.1",
- "png 0.17.7",
- "raw-window-handle 0.5.0",
+ "png",
+ "raw-window-handle 0.5.1",
"scopeguard",
"tao-macros",
"unicode-segmentation",
@@ -5736,9 +5868,9 @@ name = "tao-macros"
version = "0.1.0"
source = "git+https://github.com/tauri-apps/tao?branch=muda#676bd90a80286b893d8850cc4e3813a0c4a27dcf"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -5760,23 +5892,22 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.3.0"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
dependencies = [
"cfg-if 1.0.0",
"fastrand",
- "libc",
"redox_syscall",
- "remove_dir_all",
- "winapi 0.3.9",
+ "rustix",
+ "windows-sys 0.42.0",
]
[[package]]
name = "termcolor"
-version = "1.1.3"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
@@ -5808,8 +5939,9 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "tfc"
version = "0.6.1"
-source = "git+https://github.com/fufesou/The-Fat-Controller#a5f13e6ef80327eb8d860aeb26b0af93eb5aee2b"
+source = "git+https://github.com/fufesou/The-Fat-Controller#1dba9a39c089ac9a7853b9dd5399c1d4aa3157d3"
dependencies = [
+ "anyhow",
"core-graphics 0.22.3",
"unicode-segmentation",
"winapi 0.3.9",
@@ -5818,22 +5950,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.37"
+version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.37"
+version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -5845,17 +5977,6 @@ dependencies = [
"num_cpus",
]
-[[package]]
-name = "tiff"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
-dependencies = [
- "jpeg-decoder 0.1.22",
- "miniz_oxide 0.4.4",
- "weezl",
-]
-
[[package]]
name = "tiff"
version = "0.8.1"
@@ -5863,7 +5984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471"
dependencies = [
"flate2",
- "jpeg-decoder 0.3.0",
+ "jpeg-decoder",
"weezl",
]
@@ -5880,21 +6001,30 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.9"
+version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
- "itoa 1.0.4",
- "libc",
- "num_threads",
+ "itoa 1.0.6",
+ "serde 1.0.154",
+ "time-core",
"time-macros",
]
[[package]]
-name = "time-macros"
-version = "0.2.4"
+name = "time-core"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
[[package]]
name = "tinyvec"
@@ -5907,28 +6037,28 @@ dependencies = [
[[package]]
name = "tinyvec_macros"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.23.0"
+version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg 1.1.0",
"bytes",
"libc",
"memchr",
- "mio 0.8.5",
+ "mio 0.8.6",
"num_cpus",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
- "socket2 0.4.7",
+ "socket2 0.4.9",
"tokio-macros",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -5937,9 +6067,9 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -5971,9 +6101,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.4"
+version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
dependencies = [
"bytes",
"futures-core",
@@ -5989,11 +6119,45 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.5.9"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
+]
+
+[[package]]
+name = "toml"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
+dependencies = [
+ "serde 1.0.154",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
+dependencies = [
+ "serde 1.0.154",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
+dependencies = [
+ "indexmap",
+ "serde 1.0.154",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
]
[[package]]
@@ -6026,9 +6190,9 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -6052,9 +6216,9 @@ dependencies = [
[[package]]
name = "tray-icon"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d62801a4da61bb100b8d3174a5a46fed7b6ea03cc2ae93ee7340793b09a94ce3"
+checksum = "f87445e3a107818c17d87e8369db30a6fc25539bface8351efe2132b22e47dbc"
dependencies = [
"cocoa",
"core-graphics 0.22.3",
@@ -6064,7 +6228,7 @@ dependencies = [
"muda",
"objc",
"once_cell",
- "png 0.17.7",
+ "png",
"thiserror",
"windows-sys 0.45.0",
]
@@ -6080,9 +6244,9 @@ dependencies = [
[[package]]
name = "try-lock"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "typenum"
@@ -6090,12 +6254,6 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
-[[package]]
-name = "ucd-trie"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
-
[[package]]
name = "uds_windows"
version = "1.0.2"
@@ -6107,16 +6265,25 @@ dependencies = [
]
[[package]]
-name = "unicode-bidi"
-version = "0.3.8"
+name = "uname"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
[[package]]
name = "unicode-ident"
-version = "1.0.5"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
@@ -6129,9 +6296,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
-version = "1.10.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
@@ -6166,7 +6333,7 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -6272,9 +6439,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@@ -6282,24 +6449,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.33"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -6309,32 +6476,32 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
- "quote 1.0.21",
+ "quote 1.0.23",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wayland-client"
@@ -6393,8 +6560,8 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"xml-rs",
]
@@ -6411,9 +6578,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.60"
+version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
+checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -6462,30 +6629,11 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
-[[package]]
-name = "wepoll-ffi"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "which"
-version = "3.1.1"
+version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
-dependencies = [
- "failure",
- "libc",
-]
-
-[[package]]
-name = "which"
-version = "4.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
@@ -6494,11 +6642,10 @@ dependencies = [
[[package]]
name = "whoami"
-version = "1.2.3"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571"
+checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3"
dependencies = [
- "bumpalo",
"wasm-bindgen",
"web-sys",
]
@@ -6602,6 +6749,19 @@ dependencies = [
"windows_x86_64_msvc 0.34.0",
]
+[[package]]
+name = "windows"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647"
+dependencies = [
+ "windows_aarch64_msvc 0.37.0",
+ "windows_i686_gnu 0.37.0",
+ "windows_i686_msvc 0.37.0",
+ "windows_x86_64_gnu 0.37.0",
+ "windows_x86_64_msvc 0.37.0",
+]
+
[[package]]
name = "windows"
version = "0.44.0"
@@ -6619,9 +6779,9 @@ version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce87ca8e3417b02dc2a8a22769306658670ec92d78f1bd420d6310a67c245c6"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -6630,9 +6790,9 @@ version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853f69a591ecd4f810d29f17e902d40e349fb05b0b11fff63b08b826bfe39c7f"
dependencies = [
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
[[package]]
@@ -6660,19 +6820,6 @@ dependencies = [
"windows_x86_64_msvc 0.28.0",
]
-[[package]]
-name = "windows-sys"
-version = "0.36.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
-dependencies = [
- "windows_aarch64_msvc 0.36.1",
- "windows_i686_gnu 0.36.1",
- "windows_i686_msvc 0.36.1",
- "windows_x86_64_gnu 0.36.1",
- "windows_x86_64_msvc 0.36.1",
-]
-
[[package]]
name = "windows-sys"
version = "0.42.0"
@@ -6738,9 +6885,9 @@ checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.36.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a"
[[package]]
name = "windows_aarch64_msvc"
@@ -6768,9 +6915,9 @@ checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed"
[[package]]
name = "windows_i686_gnu"
-version = "0.36.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1"
[[package]]
name = "windows_i686_gnu"
@@ -6798,9 +6945,9 @@ checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956"
[[package]]
name = "windows_i686_msvc"
-version = "0.36.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c"
[[package]]
name = "windows_i686_msvc"
@@ -6828,9 +6975,9 @@ checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.36.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d"
[[package]]
name = "windows_x86_64_gnu"
@@ -6864,9 +7011,9 @@ checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.36.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
[[package]]
name = "windows_x86_64_msvc"
@@ -6890,9 +7037,9 @@ dependencies = [
"lazy_static",
"libc",
"log",
- "mio 0.8.5",
+ "mio 0.8.6",
"ndk 0.5.0",
- "ndk-glue 0.5.2",
+ "ndk-glue",
"ndk-sys 0.2.2",
"objc",
"parking_lot 0.11.2",
@@ -6907,6 +7054,15 @@ dependencies = [
"x11-dl",
]
+[[package]]
+name = "winnow"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "winreg"
version = "0.6.2"
@@ -6931,17 +7087,14 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
- "toml",
+ "toml 0.5.11",
]
[[package]]
name = "wol-rs"
-version = "0.9.1"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7f97e69b28b256ccfb02472c25057132e234aa8368fea3bb0268def564ce1f2"
-dependencies = [
- "clap 3.2.23",
-]
+checksum = "48dc5e486e34a31515518d370cdd8bf59ec696323fe8f92b858e43942e84a765"
[[package]]
name = "ws2_32-sys"
@@ -6973,9 +7126,9 @@ dependencies = [
[[package]]
name = "x11"
-version = "2.20.1"
+version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2638d5b9c17ac40575fb54bb461a4b1d2a8d1b4ffcc4ff237d254ec59ddeb82"
+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
dependencies = [
"libc",
"pkg-config",
@@ -6994,14 +7147,24 @@ dependencies = [
[[package]]
name = "x11rb"
-version = "0.9.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e99be55648b3ae2a52342f9a870c0e138709a3493261ce9b469afe6e4df6d8a"
+checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
dependencies = [
"gethostname",
- "nix 0.22.3",
+ "nix 0.24.3",
"winapi 0.3.9",
"winapi-wsapoll",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
+dependencies = [
+ "nix 0.24.3",
]
[[package]]
@@ -7026,7 +7189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5af43ba661cee58bd86b9f81a899e45a15ac7f42fa4401340f73c0c2950030c1"
dependencies = [
"derive_setters",
- "serde 1.0.149",
+ "serde 1.0.154",
]
[[package]]
@@ -7040,13 +7203,13 @@ dependencies = [
[[package]]
name = "zbus"
-version = "3.6.2"
+version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "938ea6da98c75c2c37a86007bd17fd8e208cbec24e086108c87ece98e9edec0d"
+checksum = "20aae5dd5b051971cd2f49f9f3b860e57b2b495ba5ba254eaec42d34ede57e97"
dependencies = [
"async-broadcast",
- "async-channel",
"async-executor",
+ "async-fs",
"async-io",
"async-lock",
"async-recursion",
@@ -7061,13 +7224,13 @@ dependencies = [
"futures-sink",
"futures-util",
"hex",
- "nix 0.25.1",
+ "nix 0.26.2",
"once_cell",
"ordered-stream",
"rand 0.8.5",
- "serde 1.0.149",
+ "serde 1.0.154",
"serde_repr",
- "sha1",
+ "sha1 0.10.5",
"static_assertions",
"tracing",
"uds_windows",
@@ -7079,24 +7242,25 @@ dependencies = [
[[package]]
name = "zbus_macros"
-version = "3.6.2"
+version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45066039ebf3330820e495e854f8b312abb68f0a39e97972d092bd72e8bb3e8e"
+checksum = "9264b3a1bcf5503d4e0348b6e7efe1da58d4f92a913c15ed9e63b52de85faaa1"
dependencies = [
- "proc-macro-crate 1.2.1",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
+ "proc-macro-crate 1.3.1",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
"regex",
- "syn 1.0.105",
+ "syn 1.0.109",
+ "zvariant_utils",
]
[[package]]
name = "zbus_names"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c737644108627748a660d038974160e0cbb62605536091bdfa28fd7f64d43c8"
+checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3"
dependencies = [
- "serde 1.0.149",
+ "serde 1.0.154",
"static_assertions",
"zvariant",
]
@@ -7132,35 +7296,47 @@ dependencies = [
[[package]]
name = "zune-inflate"
-version = "0.2.42"
+version = "0.2.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c473377c11c4a3ac6a2758f944cd336678e9c977aa0abf54f6450cf77e902d6d"
+checksum = "a01728b79fb9b7e28a8c11f715e1cd8dc2cda7416a007d66cac55cebb3a8ac6b"
dependencies = [
"simd-adler32",
]
[[package]]
name = "zvariant"
-version = "3.9.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f8c89c183461e11867ded456db252eae90874bc6769b7adbea464caa777e51"
+checksum = "46fe4914a985446d6fd287019b5fceccce38303d71407d9e6e711d44954a05d8"
dependencies = [
"byteorder",
"enumflags2",
"libc",
- "serde 1.0.149",
+ "serde 1.0.154",
"static_assertions",
"zvariant_derive",
]
[[package]]
name = "zvariant_derive"
-version = "3.9.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "155247a5d1ab55e335421c104ccd95d64f17cebbd02f50cdbc1c33385f9c4d81"
+checksum = "34c20260af4b28b3275d6676c7e2a6be0d4332e8e0aba4616d34007fd84e462a"
dependencies = [
- "proc-macro-crate 1.2.1",
- "proc-macro2 1.0.47",
- "quote 1.0.21",
- "syn 1.0.105",
+ "proc-macro-crate 1.3.1",
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b22993dbc4d128a17a3b6c92f1c63872dd67198537ee728d8b5d7c40640a8b"
+dependencies = [
+ "proc-macro2 1.0.51",
+ "quote 1.0.23",
+ "syn 1.0.109",
]
diff --git a/Cargo.toml b/Cargo.toml
index f93f776a0..7ad979f8c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,33 +45,33 @@ lazy_static = "1.4"
sha2 = "0.10"
repng = "0.2"
parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" }
-flexi_logger = { version = "0.22", features = ["async", "use_chrono_for_offset"] }
-runas = "0.2"
+runas = "1.0"
magnum-opus = { git = "https://github.com/rustdesk/magnum-opus" }
dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true }
rubato = { version = "0.12", optional = true }
samplerate = { version = "0.2", optional = true }
async-trait = "0.1"
uuid = { version = "1.0", features = ["v4"] }
-clap = "3.0"
+clap = "4.1"
rpassword = "7.0"
-base64 = "0.13"
+base64 = "0.21"
num_cpus = "1.13"
bytes = { version = "1.2", features = ["serde"] }
default-net = "0.12.0"
-wol-rs = "0.9.1"
+wol-rs = "1.0"
flutter_rust_bridge = { version = "1.61.1", optional = true }
-errno = "0.2.8"
+errno = "0.3"
rdev = { git = "https://github.com/fufesou/rdev" }
url = { version = "2.1", features = ["serde"] }
dlopen = "0.1"
+hex = "0.4.3"
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
chrono = "0.4.23"
cidr-utils = "0.5.9"
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
-cpal = "0.13.5"
+cpal = "0.14"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
machine-uid = "0.2"
@@ -81,14 +81,14 @@ sys-locale = "0.2"
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
clipboard = { path = "libs/clipboard" }
ctrlc = "3.2"
-arboard = "2.0"
+arboard = "3.2"
#minreq = { version = "2.4", features = ["punycode", "https-native"] }
-system_shutdown = "3.0.0"
+system_shutdown = "4.0"
[target.'cfg(target_os = "windows")'.dependencies]
trayicon = { git = "https://github.com/open-trade/trayicon-rs", features = ["winit"] }
winit = "0.26"
-winapi = { version = "0.3", features = ["winuser"] }
+winapi = { version = "0.3", features = ["winuser", "wincrypt"] }
winreg = "0.10"
windows-service = "0.4"
virtual_display = { path = "libs/virtual_display" }
@@ -132,6 +132,7 @@ flutter_rust_bridge = "1.61.1"
[workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/simple_rc", "libs/portable"]
+exclude = ["vdi/host"]
[package.metadata.winres]
LegalCopyright = "Copyright © 2022 Purslane, Inc."
@@ -147,6 +148,7 @@ cc = "1.0"
hbb_common = { path = "libs/hbb_common" }
simple_rc = { path = "libs/simple_rc", optional = true }
flutter_rust_bridge_codegen = "1.61.1"
+os-version = "0.2"
[dev-dependencies]
hound = "3.5"
diff --git a/README.md b/README.md
index 8af79915b..4e3b309c5 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [Українська ] | [česky ] | [中文 ] | | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Dansk ]
+ [Українська ] | [česky ] | [中文 ] | | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Dansk ] | [Ελληνικά ]
We need your help to translate this README, RustDesk UI and Doc to your native language
@@ -37,9 +37,9 @@ Below are the servers you are using for free, they may change over time. If you
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| Ukraine (Kyiv) | dc.volia (2VM) | 2 vCPU / 4GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dev Container
diff --git a/build.py b/build.py
index 727b53fe0..4a39f596d 100755
--- a/build.py
+++ b/build.py
@@ -18,14 +18,11 @@ exe_path = 'target/release/' + hbb_name
flutter_win_target_dir = 'flutter/build/windows/runner/Release/'
skip_cargo = False
-def custom_os_system(cmd):
- err = os._system(cmd)
+def system2(cmd):
+ err = os.system(cmd)
if err != 0:
print(f"Error occurred when executing: {cmd}. Exiting.")
sys.exit(-1)
-# replace prebuilt os.system
-os._system = os.system
-os.system = custom_os_system
def get_version():
with open("Cargo.toml", encoding="utf-8") as fh:
@@ -40,7 +37,7 @@ def parse_rc_features(feature):
'IddDriver': {
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip',
'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/checksum_md5',
- 'exclude': ['README.md'],
+ 'exclude': ['README.md', 'certmgr.exe', 'install_cert_runas_admin.bat'],
},
'PrivacyMode': {
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
@@ -144,8 +141,8 @@ def generate_build_script_for_docker():
# build rustdesk
./build.py --flutter --hwcodec
''')
- os.system("chmod +x /tmp/build.sh")
- os.system("bash /tmp/build.sh")
+ system2("chmod +x /tmp/build.sh")
+ system2("bash /tmp/build.sh")
def download_extract_features(features, res_dir):
@@ -250,7 +247,7 @@ def get_features(args):
def generate_control_file(version):
control_file_path = "../res/DEBIAN/control"
- os.system('/bin/rm -rf %s' % control_file_path)
+ system2('/bin/rm -rf %s' % control_file_path)
content = """Package: rustdesk
Version: %s
@@ -268,45 +265,45 @@ Description: A remote control software.
def ffi_bindgen_function_refactor():
# workaround ffigen
- os.system(
+ system2(
'sed -i "s/ffi.NativeFunction> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
- os.system('mkdir -p tmpdeb/DEBIAN')
+ system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version)
- os.system('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
+ system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
- os.system('dpkg-deb -b tmpdeb rustdesk.deb;')
+ system2('dpkg-deb -b tmpdeb rustdesk.deb;')
- os.system('/bin/rm -rf tmpdeb/')
- os.system('/bin/rm -rf ../res/DEBIAN/control')
+ system2('/bin/rm -rf tmpdeb/')
+ system2('/bin/rm -rf ../res/DEBIAN/control')
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
os.chdir("..")
@@ -314,46 +311,43 @@ def build_flutter_deb(version, features):
def build_flutter_dmg(version, features):
if not skip_cargo:
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
- os.system(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
+ system2(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
# copy dylib
- os.system(
+ system2(
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
- # ffi_bindgen_function_refactor()
- # limitations from flutter rust bridge
- os.system('sed -i "" "s/char \*\*rustdesk_core_main(int \*args_len);//" flutter/macos/Runner/bridge_generated.h')
os.chdir('flutter')
- os.system('flutter build macos --release')
- os.system(
- "create-dmg rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
+ system2('flutter build macos --release')
+ 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")
os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg")
os.chdir("..")
def build_flutter_arch_manjaro(version, features):
if not skip_cargo:
- os.system(f'cargo build --features {features} --lib --release')
+ system2(f'cargo build --features {features} --lib --release')
ffi_bindgen_function_refactor()
os.chdir('flutter')
- os.system('flutter build linux --release')
- os.system('strip build/linux/x64/release/bundle/lib/librustdesk.so')
+ system2('flutter build linux --release')
+ system2('strip build/linux/x64/release/bundle/lib/librustdesk.so')
os.chdir('../res')
- os.system('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
+ system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
def build_flutter_windows(version, features):
if not skip_cargo:
- os.system(f'cargo build --features {features} --lib --release')
+ system2(f'cargo build --features {features} --lib --release')
if not os.path.exists("target/release/librustdesk.dll"):
print("cargo build failed, please check rust source code.")
exit(-1)
os.chdir('flutter')
- os.system('flutter build windows --release')
+ system2('flutter build windows --release')
os.chdir('..')
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
flutter_win_target_dir)
os.chdir('libs/portable')
- os.system('pip3 install -r requirements.txt')
- os.system(
+ system2('pip3 install -r requirements.txt')
+ system2(
f'python3 ./generate.py -f ../../{flutter_win_target_dir} -o . -e ../../{flutter_win_target_dir}/rustdesk.exe')
os.chdir('../..')
if os.path.exists('./rustdesk_portable.exe'):
@@ -374,22 +368,15 @@ def main():
parser = make_parser()
args = parser.parse_args()
- shutil.copy2('Cargo.toml', 'Cargo.toml.bk')
- shutil.copy2('src/main.rs', 'src/main.rs.bk')
- if windows:
- txt = open('src/main.rs', encoding='utf8').read()
- with open('src/main.rs', 'wt', encoding='utf8') as fh:
- fh.write(txt.replace(
- '//#![windows_subsystem', '#![windows_subsystem'))
if os.path.exists(exe_path):
os.unlink(exe_path)
if os.path.isfile('/usr/bin/pacman'):
- os.system('git checkout src/ui/common.tis')
+ system2('git checkout src/ui/common.tis')
version = get_version()
features = ','.join(get_features(args))
flutter = args.flutter
if not flutter:
- os.system('python3 res/inline-sciter.py')
+ system2('python3 res/inline-sciter.py')
print(args.skip_cargo)
if args.skip_cargo:
skip_cargo = True
@@ -397,55 +384,55 @@ def main():
if windows:
# build virtual display dynamic library
os.chdir('libs/virtual_display/dylib')
- os.system('cargo build --release')
+ system2('cargo build --release')
os.chdir('../../..')
if flutter:
build_flutter_windows(version, features)
return
- os.system('cargo build --release --features ' + features)
- # os.system('upx.exe target/release/rustdesk.exe')
- os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe')
+ system2('cargo build --release --features ' + features)
+ # system2('upx.exe target/release/rustdesk.exe')
+ system2('mv target/release/rustdesk.exe target/release/RustDesk.exe')
pa = os.environ.get('P')
if pa:
- os.system(
+ system2(
f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com '
'target\\release\\rustdesk.exe')
else:
print('Not signed')
- os.system(
+ system2(
f'cp -rf target/release/RustDesk.exe rustdesk-{version}-win7-install.exe')
elif os.path.isfile('/usr/bin/pacman'):
# pacman -S -needed base-devel
- os.system("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)
+ system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)
if flutter:
build_flutter_arch_manjaro(version, features)
else:
- os.system('cargo build --release --features ' + features)
- os.system('git checkout src/ui/common.tis')
- os.system('strip target/release/rustdesk')
- os.system('ln -s res/pacman_install && ln -s res/PKGBUILD')
- os.system('HBB=`pwd` makepkg -f')
- os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (
+ system2('cargo build --release --features ' + features)
+ system2('git checkout src/ui/common.tis')
+ system2('strip target/release/rustdesk')
+ system2('ln -s res/pacman_install && ln -s res/PKGBUILD')
+ system2('HBB=`pwd` makepkg -f')
+ system2('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (
version, version))
# pacman -U ./rustdesk.pkg.tar.zst
elif os.path.isfile('/usr/bin/yum'):
- os.system('cargo build --release --features ' + features)
- os.system('strip target/release/rustdesk')
- os.system(
+ system2('cargo build --release --features ' + features)
+ system2('strip target/release/rustdesk')
+ system2(
"sed -i 's/Version: .*/Version: %s/g' res/rpm.spec" % version)
- os.system('HBB=`pwd` rpmbuild -ba res/rpm.spec')
- os.system(
+ system2('HBB=`pwd` rpmbuild -ba res/rpm.spec')
+ system2(
'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm' % (
version, version))
# yum localinstall rustdesk.rpm
elif os.path.isfile('/usr/bin/zypper'):
- os.system('cargo build --release --features ' + features)
- os.system('strip target/release/rustdesk')
- os.system(
+ system2('cargo build --release --features ' + features)
+ system2('strip target/release/rustdesk')
+ system2(
"sed -i 's/Version: .*/Version: %s/g' res/rpm-suse.spec" % version)
- os.system('HBB=`pwd` rpmbuild -ba res/rpm-suse.spec')
- os.system(
+ system2('HBB=`pwd` rpmbuild -ba res/rpm-suse.spec')
+ system2(
'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (
version, version))
# yum localinstall rustdesk.rpm
@@ -455,18 +442,18 @@ def main():
build_flutter_dmg(version, features)
pass
else:
- # os.system(
+ # system2(
# 'mv target/release/bundle/deb/rustdesk*.deb ./flutter/rustdesk.deb')
build_flutter_deb(version, features)
else:
- os.system('cargo bundle --release --features ' + features)
+ system2('cargo bundle --release --features ' + features)
if osx:
- os.system(
+ system2(
'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk')
- os.system(
+ system2(
'cp libsciter.dylib target/release/bundle/osx/RustDesk.app/Contents/MacOS/')
# https://github.com/sindresorhus/create-dmg
- os.system('/bin/rm -rf *.dmg')
+ system2('/bin/rm -rf *.dmg')
plist = "target/release/bundle/osx/RustDesk.app/Contents/Info.plist"
txt = open(plist).read()
with open(plist, "wt") as fh:
@@ -476,7 +463,7 @@ def main():
"""))
pa = os.environ.get('P')
if pa:
- os.system('''
+ system2('''
# buggy: rcodesign sign ... path/*, have to sign one by one
# install rcodesign via cargo install apple-codesign
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk
@@ -486,11 +473,11 @@ def main():
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/*
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app
'''.format(pa))
- os.system('create-dmg target/release/bundle/osx/RustDesk.app')
+ system2('create-dmg target/release/bundle/osx/RustDesk.app')
os.rename('RustDesk %s.dmg' %
version, 'rustdesk-%s.dmg' % version)
if pa:
- os.system('''
+ system2('''
# https://pyoxidizer.readthedocs.io/en/apple-codesign-0.14.0/apple_codesign.html
# https://pyoxidizer.readthedocs.io/en/stable/tugger_code_signing.html
# https://developer.apple.com/developer-id/
@@ -507,34 +494,32 @@ def main():
print('Not signed')
else:
# buid deb package
- os.system(
+ system2(
'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb')
- os.system('dpkg-deb -R rustdesk.deb tmpdeb')
- os.system('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
- os.system(
+ system2('dpkg-deb -R rustdesk.deb tmpdeb')
+ system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
+ system2(
'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
- os.system(
+ system2(
'cp res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
- os.system(
+ system2(
'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
- os.system(
+ system2(
'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
- os.system('cp -a res/DEBIAN/* tmpdeb/DEBIAN/')
- os.system('strip tmpdeb/usr/bin/rustdesk')
- os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
- os.system('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
- os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
+ system2('cp -a res/DEBIAN/* tmpdeb/DEBIAN/')
+ system2('strip tmpdeb/usr/bin/rustdesk')
+ system2('mkdir -p tmpdeb/usr/lib/rustdesk')
+ system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
+ system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
- os.system('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.system("mv Cargo.toml.bk Cargo.toml")
- os.system("mv src/main.rs.bk src/main.rs")
def md5_file(fn):
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest()
- os.system('echo "%s %s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
+ system2('echo "%s %s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
if __name__ == "__main__":
diff --git a/build.rs b/build.rs
index d15f27424..bf141e539 100644
--- a/build.rs
+++ b/build.rs
@@ -9,7 +9,14 @@ fn build_windows() {
#[cfg(target_os = "macos")]
fn build_mac() {
let file = "src/platform/macos.mm";
- cc::Build::new().file(file).compile("macos");
+ let mut b = cc::Build::new();
+ if let Ok(os_version::OsVersion::MacOS(v)) = os_version::detect() {
+ let v = v.version;
+ if v.contains("10.14") {
+ b.flag("-DNO_InputMonitoringAuthStatus=1");
+ }
+ }
+ b.file(file).compile("macos");
println!("cargo:rerun-if-changed={}", file);
}
diff --git a/docs/CODE_OF_CONDUCT-NL.md b/docs/CODE_OF_CONDUCT-NL.md
new file mode 100644
index 000000000..49923a2d6
--- /dev/null
+++ b/docs/CODE_OF_CONDUCT-NL.md
@@ -0,0 +1,136 @@
+
+# Gedragscode Overeenkomst Medewerkers
+
+## Onze Belofte
+
+Wij als leden, medewerkers en leiders beloven deelname aan onze
+gemeenschap een pesterij-vrije ervaring te maken voor iedereen, ongeacht leeftijd, lichaamsgrootte,
+zichtbare of onzichtbare handicap, etniciteit, geslachtskenmerken, gender
+identiteit en expressie, ervaringsniveau, opleiding, sociaal-economische status,
+nationaliteit, persoonlijk voorkomen, ras, religie of seksuele identiteit
+en geaardheid.
+
+Wij beloven te handelen en met elkaar om te gaan op manieren die bijdragen aan een open, gastvrije,
+diverse, inclusieve en gezonde gemeenschap.
+
+## Onze Normen
+
+Voorbeelden van gedrag dat bijdraagt tot een positieve omgeving voor onze
+gemeenschap omvatten:
+
+* Medeleven en vriendelijkheid tonen tegenover andere mensen
+* Respect hebben voor verschillende meningen, standpunten en ervaringen
+* Constructieve feedback geven en met dank aanvaarden
+* Verantwoordelijkheid accepteren en excuses aanbieden aan degenen die door onze fouten zijn getroffen,
+ en leren van de ervaring
+* Focussen op wat het beste is, niet alleen voor ons als individu, maar voor de
+ totale gemeenschap
+
+Voorbeelden van onaanvaardbaar gedrag zijn:
+
+* Het gebruik van seksueel getinte taal of beelden, en seksuele aandacht of
+ alle soorten avances
+* Treiteren, beledigende of denigrerende opmerkingen en persoonlijke of politieke aanvallen.
+* Openbare of persoonlijke intimidatie
+* Publiceren van andermans persoonlijke informatie, zoals een fysiek adres of e-mail,
+ zonder hun uitdrukkelijke toestemming
+* Ander gedrag dat normaal als ongepast kan worden beschouwd in een
+ professionele omgeving
+
+## Verantwoordelijkheden inzake Handhaving
+
+De leiders van de Gemeenschap zijn verantwoordelijk voor het verduidelijken
+en handhaven van onze normen voor aanvaardbaar gedrag en zullen passende
+en billijke corrigerende maatregelen nemen als reactie op gedrag dat zij ongepast,
+bedreigend, beledigend of schadelijk achten.
+
+Leiders van de Gemeenschap hebben het recht en de verantwoordelijkheid om
+commentaar, bijdragen, code, wikibewerkingen, issues en andere bijdragen die
+niet in overeenstemming zijn met deze Gedragscode te verwijderen, te bewerken of
+af te wijzen, en zullen de redenen voor moderatiebeslissingen zo nodig meedelen.
+
+## Toepassingsgebied
+
+Deze Gedragscode geldt binnen alle gemeenschapsruimtes en is ook van toepassing
+wanneer iemand de gemeenschap officieel vertegenwoordigt in openbare ruimtes.
+Voorbeelden van het vertegenwoordigen van onze gemeenschap zijn het gebruik van
+een officieel e-mailadres, het posten via een officieel sociaal media-account of het
+optreden als aangewezen vertegenwoordiger bij een online of offline evenement.
+
+## Handhaving
+
+Gevallen van beledigend, intimiderend of anderszins onaanvaardbaar gedrag kunnen
+worden gemeld aan de gemeenschapsleiders die verantwoordelijk zijn voor de
+handhaving op [info@rustdesk.com](mailto:info@rustdesk.com).
+Alle klachten zullen snel en eerlijk worden onderzocht.
+
+Alle leiders van de gemeenschap zijn verplicht de privacy en de veiligheid van
+de melder van een incident te respecteren.
+
+## Handhaving Richtlijnen
+
+De leiders van de Gemeenschap volgen deze Communautaire Impact Richtlijnen bij
+het bepalen van de consequenties voor elke actie die zij in strijd achten
+met deze Gedragscode:
+
+### 1. Rechtzetting
+
+**Gevolgen Gemeenschap**: Gebruik van ongepast taalgebruik of ander gedrag
+dat onprofessioneel of ongewenst wordt geacht in de gemeenschap.
+
+**Gevolgen**: Een persoonlijke, schriftelijke waarschuwing van de leiders van
+de gemeenschap, met duidelijkheid over de aard van de overtreding en een
+uitleg waarom het gedrag ongepast was.
+Een publieke verontschuldiging kan worden gevraagd.
+
+### 2. Waarschuwing
+
+**Gevolgen Gemeenschap**: Een overtreding door een enkel incident of
+een reeks handelingen.
+
+**Gevolgen**: Geen interactie met de betrokken personen, inclusief
+ongevraagde interactie met degenen die de Gedragscode handhaven,
+gedurende een bepaalde periode. Dit omvat het vermijden van interacties
+in gemeenschapsruimtes en externe kanalen zoals sociale media.
+Overtreding van deze voorwaarden kan leiden tot een tijdelijke
+of permanente uitsluiting.
+
+### 3. Tijdelijke Uitsluiting
+
+**Gevolgen Gemeenschap**: Een ernstige schending van de
+gemeenschapsnormen, waaronder aanhoudend ongepast gedrag.
+
+**Gevolgen**: Een tijdelijk verbod op elke vorm van interactie
+of openbare communicatie met de gemeenschap voor een bepaalde
+ periode. Geen openbare of private interactie met de betrokkenen,
+ inclusief ongevraagde interactie met degenen die de gedragscode
+ handhaven, is gedurende deze periode toegestaan.
+ Overtreding van deze voorwaarden kan leiden tot een permanente uitsluiting.
+
+### 4. Permanente Uitsluiting
+
+**Gevolgen Gemeenschap**: Aantonen van een patroon van schending van
+de gemeenschapsnormen, waaronder aanhoudend ongepast gedrag, intimidatie
+van een individu, of agressie tegen of vernedering van klassen van individuen.
+
+**Gevolgen**: Een permanente uitsluiting van elke vorm van publieke interactie
+binnen de gemeenschap.
+
+## Naamsvermelding
+
+Deze gedragscode is overgenomen uit de [Bijdrager Overeenkomst][homepagina],
+versie 2.0, beschikbaar op
+[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
+
+De Invloed op Richtlijnen voor Gemeenschap zijn gebaseerd op
+[Mozilla's gedragscode handhavingslijst][Mozilla CoC].
+
+Voor antwoorden op veelgestelde vragen over deze gedragscode, zie de FAQ op
+[https://www.contributor-covenant.org/faq][FAQ]. Vertalingen zijn beschikbaar
+op [https://www.contributor-covenant.org/translations][translations].
+
+[homepagina]: https://www.contributor-covenant.org
+[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[vertalingen]: https://www.contributor-covenant.org/translations
diff --git a/docs/CONTRIBUTING-DE.md b/docs/CONTRIBUTING-DE.md
new file mode 100644
index 000000000..6258a9a7a
--- /dev/null
+++ b/docs/CONTRIBUTING-DE.md
@@ -0,0 +1,50 @@
+# Beitrge zu RustDesk
+
+RustDesk begrt Beitrge von jedem. Hier sind die Richtlinien, wenn Sie uns
+helfen mchten:
+
+## Beitrge
+
+Beitrge zu RustDesk oder seinen Abhngigkeiten sollten in Form von Pull
+Requests auf GitHub erfolgen. Jeder Pull Request wird von einem Hauptakteur
+(jemand mit der Erlaubnis, Korrekturen einzubringen) geprft und entweder in den
+Hauptbaum eingefgt oder Feedback fr notwendige nderungen gegeben. Alle
+Beitrge sollten diesem Format folgen, auch die von Hauptakteuren.
+
+Wenn Sie an einem Problem arbeiten mchten, melden Sie es bitte zuerst an, indem
+Sie auf GitHub erklren, dass Sie daran arbeiten mchten. Damit soll verhindert
+werden, dass Beitrge zum gleichen Thema doppelt bearbeitet werden.
+
+## Checkliste fr Pull Requests
+
+- Verzweigen Sie sich vom Master-Branch und, falls ntig, wechseln Sie zum
+ aktuellen Master-Branch, bevor Sie Ihren Pull Request einreichen. Wenn das
+ Zusammenfhren mit dem Master nicht reibungslos funktioniert, werden Sie
+ mglicherweise aufgefordert, Ihre nderungen zu berarbeiten.
+
+- Commits sollten so klein wie mglich sein und gleichzeitig sicherstellen, dass
+ jeder Commit unabhngig voneinander korrekt ist (d. h., jeder Commit sollte
+ sich bersetzen lassen und Tests bestehen).
+
+- Commits sollten von einem "Herkunftszertifikat fr Entwickler"
+ (https://developercertificate.org) begleitet werden, das besagt, dass Sie (und
+ ggf. Ihr Arbeitgeber) mit den Bedingungen der [Projektlizenz](../LICENCE)
+ einverstanden sind. In Git ist dies die Option `-s` fr `git commit`.
+
+- Wenn Ihr Patch nicht begutachtet wird oder Sie eine bestimmte Person zur
+ Begutachtung bentigen, knnen Sie einem Gutachter mit @ antworten und um eine
+ Begutachtung des Pull Requests oder einen Kommentar bitten. Sie knnen auch
+ per [E-Mail](mailto:info@rustdesk.com) um eine Begutachtung bitten.
+
+- Fgen Sie Tests hinzu, die sich auf den behobenen Fehler oder die neue
+ Funktion beziehen.
+
+Spezifische Git-Anweisungen finden Sie im [GitHub-Workflow](https://github.com/servo/servo/wiki/GitHub-workflow).
+
+## Verhalten
+
+https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
+
+## Kommunikation
+
+RustDesk-Mitarbeiter arbeiten hufig im [Discord](https://discord.gg/nDceKgxnkV).
diff --git a/docs/CONTRIBUTING-NL.md b/docs/CONTRIBUTING-NL.md
new file mode 100644
index 000000000..a39e8cef1
--- /dev/null
+++ b/docs/CONTRIBUTING-NL.md
@@ -0,0 +1,50 @@
+# Bijdragen aan RustDesk
+
+RustDesk verwelkomt bijdragen van iedereen. Hier zijn de richtlijnen als u denkt
+ons te willen helpen:
+
+## Bijdragen
+
+Bijdragen aan RustDesk of haar afhankelijkheden moeten worden gedaan in de
+vorm van GitHub pull verzoeken. Elk pull verzoek zal worden beoordeeld door
+een core bijdrager (iemand met toestemming om patches te plaatsen) en ofwel
+worden geplaatst in de hoofd structuur of feedback krijgen voor veranderingen
+die nodig zouden zijn. Alle bijdragen zouden dit formaat moeten volgen,
+zelfs die van kernmedewerkers.
+
+Als je aan een onderwerp wilt werken, eis het dan eerst op door commentaar
+te geven op het GitHub onderwerp dat je eraan wilt werken. Dit is om dubbele
+inspanningen van medewerkers aan hetzelfde issue te voorkomen.
+
+## Checklist Pull Aanvragen
+
+- Maak een vertakking vanaf de master tak en, indien nodig, veranker naar de
+ huidige master tak voordat je je pull verzoek indient. Als je het niet netjes
+ samenvoegt met master kan je gevraagd worden om je wijzigingen
+ opnieuw op te bouwen.
+
+- Toezeggingen moeten zo klein mogelijk zijn, terwijl er voor gezorgd moet
+ worden dat elke toezegging onafhankelijk correct is (dat wil zeggen, elke
+ toezegging moet compileren en testen doorstaan).
+
+- Toezeggingen moeten vergezeld gaan van een Certificaat van Oorsprong
+ van de Ontwikkelaar (http://developercertificate.org) ondertekening, die aangeeft
+ dat u (en uw werkgever indien van toepassing) akkoord gaat met de
+ voorwaarden van het [project licentie](../LICENCE).
+ In git is dit de `-s` optie van `git commit`
+
+- Als je patch niet beoordeeld wordt of je hebt een specifiek persoon nodig om hem
+ te beoordelen kunt u @-reply een reviewer vragen in het pull verzoek of een
+ commentaar, of je kunt om een review vragen via [email](mailto:info@rustdesk.com).
+
+- Tests toevoegen die relevant zijn voor de gerepareerde bug of de nieuwe functie.
+
+Voor specifieke git instructies, zie [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
+
+## Gedrag
+
+https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
+
+## Communicatie
+
+RustDesk medewerkers bezoeken frequent [Discord](https://discord.gg/nDceKgxnkV).
diff --git a/docs/DEVCONTAINER-DE.md b/docs/DEVCONTAINER-DE.md
new file mode 100644
index 000000000..2a0d73f17
--- /dev/null
+++ b/docs/DEVCONTAINER-DE.md
@@ -0,0 +1,14 @@
+
+Nach dem Start von Dev-Container im Docker-Container wird ein Linux-Binrprogramm im Debug-Modus erstellt.
+
+Derzeit bietet Dev-Container Linux- und Android-Builds sowohl im Debug- als auch im Release-Modus an.
+
+Nachfolgend finden Sie eine Tabelle mit Befehlen, die im Stammverzeichnis des Projekts ausgefhrt werden mssen, um bestimmte Builds zu erstellen.
+
+Kommando|Build-Typ|Modus
+-|-|-|
+`.devcontainer/build.sh --debug linux`|Linux|debug
+`.devcontainer/build.sh --release linux`|Linux|release
+`.devcontainer/build.sh --debug android`|android-arm64|debug
+`.devcontainer/build.sh --release android`|android-arm64|release
+
diff --git a/docs/DEVCONTAINER-NL.md b/docs/DEVCONTAINER-NL.md
new file mode 100644
index 000000000..cd6ae456d
--- /dev/null
+++ b/docs/DEVCONTAINER-NL.md
@@ -0,0 +1,15 @@
+
+Na de start van devcontainer in docker container wordt een linux binaire in foutmodus aangemaakt.
+
+Momenteel biedt devcontainer linux en android builds in zowel foutopsporing- als uitgave modus.
+
+Hieronder staat de tabel met commando's die vanuit de root van het project moeten worden
+uitgevoerd om specifieke builds te maken.
+
+Commando|Build Type|Modus
+-|-|-|
+`.devcontainer/build.sh --debug linux`|Linux|debug
+`.devcontainer/build.sh --release linux`|Linux|release
+`.devcontainer/build.sh --debug android`|android-arm64|debug
+`.devcontainer/build.sh --release android`|android-arm64|debug
+
diff --git a/docs/README-AR.md b/docs/README-AR.md
index ad7303806..4f5769839 100644
--- a/docs/README-AR.md
+++ b/docs/README-AR.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [Tiếng Việt ] | [Ελληνικά ]
لغتك الأم, Doc و RustDesk UI , README نحن بحاجة إلى مساعدتك لترجمة هذا
diff --git a/docs/README-CS.md b/docs/README-CS.md
index d56464eff..74c6fcb19 100644
--- a/docs/README-CS.md
+++ b/docs/README-CS.md
@@ -5,7 +5,7 @@
Docker •
Struktura •
Ukázky
- [English ] | [Українська ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, uživatelského rozhraní aplikace RustDesk a dokumentace k ní do vašeho jazyka
diff --git a/docs/README-DA.md b/docs/README-DA.md
index dde5c7a0d..d7283d8ab 100644
--- a/docs/README-DA.md
+++ b/docs/README-DA.md
@@ -5,7 +5,7 @@
Docker •
Filstruktur •
Skærmbilleder
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Vi har brug for din hjælp til at oversætte denne README, RustDesk UI og Dokument til dit modersmål
diff --git a/docs/README-DE.md b/docs/README-DE.md
index 8ee4a51fa..2c159bd07 100644
--- a/docs/README-DE.md
+++ b/docs/README-DE.md
@@ -5,21 +5,21 @@
Docker •
Dateistruktur •
Screenshots
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Dansk ]
- Wir brauchen deine Hilfe, um dieses README, die RustDesk-Benutzeroberfläche und die Dokumentation in deine Muttersprache zu übersetzen.
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Dansk ] | [Ελληνικά ]
+ Wir brauchen Ihre Hilfe, um dieses README, die RustDesk-Benutzeroberfläche und die Dokumentation in Ihre Muttersprache zu übersetzen.
-Rede mit uns auf: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
+Reden Sie mit uns auf: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
[](https://ko-fi.com/I2I04VU09)
-RustDesk ist eine in Rust geschriebene Remote-Desktop-Software, die out of the box ohne besondere Konfiguration funktioniert. Du hast die volle Kontrolle über deine Daten und musst dir keine Sorgen um die Sicherheit machen. Du kannst unseren Rendezvous/Relay-Server nutzen, [einen eigenen Server aufsetzen](https://rustdesk.com/server) oder [einen eigenen Server programmieren](https://github.com/rustdesk/rustdesk-server-demo).
+RustDesk ist eine in Rust geschriebene Remote-Desktop-Software, die out of the box ohne besondere Konfiguration funktioniert. Sie haben die volle Kontrolle über Ihre Daten und müssen sich keine Sorgen um die Sicherheit machen. Sie können unseren Rendezvous/Relay-Server nutzen, [einen eigenen Server aufsetzen](https://rustdesk.com/server) oder [einen eigenen Server programmieren](https://github.com/rustdesk/rustdesk-server-demo).

-RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) an, wenn du Unterstützung beim Start brauchst.
+RustDesk heißt jegliche Mitarbeit willkommen. Schauen Sie sich [CONTRIBUTING-DE.md](CONTRIBUTING-DE.md) an, wenn Sie Unterstützung beim Start brauchen.
-[**Wie arbeitet RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
+[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
[**Programm herunterladen**](https://github.com/rustdesk/rustdesk/releases)
@@ -31,21 +31,29 @@ RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [`docs/CONTRIBUTING.md`
## Freie öffentliche Server
-Nachfolgend sind die Server gelistet, die du kostenlos nutzen kannst. Es kann sein, dass sich diese Liste immer mal wieder ändert. Falls du nicht in der Nähe einer dieser Server bist, kann es sein, dass deine Verbindung langsam sein wird.
+Nachfolgend sind die Server gelistet, die Sie kostenlos nutzen können. Es kann sein, dass sich diese Liste immer mal wieder ändert. Falls Sie nicht in der Nähe einer dieser Server sind, kann es sein, dass Ihre Verbindung langsam sein wird.
| Standort | Anbieter | Spezifikation |
| --------- | ------------- | ------------------ |
-| Südkorea (Seoul) | AWS lightsail | 1 vCPU / 0,5 GB RAM |
-| Deutschland | Hetzner | 2 vCPU / 4 GB RAM |
-| Deutschland | Codext | 4 vCPU / 8 GB RAM |
-| Finnland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8 GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8 GB RAM |
-| Ukraine (Kiew) | dc.volia (2VM) | 2 vCPU / 4 GB RAM |
+| Südkorea (Seoul) | [AWS lightsail](https://aws.amazon.com/de/) | 1 vCPU / 0,5 GB RAM |
+| Deutschland | [Hetzner](https://www.hetzner.com/de/) | 2 vCPU / 4 GB RAM |
+| Deutschland | [Codext](https://codext.de/) | 4 vCPU / 8 GB RAM |
+| Finnland (Helsinki) | [Netlock](https://netlockendpoint.com/de/index.html) | 4 vCPU / 8 GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com/de/index.html) | 4 vCPU / 8 GB RAM |
+| Ukraine (Kiew) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
+
+## Dev-Container
+
+[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
+
+Wenn Sie VS Code und Docker bereits installiert haben, können Sie auf das Abzeichen oben klicken, um loszulegen. Wenn Sie darauf klicken, wird VS Code automatisch die Dev-Container-Erweiterung installieren, den Quellcode in ein Container-Volume klonen und einen Dev-Container für die Verwendung aufsetzen.
+
+Weitere Informationen finden Sie in [DEVCONTAINER-DE.md](DEVCONTAINER-DE.md).
## Abhängigkeiten
Desktop-Versionen verwenden [Sciter](https://sciter.com/) oder Flutter für die GUI, dieses Tutorial ist nur für Sciter.
-Bitte lade die dynamische Bibliothek Sciter selbst herunter.
+Bitte laden Sie die dynamische Bibliothek Sciter selbst herunter.
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
@@ -53,14 +61,14 @@ Bitte lade die dynamische Bibliothek Sciter selbst herunter.
## Grobe Schritte zum Kompilieren
-- Bereite deine Rust-Entwicklungsumgebung und C++-Build-Umgebung vor
+- Bereiten Sie Ihre Rust-Entwicklungsumgebung und C++-Build-Umgebung vor
-- Installiere [vcpkg](https://github.com/microsoft/vcpkg) und füge die Systemumgebungsvariable `VCPKG_ROOT` hinzu
+- Installieren Sie [vcpkg](https://github.com/microsoft/vcpkg) und fügen Sie die Systemumgebungsvariable `VCPKG_ROOT` hinzu
- Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static`
- Linux/macOS: `vcpkg install libvpx libyuv opus`
-- Nutze `cargo run`
+- Nutzen Sie `cargo run`
## [Erstellen](https://rustdesk.com/docs/de/dev/build/)
@@ -159,7 +167,7 @@ method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=2
## Auf Docker kompilieren
-Beginne damit, das Repository zu klonen und den Docker-Container zu bauen:
+Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen:
```sh
git clone https://github.com/rustdesk/rustdesk
@@ -167,25 +175,25 @@ cd rustdesk
docker build -t "rustdesk-builder" .
```
-Führe jedes Mal, wenn du das Programm kompilieren musst, folgenden Befehl aus:
+Führen Sie jedes Mal, wenn Sie das Programm kompilieren müssen, folgenden Befehl aus:
```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
```
-Bedenke, dass das erste Kompilieren länger dauern kann, bis die Abhängigkeiten zwischengespeichert sind. Nachfolgende Kompiliervorgänge sind schneller. Wenn du verschiedene Argumente für den Kompilierbefehl angeben musst, kannst du dies am Ende des Befehls an der Position `` tun. Wenn du zum Beispiel eine optimierte Releaseversion kompilieren willst, kannst du `--release` am Ende des Befehls anhängen. Das daraus entstehende Programm findest du im Zielordner auf deinem System. Du kannst es mit folgendem Befehl ausführen:
+Bedenken Sie, dass das erste Kompilieren länger dauern kann, bis die Abhängigkeiten zwischengespeichert sind. Nachfolgende Kompiliervorgänge sind schneller. Wenn Sie verschiedene Argumente für den Kompilierbefehl angeben müssen, können Sie dies am Ende des Befehls an der Position `` tun. Wenn Sie zum Beispiel eine optimierte Releaseversion kompilieren wollen, können Sie `--release` am Ende des Befehls anhängen. Das daraus entstehende Programm finden Sie im Zielordner auf Ihrem System. Sie können es mit folgendem Befehl ausführen:
```sh
target/debug/rustdesk
```
-Oder, wenn du eine Releaseversion benutzt:
+Oder, wenn Sie eine Releaseversion benutzen:
```sh
target/release/rustdesk
```
-Bitte stelle sicher, dass du diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzt. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenke auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf deinem eigentlichen System.
+Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System.
## Dateistruktur
diff --git a/docs/README-EO.md b/docs/README-EO.md
index 7471636eb..4bca4a793 100644
--- a/docs/README-EO.md
+++ b/docs/README-EO.md
@@ -5,7 +5,7 @@
Docker •
Strukturo •
Ekrankopio
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Ni bezonas helpon traduki tiun README kaj la interfacon al via denaska lingvo
@@ -27,8 +27,9 @@ Malsupre estas la serviloj, kiuj vi uzas senpage, ĝi povas ŝanĝi laŭlonge de
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dependantaĵoj
diff --git a/docs/README-ES.md b/docs/README-ES.md
index 16f65adcc..66fc609fb 100644
--- a/docs/README-ES.md
+++ b/docs/README-ES.md
@@ -5,7 +5,7 @@
Docker •
Estructura •
Capturas de pantalla
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Necesitamos tu ayuda para traducir este README a tu idioma
@@ -34,8 +34,9 @@ A continuación se muestran los servidores gratuitos, pueden cambiar a medida qu
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dependencias
diff --git a/docs/README-FA.md b/docs/README-FA.md
index 496e81849..177e3c122 100644
--- a/docs/README-FA.md
+++ b/docs/README-FA.md
@@ -6,7 +6,7 @@
ساخت •
سرور
-[English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+[English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
برای ترجمه این سند (README)، رابط کاربری RustDesk ، و مستندات آن به زبان مادری شما به کمکتان نیازمندیم.
با ما گفتگو کنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV)
diff --git a/docs/README-FI.md b/docs/README-FI.md
index f7a087087..8674bc1b3 100644
--- a/docs/README-FI.md
+++ b/docs/README-FI.md
@@ -5,7 +5,7 @@
Docker •
Rakenne •
Tilannevedos
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi
@@ -27,8 +27,9 @@ Alla on palvelimia, joita voit käyttää ilmaiseksi, ne saattavat muuttua ajan
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Riippuvuudet
diff --git a/docs/README-FR.md b/docs/README-FR.md
index fdb253bd0..c11edc211 100644
--- a/docs/README-FR.md
+++ b/docs/README-FR.md
@@ -5,7 +5,7 @@
Docker -
Structure -
Images
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle .
diff --git a/docs/README-GR.md b/docs/README-GR.md
new file mode 100644
index 000000000..8ec98030d
--- /dev/null
+++ b/docs/README-GR.md
@@ -0,0 +1,219 @@
+
+
+ Διακομιστές •
+ Build •
+ Docker •
+ Δομή •
+ Στιγμιότυπα
+ [English ] | [Українська ] | [česky ] | [中文 ] | | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Dansk ]
+ Χρειαζόμαστε τη βοήθειά σας για να μεταφράσουμε αυτό το αρχείο README, το RustDesk UI και το Doc στη μητρική σας γλώσσα
+
+
+Επικοινωνήστε μαζί μας μέσω: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
+
+[](https://ko-fi.com/I2I04VU09)
+
+Ένα λογισμικό απομακρυσμένης επιφάνειας εργασίας, γραμμένο σε γλώσσα Rust. Δεν χρειάζεται κάποια παραμετροποίηση, λειτουργεί αμέσως μετά την εγκατάσταση. Έχετε τον πλήρη έλεγχο των δεδομένων σας, χωρίς να ανησυχείτε για την ασφάλειά τους. Μπορείτε να χρησιμοποιήσετε τους προκαθορισμένους διακομιστές rendezvous/αναμετάδοσης, [να εγκαταστήσετε τον δικό σας διακομιστή](https://rustdesk.com/server), ή [να αναπτύξετε ένα δικό σας διακομιστή rendezvous/αναμετάδοσης](https://github.com/rustdesk/rustdesk-server-demo).
+
+
+
+Το RustDesk ενθαρρύνει τη συνεισφορά όλων. Διαβάστε το [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) για βοήθεια στο πως να ξεκινήσετε.
+
+[**Συχνές ερωτήσεις**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
+
+[**Κατεβάστε τα αρχεία**](https://github.com/rustdesk/rustdesk/releases)
+
+[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
+
+[ ](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
+
+## Δωρεάν δημόσιοι διακομιστές
+
+Παρακάτω είναι οι διακομιστές που χρησιμοποιούνται δωρεάν, ενδέχεται να αλλάξουν με την πάροδο του χρόνου. Εάν δεν είστε κοντά σε ένα από αυτούς, το δίκτυό σας ίσως να είναι αργό.
+| Περιοχή | Πάροχος | Προδιαγραφές |
+| --------- | ------------- | ------------------ |
+| Σεούλ | AWS lightsail | 1 vCPU / 0.5GB RAM |
+| Γερμανία | Hetzner | 2 vCPU / 4GB RAM |
+| Γερμανία | Codext | 4 vCPU / 8GB RAM |
+| Φινλανδία (Ελσίνκι) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| ΗΠΑ (Άσμπερν) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ουκρανία (Κίεβο) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
+
+## Dev Container
+
+[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
+
+Αν έχετε εγκατεστημένα το VS Code και το Docker, μπορείτε να ξεκινήσετε κάνοντας κλικ στην παραπάνω εικόνα. Αυτό θα έχει ως αποτέλεσμα, το VS Code να εγκαταστήσει αυτόματα την επέκταση Dev Containers, εάν χρειάζεται, θα κλωνοποιήσει τον πηγαίο κώδικα σε έναν νέο container και θα εκκινήσει ένα Dev Container για χρήση προγραμματισμού.
+
+Για περισσότερες πληροφορίες μεταβείτε στο [DEVCONTAINER.md](docs/DEVCONTAINER.md).
+
+## Προαπαιτούμενα για build
+
+Στις παραθυρικές εκδόσεις χρησιμοποιείται είτε το [sciter](https://sciter.com/) είτε το Flutter, τα παρακάτω βήματα είναι μόνο για το Sciter.
+
+Παρακαλώ κατεβάστε μόνοι σας την δυναμική βιβλιοθήκη sciter.
+
+[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
+[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
+[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
+
+## Γενικά βήματα ώστε να κάνετε build
+
+- Προετοιμάστε τα περιβάλλοντα προγραμματισμού Rust και C++
+
+- Εγκαταστήσετε το [vcpkg](https://github.com/microsoft/vcpkg), και ρυθμίστε σωστά την παράμετρο συστήματος `VCPKG_ROOT`
+
+ - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
+ - Linux/MacOS: vcpkg install libvpx libyuv opus
+
+- Εκτελέστε `cargo run`
+
+## [Build](https://rustdesk.com/docs/en/dev/build/)
+
+## Πως να το κάνετε build στο Linux
+
+### Ubuntu 18 (Debian 10)
+
+```sh
+sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
+ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
+ libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
+```
+
+### openSUSE Tumbleweed
+
+```sh
+sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
+```
+### Fedora 28 (CentOS 8)
+
+```sh
+sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
+```
+
+### Arch (Manjaro)
+
+```sh
+sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
+```
+
+### Εγκατάσταση vcpkg
+
+```sh
+git clone https://github.com/microsoft/vcpkg
+cd vcpkg
+git checkout 2021.12.01
+cd ..
+vcpkg/bootstrap-vcpkg.sh
+export VCPKG_ROOT=$HOME/vcpkg
+vcpkg/vcpkg install libvpx libyuv opus
+```
+
+### Διόρθωση libvpx (για Fedora)
+
+```sh
+cd vcpkg/buildtrees/libvpx/src
+cd *
+./configure
+sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
+sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
+make
+cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
+cd
+```
+
+### Build
+
+```sh
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+source $HOME/.cargo/env
+git clone https://github.com/rustdesk/rustdesk
+cd rustdesk
+mkdir -p target/debug
+wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
+mv libsciter-gtk.so target/debug
+VCPKG_ROOT=$HOME/vcpkg cargo run
+```
+
+### Αλλαγή του Wayland σε X11 (Xorg)
+
+Το RustDesk δεν υποστηρίζει το πρωτόκολλο Wayland. Διαβάστε [εδώ](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) ώστε να ορίσετε το Xorg ως το προκαθορισμένο GNOME περιβάλλον.
+
+## Υποστήριξη Wayland
+
+Το Wayland προς το παρόν δεν διαθέτει κάποιο API το οποίο να στέλνει τα πατήματα πλήκτρων στα υπόλοιπα παράθυρα. Για τον λόγο αυτό, το Rustdesk χρησιμοποιεί ένα API από κατώτερο επίπεδο, όπως το `/dev/uinput` (Linux kernel level).
+
+Σε περίπτωση που το Wayland είναι η ελεγχόμενη πλευρά, θα πρέπει να ξεκινήσετε με τον παρακάτω τρόπο:
+```bash
+# Start uinput service
+$ sudo rustdesk --service
+$ rustdesk
+```
+**Σημείωση**: Η εγγραφή οθόνης του Wayland χρησιμοποιεί διαφορετικές διεπαφές. Το RustDesk προς το παρόν υποστηρίζει μόνο org.freedesktop.portal.ScreenCast.
+```bash
+$ dbus-send --session --print-reply \
+ --dest=org.freedesktop.portal.Desktop \
+ /org/freedesktop/portal/desktop \
+ org.freedesktop.DBus.Properties.Get \
+ string:org.freedesktop.portal.ScreenCast string:version
+# Not support
+Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
+# Support
+method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
+ variant uint32 4
+```
+
+## Πως να κάνετε build στο Docker
+
+Ξεκινήστε κλωνοποιώντας το αποθετήριο και κάνοντας build το docker container:
+
+```sh
+git clone https://github.com/rustdesk/rustdesk
+cd rustdesk
+docker build -t "rustdesk-builder" .
+```
+
+Στη συνέχεια, κάθε φορά που επιθυμείτε να κάνετε build την εφαρμογή, εκτελέστε την ακόλουθη εντολή:
+
+```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
+```
+
+Σημειώστε ότι το πρώτο build μπορεί να διαρκέσει περισσότερο, ώστε να αποθηκευτούν στην προσωρινή μνήμη οι εξαρτήσεις, τα επόμενα build θα είναι ταχύτερα. Επιπλέον, εάν πρέπει να καθορίσετε διαφορετικές παραμέτρους στην εντολή build, μπορείτε να το κάνετε στο τέλος της εντολής με την χρήση ``. Για παράδειγμα, εάν επιθυμείτε να δημιουργήσετε μια βελτιστοποιημένη έκδοση της εφαρμογής, θα εκτελέσετε την παραπάνω εντολή ακολουθούμενη από το `--release`. Το εκτελέσιμο αρχείο θα είναι διαθέσιμο στον προκαθορισμένο φάκελο στο σύστημά σας και μπορεί να εκτελεστεί με:
+
+```sh
+target/debug/rustdesk
+```
+
+Ή στην περίπτωση μιας βελτιστοποιημένης έκδοσης της εφαρμογής εκτελέστε:
+
+```sh
+target/release/rustdesk
+```
+
+Βεβαιωθείτε ότι εκτελείτε αυτές τις εντολές από την αρχική διαδρομή του αποθετηρίου του Rustdesk, διαφορετικά η εφαρμογή ενδέχεται να μην είναι σε θέση να βρεί τους απαιτούμενους πόρους. Σημειώστε επίσης ότι άλλες υποεντολές, όπως το `install` ή το `run` δεν υποστηρίζονται επί του παρόντος μέσω αυτής της μεθόδου καθώς θα εγκαταστήσουν ή θα εκτελέσουν το πρόγραμμα εντός του container αντί του κεντρικού υπολογιστή.
+
+## Δομή φακέλων
+
+- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions
+- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture
+- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control
+- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
+- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections
+- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection
+- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection
+- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code
+- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
+- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
+
+## Στιγμιότυπα
+
+
+
+
+
+
+
+
diff --git a/docs/README-HU.md b/docs/README-HU.md
index 6c22a3b7c..9582cf1c6 100644
--- a/docs/README-HU.md
+++ b/docs/README-HU.md
@@ -5,7 +5,7 @@
Docker •
Struktúra •
Képernyőképek
- [English ] | [Українська ] | [česky ] | [中文 ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Kell a segítséged, hogy lefordítsuk ezt a README-t, a RustDesk UI-t és a Dokumentációt az anyanyelvedre
@@ -35,8 +35,9 @@ Ezalatt az üzenet alatt találhatóak azok a publikus szerverek, amelyeket ingy
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dependencies
diff --git a/docs/README-ID.md b/docs/README-ID.md
index 9616cd31d..702966566 100644
--- a/docs/README-ID.md
+++ b/docs/README-ID.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Kami membutuhkan bantuan Anda untuk menerjemahkan README ini dan RustDesk UI ke bahasa asli anda
@@ -27,8 +27,9 @@ Di bawah ini adalah server yang bisa Anda gunakan secara gratis, dapat berubah s
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dependencies
diff --git a/docs/README-IT.md b/docs/README-IT.md
index f074510c9..2dec27e40 100644
--- a/docs/README-IT.md
+++ b/docs/README-IT.md
@@ -5,7 +5,7 @@
Docker •
Struttura •
Screenshots
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Abbiamo bisogno del tuo aiuto per tradurre questo README e la RustDesk UI nella tua lingua nativa
@@ -27,8 +27,9 @@ Qui sotto trovate i server che possono essere usati gratuitamente, la lista potr
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Dipendenze
diff --git a/docs/README-JP.md b/docs/README-JP.md
index 36c74dfed..fafc5ef8c 100644
--- a/docs/README-JP.md
+++ b/docs/README-JP.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。
diff --git a/docs/README-KR.md b/docs/README-KR.md
index 8cefbbcee..6f9ba2221 100644
--- a/docs/README-KR.md
+++ b/docs/README-KR.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.
diff --git a/docs/README-ML.md b/docs/README-ML.md
index 288a78db8..5b4c3782a 100644
--- a/docs/README-ML.md
+++ b/docs/README-ML.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്
diff --git a/docs/README-NL.md b/docs/README-NL.md
index 1aca2b893..4a5372e67 100644
--- a/docs/README-NL.md
+++ b/docs/README-NL.md
@@ -5,7 +5,7 @@
Docker •
Structuur •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal
@@ -27,8 +27,9 @@ Onderstaande servers zijn de servers die je gratis kunt gebruiken, ze kunnen op
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Afhankelijkheden
diff --git a/docs/README-PL.md b/docs/README-PL.md
index 85c5f4a61..df8254f3d 100644
--- a/docs/README-PL.md
+++ b/docs/README-PL.md
@@ -5,7 +5,7 @@
Docker •
Struktura •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język
@@ -27,8 +27,9 @@ Poniżej znajdują się serwery, z których można korzystać za darmo, może si
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
## Zależności
diff --git a/docs/README-PTBR.md b/docs/README-PTBR.md
index f9d5e0fc3..491d53154 100644
--- a/docs/README-PTBR.md
+++ b/docs/README-PTBR.md
@@ -5,7 +5,7 @@
Docker •
Estrutura •
Screenshots
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Precisamos de sua ajuda para traduzir este README e a UI do RustDesk para sua língua nativa
diff --git a/docs/README-RU.md b/docs/README-RU.md
index 242341a6b..b050d40ac 100644
--- a/docs/README-RU.md
+++ b/docs/README-RU.md
@@ -5,7 +5,7 @@
Docker •
Structure •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Нам нужна ваша помощь для перевода этого README и RustDesk UI на ваш родной язык
diff --git a/docs/README-UA.md b/docs/README-UA.md
index 3615b9064..222da34d2 100644
--- a/docs/README-UA.md
+++ b/docs/README-UA.md
@@ -1,11 +1,11 @@
- Servers •
- Build •
- Docker •
- Structure •
- Snapshot
- [English ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ Сервери •
+ Складання •
+ Docker •
+ Структура •
+ Знімки
+ [English ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Нам потрібна ваша допомога для перекладу цього README і RustDesk UI на вашу рідну мову
@@ -19,24 +19,37 @@
RustDesk вітає внесок кожного. Дивіться [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) для допомоги на початку роботи.
+[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
+
[**Як працює RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F)
-[**ЗАВАНТАЖИТИ ДОДАТОК**](https://github.com/rustdesk/rustdesk/releases)
+[**ЗАВАНТАЖИТИ ЗАСТОСУНОК**](https://github.com/rustdesk/rustdesk/releases)
-[ ](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
+[ ](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
## Безкоштовні загальнодоступні сервери
Нижче наведені сервери, для безкоштовного використання, вони можуть змінюватися з часом. Якщо ви не перебуваєте поруч з одним із них, ваша мережа може працювати повільно.
| Місцезнаходження | Постачальник | Технічні характеристики |
| --------- | ------------- | ------------------ |
-| Сеул | AWS lightsail | 1 vCPU / 0.5GB RAM |
+| Південна Корея (Сеул) | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Сінгапур | Vultr | 1 vCPU / 1GB RAM |
-| Даллас | Vultr | 1 vCPU / 1GB RAM
-Німеччина | Hetzner | 2 vCPU / 4GB RAM | 2 VCPU / 4GB RAM | Німеччина | Hetzner | 2 VCPU / 4GB RAM |
-| Germany | Codext | 4 vCPU / 8GB RAM |
-| Finland (Helsinki) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
-| USA (Ashburn) | 0x101 Cyber Security | 4 vCPU / 8GB RAM |
+| США (Даллас) | Vultr | 1 vCPU / 1GB RAM
+| Німеччина | Hetzner | 2 VCPU / 4GB RAM |
+| Німеччина | Codext | 4 vCPU / 8GB RAM |
+| Фінляндія (Гельсінкі) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| США (Ешберн) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
+| Україна (Київ) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
+
+## Dev Container
+
+[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
+
+Якщо у вас уже встановлено VS Code і Docker, ви можете натиснути значок вище, щоб почати. Клацання призведе до того, що VS Code автоматично встановить розширення Dev Containers, якщо це необхідно, клонує виcхідний код у том контейнера та розгорне контейнер dev для використання.
+
+Дивіться [DEVCONTAINER.md](docs/DEVCONTAINER.md) для додаткової інфо.
## Залежності
@@ -64,9 +77,16 @@ RustDesk вітає внесок кожного. Дивіться [`docs/CONTRIB
### Ubuntu 18 (Debian 10)
```sh
-sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
+sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
+ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
+ libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
```
+### openSUSE Tumbleweed
+
+```sh
+sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
+```
### Fedora 28 (CentOS 8)
```sh
@@ -91,30 +111,6 @@ export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus
```
-### Fedora 28 (CentOS 8)
-
-````sh
-sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel
-```
-
-### Arch (Manjaro)
-
-```sh
-sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
-```
-
-### Встановлення vcpkg
-
-```sh
-git clone https://github.com/microsoft/vcpkg
-cd vcpkg
-git checkout 2021.12.01
-cd ...
-vcpkg/bootstrap-vcpkg.sh
-export VCPKG_ROOT=$HOME/vcpkg
-vcpkg/vcpkg install libvpx libyuv opus
-```
-
### Виправлення libvpx (для Fedora)
```sh
@@ -183,8 +179,10 @@ target/release/rustdesk
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графічний інтерфейс користувача
- **[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/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: зв'яжіться з [rustdesk-server](https://github.com/rustdesk/rustdesk-server), дочекайтеся віддаленого прямого (обхід TCP NAT) або ретрансльованого з'єднання
+- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: комунікація з [rustdesk-server](https://github.com/rustdesk/rustdesk-server), очікування віддаленого прямого (обхід TCP NAT) або ретрансльованого з'єднання
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфічний для платформи код
+- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для мобільних пристроїв
+- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript для Flutter веб клієнту
## Знімки
diff --git a/docs/README-VN.md b/docs/README-VN.md
index 295f54c6b..2f66d011d 100644
--- a/docs/README-VN.md
+++ b/docs/README-VN.md
@@ -5,7 +5,7 @@
Docker •
Cấu trúc tệp tin •
Snapshot
- [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ]
+ [English ] | [Українська ] | [česky ] | [中文 ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Ελληνικά ]
Chúng tôi cần sự gíup đỡ của bạn để dịch trang README này, RustDesk UI và tài liệu sang ngôn ngữ bản địa của bạn
diff --git a/docs/README-ZH.md b/docs/README-ZH.md
index 7ec87ec50..27c35ff57 100644
--- a/docs/README-ZH.md
+++ b/docs/README-ZH.md
@@ -5,7 +5,7 @@
Docker •
结构 •
截图
- [English ] | [Українська ] | [česky ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ]
+ [English ] | [Українська ] | [česky ] | [Magyar ] | [Español ] | [فارسی ] | [Français ] | [Deutsch ] | [Polski ] | [Indonesian ] | [Suomi ] | [മലയാളം ] | [日本語 ] | [Nederlands ] | [Italiano ] | [Русский ] | [Português (Brasil) ] | [Esperanto ] | [한국어 ] | [العربي ] | [Tiếng Việt ] | [Ελληνικά ]
Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
diff --git a/docs/SECURITY-NL.md b/docs/SECURITY-NL.md
new file mode 100644
index 000000000..463b3228e
--- /dev/null
+++ b/docs/SECURITY-NL.md
@@ -0,0 +1,11 @@
+# Veiligheidsbeleid
+
+## Een Kwetsbaarheid Melden
+
+Wij hechten veel waarde aan de veiligheid van het project. We moedigen alle gebruikers aan om kwetsbaarheden die ze ontdekken
+aan ons te melden. Als u een beveiligingslek in het RustDesk project vindt, meld dit dan op verantwoorde wijze door
+een e-mail te sturen naar info@rustdesk.com.
+
+Op dit moment hebben we geen bug premie programma. We zijn een klein team dat een groot probleem probeert op te lossen.
+We verzoeken u dringend om alle kwetsbaarheden op verantwoorde wijze te melden, zodat we verder kunnen bouwen aan
+een veilige applicatie voor de hele gemeenschap.
diff --git a/fastlane/metadata/android/full_description.txt b/fastlane/metadata/android/full_description.txt
deleted file mode 100644
index 4a0bdb13e..000000000
--- a/fastlane/metadata/android/full_description.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-Een open-source remote desktop toepassing, het open source TeamViewer alternatief.
-Broncode: https://github.com/rustdesk/rustdesk
-Doc: https://rustdesk.com/docs/en/manual/mobile/
-
-Om ervoor te zorgen dat een extern apparaat uw Android-apparaat via muis of aanraking kan besturen, moet u RustDesk toestaan om de "Accessibility" service te gebruiken. RustDesk gebruikt de AccessibilityService API om Android afstandsbediening te implementeren.
-
-Naast afstandsbediening kunt u met RustDesk ook eenvoudig bestanden overzetten tussen Android-apparaten en pc's.
-
-U hebt volledige controle over uw gegevens, zonder zich zorgen te maken over de veiligheid. U kunt onze rendez-vous/relay server gebruiken, of zelf hosten, of uw eigen rendez-vous/relay server schrijven. Self-hosting server is gratis en open source: https://github.com/rustdesk/rustdesk-server
-
-Download en installeer de desktop versie van: https://rustdesk.com, dan kunt u uw desktop benaderen en bedienen vanaf uw mobiel, of uw mobiel bedienen vanaf uw desktop.
diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt
new file mode 100644
index 000000000..b60d52ceb
--- /dev/null
+++ b/fastlane/metadata/android/nl-NL/full_description.txt
@@ -0,0 +1,11 @@
+Een open-source toepassing voor bureaublad op afstand, het open-source alternatief voor TeamViewer.
+Bron code: https://github.com/rustdesk/rustdesk
+Doc: https://rustdesk.com/docs/en/manual/mobile/
+
+Om ervoor te zorgen dat een extern apparaat uw Android-apparaat via muis of aanraking kan besturen, moet u RustDesk toestaan de "Toegankelijkheid" service te gebruiken. RustDesk gebruikt AccessibilityService API om Android afstandsbediening te kunnen implementeren.
+
+Naast bediening op afstand kunt u met RustDesk ook eenvoudig bestanden overzetten tussen Android-apparaten en pc's.
+
+U hebt volledige controle over uw gegevens, en u hoeft zich geen zorgen te maken over de veiligheid. U kunt onze rendez-vous/relay server gebruiken, of zelf hosten, of uw eigen rendez-vous/relay server schrijven. Self-hosting server is gratis en open source: https://github.com/rustdesk/rustdesk-server
+
+Download en installeer de desktop versie vanaf: https://rustdesk.com, dan kunt u uw desktop benaderen en bedienen vanaf uw mobiel, of uw mobiel bedienen vanaf uw desktop.
diff --git a/fastlane/metadata/android/nl-NL/short_description.txt b/fastlane/metadata/android/nl-NL/short_description.txt
new file mode 100644
index 000000000..18a46000b
--- /dev/null
+++ b/fastlane/metadata/android/nl-NL/short_description.txt
@@ -0,0 +1 @@
+Een open-source toepassing voor bureaublad op afstand, het open-source alternatief voor TeamViewer.
diff --git a/fastlane/metadata/android/short_description.txt b/fastlane/metadata/android/short_description.txt
deleted file mode 100644
index ed4d6eabb..000000000
--- a/fastlane/metadata/android/short_description.txt
+++ /dev/null
@@ -1 +0,0 @@
-Een open-source remote desktop applicatie, het open source alternatief voor TeamViewer.
diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json
index d7f6e316e..3cfca4d30 100644
--- a/flatpak/rustdesk.json
+++ b/flatpak/rustdesk.json
@@ -1,9 +1,10 @@
{
- "app-id": "org.rustdesk.rustdesk",
+ "id": "com.rustdesk.RustDesk",
"runtime": "org.freedesktop.Platform",
"runtime-version": "21.08",
"sdk": "org.freedesktop.Sdk",
"command": "rustdesk",
+ "icon": "share/rustdesk/files/rustdesk.png",
"modules": [
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
"xdotool.json",
@@ -13,13 +14,22 @@
"build-commands": [
"bsdtar -zxvf rustdesk-1.2.0.deb",
"tar -xvf ./data.tar.xz",
- "cp -r ./usr /app/",
- "mkdir -p /app/bin && ln -s /app/usr/lib/rustdesk/rustdesk /app/bin/rustdesk"
+ "cp -r ./usr/* /app/",
+ "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",
+ "sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/com.rustdesk.RustDesk.desktop",
+ "sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/rustdesk-link.desktop",
+ "for size in 16 24 32 48 64 128 256 512; do\n rsvg-convert -w $size -h $size -f png -o $size.png logo.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": [
{
"type": "file",
"path": "../rustdesk-1.2.0.deb"
+ },
+ {
+ "type": "file",
+ "path": "../res/logo.svg"
}
]
}
@@ -35,4 +45,4 @@
"--socket=pulseaudio",
"--talk-name=org.freedesktop.Flatpak"
]
-}
\ No newline at end of file
+}
diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml
index 9b25f4973..b3c655917 100644
--- a/flutter/android/app/src/main/AndroidManifest.xml
+++ b/flutter/android/app/src/main/AndroidManifest.xml
@@ -11,21 +11,25 @@
-
+
+
+ android:supportsRtl="true">
+ android:enabled="true"
+ android:exported="true">
+
+
+
@@ -52,8 +56,6 @@
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
-
-
@@ -61,6 +63,11 @@
+
+
-
+
\ No newline at end of file
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
index 328701567..71bbba754 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt
@@ -1,21 +1,45 @@
package com.carriez.flutter_hbb
+import android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
+import android.util.Log
import android.widget.Toast
+import com.hjq.permissions.XXPermissions
+import io.flutter.embedding.android.FlutterActivity
+
+const val DEBUG_BOOT_COMPLETED = "com.carriez.flutter_hbb.DEBUG_BOOT_COMPLETED"
class BootReceiver : BroadcastReceiver() {
+ private val logTag = "tagBootReceiver"
+
override fun onReceive(context: Context, intent: Intent) {
- if ("android.intent.action.BOOT_COMPLETED" == intent.action){
- val it = Intent(context,MainService::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Log.d(logTag, "onReceive ${intent.action}")
+
+ if (Intent.ACTION_BOOT_COMPLETED == intent.action || DEBUG_BOOT_COMPLETED == intent.action) {
+ // check SharedPreferences config
+ val prefs = context.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
+ if (!prefs.getBoolean(KEY_START_ON_BOOT_OPT, false)) {
+ Log.d(logTag, "KEY_START_ON_BOOT_OPT is false")
+ return
}
- Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show();
+ // check pre-permission
+ if (!XXPermissions.isGranted(context, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, SYSTEM_ALERT_WINDOW)){
+ Log.d(logTag, "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS or SYSTEM_ALERT_WINDOW is not granted")
+ return
+ }
+
+ val it = Intent(context, MainService::class.java).apply {
+ action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
+ putExtra(EXT_INIT_FROM_BOOT, true)
+ }
+ Toast.makeText(context, "RustDesk is Open", Toast.LENGTH_LONG).show()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(it)
- }else{
+ } else {
context.startService(it)
}
}
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
index fd340f7ed..52a5ff75e 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt
@@ -7,35 +7,29 @@ package com.carriez.flutter_hbb
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/
-import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
-import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.IBinder
-import android.provider.Settings
import android.util.Log
import android.view.WindowManager
-import androidx.annotation.RequiresApi
+import com.hjq.permissions.XXPermissions
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
-const val MEDIA_REQUEST_CODE = 42
class MainActivity : FlutterActivity() {
companion object {
- lateinit var flutterMethodChannel: MethodChannel
+ var flutterMethodChannel: MethodChannel? = null
}
private val channelTag = "mChannel"
private val logTag = "mMainActivity"
- private var mediaProjectionResultIntent: Intent? = null
private var mainService: MainService? = null
- @RequiresApi(Build.VERSION_CODES.M)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
if (MainService.isReady) {
@@ -46,169 +40,32 @@ class MainActivity : FlutterActivity() {
flutterMethodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
channelTag
- ).apply {
- // make sure result is set, otherwise flutter will await forever
- setMethodCallHandler { call, result ->
- when (call.method) {
- "init_service" -> {
- Intent(activity, MainService::class.java).also {
- bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
- }
- if (MainService.isReady) {
- result.success(false)
- return@setMethodCallHandler
- }
- getMediaProjection()
- result.success(true)
- }
- "start_capture" -> {
- mainService?.let {
- result.success(it.startCapture())
- } ?: let {
- result.success(false)
- }
- }
- "stop_service" -> {
- Log.d(logTag, "Stop service")
- mainService?.let {
- it.destroy()
- result.success(true)
- } ?: let {
- result.success(false)
- }
- }
- "check_permission" -> {
- if (call.arguments is String) {
- result.success(checkPermission(context, call.arguments as String))
- } else {
- result.success(false)
- }
- }
- "request_permission" -> {
- if (call.arguments is String) {
- requestPermission(context, call.arguments as String)
- result.success(true)
- } else {
- result.success(false)
- }
- }
- "check_video_permission" -> {
- mainService?.let {
- result.success(it.checkMediaPermission())
- } ?: let {
- result.success(false)
- }
- }
- "check_service" -> {
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "input", "value" to InputService.isOpen.toString())
- )
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "media", "value" to MainService.isReady.toString())
- )
- result.success(true)
- }
- "init_input" -> {
- initInput()
- result.success(true)
- }
- "stop_input" -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- InputService.ctx?.disableSelf()
- }
- InputService.ctx = null
- flutterMethodChannel.invokeMethod(
- "on_state_changed",
- mapOf("name" to "input", "value" to InputService.isOpen.toString())
- )
- result.success(true)
- }
- "cancel_notification" -> {
- try {
- val id = call.arguments as Int
- mainService?.cancelNotification(id)
- } finally {
- result.success(true)
- }
- }
- "enable_soft_keyboard" -> {
- // https://blog.csdn.net/hanye2020/article/details/105553780
- try {
- if (call.arguments as Boolean) {
- window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
- } else {
- window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
- }
- } finally {
- result.success(true)
- }
- }
- else -> {
- result.error("-1", "No such method", null)
- }
- }
- }
- }
- }
-
- private fun getMediaProjection() {
- val mMediaProjectionManager =
- getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
- val mIntent = mMediaProjectionManager.createScreenCaptureIntent()
- startActivityForResult(mIntent, MEDIA_REQUEST_CODE)
- }
-
- private fun initService() {
- if (mediaProjectionResultIntent == null) {
- Log.w(logTag, "initService fail,mediaProjectionResultIntent is null")
- return
- }
- Log.d(logTag, "Init service")
- val serviceIntent = Intent(this, MainService::class.java)
- serviceIntent.action = INIT_SERVICE
- serviceIntent.putExtra(EXTRA_MP_DATA, mediaProjectionResultIntent)
-
- launchMainService(serviceIntent)
- }
-
- private fun launchMainService(intent: Intent) {
- // TEST api < O
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- startForegroundService(intent)
- } else {
- startService(intent)
- }
- }
-
- private fun initInput() {
- val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
- if (intent.resolveActivity(packageManager) != null) {
- startActivity(intent)
- }
+ )
+ initFlutterChannel(flutterMethodChannel!!)
}
override fun onResume() {
super.onResume()
val inputPer = InputService.isOpen
activity.runOnUiThread {
- flutterMethodChannel.invokeMethod(
+ flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to inputPer.toString())
)
}
}
+ private fun requestMediaProjection() {
+ val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
+ action = ACT_REQUEST_MEDIA_PROJECTION
+ }
+ startActivityForResult(intent, REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION)
+ }
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == MEDIA_REQUEST_CODE) {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mediaProjectionResultIntent = data
- initService()
- } else {
- flutterMethodChannel.invokeMethod("on_media_projection_canceled", null)
- }
+ if (requestCode == REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION && resultCode == RES_FAILED) {
+ flutterMethodChannel?.invokeMethod("on_media_projection_canceled", null)
}
}
@@ -232,4 +89,138 @@ class MainActivity : FlutterActivity() {
mainService = null
}
}
+
+ private fun initFlutterChannel(flutterMethodChannel: MethodChannel) {
+ flutterMethodChannel.setMethodCallHandler { call, result ->
+ // make sure result will be invoked, otherwise flutter will await forever
+ when (call.method) {
+ "init_service" -> {
+ Intent(activity, MainService::class.java).also {
+ bindService(it, serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+ if (MainService.isReady) {
+ result.success(false)
+ return@setMethodCallHandler
+ }
+ requestMediaProjection()
+ result.success(true)
+ }
+ "start_capture" -> {
+ mainService?.let {
+ result.success(it.startCapture())
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "stop_service" -> {
+ Log.d(logTag, "Stop service")
+ mainService?.let {
+ it.destroy()
+ result.success(true)
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "check_permission" -> {
+ if (call.arguments is String) {
+ result.success(XXPermissions.isGranted(context, call.arguments as String))
+ } else {
+ result.success(false)
+ }
+ }
+ "request_permission" -> {
+ if (call.arguments is String) {
+ requestPermission(context, call.arguments as String)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ START_ACTION -> {
+ if (call.arguments is String) {
+ startAction(context, call.arguments as String)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ "check_video_permission" -> {
+ mainService?.let {
+ result.success(it.checkMediaPermission())
+ } ?: let {
+ result.success(false)
+ }
+ }
+ "check_service" -> {
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "input", "value" to InputService.isOpen.toString())
+ )
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "media", "value" to MainService.isReady.toString())
+ )
+ result.success(true)
+ }
+ "stop_input" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ InputService.ctx?.disableSelf()
+ }
+ InputService.ctx = null
+ Companion.flutterMethodChannel?.invokeMethod(
+ "on_state_changed",
+ mapOf("name" to "input", "value" to InputService.isOpen.toString())
+ )
+ result.success(true)
+ }
+ "cancel_notification" -> {
+ if (call.arguments is Int) {
+ val id = call.arguments as Int
+ mainService?.cancelNotification(id)
+ } else {
+ result.success(true)
+ }
+ }
+ "enable_soft_keyboard" -> {
+ // https://blog.csdn.net/hanye2020/article/details/105553780
+ if (call.arguments as Boolean) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+ }
+ result.success(true)
+
+ }
+ GET_START_ON_BOOT_OPT -> {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
+ }
+ SET_START_ON_BOOT_OPT -> {
+ if (call.arguments is Boolean) {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ val edit = prefs.edit()
+ edit.putBoolean(KEY_START_ON_BOOT_OPT, call.arguments as Boolean)
+ edit.apply()
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ SYNC_APP_DIR_CONFIG_PATH -> {
+ if (call.arguments is String) {
+ val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
+ val edit = prefs.edit()
+ edit.putString(KEY_APP_DIR_CONFIG_PATH, call.arguments as String)
+ edit.apply()
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ }
+ else -> {
+ result.error("-1", "No such method", null)
+ }
+ }
+ }
+ }
}
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
index cf8e12e92..1c3fbce6c 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt
@@ -35,6 +35,7 @@ import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
+import io.flutter.embedding.android.FlutterActivity
import java.util.concurrent.Executors
import kotlin.concurrent.thread
import org.json.JSONException
@@ -43,10 +44,6 @@ import java.nio.ByteBuffer
import kotlin.math.max
import kotlin.math.min
-const val EXTRA_MP_DATA = "mp_intent"
-const val INIT_SERVICE = "init_service"
-const val ACTION_LOGIN_REQ_NOTIFY = "ACTION_LOGIN_REQ_NOTIFY"
-const val EXTRA_LOGIN_REQ_NOTIFY = "EXTRA_LOGIN_REQ_NOTIFY"
const val DEFAULT_NOTIFY_TITLE = "RustDesk"
const val DEFAULT_NOTIFY_TEXT = "Service is running"
@@ -147,7 +144,11 @@ class MainService : Service() {
// jvm call rust
private external fun init(ctx: Context)
- private external fun startServer()
+
+ /// When app start on boot, app_dir will not be passed from flutter
+ /// so pass a app_dir here to rust server
+ private external fun startServer(app_dir: String)
+ private external fun startService()
private external fun onVideoFrameUpdate(buf: ByteBuffer)
private external fun onAudioFrameUpdate(buf: ByteBuffer)
private external fun translateLocale(localeName: String, input: String): String
@@ -195,6 +196,7 @@ class MainService : Service() {
override fun onCreate() {
super.onCreate()
+ Log.d(logTag,"MainService onCreate")
HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
serviceLooper = looper
@@ -202,7 +204,13 @@ class MainService : Service() {
}
updateScreenInfo(resources.configuration.orientation)
initNotification()
- startServer()
+
+ // keep the config dir same with flutter
+ val prefs = applicationContext.getSharedPreferences(KEY_SHARED_PREFERENCES, FlutterActivity.MODE_PRIVATE)
+ val configPath = prefs.getString(KEY_APP_DIR_CONFIG_PATH, "") ?: ""
+ startServer(configPath)
+
+ createForegroundNotification()
}
override fun onDestroy() {
@@ -277,22 +285,30 @@ class MainService : Service() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- Log.d("whichService", "this service:${Thread.currentThread()}")
+ Log.d("whichService", "this service: ${Thread.currentThread()}")
super.onStartCommand(intent, flags, startId)
- if (intent?.action == INIT_SERVICE) {
- Log.d(logTag, "service starting:${startId}:${Thread.currentThread()}")
+ if (intent?.action == ACT_INIT_MEDIA_PROJECTION_AND_SERVICE) {
createForegroundNotification()
- val mMediaProjectionManager =
+
+ if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) {
+ startService()
+ }
+ Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}")
+ val mediaProjectionManager =
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
- intent.getParcelableExtra(EXTRA_MP_DATA)?.let {
+
+ intent.getParcelableExtra(EXT_MEDIA_PROJECTION_RES_INTENT)?.let {
mediaProjection =
- mMediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
+ mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it)
checkMediaPermission()
init(this)
_isReady = true
+ } ?: let {
+ Log.d(logTag, "getParcelableExtra intent null, invoke requestMediaProjection")
+ requestMediaProjection()
}
}
- return START_NOT_STICKY // don't use sticky (auto restart),the new service (from auto restart) will lose control
+ return START_NOT_STICKY // don't use sticky (auto restart), the new service (from auto restart) will lose control
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -300,6 +316,14 @@ class MainService : Service() {
updateScreenInfo(newConfig.orientation)
}
+ private fun requestMediaProjection() {
+ val intent = Intent(this, PermissionRequestTransparentActivity::class.java).apply {
+ action = ACT_REQUEST_MEDIA_PROJECTION
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ startActivity(intent)
+ }
+
@SuppressLint("WrongConstant")
private fun createSurface(): Surface? {
return if (useVP9) {
@@ -400,13 +424,13 @@ class MainService : Service() {
fun checkMediaPermission(): Boolean {
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "media", "value" to isReady.toString())
)
}
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
@@ -599,7 +623,7 @@ class MainService : Service() {
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentTitle(DEFAULT_NOTIFY_TITLE)
- .setContentText(translate(DEFAULT_NOTIFY_TEXT) + '!')
+ .setContentText(translate(DEFAULT_NOTIFY_TEXT))
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setColor(ContextCompat.getColor(this, R.color.primary))
@@ -653,8 +677,8 @@ class MainService : Service() {
@SuppressLint("UnspecifiedImmutableFlag")
private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent {
val intent = Intent(this, MainService::class.java).apply {
- action = ACTION_LOGIN_REQ_NOTIFY
- putExtra(EXTRA_LOGIN_REQ_NOTIFY, res)
+ action = ACT_LOGIN_REQ_NOTIFY
+ putExtra(EXT_LOGIN_REQ_NOTIFY, res)
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getService(this, 111, intent, FLAG_IMMUTABLE)
@@ -665,7 +689,7 @@ class MainService : Service() {
private fun setTextNotification(_title: String?, _text: String?) {
val title = _title ?: DEFAULT_NOTIFY_TITLE
- val text = _text ?: translate(DEFAULT_NOTIFY_TEXT) + '!'
+ val text = _text ?: translate(DEFAULT_NOTIFY_TEXT)
val notification = notificationBuilder
.clearActions()
.setStyle(null)
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt
new file mode 100644
index 000000000..3beb7ec6b
--- /dev/null
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt
@@ -0,0 +1,54 @@
+package com.carriez.flutter_hbb
+
+import android.app.Activity
+import android.content.Intent
+import android.media.projection.MediaProjectionManager
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+
+class PermissionRequestTransparentActivity: Activity() {
+ private val logTag = "permissionRequest"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.d(logTag, "onCreate PermissionRequestTransparentActivity: intent.action: ${intent.action}")
+
+ when (intent.action) {
+ ACT_REQUEST_MEDIA_PROJECTION -> {
+ val mediaProjectionManager =
+ getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
+ val intent = mediaProjectionManager.createScreenCaptureIntent()
+ startActivityForResult(intent, REQ_REQUEST_MEDIA_PROJECTION)
+ }
+ else -> finish()
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if (requestCode == REQ_REQUEST_MEDIA_PROJECTION) {
+ if (resultCode == RESULT_OK && data != null) {
+ launchService(data)
+ } else {
+ setResult(RES_FAILED)
+ }
+ }
+
+ finish()
+ }
+
+ private fun launchService(mediaProjectionResultIntent: Intent) {
+ Log.d(logTag, "Launch MainService")
+ val serviceIntent = Intent(this, MainService::class.java)
+ serviceIntent.action = ACT_INIT_MEDIA_PROJECTION_AND_SERVICE
+ serviceIntent.putExtra(EXT_MEDIA_PROJECTION_RES_INTENT, mediaProjectionResultIntent)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(serviceIntent)
+ } else {
+ startService(serviceIntent)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
index 4bf244a06..f8ef07fd1 100644
--- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
+++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt
@@ -1,5 +1,6 @@
package com.carriez.flutter_hbb
+import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
@@ -12,8 +13,8 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
-import android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
-import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+import android.provider.Settings
+import android.provider.Settings.*
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService
import com.hjq.permissions.Permission
@@ -22,6 +23,31 @@ import java.nio.ByteBuffer
import java.util.*
+// intent action, extra
+const val ACT_REQUEST_MEDIA_PROJECTION = "REQUEST_MEDIA_PROJECTION"
+const val ACT_INIT_MEDIA_PROJECTION_AND_SERVICE = "INIT_MEDIA_PROJECTION_AND_SERVICE"
+const val ACT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
+const val EXT_INIT_FROM_BOOT = "EXT_INIT_FROM_BOOT"
+const val EXT_MEDIA_PROJECTION_RES_INTENT = "MEDIA_PROJECTION_RES_INTENT"
+const val EXT_LOGIN_REQ_NOTIFY = "LOGIN_REQ_NOTIFY"
+
+// Activity requestCode
+const val REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION = 101
+const val REQ_REQUEST_MEDIA_PROJECTION = 201
+
+// Activity responseCode
+const val RES_FAILED = -100
+
+// Flutter channel
+const val START_ACTION = "start_action"
+const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt"
+const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt"
+const val SYNC_APP_DIR_CONFIG_PATH = "sync_app_dir"
+
+const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES"
+const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT"
+const val KEY_APP_DIR_CONFIG_PATH = "KEY_APP_DIR_CONFIG_PATH"
+
@SuppressLint("ConstantLocale")
val LOCAL_NAME = Locale.getDefault().toString()
val SCREEN_INFO = Info(0, 0, 1, 200)
@@ -30,61 +56,13 @@ data class Info(
var width: Int, var height: Int, var scale: Int, var dpi: Int
)
-@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-fun testVP9Support(): Boolean {
- return true
- val res = MediaCodecList(MediaCodecList.ALL_CODECS)
- .findEncoderForFormat(
- MediaFormat.createVideoFormat(
- MediaFormat.MIMETYPE_VIDEO_VP9,
- SCREEN_INFO.width,
- SCREEN_INFO.width
- )
- )
- return res != null
-}
-
-@RequiresApi(Build.VERSION_CODES.M)
fun requestPermission(context: Context, type: String) {
- val permission = when (type) {
- "ignore_battery_optimizations" -> {
- try {
- context.startActivity(Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
- data = Uri.parse("package:" + context.packageName)
- })
- } catch (e:Exception) {
- e.printStackTrace()
- }
- return
- }
- "application_details_settings" -> {
- try {
- context.startActivity(Intent().apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- action = "android.settings.APPLICATION_DETAILS_SETTINGS"
- data = Uri.parse("package:" + context.packageName)
- })
- } catch (e:Exception) {
- e.printStackTrace()
- }
- return
- }
- "audio" -> {
- Permission.RECORD_AUDIO
- }
- "file" -> {
- Permission.MANAGE_EXTERNAL_STORAGE
- }
- else -> {
- return
- }
- }
XXPermissions.with(context)
- .permission(permission)
+ .permission(type)
.request { _, all ->
if (all) {
Handler(Looper.getMainLooper()).post {
- MainActivity.flutterMethodChannel.invokeMethod(
+ MainActivity.flutterMethodChannel?.invokeMethod(
"on_android_permission_result",
mapOf("type" to type, "result" to all)
)
@@ -93,24 +71,18 @@ fun requestPermission(context: Context, type: String) {
}
}
-@RequiresApi(Build.VERSION_CODES.M)
-fun checkPermission(context: Context, type: String): Boolean {
- val permission = when (type) {
- "ignore_battery_optimizations" -> {
- val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
- return pw.isIgnoringBatteryOptimizations(context.packageName)
- }
- "audio" -> {
- Permission.RECORD_AUDIO
- }
- "file" -> {
- Permission.MANAGE_EXTERNAL_STORAGE
- }
- else -> {
- return false
- }
+fun startAction(context: Context, action: String) {
+ try {
+ context.startActivity(Intent(action).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // don't pass package name when launch ACTION_ACCESSIBILITY_SETTINGS
+ if (ACTION_ACCESSIBILITY_SETTINGS != action) {
+ data = Uri.parse("package:" + context.packageName)
+ }
+ })
+ } catch (e: Exception) {
+ e.printStackTrace()
}
- return XXPermissions.isGranted(context, permission)
}
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
diff --git a/flutter/android/app/src/main/res/values/styles.xml b/flutter/android/app/src/main/res/values/styles.xml
index d74aa35c2..146267c91 100644
--- a/flutter/android/app/src/main/res/values/styles.xml
+++ b/flutter/android/app/src/main/res/values/styles.xml
@@ -15,4 +15,12 @@
+
diff --git a/flutter/assets/actions.svg b/flutter/assets/actions.svg
index 3049f3b89..0a3c4bc42 100644
--- a/flutter/assets/actions.svg
+++ b/flutter/assets/actions.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/actions_mobile.svg b/flutter/assets/actions_mobile.svg
index 4185945e1..32d8dc815 100644
--- a/flutter/assets/actions_mobile.svg
+++ b/flutter/assets/actions_mobile.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/arrow.svg b/flutter/assets/arrow.svg
index d0f032bc2..050145388 100644
--- a/flutter/assets/arrow.svg
+++ b/flutter/assets/arrow.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/call_end.svg b/flutter/assets/call_end.svg
index 7c07ee25d..fb7c9d292 100644
--- a/flutter/assets/call_end.svg
+++ b/flutter/assets/call_end.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/call_wait.svg b/flutter/assets/call_wait.svg
index 530f12a97..299e3d0cf 100644
--- a/flutter/assets/call_wait.svg
+++ b/flutter/assets/call_wait.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/chat.svg b/flutter/assets/chat.svg
index c4ab3c92d..3a8bae7e3 100644
--- a/flutter/assets/chat.svg
+++ b/flutter/assets/chat.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/close.svg b/flutter/assets/close.svg
index fb18eabd2..0dd66b666 100644
--- a/flutter/assets/close.svg
+++ b/flutter/assets/close.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/display.svg b/flutter/assets/display.svg
index 9d107d699..eb8cd8cf6 100644
--- a/flutter/assets/display.svg
+++ b/flutter/assets/display.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/dots.svg b/flutter/assets/dots.svg
index 19563b849..c32e48397 100644
--- a/flutter/assets/dots.svg
+++ b/flutter/assets/dots.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/file.svg b/flutter/assets/file.svg
index 21c7fb9de..b787cf0b1 100644
--- a/flutter/assets/file.svg
+++ b/flutter/assets/file.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/folder.svg b/flutter/assets/folder.svg
index 3959f7874..22e7cdd63 100644
--- a/flutter/assets/folder.svg
+++ b/flutter/assets/folder.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/folder_new.svg b/flutter/assets/folder_new.svg
index 22b729204..69eeb4a9c 100644
--- a/flutter/assets/folder_new.svg
+++ b/flutter/assets/folder_new.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/fullscreen.svg b/flutter/assets/fullscreen.svg
index 93f27bf7b..992d21d42 100644
--- a/flutter/assets/fullscreen.svg
+++ b/flutter/assets/fullscreen.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/fullscreen_exit.svg b/flutter/assets/fullscreen_exit.svg
index f244631fe..ab93bba60 100644
--- a/flutter/assets/fullscreen_exit.svg
+++ b/flutter/assets/fullscreen_exit.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/home.svg b/flutter/assets/home.svg
index 45a018f5d..c98e485ad 100644
--- a/flutter/assets/home.svg
+++ b/flutter/assets/home.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/keyboard.svg b/flutter/assets/keyboard.svg
index d72033f6d..0e94a5a62 100644
--- a/flutter/assets/keyboard.svg
+++ b/flutter/assets/keyboard.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/pinned.svg b/flutter/assets/pinned.svg
index a8715011b..872f38a17 100644
--- a/flutter/assets/pinned.svg
+++ b/flutter/assets/pinned.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/rec.svg b/flutter/assets/rec.svg
index 09aa55e2a..70f106139 100644
--- a/flutter/assets/rec.svg
+++ b/flutter/assets/rec.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/refresh.svg b/flutter/assets/refresh.svg
index f77fcfd4c..4ca2c96b8 100644
--- a/flutter/assets/refresh.svg
+++ b/flutter/assets/refresh.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/screen.svg b/flutter/assets/screen.svg
new file mode 100644
index 000000000..dab5f87a4
--- /dev/null
+++ b/flutter/assets/screen.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/flutter/assets/search.svg b/flutter/assets/search.svg
index 295136d7e..adc061f6d 100644
--- a/flutter/assets/search.svg
+++ b/flutter/assets/search.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/transfer.svg b/flutter/assets/transfer.svg
new file mode 100644
index 000000000..ad0072bb6
--- /dev/null
+++ b/flutter/assets/transfer.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/flutter/assets/trash.svg b/flutter/assets/trash.svg
index f9037e0e1..c5436511b 100644
--- a/flutter/assets/trash.svg
+++ b/flutter/assets/trash.svg
@@ -1,2 +1,2 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/assets/unpinned.svg b/flutter/assets/unpinned.svg
index 7e93a7a35..e69da2ae4 100644
--- a/flutter/assets/unpinned.svg
+++ b/flutter/assets/unpinned.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart
index ff373cc9c..f0fb00c2b 100644
--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -109,28 +109,48 @@ class IconFont {
class ColorThemeExtension extends ThemeExtension {
const ColorThemeExtension({
required this.border,
+ required this.border2,
required this.highlight,
+ required this.drag_indicator,
+ required this.shadow,
});
final Color? border;
+ final Color? border2;
final Color? highlight;
+ final Color? drag_indicator;
+ final Color? shadow;
- static const light = ColorThemeExtension(
+ static final light = ColorThemeExtension(
border: Color(0xFFCCCCCC),
+ border2: Color(0xFFBBBBBB),
highlight: Color(0xFFE5E5E5),
+ drag_indicator: Colors.grey[800],
+ shadow: Colors.black,
);
- static const dark = ColorThemeExtension(
+ static final dark = ColorThemeExtension(
border: Color(0xFF555555),
+ border2: Color(0xFFE5E5E5),
highlight: Color(0xFF3F3F3F),
+ drag_indicator: Colors.grey,
+ shadow: Colors.grey,
);
@override
- ThemeExtension copyWith(
- {Color? border, Color? highlight}) {
+ ThemeExtension copyWith({
+ Color? border,
+ Color? border2,
+ Color? highlight,
+ Color? drag_indicator,
+ Color? shadow,
+ }) {
return ColorThemeExtension(
border: border ?? this.border,
+ border2: border2 ?? this.border2,
highlight: highlight ?? this.highlight,
+ drag_indicator: drag_indicator ?? this.drag_indicator,
+ shadow: shadow ?? this.shadow,
);
}
@@ -142,7 +162,10 @@ class ColorThemeExtension extends ThemeExtension {
}
return ColorThemeExtension(
border: Color.lerp(border, other.border, t),
+ border2: Color.lerp(border2, other.border2, t),
highlight: Color.lerp(highlight, other.highlight, t),
+ drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
+ shadow: Color.lerp(shadow, other.shadow, t),
);
}
}
@@ -150,8 +173,7 @@ class ColorThemeExtension extends ThemeExtension {
class MyTheme {
MyTheme._();
- static const Color grayBg = Color(0xFFEEEEEE);
- static const Color white = Color(0xFFFFFFFF);
+ static const Color grayBg = Color(0xFFEFEFF2);
static const Color accent = Color(0xFF0071FF);
static const Color accent50 = Color(0x770071FF);
static const Color accent80 = Color(0xAA0071FF);
@@ -167,7 +189,28 @@ class MyTheme {
static ThemeData lightTheme = ThemeData(
brightness: Brightness.light,
hoverColor: Color.fromARGB(255, 224, 224, 224),
- scaffoldBackgroundColor: Color(0xFFFFFFFF),
+ scaffoldBackgroundColor: Colors.white,
+ dialogBackgroundColor: Colors.white,
+ dialogTheme: DialogTheme(
+ elevation: 15,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(18.0),
+ side: BorderSide(
+ width: 1,
+ color: grayBg,
+ ),
+ ),
+ ),
+ inputDecorationTheme: isDesktop
+ ? InputDecorationTheme(
+ fillColor: grayBg,
+ filled: true,
+ isDense: true,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ )
+ : null,
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19, color: Colors.black87),
titleSmall: TextStyle(fontSize: 14, color: Colors.black87),
@@ -175,7 +218,7 @@ class MyTheme {
bodyMedium:
TextStyle(fontSize: 14, color: Colors.black87, height: 1.25),
labelLarge: TextStyle(fontSize: 16.0, color: MyTheme.accent80)),
- cardColor: Color(0xFFEEEEEE),
+ cardColor: grayBg,
hintColor: Color(0xFFAAAAAA),
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme(
@@ -186,13 +229,51 @@ class MyTheme {
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
- style: ButtonStyle(splashFactory: NoSplash.splashFactory),
+ style: TextButton.styleFrom(
+ splashFactory: NoSplash.splashFactory,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(18.0),
+ ),
+ ),
)
: null,
- colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith(
- brightness: Brightness.light,
- background: Color(0xFFEEEEEE),
+ elevatedButtonTheme: ElevatedButtonThemeData(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: MyTheme.accent,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ ),
),
+ outlinedButtonTheme: OutlinedButtonThemeData(
+ style: OutlinedButton.styleFrom(
+ backgroundColor: grayBg,
+ foregroundColor: Colors.black87,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ ),
+ ),
+ checkboxTheme: const CheckboxThemeData(
+ splashRadius: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(
+ Radius.circular(5),
+ ),
+ ),
+ ),
+ listTileTheme: ListTileThemeData(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(
+ Radius.circular(5),
+ ),
+ ),
+ ),
+ menuBarTheme: MenuBarThemeData(
+ style:
+ MenuStyle(backgroundColor: MaterialStatePropertyAll(Colors.white))),
+ colorScheme: ColorScheme.light(
+ primary: Colors.blue, secondary: accent, background: grayBg),
).copyWith(
extensions: >[
ColorThemeExtension.light,
@@ -203,6 +284,27 @@ class MyTheme {
brightness: Brightness.dark,
hoverColor: Color.fromARGB(255, 45, 46, 53),
scaffoldBackgroundColor: Color(0xFF18191E),
+ dialogBackgroundColor: Color(0xFF18191E),
+ dialogTheme: DialogTheme(
+ elevation: 15,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(18.0),
+ side: BorderSide(
+ width: 1,
+ color: Color(0xFF24252B),
+ ),
+ ),
+ ),
+ inputDecorationTheme: isDesktop
+ ? InputDecorationTheme(
+ fillColor: Color(0xFF24252B),
+ filled: true,
+ isDense: true,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ )
+ : null,
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19),
titleSmall: TextStyle(fontSize: 14),
@@ -215,23 +317,69 @@ class MyTheme {
tabBarTheme: const TabBarTheme(
labelColor: Colors.white70,
),
+ scrollbarTheme: ScrollbarThemeData(
+ thumbColor: MaterialStateProperty.all(Colors.grey[500]),
+ ),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
- outlinedButtonTheme: OutlinedButtonThemeData(
- style:
- OutlinedButton.styleFrom(side: BorderSide(color: Colors.white38))),
textButtonTheme: isDesktop
? TextButtonThemeData(
- style: ButtonStyle(splashFactory: NoSplash.splashFactory),
+ style: TextButton.styleFrom(
+ splashFactory: NoSplash.splashFactory,
+ disabledForegroundColor: Colors.white70,
+ foregroundColor: Colors.white70,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(18.0),
+ ),
+ ),
)
: null,
- checkboxTheme:
- const CheckboxThemeData(checkColor: MaterialStatePropertyAll(dark)),
- colorScheme: ColorScheme.fromSwatch(
- brightness: Brightness.dark,
- primarySwatch: Colors.blue,
- ).copyWith(background: Color(0xFF24252B)),
+ elevatedButtonTheme: ElevatedButtonThemeData(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: MyTheme.accent,
+ foregroundColor: Colors.white,
+ disabledForegroundColor: Colors.white70,
+ disabledBackgroundColor: Colors.white10,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ ),
+ ),
+ outlinedButtonTheme: OutlinedButtonThemeData(
+ style: OutlinedButton.styleFrom(
+ backgroundColor: Color(0xFF24252B),
+ side: BorderSide(color: Colors.white12, width: 0.5),
+ disabledForegroundColor: Colors.white70,
+ foregroundColor: Colors.white70,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ ),
+ ),
+ ),
+ checkboxTheme: const CheckboxThemeData(
+ splashRadius: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(
+ Radius.circular(5),
+ ),
+ ),
+ ),
+ listTileTheme: ListTileThemeData(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(
+ Radius.circular(5),
+ ),
+ ),
+ ),
+ menuBarTheme: MenuBarThemeData(
+ style: MenuStyle(
+ backgroundColor: MaterialStatePropertyAll(Color(0xFF121212)))),
+ colorScheme: ColorScheme.dark(
+ primary: Colors.blue,
+ secondary: accent,
+ background: Color(0xFF24252B),
+ ),
).copyWith(
extensions: >[
ColorThemeExtension.dark,
@@ -245,7 +393,7 @@ class MyTheme {
static void changeDarkMode(ThemeMode mode) async {
Get.changeThemeMode(mode);
- if (desktopType == DesktopType.main) {
+ if (desktopType == DesktopType.main || isAndroid || isIOS) {
if (mode == ThemeMode.system) {
await bind.mainSetLocalOption(key: kCommConfKeyTheme, value: '');
} else {
@@ -307,7 +455,7 @@ final ButtonStyle flatButtonStyle = TextButton.styleFrom(
);
List supportedLocales = const [
- // specify CN/TW to fix CJK issue in flutter
+ Locale('en', 'US'),
Locale('zh', 'CN'),
Locale('zh', 'TW'),
Locale('zh', 'SG'),
@@ -329,7 +477,7 @@ List supportedLocales = const [
Locale('vi'),
Locale('pl'),
Locale('kz'),
- Locale('en', 'US'),
+ Locale('es'),
];
String formatDurationToTime(Duration duration) {
@@ -456,7 +604,7 @@ class OverlayDialogManager {
BackButtonInterceptor.removeByName(dialogTag);
}
- dialog.entry = OverlayEntry(builder: (_) {
+ dialog.entry = OverlayEntry(builder: (context) {
bool innerClicked = false;
return Listener(
onPointerUp: (_) {
@@ -466,7 +614,9 @@ class OverlayDialogManager {
innerClicked = false;
},
child: Container(
- color: Colors.black12,
+ color: Theme.of(context).brightness == Brightness.light
+ ? Colors.black12
+ : Colors.black45,
child: StatefulBuilder(builder: (context, setState) {
return Listener(
onPointerUp: (_) => innerClicked = true,
@@ -648,7 +798,7 @@ class CustomAlertDialog extends StatelessWidget {
Future.delayed(Duration.zero, () {
if (!scopeNode.hasFocus) scopeNode.requestFocus();
});
- const double padding = 16;
+ const double padding = 30;
bool tabTapped = false;
return FocusScope(
node: scopeNode,
@@ -677,15 +827,15 @@ class CustomAlertDialog extends StatelessWidget {
scrollable: true,
title: title,
titlePadding: EdgeInsets.fromLTRB(padding, 24, padding, 0),
- contentPadding: EdgeInsets.fromLTRB(contentPadding ?? padding, 25,
- contentPadding ?? padding, actions is List ? 10 : padding),
+ contentPadding: EdgeInsets.fromLTRB(
+ contentPadding ?? padding,
+ 25,
+ contentPadding ?? padding,
+ actions is List ? 10 : padding,
+ ),
content: ConstrainedBox(
constraints: contentBoxConstraints,
- child: Theme(
- data: Theme.of(context).copyWith(
- inputDecorationTheme: InputDecorationTheme(
- isDense: true, contentPadding: EdgeInsets.all(15))),
- child: content),
+ child: content,
),
actions: actions,
actionsPadding: EdgeInsets.fromLTRB(padding, 0, padding, padding),
@@ -820,7 +970,6 @@ Widget msgboxContent(String type, String title, String text) {
void msgBoxCommon(OverlayDialogManager dialogManager, String title,
Widget content, List buttons,
{bool hasCancel = true}) {
- dialogManager.dismissAll();
dialogManager.show((setState, close) => CustomAlertDialog(
title: Text(
translate(title),
@@ -903,21 +1052,14 @@ class AccessibilityListener extends StatelessWidget {
}
}
-class PermissionManager {
+class AndroidPermissionManager {
static Completer? _completer;
static Timer? _timer;
static var _current = "";
- static final permissions = [
- "audio",
- "file",
- "ignore_battery_optimizations",
- "application_details_settings"
- ];
-
static bool isWaitingFile() {
if (_completer != null) {
- return !_completer!.isCompleted && _current == "file";
+ return !_completer!.isCompleted && _current == kManageExternalStorage;
}
return false;
}
@@ -926,31 +1068,33 @@ class PermissionManager {
if (isDesktop) {
return Future.value(true);
}
- if (!permissions.contains(type)) {
- return Future.error("Wrong permission!$type");
- }
return gFFI.invokeMethod("check_permission", type);
}
+ // startActivity goto Android Setting's page to request permission manually by user
+ static void startAction(String action) {
+ gFFI.invokeMethod(AndroidChannel.kStartAction, action);
+ }
+
+ /// We use XXPermissions to request permissions,
+ /// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java
static Future request(String type) {
if (isDesktop) {
return Future.value(true);
}
- if (!permissions.contains(type)) {
- return Future.error("Wrong permission!$type");
- }
gFFI.invokeMethod("request_permission", type);
- if (type == "ignore_battery_optimizations") {
- return Future.value(false);
+
+ // clear last task
+ if (_completer?.isCompleted == false) {
+ _completer?.complete(false);
}
+ _timer?.cancel();
+
_current = type;
_completer = Completer();
- gFFI.invokeMethod("request_permission", type);
- // timeout
- _timer?.cancel();
- _timer = Timer(Duration(seconds: 60), () {
+ _timer = Timer(Duration(seconds: 120), () {
if (_completer == null) return;
if (!_completer!.isCompleted) {
_completer!.complete(false);
@@ -1379,6 +1523,11 @@ bool checkArguments() {
}
String? id =
kBootArgs.length < connectIndex + 1 ? null : kBootArgs[connectIndex + 1];
+ String? password =
+ kBootArgs.length < connectIndex + 2 ? null : kBootArgs[connectIndex + 2];
+ if (password != null && password.startsWith("--")) {
+ password = null;
+ }
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid");
String? switchUuid = kBootArgs.length < switchUuidIndex + 1
? null
@@ -1392,7 +1541,8 @@ bool checkArguments() {
kBootArgs.removeAt(connectIndex);
// fallback to peer id
Future.delayed(Duration.zero, () {
- rustDeskWinManager.newRemoteDesktop(id, switch_uuid: switchUuid);
+ rustDeskWinManager.newRemoteDesktop(id,
+ password: password, switch_uuid: switchUuid);
});
return true;
}
@@ -1453,10 +1603,12 @@ connectMainDesktop(String id,
connect(BuildContext context, String id,
{bool isFileTransfer = false,
bool isTcpTunneling = false,
- bool isRDP = false,
- bool forceRelay = false}) async {
+ bool isRDP = false}) async {
if (id == '') return;
id = id.replaceAll(' ', '');
+ final oldId = id;
+ id = await bind.mainHandleRelayId(id: id);
+ final forceRelay = id != oldId;
assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type");
@@ -1478,8 +1630,8 @@ connect(BuildContext context, String id,
}
} else {
if (isFileTransfer) {
- if (!await PermissionManager.check("file")) {
- if (!await PermissionManager.request("file")) {
+ if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
+ if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
return;
}
}
@@ -1671,10 +1823,15 @@ class ServerConfig {
/// also see [encode]
/// throw when decoding failure
ServerConfig.decode(String msg) {
- final input = msg.split('').reversed.join('');
- final bytes = base64Decode(base64.normalize(input));
- final json = jsonDecode(utf8.decode(bytes));
-
+ var json = {};
+ try {
+ // back compatible
+ json = jsonDecode(msg);
+ } catch (err) {
+ final input = msg.split('').reversed.join('');
+ final bytes = base64Decode(base64.normalize(input));
+ json = jsonDecode(utf8.decode(bytes));
+ }
idServer = json['host'] ?? '';
relayServer = json['relay'] ?? '';
apiServer = json['api'] ?? '';
@@ -1706,28 +1863,43 @@ class ServerConfig {
Widget dialogButton(String text,
{required VoidCallback? onPressed,
bool isOutline = false,
+ Widget? icon,
TextStyle? style,
ButtonStyle? buttonStyle}) {
if (isDesktop) {
if (isOutline) {
- return OutlinedButton(
- onPressed: onPressed,
- child: Text(translate(text), style: style),
- );
+ return icon == null
+ ? OutlinedButton(
+ onPressed: onPressed,
+ child: Text(translate(text), style: style),
+ )
+ : OutlinedButton.icon(
+ icon: icon,
+ onPressed: onPressed,
+ label: Text(translate(text), style: style),
+ );
} else {
- return ElevatedButton(
- style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
- onPressed: onPressed,
- child: Text(translate(text), style: style),
- );
+ return icon == null
+ ? ElevatedButton(
+ style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
+ onPressed: onPressed,
+ child: Text(translate(text), style: style),
+ )
+ : ElevatedButton.icon(
+ icon: icon,
+ style: ElevatedButton.styleFrom(elevation: 0).merge(buttonStyle),
+ onPressed: onPressed,
+ label: Text(translate(text), style: style),
+ );
}
} else {
return TextButton(
- onPressed: onPressed,
- child: Text(
- translate(text),
- style: style,
- ));
+ onPressed: onPressed,
+ child: Text(
+ translate(text),
+ style: style,
+ ),
+ );
}
}
diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart
index ebac18dac..bc1a562b9 100644
--- a/flutter/lib/common/shared_state.dart
+++ b/flutter/lib/common/shared_state.dart
@@ -19,6 +19,8 @@ class PrivacyModeState {
final key = tag(id);
if (Get.isRegistered(tag: key)) {
Get.delete(tag: key);
+ } else {
+ Get.find(tag: key).value = false;
}
}
@@ -33,6 +35,8 @@ class BlockInputState {
if (!Get.isRegistered(tag: key)) {
final RxBool state = false.obs;
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = false;
}
}
@@ -54,6 +58,8 @@ class CurrentDisplayState {
if (!Get.isRegistered(tag: key)) {
final RxInt state = RxInt(0);
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = 0;
}
}
@@ -123,6 +129,8 @@ class ShowRemoteCursorState {
if (!Get.isRegistered(tag: key)) {
final RxBool state = false.obs;
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = false;
}
}
@@ -145,6 +153,8 @@ class KeyboardEnabledState {
// Server side, default true
final RxBool state = true.obs;
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = true;
}
}
@@ -164,9 +174,10 @@ class RemoteCursorMovedState {
static void init(String id) {
final key = tag(id);
if (!Get.isRegistered(tag: key)) {
- // Server side, default true
final RxBool state = false.obs;
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = false;
}
}
@@ -186,9 +197,10 @@ class RemoteCountState {
static void init() {
final key = tag();
if (!Get.isRegistered(tag: key)) {
- // Server side, default true
final RxInt state = 1.obs;
Get.put(state, tag: key);
+ } else {
+ Get.find(tag: key).value = 1;
}
}
@@ -210,6 +222,8 @@ class PeerBoolOption {
if (!Get.isRegistered(tag: key)) {
final RxBool value = RxBool(init_getter());
Get.put(value, tag: key);
+ } else {
+ Get.find(tag: key).value = init_getter();
}
}
@@ -232,6 +246,8 @@ class PeerStringOption {
if (!Get.isRegistered(tag: key)) {
final RxString value = RxString(init_getter());
Get.put(value, tag: key);
+ } else {
+ Get.find(tag: key).value = init_getter();
}
}
diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart
index 88a5aaaa3..85c77d8ed 100644
--- a/flutter/lib/common/widgets/address_book.dart
+++ b/flutter/lib/common/widgets/address_book.dart
@@ -274,11 +274,7 @@ class _AddressBookState extends State {
TextField(
controller: idController,
inputFormatters: [IDTextInputFormatter()],
- decoration: InputDecoration(
- isDense: true,
- border: OutlineInputBorder(),
- errorText: errorMsg),
- style: style,
+ decoration: InputDecoration(errorText: errorMsg),
),
Align(
alignment: Alignment.centerLeft,
@@ -289,11 +285,6 @@ class _AddressBookState extends State {
).marginOnly(top: 8, bottom: 2),
TextField(
controller: aliasController,
- decoration: InputDecoration(
- border: OutlineInputBorder(),
- isDense: true,
- ),
- style: style,
),
Align(
alignment: Alignment.centerLeft,
@@ -379,7 +370,6 @@ class _AddressBookState extends State {
child: TextField(
maxLines: null,
decoration: InputDecoration(
- border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
diff --git a/flutter/lib/common/widgets/chat_page.dart b/flutter/lib/common/widgets/chat_page.dart
index c1991633a..9460f4f41 100644
--- a/flutter/lib/common/widgets/chat_page.dart
+++ b/flutter/lib/common/widgets/chat_page.dart
@@ -73,7 +73,7 @@ class ChatPage extends StatelessWidget implements PageShape {
? InputDecoration(
isDense: true,
hintText:
- "${translate('Write a message')}...",
+ "${translate('Write a message')}",
filled: true,
fillColor:
Theme.of(context).colorScheme.background,
@@ -88,7 +88,7 @@ class ChatPage extends StatelessWidget implements PageShape {
)
: defaultInputDecoration(
hintText:
- "${translate('Write a message')}...",
+ "${translate('Write a message')}",
fillColor:
Theme.of(context).colorScheme.background),
sendButtonBuilder: defaultSendButton(
diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart
index cdce6f12a..ff1a372d0 100644
--- a/flutter/lib/common/widgets/dialog.dart
+++ b/flutter/lib/common/widgets/dialog.dart
@@ -63,8 +63,9 @@ void changeIdDialog() {
final Iterable violations = rules.where((r) => !r.validate(newId));
if (violations.isNotEmpty) {
setState(() {
- msg =
- '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}';
+ msg = isDesktop
+ ? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}'
+ : violations.map((r) => r.name).join(', ');
});
return;
}
@@ -87,7 +88,9 @@ void changeIdDialog() {
}
setState(() {
isInProgress = false;
- msg = '${translate('Prompt')}: ${translate(status)}';
+ msg = isDesktop
+ ? '${translate('Prompt')}: ${translate(status)}'
+ : translate(status);
});
}
@@ -103,7 +106,6 @@ void changeIdDialog() {
TextField(
decoration: InputDecoration(
labelText: translate('Your new ID'),
- border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
suffixText: '${rxId.value.length}/16',
suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)),
@@ -123,27 +125,26 @@ void changeIdDialog() {
const SizedBox(
height: 8.0,
),
- Obx(() => Wrap(
- runSpacing: 8,
- spacing: 4,
- children: rules.map((e) {
- var checked = e.validate(rxId.value);
- return Chip(
- label: Text(
- e.name,
- style: TextStyle(
- color: checked
- ? const Color(0xFF0A9471)
- : Color.fromARGB(255, 198, 86, 157)),
- ),
- backgroundColor: checked
- ? const Color(0xFFD0F7ED)
- : Color.fromARGB(255, 247, 205, 232));
- }).toList(),
- )),
- const SizedBox(
- height: 8.0,
- ),
+ isDesktop
+ ? Obx(() => Wrap(
+ runSpacing: 8,
+ spacing: 4,
+ children: rules.map((e) {
+ var checked = e.validate(rxId.value);
+ return Chip(
+ label: Text(
+ e.name,
+ style: TextStyle(
+ color: checked
+ ? const Color(0xFF0A9471)
+ : Color.fromARGB(255, 198, 86, 157)),
+ ),
+ backgroundColor: checked
+ ? const Color(0xFFD0F7ED)
+ : Color.fromARGB(255, 247, 205, 232));
+ }).toList(),
+ )).marginOnly(bottom: 8)
+ : SizedBox.shrink(),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
@@ -180,7 +181,6 @@ void changeWhiteList({Function()? callback}) async {
child: TextField(
maxLines: null,
decoration: InputDecoration(
- border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart
index 43dc3a658..7b37c590d 100644
--- a/flutter/lib/common/widgets/login.dart
+++ b/flutter/lib/common/widgets/login.dart
@@ -405,7 +405,6 @@ class DialogTextField extends StatelessWidget {
child: TextField(
decoration: InputDecoration(
labelText: title,
- border: const OutlineInputBorder(),
prefixIcon: prefixIcon,
helperText: helperText,
helperMaxLines: 8,
@@ -635,9 +634,7 @@ Future verificationCodeDialog(UserPayload? user) async {
offstage: user?.email == null,
child: TextField(
decoration: InputDecoration(
- labelText: "Email",
- prefixIcon: Icon(Icons.email),
- border: InputBorder.none),
+ labelText: "Email", prefixIcon: Icon(Icons.email)),
readOnly: true,
controller: TextEditingController(text: user?.email),
)),
diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart
index ba7b8a059..c67f0f7fb 100644
--- a/flutter/lib/common/widgets/overlay.dart
+++ b/flutter/lib/common/widgets/overlay.dart
@@ -331,7 +331,7 @@ class QualityMonitor extends StatelessWidget {
Expanded(
flex: 8,
child: AutoSizeText(info,
- style: TextStyle(color: MyTheme.darkGray),
+ style: TextStyle(color: Color.fromARGB(255, 210, 210, 210)),
textAlign: TextAlign.right,
maxLines: 1)),
Spacer(flex: 1),
@@ -353,7 +353,7 @@ class QualityMonitor extends StatelessWidget {
? Container(
constraints: BoxConstraints(maxWidth: 200),
padding: const EdgeInsets.all(8),
- color: MyTheme.canvasColor.withAlpha(120),
+ color: MyTheme.canvasColor.withAlpha(150),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart
index a69fc3bbe..90946ca74 100644
--- a/flutter/lib/common/widgets/peer_card.dart
+++ b/flutter/lib/common/widgets/peer_card.dart
@@ -42,6 +42,7 @@ class _PeerCardState extends State<_PeerCard>
with AutomaticKeepAliveClientMixin {
var _menuPos = RelativeRect.fill;
final double _cardRadius = 16;
+ final double _tileRadius = 5;
final double _borderWidth = 2;
@override
@@ -116,27 +117,32 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildDesktop() {
final peer = super.widget.peer;
- var deco = Rx(BoxDecoration(
+ var deco = Rx(
+ BoxDecoration(
border: Border.all(color: Colors.transparent, width: _borderWidth),
- borderRadius: peerCardUiType.value == PeerUiType.grid
- ? BorderRadius.circular(_cardRadius)
- : null));
+ borderRadius: BorderRadius.circular(
+ peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
+ ),
+ ),
+ );
return MouseRegion(
onEnter: (evt) {
deco.value = BoxDecoration(
- border: Border.all(
- color: Theme.of(context).colorScheme.primary,
- width: _borderWidth),
- borderRadius: peerCardUiType.value == PeerUiType.grid
- ? BorderRadius.circular(_cardRadius)
- : null);
+ border: Border.all(
+ color: Theme.of(context).colorScheme.primary,
+ width: _borderWidth),
+ borderRadius: BorderRadius.circular(
+ peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
+ ),
+ );
},
onExit: (evt) {
deco.value = BoxDecoration(
- border: Border.all(color: Colors.transparent, width: _borderWidth),
- borderRadius: peerCardUiType.value == PeerUiType.grid
- ? BorderRadius.circular(_cardRadius)
- : null);
+ border: Border.all(color: Colors.transparent, width: _borderWidth),
+ borderRadius: BorderRadius.circular(
+ peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius,
+ ),
+ );
},
child: GestureDetector(
onDoubleTap: () => widget.connect(context, peer.id),
@@ -163,6 +169,10 @@ class _PeerCardState extends State<_PeerCard>
Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(_tileRadius),
+ bottomLeft: Radius.circular(_tileRadius),
+ ),
),
alignment: Alignment.center,
width: 42,
@@ -171,7 +181,12 @@ class _PeerCardState extends State<_PeerCard>
Expanded(
child: Container(
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background),
+ color: Theme.of(context).colorScheme.background,
+ borderRadius: BorderRadius.only(
+ topRight: Radius.circular(_tileRadius),
+ bottomRight: Radius.circular(_tileRadius),
+ ),
+ ),
child: Row(
children: [
Expanded(
@@ -532,19 +547,7 @@ abstract class BasePeerCard extends StatelessWidget {
],
),
proc: () {
- () async {
- if (isLan) {
- bind.mainRemoveDiscovered(id: id);
- } else {
- final favs = (await bind.mainGetFav()).toList();
- if (favs.remove(id)) {
- await bind.mainStoreFav(favs: favs);
- }
- await bind.mainRemovePeer(id: id);
- }
- removePreference(id);
- await reloadFunc();
- }();
+ _delete(id, isLan, reloadFunc);
},
padding: menuPadding,
dismissOnClicked: true,
@@ -673,7 +676,13 @@ abstract class BasePeerCard extends StatelessWidget {
}
return CustomAlertDialog(
- title: Text(translate('Rename')),
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.edit_rounded, color: MyTheme.accent),
+ Text(translate('Rename')).paddingOnly(left: 10),
+ ],
+ ),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -682,9 +691,7 @@ abstract class BasePeerCard extends StatelessWidget {
child: TextFormField(
controller: controller,
autofocus: true,
- decoration: InputDecoration(
- border: OutlineInputBorder(),
- labelText: translate('Name')),
+ decoration: InputDecoration(labelText: translate('Name')),
),
),
),
@@ -694,8 +701,17 @@ abstract class BasePeerCard extends StatelessWidget {
],
),
actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: submit),
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: close,
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
],
onSubmit: submit,
onCancel: close,
@@ -705,6 +721,58 @@ abstract class BasePeerCard extends StatelessWidget {
@protected
void _update();
+
+ void _delete(String id, bool isLan, Function reloadFunc) async {
+ gFFI.dialogManager.show(
+ (setState, close) {
+ submit() async {
+ if (isLan) {
+ bind.mainRemoveDiscovered(id: id);
+ } else {
+ final favs = (await bind.mainGetFav()).toList();
+ if (favs.remove(id)) {
+ await bind.mainStoreFav(favs: favs);
+ }
+ await bind.mainRemovePeer(id: id);
+ }
+ removePreference(id);
+ await reloadFunc();
+ close();
+ }
+
+ return CustomAlertDialog(
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.delete_rounded,
+ color: Colors.red,
+ ),
+ Text(translate('Delete')).paddingOnly(
+ left: 10,
+ ),
+ ],
+ ),
+ content: SizedBox.shrink(),
+ actions: [
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: close,
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
+ ],
+ onSubmit: submit,
+ onCancel: close,
+ );
+ },
+ );
+ }
}
class RecentPeerCard extends BasePeerCard {
@@ -837,13 +905,10 @@ class DiscoveredPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id));
}
- final inRecent = await bind.mainIsInRecentPeers(id: peer.id);
- if (inRecent) {
- if (!favs.contains(peer.id)) {
- menuItems.add(_addFavAction(peer.id));
- } else {
- menuItems.add(_rmFavAction(peer.id, () async {}));
- }
+ if (!favs.contains(peer.id)) {
+ menuItems.add(_addFavAction(peer.id));
+ } else {
+ menuItems.add(_rmFavAction(peer.id, () async {}));
}
if (gFFI.userModel.userName.isNotEmpty) {
@@ -1065,7 +1130,7 @@ void _rdpDialog(String id) async {
}
return CustomAlertDialog(
- title: Text('RDP ${translate('Settings')}'),
+ title: Text(translate('RDP Settings')),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
@@ -1076,56 +1141,63 @@ void _rdpDialog(String id) async {
),
Row(
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- "${translate('Port')}:",
- textAlign: TextAlign.right,
- ).marginOnly(right: 10)),
+ isDesktop
+ ? ConstrainedBox(
+ constraints: const BoxConstraints(minWidth: 140),
+ child: Text(
+ "${translate('Port')}:",
+ textAlign: TextAlign.right,
+ ).marginOnly(right: 10))
+ : SizedBox.shrink(),
Expanded(
child: TextField(
inputFormatters: [
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])$'))
],
- decoration: const InputDecoration(
- border: OutlineInputBorder(), hintText: '3389'),
+ decoration: InputDecoration(
+ labelText: isDesktop ? null : translate('Port'),
+ hintText: '3389'),
controller: portController,
autofocus: true,
),
),
],
- ).marginOnly(bottom: 8),
+ ).marginOnly(bottom: isDesktop ? 8 : 0),
Row(
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- "${translate('Username')}:",
- textAlign: TextAlign.right,
- ).marginOnly(right: 10)),
+ isDesktop
+ ? ConstrainedBox(
+ constraints: const BoxConstraints(minWidth: 140),
+ child: Text(
+ "${translate('Username')}:",
+ textAlign: TextAlign.right,
+ ).marginOnly(right: 10))
+ : SizedBox.shrink(),
Expanded(
child: TextField(
- decoration:
- const InputDecoration(border: OutlineInputBorder()),
+ decoration: InputDecoration(
+ labelText: isDesktop ? null : translate('Username')),
controller: userController,
),
),
],
- ).marginOnly(bottom: 8),
+ ).marginOnly(bottom: isDesktop ? 8 : 0),
Row(
children: [
- ConstrainedBox(
- constraints: const BoxConstraints(minWidth: 140),
- child: Text(
- "${translate('Password')}:",
- textAlign: TextAlign.right,
- ).marginOnly(right: 10)),
+ isDesktop
+ ? ConstrainedBox(
+ constraints: const BoxConstraints(minWidth: 140),
+ child: Text(
+ "${translate('Password')}:",
+ textAlign: TextAlign.right,
+ ).marginOnly(right: 10))
+ : SizedBox.shrink(),
Expanded(
child: Obx(() => TextField(
obscureText: secure.value,
decoration: InputDecoration(
- border: const OutlineInputBorder(),
+ labelText: isDesktop ? null : translate('Password'),
suffixIcon: IconButton(
onPressed: () => secure.value = !secure.value,
icon: Icon(secure.value
@@ -1135,7 +1207,7 @@ void _rdpDialog(String id) async {
)),
),
],
- ).marginOnly(bottom: 8),
+ ).marginOnly(bottom: isDesktop ? 8 : 0),
],
),
),
diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart
index da7e37e6b..2d36d9150 100644
--- a/flutter/lib/common/widgets/peer_tab_page.dart
+++ b/flutter/lib/common/widgets/peer_tab_page.dart
@@ -1,3 +1,4 @@
+import 'dart:math';
import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
@@ -17,6 +18,7 @@ import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
+import 'package:dropdown_button2/dropdown_button2.dart';
import '../../common.dart';
import '../../models/platform_model.dart';
@@ -39,6 +41,8 @@ EdgeInsets? _menuPadding() {
class _PeerTabPageState extends State
with SingleTickerProviderStateMixin {
+ bool _hideSort = bind.getLocalFlutterConfig(k: 'peer-tab-index') == '0';
+
final List<_TabEntry> entries = [
_TabEntry(
RecentPeersView(
@@ -83,6 +87,7 @@ class _PeerTabPageState extends State
if (tabIndex < entries.length) {
gFFI.peerTabModel.setCurrentTab(tabIndex);
entries[tabIndex].load();
+ _hideSort = tabIndex == 0;
}
}
@@ -95,22 +100,27 @@ class _PeerTabPageState extends State
SizedBox(
height: 28,
child: Container(
- padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
- constraints: isDesktop ? null : kMobilePageConstraints,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- child: visibleContextMenuListener(
- _createSwitchBar(context))),
- buildScrollJumper(),
- const PeerSearchBar(),
- Offstage(
- offstage: !isDesktop,
- child: _createPeerViewTypeSwitch(context)
- .marginOnly(left: 13)),
- ],
- )),
+ padding: isDesktop ? null : EdgeInsets.symmetric(horizontal: 2),
+ constraints: isDesktop ? null : kMobilePageConstraints,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ child:
+ visibleContextMenuListener(_createSwitchBar(context))),
+ buildScrollJumper(),
+ const PeerSearchBar(),
+ Offstage(
+ offstage: !isDesktop,
+ child: _createPeerViewTypeSwitch(context)
+ .marginOnly(left: 13)),
+ Offstage(
+ offstage: _hideSort,
+ child: PeerSortDropdown().marginOnly(left: 8),
+ ),
+ ],
+ ),
+ ),
),
_createPeersView(),
],
@@ -158,7 +168,7 @@ class _PeerTabPageState extends State
color: model.currentTab == t
? Theme.of(context).colorScheme.background
: null,
- borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
+ borderRadius: BorderRadius.circular(6),
),
child: Align(
alignment: Alignment.center,
@@ -231,32 +241,32 @@ class _PeerTabPageState extends State
Widget _createPeerViewTypeSwitch(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
- final activeDeco =
- BoxDecoration(color: Theme.of(context).colorScheme.background);
- return Row(
- children: [PeerUiType.grid, PeerUiType.list]
- .map((type) => Obx(
- () => Container(
- padding: EdgeInsets.all(4.0),
- decoration: peerCardUiType.value == type ? activeDeco : null,
- child: InkWell(
- onTap: () async {
- await bind.setLocalFlutterConfig(
- k: 'peer-card-ui-type', v: type.index.toString());
- peerCardUiType.value = type;
- },
- child: Icon(
- type == PeerUiType.grid
- ? Icons.grid_view_rounded
- : Icons.list,
- size: 18,
- color:
- peerCardUiType.value == type ? textColor : textColor
- ?..withOpacity(0.5),
- )),
- ),
- ))
- .toList(),
+ final deco = BoxDecoration(
+ color: Theme.of(context).colorScheme.background,
+ borderRadius: BorderRadius.circular(5),
+ );
+ final types = [PeerUiType.grid, PeerUiType.list];
+
+ return Obx(
+ () => Container(
+ padding: EdgeInsets.all(4.0),
+ decoration: deco,
+ child: InkWell(
+ onTap: () async {
+ final type = types.elementAt(
+ peerCardUiType.value == types.elementAt(0) ? 1 : 0);
+ await bind.setLocalFlutterConfig(
+ k: 'peer-card-ui-type', v: type.index.toString());
+ peerCardUiType.value = type;
+ },
+ child: Icon(
+ peerCardUiType.value == PeerUiType.grid
+ ? Icons.list_rounded
+ : Icons.grid_view_rounded,
+ size: 18,
+ color: textColor,
+ )),
+ ),
);
}
@@ -417,3 +427,98 @@ class _PeerSearchBarState extends State {
);
}
}
+
+class PeerSortDropdown extends StatefulWidget {
+ const PeerSortDropdown({super.key});
+
+ @override
+ State createState() => _PeerSortDropdownState();
+}
+
+class _PeerSortDropdownState extends State {
+ @override
+ void initState() {
+ if (!PeerSortType.values.contains(peerSort.value)) {
+ peerSort.value = PeerSortType.remoteId;
+ bind.setLocalFlutterConfig(
+ k: "peer-sorting",
+ v: peerSort.value,
+ );
+ }
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final deco = BoxDecoration(
+ color: Theme.of(context).colorScheme.background,
+ borderRadius: BorderRadius.circular(5),
+ );
+
+ final translated_text =
+ PeerSortType.values.map((e) => translate(e)).toList();
+
+ final double max_width =
+ 50 + translated_text.map((e) => e.length).reduce(max) * 10;
+
+ return Container(
+ padding: EdgeInsets.all(4.0),
+ decoration: deco,
+ child: DropdownButtonHideUnderline(
+ child: DropdownButton2(
+ onChanged: (v) async {
+ if (v != null) {
+ setState(() => peerSort.value = v);
+ await bind.setLocalFlutterConfig(
+ k: "peer-sorting",
+ v: peerSort.value,
+ );
+ }
+ },
+ customButton: Icon(
+ Icons.sort,
+ size: 18,
+ ),
+ isExpanded: true,
+ dropdownStyleData: DropdownStyleData(
+ decoration: BoxDecoration(
+ color: Theme.of(context).cardColor,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ width: max_width,
+ ),
+ items: [
+ DropdownMenuItem(
+ alignment: Alignment.center,
+ child: Text(
+ translate("Sort by"),
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ enabled: false,
+ ),
+ ...translated_text
+ .map>(
+ (String value) => DropdownMenuItem(
+ value: value,
+ child: Row(
+ children: [
+ Icon(
+ value == peerSort.value
+ ? Icons.radio_button_checked_rounded
+ : Icons.radio_button_off_rounded,
+ size: 18,
+ ).paddingOnly(right: 12),
+ Text(
+ value,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ ),
+ )
+ .toList(),
+ ]),
+ ),
+ );
+ }
+}
diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart
index 9c98f24b8..16a6f98ef 100644
--- a/flutter/lib/common/widgets/peers_view.dart
+++ b/flutter/lib/common/widgets/peers_view.dart
@@ -1,8 +1,8 @@
import 'dart:async';
+import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
@@ -16,8 +16,36 @@ import 'peer_card.dart';
typedef PeerFilter = bool Function(Peer peer);
typedef PeerCardBuilder = Widget Function(Peer peer);
+class PeerSortType {
+ static const String remoteId = 'Remote ID';
+ static const String remoteHost = 'Remote Host';
+ static const String username = 'Username';
+ // static const String status = 'Status';
+
+ static List values = [
+ PeerSortType.remoteId,
+ PeerSortType.remoteHost,
+ PeerSortType.username,
+ // PeerSortType.status
+ ];
+}
+
+class LoadEvent {
+ static const String recent = 'load_recent_peers';
+ static const String favorite = 'load_fav_peers';
+ static const String lan = 'load_lan_peers';
+ static const String addressBook = 'load_address_book_peers';
+}
+
/// for peer search text, global obs value
final peerSearchText = "".obs;
+
+/// for peer sort, global obs value
+final peerSort = bind.getLocalFlutterConfig(k: 'peer-sorting').obs;
+
+// list for listener
+final obslist = [peerSearchText, peerSort].obs;
+
final peerSearchTextController =
TextEditingController(text: peerSearchText.value);
@@ -40,12 +68,19 @@ class _PeersView extends StatefulWidget {
/// State for the peer widget.
class _PeersViewState extends State<_PeersView> with WindowListener {
static const int _maxQueryCount = 3;
+ final HashMap _emptyMessages = HashMap.from({
+ LoadEvent.recent: 'empty_recent_tip',
+ LoadEvent.favorite: 'empty_favorite_tip',
+ LoadEvent.lan: 'empty_lan_tip',
+ LoadEvent.addressBook: 'empty_address_book_tip',
+ });
final space = isDesktop ? 12.0 : 8.0;
final _curPeers = {};
var _lastChangeTime = DateTime.now();
var _lastQueryPeers = {};
var _lastQueryTime = DateTime.now().subtract(const Duration(hours: 1));
- var _queryCoun = 0;
+ var _queryCount = 0;
+ var _loaded = false;
var _exit = false;
late final mobileWidth = () {
@@ -78,12 +113,12 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
@override
void onWindowFocus() {
- _queryCoun = 0;
+ _queryCount = 0;
}
@override
void onWindowMinimize() {
- _queryCoun = _maxQueryCount;
+ _queryCount = _maxQueryCount;
}
@override
@@ -91,17 +126,49 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
return ChangeNotifierProvider(
create: (context) => widget.peers,
child: Consumer(
- builder: (context, peers, child) => peers.peers.isEmpty
- ? Container(
- margin: EdgeInsets.only(top: kEmptyMarginTop),
- alignment: Alignment.topCenter,
- child: Text(translate("Empty")))
- : _buildPeersView(peers)),
+ builder: (context, peers, child) => peers.peers.isEmpty && _loaded
+ ? Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.sentiment_very_dissatisfied_rounded,
+ color: Theme.of(context).tabBarTheme.labelColor,
+ size: 40,
+ ).paddingOnly(bottom: 10),
+ Text(
+ translate(
+ _emptyMessages[widget.peers.loadEvent] ?? 'Empty',
+ ),
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Theme.of(context).tabBarTheme.labelColor,
+ ),
+ ),
+ ],
+ ),
+ )
+ : _buildPeersView(peers),
+ ),
);
}
+ onVisibilityChanged(VisibilityInfo info) {
+ final peerId = _peerId((info.key as ValueKey).value);
+ if (info.visibleFraction > 0.00001) {
+ _curPeers.add(peerId);
+ } else {
+ _curPeers.remove(peerId);
+ }
+ _lastChangeTime = DateTime.now();
+ }
+
+ String _cardId(String id) => widget.peers.name + id;
+ String _peerId(String cardId) => cardId.replaceAll(widget.peers.name, '');
+
Widget _buildPeersView(Peers peers) {
- final body = ObxValue((searchText) {
+ _loaded = true;
+ final body = ObxValue((filters) {
return FutureBuilder>(
builder: (context, snapshot) {
if (snapshot.hasData) {
@@ -109,16 +176,8 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
final cards = [];
for (final peer in peers) {
final visibilityChild = VisibilityDetector(
- key: ValueKey(peer.id),
- onVisibilityChanged: (info) {
- final peerId = (info.key as ValueKey).value;
- if (info.visibleFraction > 0.00001) {
- _curPeers.add(peerId);
- } else {
- _curPeers.remove(peerId);
- }
- _lastChangeTime = DateTime.now();
- },
+ key: ValueKey(_cardId(peer.id)),
+ onVisibilityChanged: onVisibilityChanged,
child: widget.peerCardBuilder(peer),
);
cards.add(isDesktop
@@ -139,9 +198,9 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
);
}
},
- future: matchPeers(searchText.value, peers.peers),
+ future: matchPeers(filters[0].value, filters[1].value, peers.peers),
);
- }, peerSearchText);
+ }, obslist);
return body;
}
@@ -149,6 +208,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
// ignore: todo
// TODO: variables walk through async tasks?
void _startCheckOnlines() {
+ final queryInterval = const Duration(seconds: 20);
() async {
while (!_exit) {
final now = DateTime.now();
@@ -158,18 +218,18 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryPeers = {..._curPeers};
- _lastQueryTime = DateTime.now();
- _queryCoun = 0;
+ _lastQueryTime = DateTime.now().subtract(queryInterval);
+ _queryCount = 0;
}
}
} else {
- if (_queryCoun < _maxQueryCount) {
- if (now.difference(_lastQueryTime) > const Duration(seconds: 20)) {
+ if (_queryCount < _maxQueryCount) {
+ if (now.difference(_lastQueryTime) >= queryInterval) {
if (_curPeers.isNotEmpty) {
platformFFI.ffiBind
.queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryTime = DateTime.now();
- _queryCoun += 1;
+ _queryCount += 1;
}
}
}
@@ -179,11 +239,40 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
}();
}
- Future>? matchPeers(String searchText, List peers) async {
+ Future>? matchPeers(
+ String searchText, String sortedBy, List peers) async {
if (widget.peerFilter != null) {
peers = peers.where((peer) => widget.peerFilter!(peer)).toList();
}
+ // fallback to id sorting
+ if (!PeerSortType.values.contains(sortedBy)) {
+ sortedBy = PeerSortType.remoteId;
+ bind.setLocalFlutterConfig(
+ k: "peer-sorting",
+ v: sortedBy,
+ );
+ }
+
+ if (widget.peers.loadEvent != LoadEvent.recent) {
+ switch (sortedBy) {
+ case PeerSortType.remoteId:
+ peers.sort((p1, p2) => p1.getId().compareTo(p2.getId()));
+ break;
+ case PeerSortType.remoteHost:
+ peers.sort((p1, p2) =>
+ p1.hostname.toLowerCase().compareTo(p2.hostname.toLowerCase()));
+ break;
+ case PeerSortType.username:
+ peers.sort((p1, p2) =>
+ p1.username.toLowerCase().compareTo(p2.username.toLowerCase()));
+ break;
+ // case PeerSortType.status:
+ // peers.sort((p1, p2) => p1.online ? -1 : 1);
+ // break;
+ }
+ }
+
searchText = searchText.trim();
if (searchText.isEmpty) {
return peers;
@@ -197,6 +286,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
filteredList.add(peers[i]);
}
}
+
return filteredList;
}
}
@@ -232,7 +322,7 @@ class RecentPeersView extends BasePeersView {
: super(
key: key,
name: 'recent peer',
- loadEvent: 'load_recent_peers',
+ loadEvent: LoadEvent.recent,
peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer,
menuPadding: menuPadding,
@@ -254,7 +344,7 @@ class FavoritePeersView extends BasePeersView {
: super(
key: key,
name: 'favorite peer',
- loadEvent: 'load_fav_peers',
+ loadEvent: LoadEvent.favorite,
peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer,
menuPadding: menuPadding,
@@ -276,7 +366,7 @@ class DiscoveredPeersView extends BasePeersView {
: super(
key: key,
name: 'discovered peer',
- loadEvent: 'load_lan_peers',
+ loadEvent: LoadEvent.lan,
peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer,
menuPadding: menuPadding,
@@ -301,7 +391,7 @@ class AddressBookPeersView extends BasePeersView {
: super(
key: key,
name: 'address book peer',
- loadEvent: 'load_address_book_peers',
+ loadEvent: LoadEvent.addressBook,
peerFilter: (Peer peer) =>
_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardBuilder: (Peer peer) => AddressBookPeerCard(
diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart
index 537784918..e2a3c6f0b 100644
--- a/flutter/lib/consts.dart
+++ b/flutter/lib/consts.dart
@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
+import 'package:flutter_hbb/models/state_model.dart';
const double kDesktopRemoteTabBarHeight = 28.0;
const int kMainWindowId = 0;
@@ -13,7 +14,10 @@ const String kPeerPlatformAndroid = "Android";
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
const String kAppTypeMain = "main";
+
+/// [kAppTypeConnectionManager] only for 'Desktop CM Page'
const String kAppTypeConnectionManager = "cm";
+
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward";
@@ -58,6 +62,12 @@ const double kDesktopFileTransferMaximumWidth = 300;
const double kDesktopFileTransferRowHeight = 30.0;
const double kDesktopFileTransferHeaderHeight = 25.0;
+EdgeInsets get kDragToResizeAreaPadding =>
+ !kUseCompatibleUiMode && Platform.isLinux
+ ? stateGlobal.fullscreen || stateGlobal.maximize
+ ? EdgeInsets.zero
+ : EdgeInsets.all(5.0)
+ : EdgeInsets.zero;
// https://en.wikipedia.org/wiki/Non-breaking_space
const int $nbsp = 0x00A0;
@@ -79,6 +89,7 @@ const kDefaultScrollAmountMultiplier = 5.0;
const kDefaultScrollDuration = Duration(milliseconds: 50);
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
const kFullScreenEdgeSize = 0.0;
+const kMaximizeEdgeSize = 0.0;
var kWindowEdgeSize = Platform.isWindows ? 1.0 : 5.0;
const kWindowBorderWidth = 1.0;
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
@@ -129,6 +140,25 @@ const kRemoteAudioDualWay = 'dual-way';
const kIgnoreDpi = true;
+/// Android constants
+const kActionApplicationDetailsSettings =
+ "android.settings.APPLICATION_DETAILS_SETTINGS";
+const kActionAccessibilitySettings = "android.settings.ACCESSIBILITY_SETTINGS";
+
+const kRecordAudio = "android.permission.RECORD_AUDIO";
+const kManageExternalStorage = "android.permission.MANAGE_EXTERNAL_STORAGE";
+const kRequestIgnoreBatteryOptimizations =
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+const kSystemAlertWindow = "android.permission.SYSTEM_ALERT_WINDOW";
+
+/// Android channel invoke type key
+class AndroidChannel {
+ static final kStartAction = "start_action";
+ static final kGetStartOnBootOpt = "get_start_on_boot_opt";
+ static final kSetStartOnBootOpt = "set_start_on_boot_opt";
+ static final kSyncAppDirConfigPath = "sync_app_dir";
+}
+
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
/// see [LogicalKeyboardKey.keyLabel]
const Map logicalKeyMap = {
diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart
index 4aad66eee..b6debbd8d 100644
--- a/flutter/lib/desktop/pages/connection_page.dart
+++ b/flutter/lib/desktop/pages/connection_page.dart
@@ -151,10 +151,7 @@ class _ConnectionPageState extends State
/// Connects to the selected peer.
void onConnect({bool isFileTransfer = false}) {
var id = _idController.id;
- var forceRelay = id.endsWith(r'/r');
- if (forceRelay) id = id.substring(0, id.length - 2);
- connect(context, id,
- isFileTransfer: isFileTransfer, forceRelay: forceRelay);
+ connect(context, id, isFileTransfer: isFileTransfer);
}
/// UI for the remote ID TextField.
@@ -164,9 +161,8 @@ class _ConnectionPageState extends State
width: 320 + 20 * 2,
padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.background,
- borderRadius: const BorderRadius.all(Radius.circular(13)),
- ),
+ borderRadius: const BorderRadius.all(Radius.circular(13)),
+ border: Border.all(color: Theme.of(context).colorScheme.background)),
child: Ink(
child: Column(
children: [
@@ -197,32 +193,19 @@ class _ConnectionPageState extends State
style: const TextStyle(
fontFamily: 'WorkSans',
fontSize: 22,
- height: 1.25,
+ height: 1.4,
),
maxLines: 1,
cursorColor:
Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
+ filled: false,
counterText: '',
hintText: _idInputFocused.value
? null
: translate('Enter Remote ID'),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.zero,
- borderSide: BorderSide(
- color: MyTheme.color(context).border!)),
- enabledBorder: OutlineInputBorder(
- borderRadius: BorderRadius.zero,
- borderSide: BorderSide(
- color: MyTheme.color(context).border!)),
- focusedBorder: const OutlineInputBorder(
- borderRadius: BorderRadius.zero,
- borderSide:
- BorderSide(color: MyTheme.button, width: 3),
- ),
- isDense: true,
contentPadding: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 12)),
+ horizontal: 15, vertical: 13)),
controller: _idController,
inputFormatters: [IDTextInputFormatter()],
onSubmitted: (s) {
diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart
index dfa5762b0..e66e3f284 100644
--- a/flutter/lib/desktop/pages/desktop_home_page.dart
+++ b/flutter/lib/desktop/pages/desktop_home_page.dart
@@ -3,7 +3,7 @@ import 'dart:io';
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
-import 'package:flutter/material.dart' hide MenuItem;
+import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/widgets/custom_password.dart';
@@ -14,7 +14,6 @@ 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/server_model.dart';
-import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
@@ -55,10 +54,7 @@ class _DesktopHomePageState extends State
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildLeftPane(context),
- const VerticalDivider(
- width: 1,
- thickness: 1,
- ),
+ const VerticalDivider(width: 1),
Expanded(
child: buildRightPane(context),
),
@@ -158,7 +154,7 @@ class _DesktopHomePageState extends State
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
- contentPadding: EdgeInsets.only(bottom: 20),
+ contentPadding: EdgeInsets.only(top: 10, bottom: 10),
),
style: TextStyle(
fontSize: 22,
@@ -242,7 +238,8 @@ class _DesktopHomePageState extends State
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
- contentPadding: EdgeInsets.only(bottom: 2),
+ contentPadding:
+ EdgeInsets.only(top: 14, bottom: 10),
),
style: TextStyle(fontSize: 15),
),
@@ -254,9 +251,9 @@ class _DesktopHomePageState extends State
Icons.refresh,
color: refreshHover.value
? textColor
- : Color(0xFFDDDDDD), // TODO
+ : Color(0xFFDDDDDD),
size: 22,
- ).marginOnly(right: 8, bottom: 2),
+ ).marginOnly(right: 8, top: 4),
),
onTap: () => bind.mainUpdateTemporaryPassword(),
onHover: (value) => refreshHover.value = value,
@@ -265,11 +262,10 @@ class _DesktopHomePageState extends State
child: Obx(
() => Icon(
Icons.edit,
- color: editHover.value
- ? textColor
- : Color(0xFFDDDDDD), // TODO
+ color:
+ editHover.value ? textColor : Color(0xFFDDDDDD),
size: 22,
- ).marginOnly(right: 8, bottom: 2),
+ ).marginOnly(right: 8, top: 4),
),
onTap: () => DesktopSettingPage.switch2page(1),
onHover: (value) => editHover.value = value,
@@ -638,7 +634,6 @@ void setPasswordDialog() async {
obscureText: true,
decoration: InputDecoration(
labelText: translate('Password'),
- border: const OutlineInputBorder(),
errorText: errMsg0.isNotEmpty ? errMsg0 : null),
controller: p0,
autofocus: true,
@@ -666,7 +661,6 @@ void setPasswordDialog() async {
child: TextField(
obscureText: true,
decoration: InputDecoration(
- border: const OutlineInputBorder(),
labelText: translate('Confirmation'),
errorText: errMsg1.isNotEmpty ? errMsg1 : null),
controller: p1,
diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart
index e041b591d..66ef83d31 100644
--- a/flutter/lib/desktop/pages/desktop_setting_page.dart
+++ b/flutter/lib/desktop/pages/desktop_setting_page.dart
@@ -19,7 +19,7 @@ import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
-const double _kTabWidth = 235;
+const double _kTabWidth = 200;
const double _kTabHeight = 42;
const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15;
@@ -120,7 +120,7 @@ class _DesktopSettingPageState extends State
],
),
),
- const VerticalDivider(thickness: 1, width: 1),
+ const VerticalDivider(width: 1),
Expanded(
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
@@ -381,8 +381,13 @@ class _GeneralState extends State<_General> {
),
ElevatedButton(
onPressed: () async {
+ String? initialDirectory;
+ if (await Directory.fromUri(Uri.directory(dir))
+ .exists()) {
+ initialDirectory = dir;
+ }
String? selectedDirectory = await FilePicker.platform
- .getDirectoryPath(initialDirectory: dir);
+ .getDirectoryPath(initialDirectory: initialDirectory);
if (selectedDirectory != null) {
await bind.mainSetOption(
key: 'video-save-directory',
@@ -538,6 +543,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
translate('Screen Share'),
translate('Deny remote access'),
],
+ enabled: enabled,
initialKey: initialKey,
onChanged: (mode) async {
String modeValue;
@@ -667,6 +673,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
return _Card(title: 'Password', children: [
_ComboBox(
+ enabled: !locked,
keys: modeKeys,
values: modeValues,
initialKey: modeInitialKey,
@@ -762,7 +769,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
'Port',
Row(children: [
SizedBox(
- width: 80,
+ width: 95,
child: TextField(
controller: controller,
enabled: enabled && !locked,
@@ -771,13 +778,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
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])$')),
],
- textAlign: TextAlign.end,
decoration: const InputDecoration(
hintText: '21118',
- border: OutlineInputBorder(),
contentPadding:
- EdgeInsets.only(bottom: 10, top: 10, right: 10),
- isCollapsed: true,
+ EdgeInsets.symmetric(vertical: 12, horizontal: 12),
),
).marginOnly(right: 15),
),
@@ -1311,6 +1315,8 @@ class _DisplayState extends State<_Display> {
Widget other(BuildContext context) {
return _Card(title: 'Other Default Options', children: [
+ otherRow('View Mode', 'view_only'),
+ otherRow('show_monitors_tip', 'show_monitors_toolbar'),
otherRow('Show remote cursor', 'show_remote_cursor'),
otherRow('Zoom cursor', 'zoom-cursor'),
otherRow('Show quality monitor', 'show_quality_monitor'),
@@ -1695,9 +1701,6 @@ _LabeledTextField(
enabled: enabled,
obscureText: secure,
decoration: InputDecoration(
- isDense: true,
- border: OutlineInputBorder(),
- contentPadding: EdgeInsets.fromLTRB(14, 15, 14, 15),
errorText: errorText.isNotEmpty ? errorText : null),
style: TextStyle(
color: _disabledTextColor(context, enabled),
@@ -1722,7 +1725,6 @@ class _ComboBox extends StatelessWidget {
required this.values,
required this.initialKey,
required this.onChanged,
- // ignore: unused_element
this.enabled = true,
}) : super(key: key);
@@ -1735,19 +1737,29 @@ class _ComboBox extends StatelessWidget {
var ref = values[index].obs;
current = keys[index];
return Container(
- decoration: BoxDecoration(border: Border.all(color: MyTheme.border)),
- height: 30,
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: enabled
+ ? MyTheme.color(context).border2 ?? MyTheme.border
+ : MyTheme.border,
+ ),
+ borderRadius:
+ BorderRadius.circular(8), //border raiuds of dropdown button
+ ),
+ height: 42, // should be the height of a TextField
child: Obx(() => DropdownButton(
isExpanded: true,
value: ref.value,
elevation: 16,
- underline: Container(
- height: 25,
- ),
+ underline: Container(),
+ style: TextStyle(
+ color: enabled
+ ? Theme.of(context).textTheme.titleMedium?.color
+ : _disabledTextColor(context, enabled)),
icon: const Icon(
Icons.expand_more_sharp,
size: 20,
- ),
+ ).marginOnly(right: 15),
onChanged: enabled
? (String? newValue) {
if (newValue != null && newValue != ref.value) {
@@ -1764,11 +1776,11 @@ class _ComboBox extends StatelessWidget {
value,
style: const TextStyle(fontSize: _kContentFontSize),
overflow: TextOverflow.ellipsis,
- ).marginOnly(left: 5),
+ ).marginOnly(left: 15),
);
}).toList(),
)),
- );
+ ).marginOnly(bottom: 5);
}
}
@@ -1845,7 +1857,6 @@ void changeSocks5Proxy() async {
Expanded(
child: TextField(
decoration: InputDecoration(
- border: const OutlineInputBorder(),
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
controller: proxyController,
autofocus: true,
@@ -1863,9 +1874,6 @@ void changeSocks5Proxy() async {
).marginOnly(right: 10)),
Expanded(
child: TextField(
- decoration: const InputDecoration(
- border: OutlineInputBorder(),
- ),
controller: userController,
),
),
@@ -1883,7 +1891,6 @@ void changeSocks5Proxy() async {
child: Obx(() => TextField(
obscureText: obscure.value,
decoration: InputDecoration(
- border: const OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: () => obscure.value = !obscure.value,
icon: Icon(obscure.value
diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart
index 053a2d8a2..4a1a40242 100644
--- a/flutter/lib/desktop/pages/desktop_tab_page.dart
+++ b/flutter/lib/desktop/pages/desktop_tab_page.dart
@@ -75,7 +75,7 @@ class _DesktopTabPageState extends State {
isClose: false,
),
)));
- return Platform.isMacOS
+ return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: Obx(
() => DragToResizeArea(
diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart
index c8cb7c935..602dd5171 100644
--- a/flutter/lib/desktop/pages/file_manager_page.dart
+++ b/flutter/lib/desktop/pages/file_manager_page.dart
@@ -15,7 +15,6 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
-import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import '../../consts.dart';
@@ -61,52 +60,15 @@ class FileManagerPage extends StatefulWidget {
class _FileManagerPageState extends State
with AutomaticKeepAliveClientMixin {
- final _localSelectedItems = SelectedItems();
- final _remoteSelectedItems = SelectedItems();
-
- final _locationStatusLocal = LocationStatus.bread.obs;
- final _locationStatusRemote = LocationStatus.bread.obs;
- final _locationNodeLocal = FocusNode(debugLabel: "locationNodeLocal");
- final _locationNodeRemote = FocusNode(debugLabel: "locationNodeRemote");
- final _locationBarKeyLocal = GlobalKey(debugLabel: "locationBarKeyLocal");
- final _locationBarKeyRemote = GlobalKey(debugLabel: "locationBarKeyRemote");
- final _searchTextLocal = "".obs;
- final _searchTextRemote = "".obs;
- final _breadCrumbScrollerLocal = ScrollController();
- final _breadCrumbScrollerRemote = ScrollController();
final _mouseFocusScope = Rx(MouseFocusScope.none);
- final _keyboardNodeLocal = FocusNode(debugLabel: "keyboardNodeLocal");
- final _keyboardNodeRemote = FocusNode(debugLabel: "keyboardNodeRemote");
- final _listSearchBufferLocal = TimeoutStringBuffer();
- final _listSearchBufferRemote = TimeoutStringBuffer();
- final _nameColWidthLocal = kDesktopFileTransferNameColWidth.obs;
- final _modifiedColWidthLocal = kDesktopFileTransferModifiedColWidth.obs;
- final _nameColWidthRemote = kDesktopFileTransferNameColWidth.obs;
- final _modifiedColWidthRemote = kDesktopFileTransferModifiedColWidth.obs;
-
- /// [_lastClickTime], [_lastClickEntry] help to handle double click
- int _lastClickTime =
- DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000;
- Entry? _lastClickEntry;
final _dropMaskVisible = false.obs; // TODO impl drop mask
final _overlayKeyState = OverlayKeyState();
- ScrollController getBreadCrumbScrollController(bool isLocal) {
- return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
- }
-
- GlobalKey getLocationBarKey(bool isLocal) {
- return isLocal ? _locationBarKeyLocal : _locationBarKeyRemote;
- }
-
late FFI _ffi;
FileModel get model => _ffi.fileModel;
-
- SelectedItems getSelectedItems(bool isLocal) {
- return isLocal ? _localSelectedItems : _remoteSelectedItems;
- }
+ JobController get jobController => model.jobController;
@override
void initState() {
@@ -122,613 +84,353 @@ class _FileManagerPageState extends State
Wakelock.enable();
}
debugPrint("File manager page init success with id ${widget.id}");
- model.onDirChanged = breadCrumbScrollToEnd;
- // register location listener
- _locationNodeLocal.addListener(onLocalLocationFocusChanged);
- _locationNodeRemote.addListener(onRemoteLocationFocusChanged);
_ffi.dialogManager.setOverlayState(_overlayKeyState);
}
@override
void dispose() {
- model.onClose().whenComplete(() {
+ model.close().whenComplete(() {
_ffi.close();
_ffi.dialogManager.dismissAll();
if (!Platform.isLinux) {
Wakelock.disable();
}
Get.delete(tag: 'ft_${widget.id}');
- _locationNodeLocal.removeListener(onLocalLocationFocusChanged);
- _locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
- _locationNodeLocal.dispose();
- _locationNodeRemote.dispose();
});
super.dispose();
}
+ @override
+ bool get wantKeepAlive => true;
+
@override
Widget build(BuildContext context) {
super.build(context);
return Overlay(key: _overlayKeyState.key, initialEntries: [
OverlayEntry(builder: (_) {
- return ChangeNotifierProvider.value(
- value: _ffi.fileModel,
- child: Consumer(builder: (context, model, child) {
- return Scaffold(
- backgroundColor: Theme.of(context).scaffoldBackgroundColor,
- body: Row(
- children: [
- Flexible(flex: 3, child: body(isLocal: true)),
- Flexible(flex: 3, child: body(isLocal: false)),
- Flexible(flex: 2, child: statusList())
- ],
- ),
- );
- }));
+ return Scaffold(
+ backgroundColor: Theme.of(context).scaffoldBackgroundColor,
+ body: Row(
+ children: [
+ Flexible(
+ flex: 3,
+ child: dropArea(FileManagerView(
+ model.localController, _ffi, _mouseFocusScope))),
+ Flexible(
+ flex: 3,
+ child: dropArea(FileManagerView(
+ model.remoteController, _ffi, _mouseFocusScope))),
+ Flexible(flex: 2, child: statusList())
+ ],
+ ),
+ );
})
]);
}
- Widget menu({bool isLocal = false}) {
- var menuPos = RelativeRect.fill;
-
- final List> items = [
- MenuEntrySwitch(
- switchType: SwitchType.scheckbox,
- text: translate("Show Hidden Files"),
- getter: () async {
- return model.getCurrentShowHidden(isLocal);
- },
- setter: (bool v) async {
- model.toggleShowHidden(local: isLocal);
- },
- padding: kDesktopMenuPadding,
- dismissOnClicked: true,
- ),
- MenuEntryButton(
- childBuilder: (style) => Text(translate("Select All"), style: style),
- proc: () => setState(() => getSelectedItems(isLocal)
- .selectAll(model.getCurrentDir(isLocal).entries)),
- padding: kDesktopMenuPadding,
- dismissOnClicked: true),
- MenuEntryButton(
- childBuilder: (style) =>
- Text(translate("Unselect All"), style: style),
- proc: () => setState(() => getSelectedItems(isLocal).clear()),
- padding: kDesktopMenuPadding,
- dismissOnClicked: true)
- ];
-
- return Listener(
- onPointerDown: (e) {
- final x = e.position.dx;
- final y = e.position.dy;
- menuPos = RelativeRect.fromLTRB(x, y, x, y);
- },
- child: MenuButton(
- onPressed: () => mod_menu.showMenu(
- context: context,
- position: menuPos,
- items: items
- .map(
- (e) => e.build(
- context,
- MenuConfig(
- commonColor: CustomPopupMenuTheme.commonColor,
- height: CustomPopupMenuTheme.height,
- dividerHeight: CustomPopupMenuTheme.dividerHeight),
- ),
- )
- .expand((i) => i)
- .toList(),
- elevation: 8,
- ),
- child: SvgPicture.asset(
- "assets/dots.svg",
- color: Theme.of(context).tabBarTheme.labelColor,
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
- );
- }
-
- Widget body({bool isLocal = false}) {
- final scrollController = ScrollController();
- return Container(
- margin: const EdgeInsets.all(16.0),
- padding: const EdgeInsets.all(8.0),
- child: DropTarget(
- onDragDone: (detail) => handleDragDone(detail, isLocal),
+ Widget dropArea(FileManagerView fileView) {
+ return DropTarget(
+ onDragDone: (detail) =>
+ handleDragDone(detail, fileView.controller.isLocal),
onDragEntered: (enter) {
_dropMaskVisible.value = true;
},
onDragExited: (exit) {
_dropMaskVisible.value = false;
},
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- headTools(isLocal),
- Expanded(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Expanded(
- child: _buildFileList(context, isLocal, scrollController),
- )
- ],
- ),
- ),
- ],
+ child: fileView);
+ }
+
+ Widget generateCard(Widget child) {
+ return Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).cardColor,
+ borderRadius: BorderRadius.all(
+ Radius.circular(15.0),
),
),
+ child: child,
);
}
- Widget _buildFileList(
- BuildContext context, bool isLocal, ScrollController scrollController) {
- final fd = model.getCurrentDir(isLocal);
- final entries = fd.entries;
- final selectedEntries = getSelectedItems(isLocal);
-
- return MouseRegion(
- onEnter: (evt) {
- _mouseFocusScope.value =
- isLocal ? MouseFocusScope.local : MouseFocusScope.remote;
- if (isLocal) {
- _keyboardNodeLocal.requestFocus();
- } else {
- _keyboardNodeRemote.requestFocus();
- }
- },
- onExit: (evt) {
- _mouseFocusScope.value = MouseFocusScope.none;
- },
- child: ListSearchActionListener(
- node: isLocal ? _keyboardNodeLocal : _keyboardNodeRemote,
- buffer: isLocal ? _listSearchBufferLocal : _listSearchBufferRemote,
- onNext: (buffer) {
- debugPrint("searching next for $buffer");
- assert(buffer.length == 1);
- assert(selectedEntries.length <= 1);
- var skipCount = 0;
- if (selectedEntries.items.isNotEmpty) {
- final index = entries.indexOf(selectedEntries.items.first);
- if (index < 0) {
- return;
- }
- skipCount = index + 1;
- }
- var searchResult = entries
- .skip(skipCount)
- .where((element) => element.name.toLowerCase().startsWith(buffer));
- if (searchResult.isEmpty) {
- // cannot find next, lets restart search from head
- debugPrint("restart search from head");
- searchResult =
- entries.where((element) => element.name.toLowerCase().startsWith(buffer));
- }
- if (searchResult.isEmpty) {
- setState(() {
- getSelectedItems(isLocal).clear();
- });
- return;
- }
- _jumpToEntry(isLocal, searchResult.first, scrollController,
- kDesktopFileTransferRowHeight);
- },
- onSearch: (buffer) {
- debugPrint("searching for $buffer");
- final selectedEntries = getSelectedItems(isLocal);
- final searchResult =
- entries.where((element) => element.name.toLowerCase().startsWith(buffer));
- selectedEntries.clear();
- if (searchResult.isEmpty) {
- setState(() {
- getSelectedItems(isLocal).clear();
- });
- return;
- }
- _jumpToEntry(isLocal, searchResult.first, scrollController,
- kDesktopFileTransferRowHeight);
- },
- child: ObxValue(
- (searchText) {
- final filteredEntries = searchText.isNotEmpty
- ? entries.where((element) {
- return element.name.contains(searchText.value);
- }).toList(growable: false)
- : entries;
- final rows = filteredEntries.map((entry) {
- final sizeStr =
- entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
- final lastModifiedStr = entry.isDrive
- ? " "
- : "${entry.lastModified().toString().replaceAll(".000", "")} ";
- final isSelected = selectedEntries.contains(entry);
- return Padding(
- padding: EdgeInsets.symmetric(vertical: 1),
- child: Container(
- decoration: BoxDecoration(
- color: isSelected
- ? Theme.of(context).hoverColor
- : Theme.of(context).cardColor,
- borderRadius: BorderRadius.all(
- Radius.circular(5.0),
- ),
- ),
- key: ValueKey(entry.name),
- height: kDesktopFileTransferRowHeight,
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- Expanded(
- child: InkWell(
- child: Row(
- children: [
- GestureDetector(
- child: Obx(
- () => Container(
- width: isLocal
- ? _nameColWidthLocal.value
- : _nameColWidthRemote.value,
- child: Tooltip(
- waitDuration:
- Duration(milliseconds: 500),
- message: entry.name,
- child: Row(children: [
- entry.isDrive
- ? Image(
- image: iconHardDrive,
- fit: BoxFit.scaleDown,
- color: Theme.of(context)
- .iconTheme
- .color
- ?.withOpacity(0.7))
- .paddingAll(4)
- : SvgPicture.asset(
- entry.isFile
- ? "assets/file.svg"
- : "assets/folder.svg",
- color: Theme.of(context)
- .tabBarTheme
- .labelColor,
- ),
- Expanded(
- child: Text(
- entry.name.nonBreaking,
- overflow:
- TextOverflow.ellipsis))
- ]),
- )),
- ),
- onTap: () {
- final items = getSelectedItems(isLocal);
- // handle double click
- if (_checkDoubleClick(entry)) {
- openDirectory(entry.path,
- isLocal: isLocal);
- items.clear();
- return;
- }
- _onSelectedChanged(
- items, filteredEntries, entry, isLocal);
- },
- ),
- SizedBox(
- width: 2.0,
- ),
- GestureDetector(
- child: Obx(
- () => SizedBox(
- width: isLocal
- ? _modifiedColWidthLocal.value
- : _modifiedColWidthRemote.value,
- child: Tooltip(
- waitDuration:
- Duration(milliseconds: 500),
- message: lastModifiedStr,
- child: Text(
- lastModifiedStr,
- style: TextStyle(
- fontSize: 12,
- color: MyTheme.darkGray,
- ),
- )),
- ),
- ),
- ),
- // Divider from header.
- SizedBox(
- width: 2.0,
- ),
- Expanded(
- // width: 100,
- child: GestureDetector(
- child: Tooltip(
- waitDuration: Duration(milliseconds: 500),
- message: sizeStr,
- child: Text(
- sizeStr,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: 10,
- color: MyTheme.darkGray),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- )),
- );
- }).toList(growable: false);
-
- return Column(
- children: [
- // Header
- Row(
- children: [
- Expanded(child: _buildFileBrowserHeader(context, isLocal)),
- ],
- ),
- // Body
- Expanded(
- child: ListView.builder(
- controller: scrollController,
- itemExtent: kDesktopFileTransferRowHeight,
- itemBuilder: (context, index) {
- return rows[index];
- },
- itemCount: rows.length,
- ),
- ),
- ],
- );
- },
- isLocal ? _searchTextLocal : _searchTextRemote,
- ),
- ),
- );
- }
-
- void _jumpToEntry(bool isLocal, Entry entry,
- ScrollController scrollController, double rowHeight) {
- final entries = model.getCurrentDir(isLocal).entries;
- final index = entries.indexOf(entry);
- if (index == -1) {
- debugPrint("entry is not valid: ${entry.path}");
- }
- final selectedEntries = getSelectedItems(isLocal);
- final searchResult =
- entries.where((element) => element == entry);
- selectedEntries.clear();
- if (searchResult.isEmpty) {
- return;
- }
- final offset = min(
- max(scrollController.position.minScrollExtent,
- entries.indexOf(searchResult.first) * rowHeight),
- scrollController.position.maxScrollExtent);
- scrollController.jumpTo(offset);
- setState(() {
- selectedEntries.add(isLocal, searchResult.first);
- debugPrint("focused on ${searchResult.first.name}");
- });
- }
-
- void _onSelectedChanged(SelectedItems selectedItems, List entries,
- Entry entry, bool isLocal) {
- final isCtrlDown = RawKeyboard.instance.keysPressed
- .contains(LogicalKeyboardKey.controlLeft);
- final isShiftDown =
- RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft);
- if (isCtrlDown) {
- if (selectedItems.contains(entry)) {
- selectedItems.remove(entry);
- } else {
- selectedItems.add(isLocal, entry);
- }
- } else if (isShiftDown) {
- final List indexGroup = [];
- for (var selected in selectedItems.items) {
- indexGroup.add(entries.indexOf(selected));
- }
- indexGroup.add(entries.indexOf(entry));
- indexGroup.removeWhere((e) => e == -1);
- final maxIndex = indexGroup.reduce(max);
- final minIndex = indexGroup.reduce(min);
- selectedItems.clear();
- entries
- .getRange(minIndex, maxIndex + 1)
- .forEach((e) => selectedItems.add(isLocal, e));
- } else {
- selectedItems.clear();
- selectedItems.add(isLocal, entry);
- }
- setState(() {});
- }
-
- bool _checkDoubleClick(Entry entry) {
- final current = DateTime.now().millisecondsSinceEpoch;
- final elapsed = current - _lastClickTime;
- _lastClickTime = current;
- if (_lastClickEntry == entry) {
- if (elapsed < bind.getDoubleClickTime()) {
- return true;
- }
- } else {
- _lastClickEntry = entry;
- }
- return false;
- }
-
/// transfer status list
/// watch transfer status
Widget statusList() {
- return PreferredSize(
- preferredSize: const Size(200, double.infinity),
- child: model.jobTable.isEmpty
- ? Center(child: Text(translate("Empty")))
- : Container(
- margin:
- const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
- padding: const EdgeInsets.all(8.0),
- child: Obx(
- () => ListView.builder(
- controller: ScrollController(),
- itemBuilder: (BuildContext context, int index) {
- final item = model.jobTable[index];
- return Padding(
- padding: const EdgeInsets.only(bottom: 5),
- child: Container(
- decoration: BoxDecoration(
- color: Theme.of(context).cardColor,
- borderRadius: BorderRadius.all(
- Radius.circular(15.0),
- ),
+ statusListView(List jobs) => ListView.builder(
+ controller: ScrollController(),
+ itemBuilder: (BuildContext context, int index) {
+ final item = jobs[index];
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 5),
+ child: generateCard(
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Transform.rotate(
+ angle: item.isRemoteToLocal ? pi : 0,
+ child: SvgPicture.asset(
+ "assets/arrow.svg",
+ color: Theme.of(context).tabBarTheme.labelColor,
),
+ ).paddingOnly(left: 15),
+ const SizedBox(
+ width: 16.0,
+ ),
+ Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Transform.rotate(
- angle: item.isRemote ? pi : 0,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- color: Theme.of(context)
- .tabBarTheme
- .labelColor,
- ),
- ).paddingOnly(left: 15),
- const SizedBox(
- width: 16.0,
+ Tooltip(
+ waitDuration: Duration(milliseconds: 500),
+ message: item.jobName,
+ child: Text(
+ item.fileName,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ).paddingSymmetric(vertical: 10),
+ ),
+ Text(
+ '${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
+ style: TextStyle(
+ fontSize: 12,
+ color: MyTheme.darkGray,
+ ),
+ ),
+ Offstage(
+ offstage: item.state != JobState.inProgress,
+ child: Text(
+ '${translate("Speed")} ${readableFileSize(item.speed)}/s',
+ style: TextStyle(
+ fontSize: 12,
+ color: MyTheme.darkGray,
),
- Expanded(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.start,
- children: [
- Tooltip(
- waitDuration:
- Duration(milliseconds: 500),
- message: item.jobName,
- child: Text(
- item.jobName,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ).paddingSymmetric(vertical: 10),
- ),
- Text(
- '${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
- style: TextStyle(
- fontSize: 12,
- color: MyTheme.darkGray,
- ),
- ),
- Offstage(
- offstage:
- item.state != JobState.inProgress,
- child: Text(
- '${translate("Speed")} ${readableFileSize(item.speed)}/s',
- style: TextStyle(
- fontSize: 12,
- color: MyTheme.darkGray,
- ),
- ),
- ),
- Offstage(
- offstage:
- item.state == JobState.inProgress,
- child: Text(
- translate(
- item.display(),
- ),
- style: TextStyle(
- fontSize: 12,
- color: MyTheme.darkGray,
- ),
- ),
- ),
- Offstage(
- offstage:
- item.state != JobState.inProgress,
- child: LinearPercentIndicator(
- padding: EdgeInsets.only(right: 15),
- animateFromLastPercent: true,
- center: Text(
- '${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
- ),
- barRadius: Radius.circular(15),
- percent: item.finishedSize /
- item.totalSize,
- progressColor: MyTheme.accent,
- backgroundColor:
- Theme.of(context).hoverColor,
- lineHeight:
- kDesktopFileTransferRowHeight,
- ).paddingSymmetric(vertical: 15),
- ),
- ],
- ),
+ ),
+ ),
+ Offstage(
+ offstage: item.state == JobState.inProgress,
+ child: Text(
+ translate(
+ item.display(),
),
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Offstage(
- offstage: item.state != JobState.paused,
- child: MenuButton(
- onPressed: () {
- model.resumeJob(item.id);
- },
- child: SvgPicture.asset(
- "assets/refresh.svg",
- color: Colors.white,
- ),
- color: MyTheme.accent,
- hoverColor: MyTheme.accent80,
- ),
- ),
- MenuButton(
- padding: EdgeInsets.only(right: 15),
- child: SvgPicture.asset(
- "assets/close.svg",
- color: Colors.white,
- ),
- onPressed: () {
- model.jobTable.removeAt(index);
- model.cancelJob(item.id);
- },
- color: MyTheme.accent,
- hoverColor: MyTheme.accent80,
- ),
- ],
+ style: TextStyle(
+ fontSize: 12,
+ color: MyTheme.darkGray,
),
- ],
+ ),
+ ),
+ Offstage(
+ offstage: item.state != JobState.inProgress,
+ child: LinearPercentIndicator(
+ padding: EdgeInsets.only(right: 15),
+ animateFromLastPercent: true,
+ center: Text(
+ '${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
+ ),
+ barRadius: Radius.circular(15),
+ percent: item.finishedSize / item.totalSize,
+ progressColor: MyTheme.accent,
+ backgroundColor: Theme.of(context).hoverColor,
+ lineHeight: kDesktopFileTransferRowHeight,
+ ).paddingSymmetric(vertical: 15),
),
],
- ).paddingSymmetric(vertical: 10),
+ ),
),
- );
- },
- itemCount: model.jobTable.length,
- ),
- ),
- ));
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ Offstage(
+ offstage: item.state != JobState.paused,
+ child: MenuButton(
+ onPressed: () {
+ jobController.resumeJob(item.id);
+ },
+ child: SvgPicture.asset(
+ "assets/refresh.svg",
+ color: Colors.white,
+ ),
+ color: MyTheme.accent,
+ hoverColor: MyTheme.accent80,
+ ),
+ ),
+ MenuButton(
+ padding: EdgeInsets.only(right: 15),
+ child: SvgPicture.asset(
+ "assets/close.svg",
+ color: Colors.white,
+ ),
+ onPressed: () {
+ jobController.jobTable.removeAt(index);
+ jobController.cancelJob(item.id);
+ },
+ color: MyTheme.accent,
+ hoverColor: MyTheme.accent80,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ ).paddingSymmetric(vertical: 10),
+ ),
+ );
+ },
+ itemCount: jobController.jobTable.length,
+ );
+
+ return PreferredSize(
+ preferredSize: const Size(200, double.infinity),
+ child: Container(
+ margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
+ padding: const EdgeInsets.all(8.0),
+ child: Obx(
+ () => jobController.jobTable.isEmpty
+ ? generateCard(
+ Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset(
+ "assets/transfer.svg",
+ color: Theme.of(context).tabBarTheme.labelColor,
+ height: 40,
+ ).paddingOnly(bottom: 10),
+ Text(
+ translate("No transfers in progress"),
+ textAlign: TextAlign.center,
+ textScaleFactor: 1.20,
+ style: TextStyle(
+ color:
+ Theme.of(context).tabBarTheme.labelColor),
+ ),
+ ],
+ ),
+ ),
+ )
+ : statusListView(jobController.jobTable),
+ )),
+ );
}
- Widget headTools(bool isLocal) {
- final locationStatus =
- isLocal ? _locationStatusLocal : _locationStatusRemote;
- final locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
- final selectedItems = getSelectedItems(isLocal);
+ void handleDragDone(DropDoneDetails details, bool isLocal) {
+ if (isLocal) {
+ // ignore local
+ return;
+ }
+ final items = SelectedItems(isLocal: false);
+ for (var file in details.files) {
+ final f = File(file.path);
+ items.add(Entry()
+ ..path = file.path
+ ..name = file.name
+ ..size = FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
+ }
+ final otherSideData = model.localController.directoryData();
+ model.remoteController.sendFiles(items, otherSideData);
+ }
+}
+
+class FileManagerView extends StatefulWidget {
+ final FileController controller;
+ final FFI _ffi;
+ final Rx _mouseFocusScope;
+
+ FileManagerView(this.controller, this._ffi, this._mouseFocusScope);
+
+ @override
+ State createState() => _FileManagerViewState();
+}
+
+class _FileManagerViewState extends State {
+ final _locationStatus = LocationStatus.bread.obs;
+ final _locationNode = FocusNode();
+ final _locationBarKey = GlobalKey();
+ final _searchText = "".obs;
+ final _breadCrumbScroller = ScrollController();
+ final _keyboardNode = FocusNode();
+ final _listSearchBuffer = TimeoutStringBuffer();
+ final _nameColWidth = kDesktopFileTransferNameColWidth.obs;
+ final _modifiedColWidth = kDesktopFileTransferModifiedColWidth.obs;
+ final _fileListScrollController = ScrollController();
+
+ /// [_lastClickTime], [_lastClickEntry] help to handle double click
+ var _lastClickTime =
+ DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000;
+ Entry? _lastClickEntry;
+
+ FileController get controller => widget.controller;
+ bool get isLocal => widget.controller.isLocal;
+ FFI get _ffi => widget._ffi;
+ SelectedItems get selectedItems => controller.selectedItems;
+
+ @override
+ void initState() {
+ super.initState();
+ // register location listener
+ _locationNode.addListener(onLocationFocusChanged);
+ controller.directory.listen((e) => breadCrumbScrollToEnd());
+ }
+
+ @override
+ void dispose() {
+ _locationNode.removeListener(onLocationFocusChanged);
+ _locationNode.dispose();
+ _keyboardNode.dispose();
+ _breadCrumbScroller.dispose();
+ _fileListScrollController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ margin: const EdgeInsets.all(16.0),
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ headTools(),
+ Expanded(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: MouseRegion(
+ onEnter: (evt) {
+ widget._mouseFocusScope.value = isLocal
+ ? MouseFocusScope.local
+ : MouseFocusScope.remote;
+ _keyboardNode.requestFocus();
+ },
+ onExit: (evt) =>
+ widget._mouseFocusScope.value = MouseFocusScope.none,
+ child: _buildFileList(context, _fileListScrollController),
+ ))
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ void onLocationFocusChanged() {
+ debugPrint("focus changed on local");
+ if (_locationNode.hasFocus) {
+ // ignore
+ } else {
+ // lost focus, change to bread
+ if (_locationStatus.value != LocationStatus.fileSearchBar) {
+ _locationStatus.value = LocationStatus.bread;
+ }
+ }
+ }
+
+ Widget headTools() {
return Container(
child: Column(
children: [
@@ -788,7 +490,7 @@ class _FileManagerPageState extends State
hoverColor: Theme.of(context).hoverColor,
onPressed: () {
selectedItems.clear();
- model.goBack(isLocal: isLocal);
+ controller.goBack();
},
),
MenuButton(
@@ -803,7 +505,7 @@ class _FileManagerPageState extends State
hoverColor: Theme.of(context).hoverColor,
onPressed: () {
selectedItems.clear();
- model.goToParentDirectory(isLocal: isLocal);
+ controller.goToParentDirectory();
},
),
],
@@ -822,14 +524,14 @@ class _FileManagerPageState extends State
padding: EdgeInsets.symmetric(vertical: 2.5),
child: GestureDetector(
onTap: () {
- locationStatus.value =
- locationStatus.value == LocationStatus.bread
+ _locationStatus.value =
+ _locationStatus.value == LocationStatus.bread
? LocationStatus.pathLocation
: LocationStatus.bread;
Future.delayed(Duration.zero, () {
- if (locationStatus.value ==
+ if (_locationStatus.value ==
LocationStatus.pathLocation) {
- locationFocus.requestFocus();
+ _locationNode.requestFocus();
}
});
},
@@ -838,10 +540,10 @@ class _FileManagerPageState extends State
child: Row(
children: [
Expanded(
- child: locationStatus.value ==
+ child: _locationStatus.value ==
LocationStatus.bread
- ? buildBread(isLocal)
- : buildPathLocation(isLocal)),
+ ? buildBread()
+ : buildPathLocation()),
],
),
),
@@ -852,15 +554,13 @@ class _FileManagerPageState extends State
),
),
Obx(() {
- switch (locationStatus.value) {
+ switch (_locationStatus.value) {
case LocationStatus.bread:
return MenuButton(
onPressed: () {
- locationStatus.value = LocationStatus.fileSearchBar;
- final focusNode =
- isLocal ? _locationNodeLocal : _locationNodeRemote;
+ _locationStatus.value = LocationStatus.fileSearchBar;
Future.delayed(
- Duration.zero, () => focusNode.requestFocus());
+ Duration.zero, () => _locationNode.requestFocus());
},
child: SvgPicture.asset(
"assets/search.svg",
@@ -883,7 +583,7 @@ class _FileManagerPageState extends State
return MenuButton(
onPressed: () {
onSearchText("", isLocal);
- locationStatus.value = LocationStatus.bread;
+ _locationStatus.value = LocationStatus.bread;
},
child: SvgPicture.asset(
"assets/close.svg",
@@ -899,7 +599,7 @@ class _FileManagerPageState extends State
left: 3,
),
onPressed: () {
- model.refresh(isLocal: isLocal);
+ controller.refresh();
},
child: SvgPicture.asset(
"assets/refresh.svg",
@@ -923,7 +623,7 @@ class _FileManagerPageState extends State
right: 3,
),
onPressed: () {
- model.goHome(isLocal: isLocal);
+ controller.goToHomeDirectory();
},
child: SvgPicture.asset(
"assets/home.svg",
@@ -938,26 +638,37 @@ class _FileManagerPageState extends State
_ffi.dialogManager.show((setState, close) {
submit() {
if (name.value.text.isNotEmpty) {
- model.createDir(
- PathUtil.join(
- model.getCurrentDir(isLocal).path,
- name.value.text,
- model.getCurrentIsWindows(isLocal)),
- isLocal: isLocal);
+ controller.createDir(PathUtil.join(
+ controller.directory.value.path,
+ name.value.text,
+ controller.options.value.isWindows,
+ ));
close();
}
}
cancel() => close(false);
return CustomAlertDialog(
- title: Text(translate("Create Folder")),
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset("assets/folder_new.svg",
+ color: MyTheme.accent),
+ Text(
+ translate("Create Folder"),
+ ).paddingOnly(
+ left: 10,
+ ),
+ ],
+ ),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
decoration: InputDecoration(
labelText: translate(
- "Please enter the folder name"),
+ "Please enter the folder name",
+ ),
),
controller: name,
autofocus: true,
@@ -965,9 +676,17 @@ class _FileManagerPageState extends State
],
),
actions: [
- dialogButton("Cancel",
- onPressed: cancel, isOutline: true),
- dialogButton("OK", onPressed: submit)
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: cancel,
+ isOutline: true,
+ ),
+ dialogButton(
+ "Ok",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
],
onSubmit: submit,
onCancel: cancel,
@@ -981,83 +700,93 @@ class _FileManagerPageState extends State
color: Theme.of(context).cardColor,
hoverColor: Theme.of(context).hoverColor,
),
- MenuButton(
- onPressed: validItems(selectedItems)
- ? () async {
- await (model.removeAction(selectedItems,
- isLocal: isLocal));
- selectedItems.clear();
- }
- : null,
- child: SvgPicture.asset(
- "assets/trash.svg",
- color: Theme.of(context).tabBarTheme.labelColor,
- ),
- color: Theme.of(context).cardColor,
- hoverColor: Theme.of(context).hoverColor,
- ),
+ Obx(() => MenuButton(
+ onPressed: SelectedItems.valid(selectedItems.items)
+ ? () async {
+ await (controller
+ .removeAction(selectedItems));
+ selectedItems.clear();
+ }
+ : null,
+ child: SvgPicture.asset(
+ "assets/trash.svg",
+ color: Theme.of(context).tabBarTheme.labelColor,
+ ),
+ color: Theme.of(context).cardColor,
+ hoverColor: Theme.of(context).hoverColor,
+ )),
menu(isLocal: isLocal),
],
),
),
- ElevatedButton.icon(
- style: ButtonStyle(
- padding: MaterialStateProperty.all(isLocal
- ? EdgeInsets.only(left: 10)
- : EdgeInsets.only(right: 10)),
- backgroundColor: MaterialStateProperty.all(
- selectedItems.length == 0
- ? MyTheme.accent80
- : MyTheme.accent,
- ),
- shape: MaterialStateProperty.all(
- RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(18.0),
+ Obx(() => ElevatedButton.icon(
+ style: ButtonStyle(
+ padding: MaterialStateProperty.all(
+ isLocal
+ ? EdgeInsets.only(left: 10)
+ : EdgeInsets.only(right: 10)),
+ backgroundColor: MaterialStateProperty.all(
+ selectedItems.items.isEmpty
+ ? MyTheme.accent80
+ : MyTheme.accent,
+ ),
),
- ),
- ),
- onPressed: validItems(selectedItems)
- ? () {
- model.sendFiles(selectedItems, isRemote: !isLocal);
- selectedItems.clear();
- }
- : null,
- icon: isLocal
- ? Text(
- translate('Send'),
- textAlign: TextAlign.right,
- style: TextStyle(
- color: selectedItems.length == 0
- ? MyTheme.darkGray
- : Colors.white,
- ),
- )
- : RotatedBox(
- quarterTurns: 2,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- color: selectedItems.length == 0
- ? MyTheme.darkGray
- : Colors.white,
- alignment: Alignment.bottomRight,
- ),
- ),
- label: isLocal
- ? SvgPicture.asset(
- "assets/arrow.svg",
- color: selectedItems.length == 0
- ? MyTheme.darkGray
- : Colors.white,
- )
- : Text(
- translate('Receive'),
- style: TextStyle(
- color: selectedItems.length == 0
- ? MyTheme.darkGray
- : Colors.white,
- ),
- ),
- ),
+ onPressed: SelectedItems.valid(selectedItems.items)
+ ? () {
+ final otherSideData =
+ controller.getOtherSideDirectoryData();
+ controller.sendFiles(selectedItems, otherSideData);
+ selectedItems.clear();
+ }
+ : null,
+ icon: isLocal
+ ? Text(
+ translate('Send'),
+ textAlign: TextAlign.right,
+ style: TextStyle(
+ color: selectedItems.items.isEmpty
+ ? Theme.of(context).brightness ==
+ Brightness.light
+ ? MyTheme.grayBg
+ : MyTheme.darkGray
+ : Colors.white,
+ ),
+ )
+ : RotatedBox(
+ quarterTurns: 2,
+ child: SvgPicture.asset(
+ "assets/arrow.svg",
+ color: selectedItems.items.isEmpty
+ ? Theme.of(context).brightness ==
+ Brightness.light
+ ? MyTheme.grayBg
+ : MyTheme.darkGray
+ : Colors.white,
+ alignment: Alignment.bottomRight,
+ ),
+ ),
+ label: isLocal
+ ? SvgPicture.asset(
+ "assets/arrow.svg",
+ color: selectedItems.items.isEmpty
+ ? Theme.of(context).brightness ==
+ Brightness.light
+ ? MyTheme.grayBg
+ : MyTheme.darkGray
+ : Colors.white,
+ )
+ : Text(
+ translate('Receive'),
+ style: TextStyle(
+ color: selectedItems.items.isEmpty
+ ? Theme.of(context).brightness ==
+ Brightness.light
+ ? MyTheme.grayBg
+ : MyTheme.darkGray
+ : Colors.white,
+ ),
+ ),
+ )),
],
).marginOnly(top: 8.0)
],
@@ -1065,55 +794,443 @@ class _FileManagerPageState extends State
);
}
- bool validItems(SelectedItems items) {
- if (items.length > 0) {
- // exclude DirDrive type
- return items.items.any((item) => !item.isDrive);
+ Widget menu({bool isLocal = false}) {
+ var menuPos = RelativeRect.fill;
+
+ final List> items = [
+ MenuEntrySwitch(
+ switchType: SwitchType.scheckbox,
+ text: translate("Show Hidden Files"),
+ getter: () async {
+ return controller.options.value.isWindows;
+ },
+ setter: (bool v) async {
+ controller.toggleShowHidden();
+ },
+ padding: kDesktopMenuPadding,
+ dismissOnClicked: true,
+ ),
+ MenuEntryButton(
+ childBuilder: (style) => Text(translate("Select All"), style: style),
+ proc: () => setState(() =>
+ selectedItems.selectAll(controller.directory.value.entries)),
+ padding: kDesktopMenuPadding,
+ dismissOnClicked: true),
+ MenuEntryButton(
+ childBuilder: (style) =>
+ Text(translate("Unselect All"), style: style),
+ proc: () => selectedItems.clear(),
+ padding: kDesktopMenuPadding,
+ dismissOnClicked: true)
+ ];
+
+ return Listener(
+ onPointerDown: (e) {
+ final x = e.position.dx;
+ final y = e.position.dy;
+ menuPos = RelativeRect.fromLTRB(x, y, x, y);
+ },
+ child: MenuButton(
+ onPressed: () => mod_menu.showMenu(
+ context: context,
+ position: menuPos,
+ items: items
+ .map(
+ (e) => e.build(
+ context,
+ MenuConfig(
+ commonColor: CustomPopupMenuTheme.commonColor,
+ height: CustomPopupMenuTheme.height,
+ dividerHeight: CustomPopupMenuTheme.dividerHeight),
+ ),
+ )
+ .expand((i) => i)
+ .toList(),
+ elevation: 8,
+ ),
+ child: SvgPicture.asset(
+ "assets/dots.svg",
+ color: Theme.of(context).tabBarTheme.labelColor,
+ ),
+ color: Theme.of(context).cardColor,
+ hoverColor: Theme.of(context).hoverColor,
+ ),
+ );
+ }
+
+ Widget _buildFileList(
+ BuildContext context, ScrollController scrollController) {
+ final fd = controller.directory.value;
+ final entries = fd.entries;
+
+ return ListSearchActionListener(
+ node: _keyboardNode,
+ buffer: _listSearchBuffer,
+ onNext: (buffer) {
+ debugPrint("searching next for $buffer");
+ assert(buffer.length == 1);
+ assert(selectedItems.items.length <= 1);
+ var skipCount = 0;
+ if (selectedItems.items.isNotEmpty) {
+ final index = entries.indexOf(selectedItems.items.first);
+ if (index < 0) {
+ return;
+ }
+ skipCount = index + 1;
+ }
+ var searchResult = entries
+ .skip(skipCount)
+ .where((element) => element.name.toLowerCase().startsWith(buffer));
+ if (searchResult.isEmpty) {
+ // cannot find next, lets restart search from head
+ debugPrint("restart search from head");
+ searchResult = entries.where(
+ (element) => element.name.toLowerCase().startsWith(buffer));
+ }
+ if (searchResult.isEmpty) {
+ selectedItems.clear();
+ return;
+ }
+ _jumpToEntry(isLocal, searchResult.first, scrollController,
+ kDesktopFileTransferRowHeight);
+ },
+ onSearch: (buffer) {
+ debugPrint("searching for $buffer");
+ final selectedEntries = selectedItems;
+ final searchResult = entries
+ .where((element) => element.name.toLowerCase().startsWith(buffer));
+ selectedEntries.clear();
+ if (searchResult.isEmpty) {
+ selectedItems.clear();
+ return;
+ }
+ _jumpToEntry(isLocal, searchResult.first, scrollController,
+ kDesktopFileTransferRowHeight);
+ },
+ child: Obx(() {
+ final entries = controller.directory.value.entries;
+ final filteredEntries = _searchText.isNotEmpty
+ ? entries.where((element) {
+ return element.name.contains(_searchText.value);
+ }).toList(growable: false)
+ : entries;
+ final rows = filteredEntries.map((entry) {
+ final sizeStr =
+ entry.isFile ? readableFileSize(entry.size.toDouble()) : "";
+ final lastModifiedStr = entry.isDrive
+ ? " "
+ : "${entry.lastModified().toString().replaceAll(".000", "")} ";
+ return Padding(
+ padding: EdgeInsets.symmetric(vertical: 1),
+ child: Obx(() => Container(
+ decoration: BoxDecoration(
+ color: selectedItems.items.contains(entry)
+ ? Theme.of(context).hoverColor
+ : Theme.of(context).cardColor,
+ borderRadius: BorderRadius.all(
+ Radius.circular(5.0),
+ ),
+ ),
+ key: ValueKey(entry.name),
+ height: kDesktopFileTransferRowHeight,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ Expanded(
+ child: InkWell(
+ child: Row(
+ children: [
+ GestureDetector(
+ child: Obx(
+ () => Container(
+ width: _nameColWidth.value,
+ child: Tooltip(
+ waitDuration: Duration(milliseconds: 500),
+ message: entry.name,
+ child: Row(children: [
+ entry.isDrive
+ ? Image(
+ image: iconHardDrive,
+ fit: BoxFit.scaleDown,
+ color: Theme.of(context)
+ .iconTheme
+ .color
+ ?.withOpacity(0.7))
+ .paddingAll(4)
+ : SvgPicture.asset(
+ entry.isFile
+ ? "assets/file.svg"
+ : "assets/folder.svg",
+ color: Theme.of(context)
+ .tabBarTheme
+ .labelColor,
+ ),
+ Expanded(
+ child: Text(entry.name.nonBreaking,
+ overflow:
+ TextOverflow.ellipsis))
+ ]),
+ )),
+ ),
+ onTap: () {
+ final items = selectedItems;
+ // handle double click
+ if (_checkDoubleClick(entry)) {
+ controller.openDirectory(entry.path);
+ items.clear();
+ return;
+ }
+ _onSelectedChanged(
+ items, filteredEntries, entry, isLocal);
+ },
+ ),
+ SizedBox(
+ width: 2.0,
+ ),
+ GestureDetector(
+ child: Obx(
+ () => SizedBox(
+ width: _modifiedColWidth.value,
+ child: Tooltip(
+ waitDuration: Duration(milliseconds: 500),
+ message: lastModifiedStr,
+ child: Text(
+ lastModifiedStr,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 12,
+ color: MyTheme.darkGray,
+ ),
+ )),
+ ),
+ ),
+ ),
+ // Divider from header.
+ SizedBox(
+ width: 2.0,
+ ),
+ Expanded(
+ // width: 100,
+ child: GestureDetector(
+ child: Tooltip(
+ waitDuration: Duration(milliseconds: 500),
+ message: sizeStr,
+ child: Text(
+ sizeStr,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 10, color: MyTheme.darkGray),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ))),
+ );
+ }).toList(growable: false);
+
+ return Column(
+ children: [
+ // Header
+ Row(
+ children: [
+ Expanded(child: _buildFileBrowserHeader(context)),
+ ],
+ ),
+ // Body
+ Expanded(
+ child: ListView.builder(
+ controller: scrollController,
+ itemExtent: kDesktopFileTransferRowHeight,
+ itemBuilder: (context, index) {
+ return rows[index];
+ },
+ itemCount: rows.length,
+ ),
+ ),
+ ],
+ );
+ }),
+ );
+ }
+
+ onSearchText(String searchText, bool isLocal) {
+ selectedItems.clear();
+ _searchText.value = searchText;
+ }
+
+ void _jumpToEntry(bool isLocal, Entry entry,
+ ScrollController scrollController, double rowHeight) {
+ final entries = controller.directory.value.entries;
+ final index = entries.indexOf(entry);
+ if (index == -1) {
+ debugPrint("entry is not valid: ${entry.path}");
+ }
+ final selectedEntries = selectedItems;
+ final searchResult = entries.where((element) => element == entry);
+ selectedEntries.clear();
+ if (searchResult.isEmpty) {
+ return;
+ }
+ final offset = min(
+ max(scrollController.position.minScrollExtent,
+ entries.indexOf(searchResult.first) * rowHeight),
+ scrollController.position.maxScrollExtent);
+ scrollController.jumpTo(offset);
+ selectedEntries.add(searchResult.first);
+ debugPrint("focused on ${searchResult.first.name}");
+ }
+
+ void _onSelectedChanged(SelectedItems selectedItems, List entries,
+ Entry entry, bool isLocal) {
+ final isCtrlDown = RawKeyboard.instance.keysPressed
+ .contains(LogicalKeyboardKey.controlLeft);
+ final isShiftDown =
+ RawKeyboard.instance.keysPressed.contains(LogicalKeyboardKey.shiftLeft);
+ if (isCtrlDown) {
+ if (selectedItems.items.contains(entry)) {
+ selectedItems.remove(entry);
+ } else {
+ selectedItems.add(entry);
+ }
+ } else if (isShiftDown) {
+ final List indexGroup = [];
+ for (var selected in selectedItems.items) {
+ indexGroup.add(entries.indexOf(selected));
+ }
+ indexGroup.add(entries.indexOf(entry));
+ indexGroup.removeWhere((e) => e == -1);
+ final maxIndex = indexGroup.reduce(max);
+ final minIndex = indexGroup.reduce(min);
+ selectedItems.clear();
+ entries
+ .getRange(minIndex, maxIndex + 1)
+ .forEach((e) => selectedItems.add(e));
+ } else {
+ selectedItems.clear();
+ selectedItems.add(entry);
+ }
+ setState(() {});
+ }
+
+ bool _checkDoubleClick(Entry entry) {
+ final current = DateTime.now().millisecondsSinceEpoch;
+ final elapsed = current - _lastClickTime;
+ _lastClickTime = current;
+ if (_lastClickEntry == entry) {
+ if (elapsed < bind.getDoubleClickTime()) {
+ return true;
+ }
+ } else {
+ _lastClickEntry = entry;
}
return false;
}
- @override
- bool get wantKeepAlive => true;
-
- void onLocalLocationFocusChanged() {
- debugPrint("focus changed on local");
- if (_locationNodeLocal.hasFocus) {
- // ignore
- } else {
- // lost focus, change to bread
- if (_locationStatusLocal.value != LocationStatus.fileSearchBar) {
- _locationStatusLocal.value = LocationStatus.bread;
- }
- }
+ Widget _buildFileBrowserHeader(BuildContext context) {
+ final padding = EdgeInsets.all(1.0);
+ return SizedBox(
+ height: kDesktopFileTransferHeaderHeight,
+ child: Row(
+ children: [
+ Obx(
+ () => headerItemFunc(
+ _nameColWidth.value, SortBy.name, translate("Name")),
+ ),
+ DraggableDivider(
+ axis: Axis.vertical,
+ onPointerMove: (dx) {
+ _nameColWidth.value += dx;
+ _nameColWidth.value = min(kDesktopFileTransferMaximumWidth,
+ max(kDesktopFileTransferMinimumWidth, _nameColWidth.value));
+ },
+ padding: padding,
+ ),
+ Obx(
+ () => headerItemFunc(_modifiedColWidth.value, SortBy.modified,
+ translate("Modified")),
+ ),
+ DraggableDivider(
+ axis: Axis.vertical,
+ onPointerMove: (dx) {
+ _modifiedColWidth.value += dx;
+ _modifiedColWidth.value = min(
+ kDesktopFileTransferMaximumWidth,
+ max(kDesktopFileTransferMinimumWidth,
+ _modifiedColWidth.value));
+ },
+ padding: padding),
+ Expanded(child: headerItemFunc(null, SortBy.size, translate("Size")))
+ ],
+ ),
+ );
}
- void onRemoteLocationFocusChanged() {
- debugPrint("focus changed on remote");
- if (_locationNodeRemote.hasFocus) {
- // ignore
- } else {
- // lost focus, change to bread
- if (_locationStatusRemote.value != LocationStatus.fileSearchBar) {
- _locationStatusRemote.value = LocationStatus.bread;
+ Widget headerItemFunc(double? width, SortBy sortBy, String name) {
+ final headerTextStyle =
+ Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle();
+ return ObxValue>(
+ (ascending) => InkWell(
+ onTap: () {
+ if (ascending.value == null) {
+ ascending.value = true;
+ } else {
+ ascending.value = !ascending.value!;
+ }
+ controller.changeSortStyle(sortBy,
+ isLocal: isLocal, ascending: ascending.value!);
+ },
+ child: SizedBox(
+ width: width,
+ height: kDesktopFileTransferHeaderHeight,
+ child: Row(
+ children: [
+ Flexible(
+ flex: 2,
+ child: Text(
+ name,
+ style: headerTextStyle,
+ overflow: TextOverflow.ellipsis,
+ ).marginSymmetric(horizontal: 4),
+ ),
+ Flexible(
+ flex: 1,
+ child: ascending.value != null
+ ? Icon(
+ ascending.value!
+ ? Icons.keyboard_arrow_up_rounded
+ : Icons.keyboard_arrow_down_rounded,
+ )
+ : const Offstage())
+ ],
+ ),
+ ),
+ ), () {
+ if (controller.sortBy.value == sortBy) {
+ return controller.sortAscending.obs;
+ } else {
+ return Rx(null);
}
- }
+ }());
}
- Widget buildBread(bool isLocal) {
+ Widget buildBread() {
final items = getPathBreadCrumbItems(isLocal, (list) {
var path = "";
for (var item in list) {
- path = PathUtil.join(path, item, model.getCurrentIsWindows(isLocal));
+ path = PathUtil.join(path, item, controller.options.value.isWindows);
}
- openDirectory(path, isLocal: isLocal);
+ controller.openDirectory(path);
});
- final locationBarKey = getLocationBarKey(isLocal);
return items.isEmpty
? Offstage()
: Row(
- key: locationBarKey,
+ key: _locationBarKey,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
@@ -1121,7 +1238,7 @@ class _FileManagerPageState extends State
// handle mouse wheel
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
- final sc = getBreadCrumbScrollController(isLocal);
+ final sc = _breadCrumbScroller;
final scale = Platform.isWindows ? 2 : 4;
sc.jumpTo(sc.offset + e.scrollDelta.dy / scale);
}
@@ -1130,7 +1247,7 @@ class _FileManagerPageState extends State
items: items,
divider: const Icon(Icons.keyboard_arrow_right_rounded),
overflow: ScrollableOverflow(
- controller: getBreadCrumbScrollController(isLocal),
+ controller: _breadCrumbScroller,
),
),
),
@@ -1139,9 +1256,9 @@ class _FileManagerPageState extends State
message: "",
icon: Icons.keyboard_arrow_down_rounded,
onTap: () async {
- final renderBox = locationBarKey.currentContext
+ final renderBox = _locationBarKey.currentContext
?.findRenderObject() as RenderBox;
- locationBarKey.currentContext?.size;
+ _locationBarKey.currentContext?.size;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
@@ -1149,17 +1266,17 @@ class _FileManagerPageState extends State
final x = offset.dx;
final y = offset.dy + size.height + 1;
- final isPeerWindows = model.getCurrentIsWindows(isLocal);
+ final isPeerWindows = controller.options.value.isWindows;
final List menuItems = [
MenuEntryButton(
childBuilder: (TextStyle? style) => isPeerWindows
- ? buildWindowsThisPC(style)
+ ? buildWindowsThisPC(context, style)
: Text(
'/',
style: style,
),
proc: () {
- openDirectory('/', isLocal: isLocal);
+ controller.openDirectory('/');
},
dismissOnClicked: true),
MenuEntryDivider()
@@ -1170,8 +1287,9 @@ class _FileManagerPageState extends State
loadingTag = _ffi.dialogManager.showLoading("Waiting");
}
try {
- final fd =
- await model.fetchDirectory("/", isLocal, isLocal);
+ final showHidden = controller.options.value.showHidden;
+ final fd = await controller.fileFetcher
+ .fetchDirectory("/", isLocal, showHidden);
for (var entry in fd.entries) {
menuItems.add(MenuEntryButton(
childBuilder: (TextStyle? style) =>
@@ -1190,11 +1308,11 @@ class _FileManagerPageState extends State
)
]),
proc: () {
- openDirectory('${entry.name}\\',
- isLocal: isLocal);
+ controller.openDirectory('${entry.name}\\');
},
dismissOnClicked: true));
}
+ menuItems.add(MenuEntryDivider());
} catch (e) {
debugPrint("buildBread fetchDirectory err=$e");
} finally {
@@ -1203,7 +1321,6 @@ class _FileManagerPageState extends State
}
}
}
- menuItems.add(MenuEntryDivider());
mod_menu.showMenu(
context: context,
position: RelativeRect.fromLTRB(x, y, x, y),
@@ -1226,24 +1343,15 @@ class _FileManagerPageState extends State
]);
}
- Widget buildWindowsThisPC([TextStyle? textStyle]) {
- final color = Theme.of(context).iconTheme.color?.withOpacity(0.7);
- return Row(children: [
- Icon(Icons.computer, size: 20, color: color),
- SizedBox(width: 10),
- Text(translate('This PC'), style: textStyle)
- ]);
- }
-
List getPathBreadCrumbItems(
bool isLocal, void Function(List) onPressed) {
- final path = model.getCurrentDir(isLocal).path;
+ final path = controller.directory.value.path;
final breadCrumbList = List.empty(growable: true);
- final isWindows = model.getCurrentIsWindows(isLocal);
+ final isWindows = controller.options.value.isWindows;
if (isWindows && path == '/') {
breadCrumbList.add(BreadCrumbItem(
content: TextButton(
- child: buildWindowsThisPC(),
+ child: buildWindowsThisPC(context),
style: ButtonStyle(
minimumSize: MaterialStateProperty.all(Size(0, 0))),
onPressed: () => onPressed(['/']))
@@ -1271,39 +1379,34 @@ class _FileManagerPageState extends State
return breadCrumbList;
}
- breadCrumbScrollToEnd(bool isLocal) {
+ breadCrumbScrollToEnd() {
Future.delayed(Duration(milliseconds: 200), () {
- final breadCrumbScroller = getBreadCrumbScrollController(isLocal);
- if (breadCrumbScroller.hasClients) {
- breadCrumbScroller.animateTo(
- breadCrumbScroller.position.maxScrollExtent,
+ if (_breadCrumbScroller.hasClients) {
+ _breadCrumbScroller.animateTo(
+ _breadCrumbScroller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
}
});
}
- Widget buildPathLocation(bool isLocal) {
- final searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
- final locationStatus =
- isLocal ? _locationStatusLocal : _locationStatusRemote;
- final focusNode = isLocal ? _locationNodeLocal : _locationNodeRemote;
- final text = locationStatus.value == LocationStatus.pathLocation
- ? model.getCurrentDir(isLocal).path
- : searchTextObs.value;
+ Widget buildPathLocation() {
+ final text = _locationStatus.value == LocationStatus.pathLocation
+ ? controller.directory.value.path
+ : _searchText.value;
final textController = TextEditingController(text: text)
..selection = TextSelection.collapsed(offset: text.length);
return Row(
children: [
SvgPicture.asset(
- locationStatus.value == LocationStatus.pathLocation
+ _locationStatus.value == LocationStatus.pathLocation
? "assets/folder.svg"
: "assets/search.svg",
color: Theme.of(context).tabBarTheme.labelColor,
),
Expanded(
child: TextField(
- focusNode: focusNode,
+ focusNode: _locationNode,
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
@@ -1313,9 +1416,9 @@ class _FileManagerPageState extends State
),
controller: textController,
onSubmitted: (path) {
- openDirectory(path, isLocal: isLocal);
+ controller.openDirectory(path);
},
- onChanged: locationStatus.value == LocationStatus.fileSearchBar
+ onChanged: _locationStatus.value == LocationStatus.fileSearchBar
? (searchText) => onSearchText(searchText, isLocal)
: null,
),
@@ -1324,141 +1427,16 @@ class _FileManagerPageState extends State
);
}
- onSearchText(String searchText, bool isLocal) {
- if (isLocal) {
- _localSelectedItems.clear();
- _searchTextLocal.value = searchText;
- } else {
- _remoteSelectedItems.clear();
- _searchTextRemote.value = searchText;
- }
- }
-
- openDirectory(String path, {bool isLocal = false}) {
- model.openDirectory(path, isLocal: isLocal);
- }
-
- void handleDragDone(DropDoneDetails details, bool isLocal) {
- if (isLocal) {
- // ignore local
- return;
- }
- var items = SelectedItems();
- for (var file in details.files) {
- final f = File(file.path);
- items.add(
- true,
- Entry()
- ..path = file.path
- ..name = file.name
- ..size =
- FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
- }
- model.sendFiles(items, isRemote: false);
- }
-
- void refocusKeyboardListener(bool isLocal) {
- Future.delayed(Duration.zero, () {
- if (isLocal) {
- _keyboardNodeLocal.requestFocus();
- } else {
- _keyboardNodeRemote.requestFocus();
- }
- });
- }
-
- Widget headerItemFunc(
- double? width, SortBy sortBy, String name, bool isLocal) {
- final headerTextStyle =
- Theme.of(context).dataTableTheme.headingTextStyle ?? TextStyle();
- return ObxValue>(
- (ascending) => InkWell(
- onTap: () {
- if (ascending.value == null) {
- ascending.value = true;
- } else {
- ascending.value = !ascending.value!;
- }
- model.changeSortStyle(sortBy,
- isLocal: isLocal, ascending: ascending.value!);
- },
- child: SizedBox(
- width: width,
- height: kDesktopFileTransferHeaderHeight,
- child: Row(
- children: [
- Flexible(
- flex: 2,
- child: Text(
- name,
- style: headerTextStyle,
- overflow: TextOverflow.ellipsis,
- ).marginSymmetric(horizontal: 4),
- ),
- Flexible(
- flex: 1,
- child: ascending.value != null
- ? Icon(
- ascending.value!
- ? Icons.keyboard_arrow_up_rounded
- : Icons.keyboard_arrow_down_rounded,
- )
- : const Offstage())
- ],
- ),
- ),
- ), () {
- if (model.getSortStyle(isLocal) == sortBy) {
- return model.getSortAscending(isLocal).obs;
- } else {
- return Rx(null);
- }
- }());
- }
-
- Widget _buildFileBrowserHeader(BuildContext context, bool isLocal) {
- final nameColWidth = isLocal ? _nameColWidthLocal : _nameColWidthRemote;
- final modifiedColWidth =
- isLocal ? _modifiedColWidthLocal : _modifiedColWidthRemote;
- final padding = EdgeInsets.all(1.0);
- return SizedBox(
- height: kDesktopFileTransferHeaderHeight,
- child: Row(
- children: [
- Obx(
- () => headerItemFunc(
- nameColWidth.value, SortBy.name, translate("Name"), isLocal),
- ),
- DraggableDivider(
- axis: Axis.vertical,
- onPointerMove: (dx) {
- nameColWidth.value += dx;
- nameColWidth.value = min(
- kDesktopFileTransferMaximumWidth,
- max(kDesktopFileTransferMinimumWidth,
- nameColWidth.value));
- },
- padding: padding,
- ),
- Obx(
- () => headerItemFunc(modifiedColWidth.value, SortBy.modified,
- translate("Modified"), isLocal),
- ),
- DraggableDivider(
- axis: Axis.vertical,
- onPointerMove: (dx) {
- modifiedColWidth.value += dx;
- modifiedColWidth.value = min(
- kDesktopFileTransferMaximumWidth,
- max(kDesktopFileTransferMinimumWidth,
- modifiedColWidth.value));
- },
- padding: padding),
- Expanded(
- child:
- headerItemFunc(null, SortBy.size, translate("Size"), isLocal))
- ],
- ),
- );
- }
+ // openDirectory(String path, {bool isLocal = false}) {
+ // model.openDirectory(path, isLocal: isLocal);
+ // }
+}
+
+Widget buildWindowsThisPC(BuildContext context, [TextStyle? textStyle]) {
+ final color = Theme.of(context).iconTheme.color?.withOpacity(0.7);
+ return Row(children: [
+ Icon(Icons.computer, size: 20, color: color),
+ SizedBox(width: 10),
+ Text(translate('This PC'), style: textStyle)
+ ]);
}
diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart
index 148d928d9..39958e88e 100644
--- a/flutter/lib/desktop/pages/file_manager_tab_page.dart
+++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart
@@ -98,7 +98,7 @@ class _FileManagerTabPageState extends State {
labelGetter: DesktopTab.labelGetterAlias,
)),
);
- return Platform.isMacOS
+ return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: SubWindowDragToResizeArea(
child: tabWidget,
diff --git a/flutter/lib/desktop/pages/install_page.dart b/flutter/lib/desktop/pages/install_page.dart
index e7bb28813..adc0df138 100644
--- a/flutter/lib/desktop/pages/install_page.dart
+++ b/flutter/lib/desktop/pages/install_page.dart
@@ -1,7 +1,11 @@
+import 'dart:io';
+
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
+import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/platform_model.dart';
+import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
@@ -13,10 +17,55 @@ class InstallPage extends StatefulWidget {
State createState() => _InstallPageState();
}
-class _InstallPageState extends State with WindowListener {
+class _InstallPageState extends State {
+ final tabController = DesktopTabController(tabType: DesktopTabType.main);
+
+ @override
+ void initState() {
+ super.initState();
+ Get.put(tabController);
+ const lable = "install";
+ tabController.add(TabInfo(
+ key: lable,
+ label: lable,
+ closable: false,
+ page: _InstallPageBody(
+ key: const ValueKey(lable),
+ )));
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ Get.delete();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return DragToResizeArea(
+ resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
+ child: Container(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.background,
+ body: DesktopTab(controller: tabController)),
+ ),
+ );
+ }
+}
+
+class _InstallPageBody extends StatefulWidget {
+ const _InstallPageBody({Key? key}) : super(key: key);
+
+ @override
+ State<_InstallPageBody> createState() => _InstallPageBodyState();
+}
+
+class _InstallPageBodyState extends State<_InstallPageBody>
+ with WindowListener {
late final TextEditingController controller;
final RxBool startmenu = true.obs;
final RxBool desktopicon = true.obs;
+ final RxBool driverCert = true.obs;
final RxBool showProgress = false.obs;
final RxBool btnEnabled = true.obs;
@@ -46,15 +95,19 @@ class _InstallPageState extends State with WindowListener {
final double em = 13;
final btnFontSize = 0.9 * em;
final double button_radius = 6;
+ final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
final buttonStyle = OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
));
final inputBorder = OutlineInputBorder(
borderRadius: BorderRadius.zero,
- borderSide: BorderSide(color: Colors.black12));
+ borderSide:
+ BorderSide(color: isDarkTheme ? Colors.white70 : Colors.black12));
+ final textColor = isDarkTheme ? null : Colors.black87;
+ final dividerColor = isDarkTheme ? Colors.white70 : Colors.black87;
return Scaffold(
- backgroundColor: Colors.white,
+ backgroundColor: null,
body: SingleChildScrollView(
child: Column(
children: [
@@ -91,30 +144,66 @@ class _InstallPageState extends State with WindowListener {
style: buttonStyle,
child: Text(translate('Change Path'),
style: TextStyle(
- color: Colors.black87,
- fontSize: btnFontSize)))
+ color: textColor, fontSize: btnFontSize)))
.marginOnly(left: em))
],
).marginSymmetric(vertical: 2 * em),
- Row(
- children: [
- Obx(() => Checkbox(
- value: startmenu.value,
- onChanged: (b) {
- if (b != null) startmenu.value = b;
- })),
- Text(translate('Create start menu shortcuts'))
- ],
+ TextButton(
+ onPressed: () => startmenu.value = !startmenu.value,
+ child: Row(
+ children: [
+ Obx(() => Checkbox(
+ value: startmenu.value,
+ onChanged: (b) {
+ if (b != null) startmenu.value = b;
+ })),
+ RichText(
+ text: TextSpan(
+ text: translate('Create start menu shortcuts'),
+ style: DefaultTextStyle.of(context).style,
+ ),
+ ),
+ ],
+ ),
),
- Row(
- children: [
- Obx(() => Checkbox(
- value: desktopicon.value,
- onChanged: (b) {
- if (b != null) desktopicon.value = b;
- })),
- Text(translate('Create desktop icon'))
- ],
+ TextButton(
+ onPressed: () => desktopicon.value = !desktopicon.value,
+ child: Row(
+ children: [
+ Obx(() => Checkbox(
+ value: desktopicon.value,
+ onChanged: (b) {
+ if (b != null) desktopicon.value = b;
+ })),
+ RichText(
+ text: TextSpan(
+ text: translate('Create desktop icon'),
+ style: DefaultTextStyle.of(context).style,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Offstage(
+ offstage: !Platform.isWindows,
+ child: TextButton(
+ onPressed: () => driverCert.value = !driverCert.value,
+ child: Row(
+ children: [
+ Obx(() => Checkbox(
+ value: driverCert.value,
+ onChanged: (b) {
+ if (b != null) driverCert.value = b;
+ })),
+ RichText(
+ text: TextSpan(
+ text: translate('idd_driver_tip'),
+ style: DefaultTextStyle.of(context).style,
+ ),
+ ),
+ ],
+ ),
+ ),
),
GestureDetector(
onTap: () => launchUrlString('http://rustdesk.com/privacy'),
@@ -127,8 +216,7 @@ class _InstallPageState extends State with WindowListener {
)).marginOnly(top: 2 * em),
Row(children: [Text(translate('agreement_tip'))])
.marginOnly(top: em),
- Divider(color: Colors.black87)
- .marginSymmetric(vertical: 0.5 * em),
+ Divider(color: dividerColor).marginSymmetric(vertical: 0.5 * em),
Row(
children: [
Expanded(
@@ -143,8 +231,7 @@ class _InstallPageState extends State with WindowListener {
style: buttonStyle,
child: Text(translate('Cancel'),
style: TextStyle(
- color: Colors.black87,
- fontSize: btnFontSize)))
+ color: textColor, fontSize: btnFontSize)))
.marginOnly(right: 2 * em)),
Obx(() => ElevatedButton(
onPressed: btnEnabled.value ? install : null,
@@ -167,8 +254,7 @@ class _InstallPageState extends State with WindowListener {
style: buttonStyle,
child: Text(translate('Run without install'),
style: TextStyle(
- color: Colors.black87,
- fontSize: btnFontSize)))
+ color: textColor, fontSize: btnFontSize)))
.marginOnly(left: 2 * em)),
),
],
@@ -179,12 +265,47 @@ class _InstallPageState extends State with WindowListener {
}
void install() {
- btnEnabled.value = false;
- showProgress.value = true;
- String args = '';
- if (startmenu.value) args += ' startmenu';
- if (desktopicon.value) args += ' desktopicon';
- bind.installInstallMe(options: args, path: controller.text);
+ do_install() {
+ btnEnabled.value = false;
+ showProgress.value = true;
+ String args = '';
+ if (startmenu.value) args += ' startmenu';
+ if (desktopicon.value) args += ' desktopicon';
+ if (driverCert.value) args += ' driverCert';
+ bind.installInstallMe(options: args, path: controller.text);
+ }
+
+ if (driverCert.isTrue) {
+ final tag = 'install-info-install-cert-confirm';
+ final btns = [
+ dialogButton(
+ 'Cancel',
+ onPressed: () => gFFI.dialogManager.dismissByTag(tag),
+ isOutline: true,
+ ),
+ dialogButton(
+ 'OK',
+ onPressed: () {
+ gFFI.dialogManager.dismissByTag(tag);
+ do_install();
+ },
+ isOutline: false,
+ ),
+ ];
+ gFFI.dialogManager.show(
+ (setState, close) => CustomAlertDialog(
+ title: null,
+ content: SelectionArea(
+ child:
+ msgboxContent('info', 'Warning', 'confirm_idd_driver_tip')),
+ actions: btns,
+ onCancel: close,
+ ),
+ tag: tag,
+ );
+ } else {
+ do_install();
+ }
}
void selectInstallPath() async {
diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart
index ae070b47b..06b2cf3a7 100644
--- a/flutter/lib/desktop/pages/port_forward_page.dart
+++ b/flutter/lib/desktop/pages/port_forward_page.dart
@@ -11,7 +11,7 @@ import 'package:wakelock/wakelock.dart';
const double _kColumn1Width = 30;
const double _kColumn4Width = 100;
-const double _kRowHeight = 50;
+const double _kRowHeight = 60;
const double _kTextLeftMargin = 20;
class _PortForward {
@@ -183,8 +183,6 @@ class _PortForwardPageState extends State
controller: remotePortController,
inputFormatters: portInputFormatter),
ElevatedButton(
- style: ElevatedButton.styleFrom(
- elevation: 0, side: const BorderSide(color: MyTheme.border)),
onPressed: () async {
int? localPort = int.tryParse(localPortController.text);
int? remotePort = int.tryParse(remotePortController.text);
@@ -208,7 +206,7 @@ class _PortForwardPageState extends State
child: Text(
translate('Add'),
),
- ).marginAll(10),
+ ).marginSymmetric(horizontal: 10),
]),
);
}
@@ -217,26 +215,15 @@ class _PortForwardPageState extends State
{required TextEditingController controller,
List? inputFormatters,
String? hint}) {
- final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Expanded(
- child: TextField(
- controller: controller,
- inputFormatters: inputFormatters,
- cursorColor: textColor,
- cursorHeight: 20,
- cursorWidth: 1,
- decoration: InputDecoration(
- border: OutlineInputBorder(
- borderSide: BorderSide(color: MyTheme.color(context).border!)),
- focusedBorder: OutlineInputBorder(
- borderSide: BorderSide(color: MyTheme.color(context).border!)),
- fillColor: Theme.of(context).colorScheme.background,
- contentPadding: const EdgeInsets.all(10),
- hintText: hint,
- hintStyle:
- TextStyle(color: Theme.of(context).hintColor, fontSize: 16)),
- style: TextStyle(color: textColor, fontSize: 16),
- ).marginAll(10),
+ child: Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: TextField(
+ controller: controller,
+ inputFormatters: inputFormatters,
+ decoration: InputDecoration(
+ hintText: hint,
+ ))),
);
}
@@ -322,14 +309,9 @@ class _PortForwardPageState extends State
child: SizedBox(
width: 120,
child: ElevatedButton(
- style: ElevatedButton.styleFrom(
- elevation: 0,
- side: const BorderSide(color: MyTheme.border)),
onPressed: () => bind.sessionNewRdp(id: widget.id),
child: Text(
translate('New RDP'),
- style: const TextStyle(
- fontWeight: FontWeight.w300, fontSize: 14),
),
).marginSymmetric(vertical: 10),
).marginOnly(left: 20),
diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart
index f2d75d00f..32f02c9b7 100644
--- a/flutter/lib/desktop/pages/port_forward_tab_page.dart
+++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart
@@ -107,13 +107,15 @@ class _PortForwardTabPageState extends State {
labelGetter: DesktopTab.labelGetterAlias,
)),
);
- return Platform.isMacOS
+ return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
- : SubWindowDragToResizeArea(
- child: tabWidget,
- resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
- windowId: stateGlobal.windowId,
- );
+ : Obx(
+ () => SubWindowDragToResizeArea(
+ child: tabWidget,
+ resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
+ windowId: stateGlobal.windowId,
+ ),
+ );
}
void onRemoveId(String id) {
diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart
index ab0daece7..aea073ace 100644
--- a/flutter/lib/desktop/pages/remote_page.dart
+++ b/flutter/lib/desktop/pages/remote_page.dart
@@ -22,22 +22,23 @@ import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import '../../utils/image.dart';
-import '../widgets/remote_menubar.dart';
+import '../widgets/remote_toolbar.dart';
import '../widgets/kb_layout_type_chooser.dart';
-bool _isCustomCursorInited = false;
final SimpleWrapper _firstEnterImage = SimpleWrapper(false);
class RemotePage extends StatefulWidget {
RemotePage({
Key? key,
required this.id,
+ required this.password,
required this.menubarState,
this.switchUuid,
this.forceRelay,
}) : super(key: key);
final String id;
+ final String? password;
final MenubarState menubarState;
final String? switchUuid;
final bool? forceRelay;
@@ -113,6 +114,7 @@ class _RemotePageState extends State
});
_ffi.start(
widget.id,
+ password: widget.password,
switchUuid: widget.switchUuid,
forceRelay: widget.forceRelay,
);
@@ -308,6 +310,10 @@ class _RemotePageState extends State
}
void leaveView(PointerExitEvent evt) {
+ if (_ffi.ffiModel.keyboard()) {
+ _ffi.inputModel.tryMoveEdgeOnExit(evt.position);
+ }
+
_cursorOverImage.value = false;
_firstEnterImage.value = false;
if (_onEnterOrLeaveImage4Menubar != null) {
@@ -329,8 +335,8 @@ class _RemotePageState extends State
PointerExitEventListener? onExit,
) {
return RawPointerMouseRegion(
- onEnter: enterView,
- onExit: leaveView,
+ onEnter: onEnter,
+ onExit: onExit,
onPointerDown: (event) {
// A double check for blur status.
// Note: If there's an `onPointerDown` event is triggered, `_isWindowBlur` is expected being false.
diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart
index 0deb646c0..f7bb85a37 100644
--- a/flutter/lib/desktop/pages/remote_tab_page.dart
+++ b/flutter/lib/desktop/pages/remote_tab_page.dart
@@ -9,7 +9,7 @@ import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
-import 'package:flutter_hbb/desktop/widgets/remote_menubar.dart';
+import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
as mod_menu;
@@ -23,9 +23,6 @@ import '../../models/platform_model.dart';
class _MenuTheme {
static const Color blueColor = MyTheme.button;
- static const Color hoverBlueColor = MyTheme.accent;
- static const Color redColor = Colors.redAccent;
- static const Color hoverRedColor = Colors.red;
// kMinInteractiveDimension
static const double height = 20.0;
static const double dividerHeight = 12.0;
@@ -71,6 +68,7 @@ class _ConnectionTabPageState extends State {
page: RemotePage(
key: ValueKey(peerId),
id: peerId,
+ password: params['password'],
menubarState: _menubarState,
switchUuid: params['switch_uuid'],
forceRelay: params['forceRelay'],
@@ -106,6 +104,7 @@ class _ConnectionTabPageState extends State {
page: RemotePage(
key: ValueKey(id),
id: id,
+ password: args['password'],
menubarState: _menubarState,
switchUuid: switchUuid,
forceRelay: args['forceRelay'],
@@ -205,11 +204,13 @@ class _ConnectionTabPageState extends State {
),
),
);
- return Platform.isMacOS
+ return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: Obx(() => SubWindowDragToResizeArea(
key: contentKey,
child: tabWidget,
+ // Specially configured for a better resize area and remote control.
+ childPadding: kDragToResizeAreaPadding,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
));
@@ -258,7 +259,7 @@ class _ConnectionTabPageState extends State {
),
]);
- if (!ffi.canvasModel.cursorEmbedded) {
+ if (!ffi.canvasModel.cursorEmbedded && !ffi.ffiModel.viewOnly) {
menu.add(MenuEntryDivider());
menu.add(RemoteMenuEntry.showRemoteCursor(
key,
@@ -267,7 +268,7 @@ class _ConnectionTabPageState extends State {
));
}
- if (perms['keyboard'] != false) {
+ if (perms['keyboard'] != false && !ffi.ffiModel.viewOnly) {
if (perms['clipboard'] != false) {
menu.add(RemoteMenuEntry.disableClipboard(key, padding,
dismissFunc: cancelFunc));
diff --git a/flutter/lib/desktop/screen/desktop_remote_screen.dart b/flutter/lib/desktop/screen/desktop_remote_screen.dart
index bb6bc431b..64af41401 100644
--- a/flutter/lib/desktop/screen/desktop_remote_screen.dart
+++ b/flutter/lib/desktop/screen/desktop_remote_screen.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart';
@@ -26,6 +28,9 @@ class DesktopRemoteScreen extends StatelessWidget {
ChangeNotifierProvider.value(value: gFFI.canvasModel),
],
child: Scaffold(
+ // Set transparent background for padding the resize area out of the flutter view.
+ // This allows the wallpaper goes through our resize area. (Linux only now).
+ backgroundColor: Platform.isLinux ? Colors.transparent : null,
body: ConnectionTabPage(
params: params,
),
diff --git a/flutter/lib/desktop/widgets/menu_button.dart b/flutter/lib/desktop/widgets/menu_button.dart
index df2c48ab4..17b160fed 100644
--- a/flutter/lib/desktop/widgets/menu_button.dart
+++ b/flutter/lib/desktop/widgets/menu_button.dart
@@ -37,7 +37,7 @@ class _MenuButtonState extends State {
message: widget.tooltip,
child: Material(
type: MaterialType.transparency,
- child: Ink(
+ child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_borderRadius),
color: _isHover ? widget.hoverColor : widget.color,
diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart
similarity index 82%
rename from flutter/lib/desktop/widgets/remote_menubar.dart
rename to flutter/lib/desktop/widgets/remote_toolbar.dart
index 37bbbd66a..30dc09a6e 100644
--- a/flutter/lib/desktop/widgets/remote_menubar.dart
+++ b/flutter/lib/desktop/widgets/remote_toolbar.dart
@@ -23,6 +23,10 @@ import '../../common/shared_state.dart';
import './popup_menu.dart';
import './kb_layout_type_chooser.dart';
+const _kKeyLegacyMode = 'legacy';
+const _kKeyMapMode = 'map';
+const _kKeyTranslateMode = 'translate';
+
class MenubarState {
final kStoreKey = 'remoteMenubarState';
late RxBool show;
@@ -104,6 +108,7 @@ class _MenubarTheme {
static const double buttonHMargin = 3;
static const double buttonVMargin = 6;
static const double iconRadius = 8;
+ static const double elevation = 3;
}
typedef DismissFunc = void Function();
@@ -351,7 +356,7 @@ class _RemoteMenubarState extends State {
return Align(
alignment: Alignment.topCenter,
child: Obx(() => show.value
- ? _buildMenubar(context)
+ ? _buildToolbar(context)
: _buildDraggableShowHide(context)),
);
}
@@ -365,59 +370,78 @@ class _RemoteMenubarState extends State {
alignment: FractionalOffset(_fractionX.value, 0),
child: Offstage(
offstage: _dragging.isTrue,
- child: _DraggableShowHide(
- dragging: _dragging,
- fractionX: _fractionX,
- show: show,
+ child: Material(
+ elevation: _MenubarTheme.elevation,
+ shadowColor: MyTheme.color(context).shadow,
+ child: _DraggableShowHide(
+ dragging: _dragging,
+ fractionX: _fractionX,
+ show: show,
+ ),
),
),
);
});
}
- Widget _buildMenubar(BuildContext context) {
- final List menubarItems = [];
+ Widget _buildToolbar(BuildContext context) {
+ final List toolbarItems = [];
if (!isWebDesktop) {
- menubarItems.add(_PinMenu(state: widget.state));
- menubarItems.add(
+ toolbarItems.add(_PinMenu(state: widget.state));
+ toolbarItems.add(
_FullscreenMenu(state: widget.state, setFullscreen: _setFullscreen));
- menubarItems.add(_MobileActionMenu(ffi: widget.ffi));
+ toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
}
- menubarItems.add(_MonitorMenu(id: widget.id, ffi: widget.ffi));
- menubarItems
+
+ if (PrivacyModeState.find(widget.id).isFalse &&
+ stateGlobal.displaysCount.value > 1) {
+ toolbarItems.add(
+ bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'
+ ? _MultiMonitorMenu(id: widget.id, ffi: widget.ffi)
+ : _MonitorMenu(id: widget.id, ffi: widget.ffi),
+ );
+ }
+
+ toolbarItems
.add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state));
- menubarItems.add(_DisplayMenu(
+ toolbarItems.add(_DisplayMenu(
id: widget.id,
ffi: widget.ffi,
state: widget.state,
setFullscreen: _setFullscreen,
));
- menubarItems.add(_KeyboardMenu(id: widget.id, ffi: widget.ffi));
+ toolbarItems.add(_KeyboardMenu(id: widget.id, ffi: widget.ffi));
if (!isWeb) {
- menubarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
- menubarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
+ toolbarItems.add(_ChatMenu(id: widget.id, ffi: widget.ffi));
+ toolbarItems.add(_VoiceCallMenu(id: widget.id, ffi: widget.ffi));
}
- menubarItems.add(_RecordMenu());
- menubarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
+ toolbarItems.add(_RecordMenu());
+ toolbarItems.add(_CloseMenu(id: widget.id, ffi: widget.ffi));
return Column(
mainAxisSize: MainAxisSize.min,
children: [
- Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.all(Radius.circular(10)),
- ),
+ Material(
+ elevation: _MenubarTheme.elevation,
+ shadowColor: MyTheme.color(context).shadow,
+ borderRadius: BorderRadius.all(Radius.circular(4.0)),
+ color: Theme.of(context)
+ .menuBarTheme
+ .style
+ ?.backgroundColor
+ ?.resolve(MaterialState.values.toSet()),
child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- child: Theme(
- data: themeData(),
- child: MenuBar(
- children: [
- SizedBox(width: _MenubarTheme.buttonHMargin),
- ...menubarItems,
- SizedBox(width: _MenubarTheme.buttonHMargin)
- ],
- ),
- )),
+ scrollDirection: Axis.horizontal,
+ child: Theme(
+ data: themeData(),
+ child: Row(
+ children: [
+ SizedBox(width: _MenubarTheme.buttonHMargin * 2),
+ ...toolbarItems,
+ SizedBox(width: _MenubarTheme.buttonHMargin * 2)
+ ],
+ ),
+ ),
+ ),
),
_buildDraggableShowHide(context),
],
@@ -427,11 +451,22 @@ class _RemoteMenubarState extends State {
ThemeData themeData() {
return Theme.of(context).copyWith(
menuButtonTheme: MenuButtonThemeData(
- style: ButtonStyle(
- minimumSize: MaterialStatePropertyAll(Size(64, 36)),
- textStyle: MaterialStatePropertyAll(
- TextStyle(fontWeight: FontWeight.normal)))),
+ style: ButtonStyle(
+ minimumSize: MaterialStatePropertyAll(Size(64, 36)),
+ textStyle: MaterialStatePropertyAll(
+ TextStyle(fontWeight: FontWeight.normal),
+ ),
+ ),
+ ),
dividerTheme: DividerThemeData(space: 4),
+ menuBarTheme: MenuBarThemeData(
+ style: MenuStyle(
+ padding: MaterialStatePropertyAll(EdgeInsets.zero),
+ elevation: MaterialStatePropertyAll(0),
+ shape: MaterialStatePropertyAll(BeveledRectangleBorder()),
+ ).copyWith(
+ backgroundColor:
+ Theme.of(context).menuBarTheme.style?.backgroundColor)),
);
}
}
@@ -501,8 +536,8 @@ class _MonitorMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
- if (stateGlobal.displaysCount.value < 2) return Offstage();
return _IconSubmenuButton(
+ tooltip: 'Select Monitor',
icon: icon(),
ffi: ffi,
color: _MenubarTheme.blueColor,
@@ -519,19 +554,20 @@ class _MonitorMenu extends StatelessWidget {
alignment: Alignment.center,
children: [
SvgPicture.asset(
- "assets/display.svg",
+ "assets/screen.svg",
color: Colors.white,
),
- Padding(
- padding: const EdgeInsets.only(bottom: 3.9),
- child: Obx(() {
- RxInt display = CurrentDisplayState.find(id);
- return Text(
- '${display.value + 1}/${pi.displays.length}',
- style: const TextStyle(color: Colors.white, fontSize: 8),
- );
- }),
- )
+ Obx(() {
+ RxInt display = CurrentDisplayState.find(id);
+ return Text(
+ '${display.value + 1}/${pi.displays.length}',
+ style: const TextStyle(
+ color: _MenubarTheme.blueColor,
+ fontSize: 8,
+ fontWeight: FontWeight.bold,
+ ),
+ );
+ }),
],
);
}
@@ -541,6 +577,7 @@ class _MonitorMenu extends StatelessWidget {
final pi = ffi.ffiModel.pi;
for (int i = 0; i < pi.displays.length; i++) {
rowChildren.add(_IconMenuButton(
+ topLevel: false,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
tooltip: "",
@@ -553,19 +590,17 @@ class _MonitorMenu extends StatelessWidget {
alignment: Alignment.center,
children: [
SvgPicture.asset(
- "assets/display.svg",
+ "assets/screen.svg",
color: Colors.white,
),
- Padding(
- padding: const EdgeInsets.only(bottom: 3.5 /*2.5*/),
- child: Text(
- (i + 1).toString(),
- style: TextStyle(
- color: Colors.white,
- fontSize: 12,
- ),
+ Text(
+ (i + 1).toString(),
+ style: TextStyle(
+ color: _MenubarTheme.blueColor,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
),
- )
+ ),
],
),
),
@@ -593,6 +628,7 @@ class _ControlMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _IconSubmenuButton(
+ tooltip: 'Control Actions',
svg: "assets/actions.svg",
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
@@ -651,26 +687,44 @@ class _ControlMenu extends StatelessWidget {
}
return CustomAlertDialog(
- title: Text(translate('OS Password')),
- content: Column(mainAxisSize: MainAxisSize.min, children: [
- PasswordWidget(controller: controller),
- CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- translate('Auto Login'),
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.password_rounded, color: MyTheme.accent),
+ Text(translate('OS Password')).paddingOnly(left: 10),
+ ],
+ ),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ PasswordWidget(controller: controller),
+ CheckboxListTile(
+ contentPadding: const EdgeInsets.all(0),
+ dense: true,
+ controlAffinity: ListTileControlAffinity.leading,
+ title: Text(
+ translate('Auto Login'),
+ ),
+ value: autoLogin,
+ onChanged: (v) {
+ if (v == null) return;
+ setState(() => autoLogin = v);
+ },
),
- value: autoLogin,
- onChanged: (v) {
- if (v == null) return;
- setState(() => autoLogin = v);
- },
- ),
- ]),
+ ],
+ ),
actions: [
- dialogButton('Cancel', onPressed: close, isOutline: true),
- dialogButton('OK', onPressed: submit),
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: close,
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
],
onSubmit: submit,
onCancel: close,
@@ -766,8 +820,10 @@ class _ControlMenu extends StatelessWidget {
ctrlAltDel() {
final perms = ffi.ffiModel.permissions;
+ final viewOnly = ffi.ffiModel.viewOnly;
final pi = ffi.ffiModel.pi;
- final visible = perms['keyboard'] != false &&
+ final visible = !viewOnly &&
+ perms['keyboard'] != false &&
(pi.platform == kPeerPlatformLinux || pi.sasEnabled);
if (!visible) return Offstage();
return _MenuItemButton(
@@ -792,7 +848,8 @@ class _ControlMenu extends StatelessWidget {
insertLock() {
final perms = ffi.ffiModel.permissions;
- final visible = perms['keyboard'] != false;
+ final viewOnly = ffi.ffiModel.viewOnly;
+ final visible = !viewOnly && perms['keyboard'] != false;
if (!visible) return Offstage();
return _MenuItemButton(
child: Text(translate('Insert Lock')),
@@ -896,6 +953,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
Widget build(BuildContext context) {
_updateScreen();
return _IconSubmenuButton(
+ tooltip: 'Display Settings',
svg: "assets/display.svg",
ffi: widget.ffi,
color: _MenubarTheme.blueColor,
@@ -916,6 +974,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
disableClipboard(),
lockAfterSessionEnd(),
privacyMode(),
+ swapKey(),
]);
}
@@ -949,12 +1008,13 @@ class _DisplayMenuState extends State<_DisplayMenu> {
final canvasModel = widget.ffi.canvasModel;
final width = (canvasModel.getDisplayWidth() * canvasModel.scale +
- canvasModel.windowBorderWidth * 2) *
+ CanvasModel.leftToEdge +
+ CanvasModel.rightToEdge) *
scale +
magicWidth;
final height = (canvasModel.getDisplayHeight() * canvasModel.scale +
- canvasModel.tabBarHeight +
- canvasModel.windowBorderWidth * 2) *
+ CanvasModel.topToEdge +
+ CanvasModel.bottomToEdge) *
scale +
magicHeight;
double left = wndRect.left + (wndRect.width - width) / 2;
@@ -1023,10 +1083,10 @@ class _DisplayMenuState extends State<_DisplayMenu> {
final canvasModel = widget.ffi.canvasModel;
final displayWidth = canvasModel.getDisplayWidth();
final displayHeight = canvasModel.getDisplayHeight();
- final requiredWidth = displayWidth +
- (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
- final requiredHeight = displayHeight +
- (canvasModel.tabBarHeight + canvasModel.windowBorderWidth * 2);
+ final requiredWidth =
+ CanvasModel.leftToEdge + displayWidth + CanvasModel.rightToEdge;
+ final requiredHeight =
+ CanvasModel.topToEdge + displayHeight + CanvasModel.bottomToEdge;
return selfWidth > (requiredWidth * scale) &&
selfHeight > (requiredHeight * scale);
}
@@ -1344,14 +1404,14 @@ class _DisplayMenuState extends State<_DisplayMenu> {
child: Text(translate('H264')),
value: 'h264',
groupValue: groupValue,
- onChanged: onChanged,
+ onChanged: codecs[0] ? onChanged : null,
ffi: widget.ffi,
),
_RadioMenuButton(
child: Text(translate('H265')),
value: 'h265',
groupValue: groupValue,
- onChanged: onChanged,
+ onChanged: codecs[1] ? onChanged : null,
ffi: widget.ffi,
),
]);
@@ -1400,23 +1460,33 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
showRemoteCursor() {
+ if (widget.ffi.ffiModel.pi.platform == kPeerPlatformAndroid) {
+ return Offstage();
+ }
+ final ffiModel = widget.ffi.ffiModel;
final visible = !widget.ffi.canvasModel.cursorEmbedded;
if (!visible) return Offstage();
+ final enabled = !ffiModel.viewOnly;
final state = ShowRemoteCursorState.find(widget.id);
final option = 'show-remote-cursor';
return _CheckboxMenuButton(
value: state.value,
- onChanged: (value) async {
- if (value == null) return;
- await bind.sessionToggleOption(id: widget.id, value: option);
- state.value =
- bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
- },
+ onChanged: enabled
+ ? (value) async {
+ if (value == null) return;
+ await bind.sessionToggleOption(id: widget.id, value: option);
+ state.value =
+ bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
+ }
+ : null,
ffi: widget.ffi,
child: Text(translate('Show remote cursor')));
}
zoomCursor() {
+ if (widget.ffi.ffiModel.pi.platform == kPeerPlatformAndroid) {
+ return Offstage();
+ }
final visible = widget.state.viewStyle.value != kRemoteViewStyleOriginal;
if (!visible) return Offstage();
final option = 'zoom-cursor';
@@ -1480,16 +1550,21 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
disableClipboard() {
+ final ffiModel = widget.ffi.ffiModel;
final visible = perms['keyboard'] != false && perms['clipboard'] != false;
if (!visible) return Offstage();
+ final enabled = !ffiModel.viewOnly;
final option = 'disable-clipboard';
- final value = bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
+ var value = bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
+ if (ffiModel.viewOnly) value = true;
return _CheckboxMenuButton(
value: value,
- onChanged: (value) {
- if (value == null) return;
- bind.sessionToggleOption(id: widget.id, value: option);
- },
+ onChanged: enabled
+ ? (value) {
+ if (value == null) return;
+ bind.sessionToggleOption(id: widget.id, value: option);
+ }
+ : null,
ffi: widget.ffi,
child: Text(translate('Disable clipboard')));
}
@@ -1518,11 +1593,38 @@ class _DisplayMenuState extends State<_DisplayMenu> {
value: rxValue.value,
onChanged: (value) {
if (value == null) return;
+ if (widget.ffi.ffiModel.pi.currentDisplay != 0) {
+ msgBox(
+ widget.id,
+ 'custom-nook-nocancel-hasclose',
+ 'info',
+ 'Please switch to Display 1 first',
+ '',
+ widget.ffi.dialogManager);
+ return;
+ }
bind.sessionToggleOption(id: widget.id, value: option);
},
ffi: widget.ffi,
child: Text(translate('Privacy mode')));
}
+
+ swapKey() {
+ final visible = perms['keyboard'] != false &&
+ ((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) ||
+ (!Platform.isMacOS && pi.platform == kPeerPlatformMacOS));
+ if (!visible) return Offstage();
+ final option = 'allow_swap_key';
+ final value = bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
+ return _CheckboxMenuButton(
+ value: value,
+ onChanged: (value) {
+ if (value == null) return;
+ bind.sessionToggleOption(id: widget.id, value: option);
+ },
+ ffi: widget.ffi,
+ child: Text(translate('Swap control-command key')));
+ }
}
class _KeyboardMenu extends StatelessWidget {
@@ -1540,30 +1642,41 @@ class _KeyboardMenu extends StatelessWidget {
Widget build(BuildContext context) {
var ffiModel = Provider.of(context);
if (ffiModel.permissions['keyboard'] == false) return Offstage();
- // Do not support peer 1.1.9.
if (stateGlobal.grabKeyboard) {
- bind.sessionSetKeyboardMode(id: id, value: 'map');
+ if (bind.sessionIsKeyboardModeSupported(id: id, mode: _kKeyMapMode)) {
+ bind.sessionSetKeyboardMode(id: id, value: _kKeyMapMode);
+ } else if (bind.sessionIsKeyboardModeSupported(
+ id: id, mode: _kKeyLegacyMode)) {
+ bind.sessionSetKeyboardMode(id: id, value: _kKeyLegacyMode);
+ }
return Offstage();
}
return _IconSubmenuButton(
+ tooltip: 'Keyboard Settings',
svg: "assets/keyboard.svg",
ffi: ffi,
color: _MenubarTheme.blueColor,
hoverColor: _MenubarTheme.hoverBlueColor,
- menuChildren: [mode(), localKeyboardType()]);
+ menuChildren: [
+ mode(),
+ localKeyboardType(),
+ Divider(),
+ view_mode(),
+ ]);
}
mode() {
return futureBuilder(future: () async {
- return await bind.sessionGetKeyboardMode(id: id) ?? 'legacy';
+ return await bind.sessionGetKeyboardMode(id: id) ?? _kKeyLegacyMode;
}(), hasData: (data) {
final groupValue = data as String;
List modes = [
- KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'),
- KeyboardModeMenu(key: 'map', menu: 'Map mode'),
- KeyboardModeMenu(key: 'translate', menu: 'Translate mode'),
+ KeyboardModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
+ KeyboardModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
+ KeyboardModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
];
List<_RadioMenuButton> list = [];
+ final enabled = !ffi.ffiModel.viewOnly;
onChanged(String? value) async {
if (value == null) return;
await bind.sessionSetKeyboardMode(id: id, value: value);
@@ -1571,20 +1684,20 @@ class _KeyboardMenu extends StatelessWidget {
for (KeyboardModeMenu mode in modes) {
if (bind.sessionIsKeyboardModeSupported(id: id, mode: mode.key)) {
- if (mode.key == 'translate') {
- if (Platform.isLinux || pi.platform == kPeerPlatformLinux) {
+ if (mode.key == _kKeyTranslateMode) {
+ if (Platform.isLinux) {
continue;
}
}
var text = translate(mode.menu);
- if (mode.key == 'translate') {
+ if (mode.key == _kKeyTranslateMode) {
text = '$text beta';
}
list.add(_RadioMenuButton(
child: Text(text),
value: mode.key,
groupValue: groupValue,
- onChanged: onChanged,
+ onChanged: enabled ? onChanged : null,
ffi: ffi,
));
}
@@ -1597,6 +1710,7 @@ class _KeyboardMenu extends StatelessWidget {
final localPlatform = getLocalPlatformForKBLayoutType(pi.platform);
final visible = localPlatform != '';
if (!visible) return Offstage();
+ final enabled = !ffi.ffiModel.viewOnly;
return Column(
children: [
Divider(),
@@ -1605,12 +1719,30 @@ class _KeyboardMenu extends StatelessWidget {
'${translate('Local keyboard type')}: ${KBLayoutType.value}'),
trailingIcon: const Icon(Icons.settings),
ffi: ffi,
- onPressed: () =>
- showKBLayoutTypeChooser(localPlatform, ffi.dialogManager),
+ onPressed: enabled
+ ? () => showKBLayoutTypeChooser(localPlatform, ffi.dialogManager)
+ : null,
)
],
);
}
+
+ view_mode() {
+ final ffiModel = ffi.ffiModel;
+ final enabled = version_cmp(pi.version, '1.2.0') >= 0 &&
+ ffiModel.permissions["keyboard"] != false;
+ return _CheckboxMenuButton(
+ value: ffiModel.viewOnly,
+ onChanged: enabled
+ ? (value) async {
+ if (value == null) return;
+ await bind.sessionToggleOption(id: id, value: 'view-only');
+ ffiModel.setViewOnly(id, value);
+ }
+ : null,
+ ffi: ffi,
+ child: Text(translate('View Mode')));
+ }
}
class _ChatMenu extends StatefulWidget {
@@ -1633,6 +1765,7 @@ class _ChatMenuState extends State<_ChatMenu> {
@override
Widget build(BuildContext context) {
return _IconSubmenuButton(
+ tooltip: 'Chat',
key: chatButtonKey,
svg: 'assets/chat.svg',
ffi: widget.ffi,
@@ -1751,22 +1884,24 @@ class _CloseMenu extends StatelessWidget {
class _IconMenuButton extends StatefulWidget {
final String? assetName;
final Widget? icon;
- final String tooltip;
+ final String? tooltip;
final Color color;
final Color hoverColor;
final VoidCallback? onPressed;
final double? hMargin;
final double? vMargin;
+ final bool topLevel;
const _IconMenuButton({
Key? key,
this.assetName,
this.icon,
- required this.tooltip,
+ this.tooltip,
required this.color,
required this.hoverColor,
required this.onPressed,
this.hMargin,
this.vMargin,
+ this.topLevel = true,
}) : super(key: key);
@override
@@ -1786,36 +1921,40 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
width: _MenubarTheme.buttonSize,
height: _MenubarTheme.buttonSize,
);
- return SizedBox(
+ final button = SizedBox(
width: _MenubarTheme.buttonSize,
height: _MenubarTheme.buttonSize,
child: MenuItemButton(
style: ButtonStyle(
+ backgroundColor: MaterialStatePropertyAll(Colors.transparent),
padding: MaterialStatePropertyAll(EdgeInsets.zero),
overlayColor: MaterialStatePropertyAll(Colors.transparent)),
onHover: (value) => setState(() {
hover = value;
}),
onPressed: widget.onPressed,
- child: Tooltip(
- message: translate(widget.tooltip),
- child: Material(
- type: MaterialType.transparency,
- child: Ink(
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(_MenubarTheme.iconRadius),
- color: hover ? widget.hoverColor : widget.color,
- ),
- child: icon))),
+ child: Material(
+ type: MaterialType.transparency,
+ child: Ink(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(_MenubarTheme.iconRadius),
+ color: hover ? widget.hoverColor : widget.color,
+ ),
+ child: icon)),
),
).marginSymmetric(
horizontal: widget.hMargin ?? _MenubarTheme.buttonHMargin,
vertical: widget.vMargin ?? _MenubarTheme.buttonVMargin);
+ if (widget.topLevel) {
+ return MenuBar(children: [button]);
+ } else {
+ return button;
+ }
}
}
class _IconSubmenuButton extends StatefulWidget {
+ final String tooltip;
final String? svg;
final Widget? icon;
final Color color;
@@ -1828,6 +1967,7 @@ class _IconSubmenuButton extends StatefulWidget {
{Key? key,
this.svg,
this.icon,
+ required this.tooltip,
required this.color,
required this.hoverColor,
required this.menuChildren,
@@ -1852,32 +1992,35 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
width: _MenubarTheme.buttonSize,
height: _MenubarTheme.buttonSize,
);
- return SizedBox(
- width: _MenubarTheme.buttonSize,
- height: _MenubarTheme.buttonSize,
- child: SubmenuButton(
- menuStyle: widget.menuStyle,
- style: ButtonStyle(
- padding: MaterialStatePropertyAll(EdgeInsets.zero),
- overlayColor: MaterialStatePropertyAll(Colors.transparent)),
- onHover: (value) => setState(() {
- hover = value;
- }),
- child: Material(
- type: MaterialType.transparency,
- child: Ink(
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(_MenubarTheme.iconRadius),
- color: hover ? widget.hoverColor : widget.color,
- ),
- child: icon)),
- menuChildren: widget.menuChildren
- .map((e) => _buildPointerTrackWidget(e, widget.ffi))
- .toList()))
- .marginSymmetric(
- horizontal: _MenubarTheme.buttonHMargin,
- vertical: _MenubarTheme.buttonVMargin);
+ final button = SizedBox(
+ width: _MenubarTheme.buttonSize,
+ height: _MenubarTheme.buttonSize,
+ child: SubmenuButton(
+ menuStyle: widget.menuStyle,
+ style: ButtonStyle(
+ backgroundColor: MaterialStatePropertyAll(Colors.transparent),
+ padding: MaterialStatePropertyAll(EdgeInsets.zero),
+ overlayColor: MaterialStatePropertyAll(Colors.transparent)),
+ onHover: (value) => setState(() {
+ hover = value;
+ }),
+ child: Material(
+ type: MaterialType.transparency,
+ child: Ink(
+ decoration: BoxDecoration(
+ borderRadius:
+ BorderRadius.circular(_MenubarTheme.iconRadius),
+ color: hover ? widget.hoverColor : widget.color,
+ ),
+ child: icon)),
+ menuChildren: widget.menuChildren
+ .map((e) => _buildPointerTrackWidget(e, widget.ffi))
+ .toList()));
+ return MenuBar(children: [
+ button.marginSymmetric(
+ horizontal: _MenubarTheme.buttonHMargin,
+ vertical: _MenubarTheme.buttonVMargin)
+ ]);
}
}
@@ -2016,7 +2159,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
child: Icon(
Icons.drag_indicator,
size: 20,
- color: Colors.grey[800],
+ color: MyTheme.color(context).drag_indicator,
),
feedback: widget,
onDragStarted: (() {
@@ -2068,7 +2211,11 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
data: TextButtonThemeData(style: buttonStyle),
child: Container(
decoration: BoxDecoration(
- color: Colors.white,
+ color: Theme.of(context)
+ .menuBarTheme
+ .style
+ ?.backgroundColor
+ ?.resolve(MaterialState.values.toSet()),
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(5),
),
@@ -2100,3 +2247,69 @@ Widget _buildPointerTrackWidget(Widget child, FFI ffi) {
),
);
}
+
+class _MultiMonitorMenu extends StatelessWidget {
+ final String id;
+ final FFI ffi;
+
+ const _MultiMonitorMenu({
+ super.key,
+ required this.id,
+ required this.ffi,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final List rowChildren = [];
+ final pi = ffi.ffiModel.pi;
+
+ for (int i = 0; i < pi.displays.length; i++) {
+ rowChildren.add(
+ Obx(() {
+ RxInt display = CurrentDisplayState.find(id);
+ return _IconMenuButton(
+ topLevel: false,
+ color: i == display.value
+ ? _MenubarTheme.blueColor
+ : Colors.grey[800]!,
+ hoverColor: i == display.value
+ ? _MenubarTheme.hoverBlueColor
+ : Colors.grey[850]!,
+ icon: Container(
+ alignment: AlignmentDirectional.center,
+ constraints:
+ const BoxConstraints(minHeight: _MenubarTheme.height),
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ SvgPicture.asset(
+ "assets/screen.svg",
+ color: Colors.white,
+ ),
+ Obx(
+ () => Text(
+ (i + 1).toString(),
+ style: TextStyle(
+ color: i == display.value
+ ? _MenubarTheme.blueColor
+ : Colors.grey[800]!,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ onPressed: () {
+ if (display.value != i) {
+ bind.sessionSwitchDisplay(id: id, value: i);
+ }
+ },
+ );
+ }),
+ );
+ }
+ return Row(children: rowChildren);
+ }
+}
diff --git a/flutter/lib/desktop/widgets/scroll_wrapper.dart b/flutter/lib/desktop/widgets/scroll_wrapper.dart
index 32ed149e5..c5bc3394b 100644
--- a/flutter/lib/desktop/widgets/scroll_wrapper.dart
+++ b/flutter/lib/desktop/widgets/scroll_wrapper.dart
@@ -14,6 +14,7 @@ class DesktopScrollWrapper extends StatelessWidget {
return ImprovedScrolling(
scrollController: scrollController,
enableCustomMouseWheelScrolling: true,
+ // enableKeyboardScrolling: true, // strange behavior on mac
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
scrollDuration: kDefaultScrollDuration,
scrollCurve: Curves.linearToEaseOut,
diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart
index ee3aaaf2c..4211911ff 100644
--- a/flutter/lib/desktop/widgets/tabbar_widget.dart
+++ b/flutter/lib/desktop/widgets/tabbar_widget.dart
@@ -53,6 +53,7 @@ enum DesktopTabType {
remoteScreen,
fileTransfer,
portForward,
+ install,
}
class DesktopTabState {
@@ -249,8 +250,9 @@ class DesktopTab extends StatelessWidget {
this.unSelectedTabBackgroundColor,
}) : super(key: key) {
tabType = controller.tabType;
- isMainWindow =
- tabType == DesktopTabType.main || tabType == DesktopTabType.cm;
+ isMainWindow = tabType == DesktopTabType.main ||
+ tabType == DesktopTabType.cm ||
+ tabType == DesktopTabType.install;
}
static RxString labelGetterAlias(String peerId) {
@@ -278,7 +280,6 @@ class DesktopTab extends StatelessWidget {
),
const Divider(
height: 1,
- thickness: 1,
),
],
),
@@ -361,7 +362,8 @@ class DesktopTab extends StatelessWidget {
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem() {
return state.value.tabs.length == 1 &&
- controller.tabType == DesktopTabType.main;
+ (controller.tabType == DesktopTabType.main ||
+ controller.tabType == DesktopTabType.install);
}
Widget _buildBar() {
@@ -523,12 +525,18 @@ class WindowActionPanelState extends State
super.dispose();
}
+ void _setMaximize(bool maximize) {
+ stateGlobal.setMaximize(maximize);
+ setState(() {});
+ }
+
@override
void onWindowMaximize() {
// catch maximize from system
if (!widget.isMaximized.value) {
widget.isMaximized.value = true;
}
+ _setMaximize(true);
super.onWindowMaximize();
}
@@ -538,6 +546,7 @@ class WindowActionPanelState extends State
if (widget.isMaximized.value) {
widget.isMaximized.value = false;
}
+ _setMaximize(false);
super.onWindowUnmaximize();
}
@@ -599,7 +608,7 @@ class WindowActionPanelState extends State
offstage: !widget.showMaximize || Platform.isMacOS,
child: Obx(() => ActionIcon(
message:
- widget.isMaximized.value ? "Restore" : "Maximize",
+ widget.isMaximized.value ? 'Restore' : 'Maximize',
icon: widget.isMaximized.value
? IconFont.restore
: IconFont.max,
@@ -752,7 +761,8 @@ class _ListView extends StatelessWidget {
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem() {
return state.value.tabs.length == 1 &&
- controller.tabType == DesktopTabType.main;
+ controller.tabType == DesktopTabType.main ||
+ controller.tabType == DesktopTabType.install;
}
@override
@@ -946,7 +956,6 @@ class _TabState extends State<_Tab> with RestorationMixin {
indent: _kDividerIndent,
endIndent: _kDividerIndent,
color: MyTheme.tabbar(context).dividerColor,
- thickness: 1,
),
)
],
diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart
index c61287d4f..f0a9a938f 100644
--- a/flutter/lib/main.dart
+++ b/flutter/lib/main.dart
@@ -153,6 +153,7 @@ void runMainApp(bool startService) async {
void runMobileApp() async {
await initEnv(kAppTypeMain);
if (isAndroid) androidChannelInit();
+ platformFFI.syncAndroidServiceAppDirConfigPath();
runApp(App());
}
@@ -216,7 +217,6 @@ void runMultiWindow(
void runConnectionManagerScreen(bool hide) async {
await initEnv(kAppTypeConnectionManager);
- await bind.cmStartListenIpcThread();
_runApp(
'',
const DesktopServerPage(),
@@ -291,17 +291,20 @@ void _runApp(
void runInstallPage() async {
await windowManager.ensureInitialized();
await initEnv(kAppTypeMain);
- _runApp('', const InstallPage(), ThemeMode.light);
- windowManager.waitUntilReadyToShow(
- WindowOptions(size: Size(800, 600), center: true), () async {
+ _runApp('', const InstallPage(), MyTheme.currentThemeMode());
+ WindowOptions windowOptions =
+ getHiddenTitleBarWindowOptions(size: Size(800, 600), center: true);
+ windowManager.waitUntilReadyToShow(windowOptions, () async {
windowManager.show();
windowManager.focus();
windowManager.setOpacity(1);
windowManager.setAlignment(Alignment.center); // ensure
+ windowManager.setTitle(getWindowName());
});
}
-WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
+WindowOptions getHiddenTitleBarWindowOptions(
+ {Size? size, bool center = false}) {
var defaultTitleBarStyle = TitleBarStyle.hidden;
// we do not hide titlebar on win7 because of the frame overflow.
if (kUseCompatibleUiMode) {
@@ -309,7 +312,7 @@ WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
}
return WindowOptions(
size: size,
- center: false,
+ center: center,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: defaultTitleBarStyle,
diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart
index 7aa9a0005..c6ba42d31 100644
--- a/flutter/lib/mobile/pages/file_manager_page.dart
+++ b/flutter/lib/mobile/pages/file_manager_page.dart
@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
import 'package:flutter_hbb/models/file_model.dart';
-import 'package:provider/provider.dart';
+import 'package:get/get.dart';
import 'package:toggle_switch/toggle_switch.dart';
import 'package:wakelock/wakelock.dart';
@@ -18,10 +18,51 @@ class FileManagerPage extends StatefulWidget {
State createState() => _FileManagerPageState();
}
+enum SelectMode { local, remote, none }
+
+extension SelectModeEq on SelectMode {
+ bool eq(bool? currentIsLocal) {
+ if (currentIsLocal == null) {
+ return false;
+ }
+ if (currentIsLocal) {
+ return this == SelectMode.local;
+ } else {
+ return this == SelectMode.remote;
+ }
+ }
+}
+
+extension SelectModeExt on Rx {
+ void toggle(bool currentIsLocal) {
+ switch (value) {
+ case SelectMode.local:
+ value = SelectMode.none;
+ break;
+ case SelectMode.remote:
+ value = SelectMode.none;
+ break;
+ case SelectMode.none:
+ if (currentIsLocal) {
+ value = SelectMode.local;
+ } else {
+ value = SelectMode.remote;
+ }
+ break;
+ }
+ }
+}
+
class _FileManagerPageState extends State {
final model = gFFI.fileModel;
- final _selectedItems = SelectedItems();
- final _breadCrumbScroller = ScrollController();
+ final selectMode = SelectMode.none.obs;
+
+ var showLocal = true;
+
+ FileController get currentFileController =>
+ showLocal ? model.localController : model.remoteController;
+ FileDirectory get currentDir => currentFileController.directory.value;
+ DirectoryOptions get currentOptions => currentFileController.options.value;
@override
void initState() {
@@ -32,13 +73,12 @@ class _FileManagerPageState extends State {
.showLoading(translate('Connecting...'), onCancel: closeConnection);
});
gFFI.ffiModel.updateEventListener(widget.id);
- model.onDirChanged = (_) => breadCrumbScrollToEnd();
Wakelock.enable();
}
@override
void dispose() {
- model.onClose().whenComplete(() {
+ model.close().whenComplete(() {
gFFI.close();
gFFI.dialogManager.dismissAll();
Wakelock.disable();
@@ -47,288 +87,455 @@ class _FileManagerPageState extends State {
}
@override
- Widget build(BuildContext context) => ChangeNotifierProvider.value(
- value: model,
- child: Consumer(builder: (_context, _model, _child) {
- return WillPopScope(
- onWillPop: () async {
- if (model.selectMode) {
- model.toggleSelectMode();
- } else {
- model.goBack();
+ Widget build(BuildContext context) => WillPopScope(
+ onWillPop: () async {
+ if (selectMode.value != SelectMode.none) {
+ selectMode.value = SelectMode.none;
+ setState(() {});
+ } else {
+ currentFileController.goBack();
+ }
+ return false;
+ },
+ child: Scaffold(
+ // backgroundColor: MyTheme.grayBg,
+ appBar: AppBar(
+ leading: Row(children: [
+ IconButton(
+ icon: Icon(Icons.close),
+ onPressed: () => clientClose(widget.id, gFFI.dialogManager)),
+ ]),
+ centerTitle: true,
+ title: ToggleSwitch(
+ initialLabelIndex: showLocal ? 0 : 1,
+ activeBgColor: [MyTheme.idColor],
+ inactiveBgColor: Theme.of(context).brightness == Brightness.light
+ ? MyTheme.grayBg
+ : null,
+ inactiveFgColor: Theme.of(context).brightness == Brightness.light
+ ? Colors.black54
+ : null,
+ totalSwitches: 2,
+ minWidth: 100,
+ fontSize: 15,
+ iconSize: 18,
+ labels: [translate("Local"), translate("Remote")],
+ icons: [Icons.phone_android_sharp, Icons.screen_share],
+ onToggle: (index) {
+ final current = showLocal ? 0 : 1;
+ if (index != current) {
+ setState(() => showLocal = !showLocal);
}
- return false;
},
- child: Scaffold(
- // backgroundColor: MyTheme.grayBg,
- appBar: AppBar(
- leading: Row(children: [
- IconButton(
- icon: Icon(Icons.close),
- onPressed: () =>
- clientClose(widget.id, gFFI.dialogManager)),
- ]),
- centerTitle: true,
- title: ToggleSwitch(
- initialLabelIndex: model.isLocal ? 0 : 1,
- activeBgColor: [MyTheme.idColor],
- inactiveBgColor:
- Theme.of(context).brightness == Brightness.light
- ? MyTheme.grayBg
- : null,
- inactiveFgColor:
- Theme.of(context).brightness == Brightness.light
- ? Colors.black54
- : null,
- totalSwitches: 2,
- minWidth: 100,
- fontSize: 15,
- iconSize: 18,
- labels: [translate("Local"), translate("Remote")],
- icons: [Icons.phone_android_sharp, Icons.screen_share],
- onToggle: (index) {
- final current = model.isLocal ? 0 : 1;
- if (index != current) {
- model.togglePage();
- }
- },
- ),
- actions: [
- PopupMenuButton(
- icon: Icon(Icons.more_vert),
- itemBuilder: (context) {
- return [
- PopupMenuItem(
- child: Row(
- children: [
- Icon(Icons.refresh,
- color: Theme.of(context).iconTheme.color),
- SizedBox(width: 5),
- Text(translate("Refresh File"))
- ],
- ),
- value: "refresh",
- ),
- PopupMenuItem(
- enabled: model.currentDir.path != "/",
- child: Row(
- children: [
- Icon(Icons.check,
- color: Theme.of(context).iconTheme.color),
- SizedBox(width: 5),
- Text(translate("Multi Select"))
- ],
- ),
- value: "select",
- ),
- PopupMenuItem(
- enabled: model.currentDir.path != "/",
- child: Row(
- children: [
- Icon(Icons.folder_outlined,
- color: Theme.of(context).iconTheme.color),
- SizedBox(width: 5),
- Text(translate("Create Folder"))
- ],
- ),
- value: "folder",
- ),
- PopupMenuItem(
- enabled: model.currentDir.path != "/",
- child: Row(
- children: [
- Icon(
- model.getCurrentShowHidden()
- ? Icons.check_box_outlined
- : Icons.check_box_outline_blank,
- color: Theme.of(context).iconTheme.color),
- SizedBox(width: 5),
- Text(translate("Show Hidden Files"))
- ],
- ),
- value: "hidden",
- )
- ];
- },
- onSelected: (v) {
- if (v == "refresh") {
- model.refresh();
- } else if (v == "select") {
- _selectedItems.clear();
- model.toggleSelectMode();
- } else if (v == "folder") {
- final name = TextEditingController();
- gFFI.dialogManager
- .show((setState, close) => CustomAlertDialog(
- title: Text(translate("Create Folder")),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextFormField(
- decoration: InputDecoration(
- labelText: translate(
- "Please enter the folder name"),
- ),
- controller: name,
- ),
- ],
+ ),
+ actions: [
+ PopupMenuButton(
+ icon: Icon(Icons.more_vert),
+ itemBuilder: (context) {
+ return [
+ PopupMenuItem(
+ child: Row(
+ children: [
+ Icon(Icons.refresh,
+ color: Theme.of(context).iconTheme.color),
+ SizedBox(width: 5),
+ Text(translate("Refresh File"))
+ ],
+ ),
+ value: "refresh",
+ ),
+ PopupMenuItem(
+ enabled: currentDir.path != "/",
+ child: Row(
+ children: [
+ Icon(Icons.check,
+ color: Theme.of(context).iconTheme.color),
+ SizedBox(width: 5),
+ Text(translate("Multi Select"))
+ ],
+ ),
+ value: "select",
+ ),
+ PopupMenuItem(
+ enabled: currentDir.path != "/",
+ child: Row(
+ children: [
+ Icon(Icons.folder_outlined,
+ color: Theme.of(context).iconTheme.color),
+ SizedBox(width: 5),
+ Text(translate("Create Folder"))
+ ],
+ ),
+ value: "folder",
+ ),
+ PopupMenuItem(
+ enabled: currentDir.path != "/",
+ child: Row(
+ children: [
+ Icon(
+ currentOptions.showHidden
+ ? Icons.check_box_outlined
+ : Icons.check_box_outline_blank,
+ color: Theme.of(context).iconTheme.color),
+ SizedBox(width: 5),
+ Text(translate("Show Hidden Files"))
+ ],
+ ),
+ value: "hidden",
+ )
+ ];
+ },
+ onSelected: (v) {
+ if (v == "refresh") {
+ currentFileController.refresh();
+ } else if (v == "select") {
+ model.localController.selectedItems.clear();
+ model.remoteController.selectedItems.clear();
+ selectMode.toggle(showLocal);
+ setState(() {});
+ } else if (v == "folder") {
+ final name = TextEditingController();
+ gFFI.dialogManager
+ .show((setState, close) => CustomAlertDialog(
+ title: Text(translate("Create Folder")),
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TextFormField(
+ decoration: InputDecoration(
+ labelText: translate(
+ "Please enter the folder name"),
),
- actions: [
- dialogButton("Cancel",
- onPressed: () => close(false),
- isOutline: true),
- dialogButton("OK", onPressed: () {
- if (name.value.text.isNotEmpty) {
- model.createDir(PathUtil.join(
- model.currentDir.path,
- name.value.text,
- model.getCurrentIsWindows()));
- close();
- }
- })
- ]));
- } else if (v == "hidden") {
- model.toggleShowHidden();
- }
- }),
- ],
+ controller: name,
+ ),
+ ],
+ ),
+ actions: [
+ dialogButton("Cancel",
+ onPressed: () => close(false),
+ isOutline: true),
+ dialogButton("OK", onPressed: () {
+ if (name.value.text.isNotEmpty) {
+ currentFileController.createDir(
+ PathUtil.join(
+ currentDir.path,
+ name.value.text,
+ currentOptions.isWindows));
+ close();
+ }
+ })
+ ]));
+ } else if (v == "hidden") {
+ currentFileController.toggleShowHidden();
+ }
+ }),
+ ],
+ ),
+ body: showLocal
+ ? FileManagerView(
+ controller: model.localController,
+ selectMode: selectMode,
+ )
+ : FileManagerView(
+ controller: model.remoteController,
+ selectMode: selectMode,
),
- body: body(),
- bottomSheet: bottomSheet(),
- ));
- }));
+ bottomSheet: bottomSheet(),
+ ));
- bool showCheckBox() {
- if (!model.selectMode) {
- return false;
- }
- return !_selectedItems.isOtherPage(model.isLocal);
+ Widget? bottomSheet() {
+ return Obx(() {
+ final selectedItems = getActiveSelectedItems();
+ final jobTable = model.jobController.jobTable;
+
+ final localLabel = selectedItems?.isLocal == null
+ ? ""
+ : " [${selectedItems!.isLocal ? translate("Local") : translate("Remote")}]";
+ if (!(selectMode.value == SelectMode.none)) {
+ final selectedItemsLen =
+ "${selectedItems?.items.length ?? 0} ${translate("items")}";
+ if (selectedItems == null ||
+ selectedItems.items.isEmpty ||
+ selectMode.value.eq(showLocal)) {
+ return BottomSheetBody(
+ leading: Icon(Icons.check),
+ title: translate("Selected"),
+ text: selectedItemsLen + localLabel,
+ onCanceled: () {
+ selectedItems?.items.clear();
+ selectMode.value = SelectMode.none;
+ setState(() {});
+ },
+ actions: [
+ IconButton(
+ icon: Icon(Icons.compare_arrows),
+ onPressed: () => setState(() => showLocal = !showLocal),
+ ),
+ IconButton(
+ icon: Icon(Icons.delete_forever),
+ onPressed: selectedItems != null
+ ? () async {
+ if (selectedItems.items.isNotEmpty) {
+ await currentFileController
+ .removeAction(selectedItems);
+ selectedItems.items.clear();
+ selectMode.value = SelectMode.none;
+ }
+ }
+ : null,
+ )
+ ]);
+ } else {
+ return BottomSheetBody(
+ leading: Icon(Icons.input),
+ title: translate("Paste here?"),
+ text: selectedItemsLen + localLabel,
+ onCanceled: () {
+ selectedItems.items.clear();
+ selectMode.value = SelectMode.none;
+ setState(() {});
+ },
+ actions: [
+ IconButton(
+ icon: Icon(Icons.compare_arrows),
+ onPressed: () => setState(() => showLocal = !showLocal),
+ ),
+ IconButton(
+ icon: Icon(Icons.paste),
+ onPressed: () {
+ selectMode.value = SelectMode.none;
+ final otherSide = showLocal
+ ? model.remoteController
+ : model.localController;
+ final thisSideData =
+ DirectoryData(currentDir, currentOptions);
+ otherSide.sendFiles(selectedItems, thisSideData);
+ selectedItems.items.clear();
+ selectMode.value = SelectMode.none;
+ },
+ )
+ ]);
+ }
+ }
+
+ if (jobTable.isEmpty) {
+ return Offstage();
+ }
+
+ switch (jobTable.last.state) {
+ case JobState.inProgress:
+ return BottomSheetBody(
+ leading: CircularProgressIndicator(),
+ title: translate("Waiting"),
+ text:
+ "${translate("Speed")}: ${readableFileSize(jobTable.last.speed)}/s",
+ onCanceled: () {
+ model.jobController.cancelJob(jobTable.last.id);
+ jobTable.clear();
+ },
+ );
+ case JobState.done:
+ return BottomSheetBody(
+ leading: Icon(Icons.check),
+ title: "${translate("Successful")}!",
+ text: jobTable.last.display(),
+ onCanceled: () => jobTable.clear(),
+ );
+ case JobState.error:
+ return BottomSheetBody(
+ leading: Icon(Icons.error),
+ title: "${translate("Error")}!",
+ text: "",
+ onCanceled: () => jobTable.clear(),
+ );
+ case JobState.none:
+ break;
+ case JobState.paused:
+ // TODO: Handle this case.
+ break;
+ }
+ return Offstage();
+ });
}
- Widget body() {
- final isLocal = model.isLocal;
- final fd = model.currentDir;
- final entries = fd.entries;
+ SelectedItems? getActiveSelectedItems() {
+ final localSelectedItems = model.localController.selectedItems;
+ final remoteSelectedItems = model.remoteController.selectedItems;
+
+ if (localSelectedItems.items.isNotEmpty &&
+ remoteSelectedItems.items.isNotEmpty) {
+ // assert unreachable
+ debugPrint("Wrong SelectedItems state, reset");
+ localSelectedItems.clear();
+ remoteSelectedItems.clear();
+ }
+
+ if (localSelectedItems.items.isEmpty && remoteSelectedItems.items.isEmpty) {
+ return null;
+ }
+
+ if (localSelectedItems.items.length > remoteSelectedItems.items.length) {
+ return localSelectedItems;
+ } else {
+ return remoteSelectedItems;
+ }
+ }
+}
+
+class FileManagerView extends StatefulWidget {
+ final FileController controller;
+ final Rx selectMode;
+
+ FileManagerView({required this.controller, required this.selectMode});
+
+ @override
+ State createState() => _FileManagerViewState();
+}
+
+class _FileManagerViewState extends State {
+ final _listScrollController = ScrollController();
+ final _breadCrumbScroller = ScrollController();
+
+ bool get isLocal => widget.controller.isLocal;
+ FileController get controller => widget.controller;
+ SelectedItems get _selectedItems => widget.controller.selectedItems;
+
+ @override
+ void initState() {
+ super.initState();
+ controller.directory.listen((e) => breadCrumbScrollToEnd());
+ }
+
+ @override
+ Widget build(BuildContext context) {
return Column(children: [
headTools(),
- Expanded(
- child: ListView.builder(
- controller: ScrollController(),
- itemCount: entries.length + 1,
- itemBuilder: (context, index) {
- if (index >= entries.length) {
- return listTail();
- }
- var selected = false;
- if (model.selectMode) {
- selected = _selectedItems.contains(entries[index]);
- }
+ Expanded(child: Obx(() {
+ final entries = controller.directory.value.entries;
+ return ListView.builder(
+ controller: _listScrollController,
+ itemCount: entries.length + 1,
+ itemBuilder: (context, index) {
+ if (index >= entries.length) {
+ return listTail();
+ }
+ var selected = false;
+ if (widget.selectMode.value != SelectMode.none) {
+ selected = _selectedItems.items.contains(entries[index]);
+ }
- final sizeStr = entries[index].isFile
- ? readableFileSize(entries[index].size.toDouble())
- : "";
- return Card(
- child: ListTile(
- leading: entries[index].isDrive
- ? Padding(
- padding: EdgeInsets.symmetric(vertical: 8),
- child: Image(
- image: iconHardDrive,
- fit: BoxFit.scaleDown,
- color: Theme.of(context)
- .iconTheme
- .color
- ?.withOpacity(0.7)))
- : Icon(
- entries[index].isFile
- ? Icons.feed_outlined
- : Icons.folder,
- size: 40),
- title: Text(entries[index].name),
- selected: selected,
- subtitle: entries[index].isDrive
- ? null
- : Text(
- "${entries[index].lastModified().toString().replaceAll(".000", "")} $sizeStr",
- style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
- ),
- trailing: entries[index].isDrive
- ? null
- : showCheckBox()
- ? Checkbox(
- value: selected,
- onChanged: (v) {
- if (v == null) return;
- if (v && !selected) {
- _selectedItems.add(isLocal, entries[index]);
- } else if (!v && selected) {
- _selectedItems.remove(entries[index]);
- }
- setState(() {});
- })
- : PopupMenuButton(
- icon: Icon(Icons.more_vert),
- itemBuilder: (context) {
- return [
- PopupMenuItem(
- child: Text(translate("Delete")),
- value: "delete",
- ),
- PopupMenuItem(
- child: Text(translate("Multi Select")),
- value: "multi_select",
- ),
- PopupMenuItem(
- child: Text(translate("Properties")),
- value: "properties",
- enabled: false,
- )
- ];
- },
- onSelected: (v) {
- if (v == "delete") {
- final items = SelectedItems();
- items.add(isLocal, entries[index]);
- model.removeAction(items);
- } else if (v == "multi_select") {
- _selectedItems.clear();
- model.toggleSelectMode();
- }
- }),
- onTap: () {
- if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
- if (selected) {
- _selectedItems.remove(entries[index]);
- } else {
- _selectedItems.add(isLocal, entries[index]);
+ final sizeStr = entries[index].isFile
+ ? readableFileSize(entries[index].size.toDouble())
+ : "";
+
+ final showCheckBox = () {
+ return widget.selectMode.value != SelectMode.none &&
+ widget.selectMode.value.eq(controller.selectedItems.isLocal);
+ }();
+ return Card(
+ child: ListTile(
+ leading: entries[index].isDrive
+ ? Padding(
+ padding: EdgeInsets.symmetric(vertical: 8),
+ child: Image(
+ image: iconHardDrive,
+ fit: BoxFit.scaleDown,
+ color: Theme.of(context)
+ .iconTheme
+ .color
+ ?.withOpacity(0.7)))
+ : Icon(
+ entries[index].isFile
+ ? Icons.feed_outlined
+ : Icons.folder,
+ size: 40),
+ title: Text(entries[index].name),
+ selected: selected,
+ subtitle: entries[index].isDrive
+ ? null
+ : Text(
+ "${entries[index].lastModified().toString().replaceAll(".000", "")} $sizeStr",
+ style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
+ ),
+ trailing: entries[index].isDrive
+ ? null
+ : showCheckBox
+ ? Checkbox(
+ value: selected,
+ onChanged: (v) {
+ if (v == null) return;
+ if (v && !selected) {
+ _selectedItems.add(entries[index]);
+ } else if (!v && selected) {
+ _selectedItems.remove(entries[index]);
+ }
+ setState(() {});
+ })
+ : PopupMenuButton(
+ icon: Icon(Icons.more_vert),
+ itemBuilder: (context) {
+ return [
+ PopupMenuItem(
+ child: Text(translate("Delete")),
+ value: "delete",
+ ),
+ PopupMenuItem(
+ child: Text(translate("Multi Select")),
+ value: "multi_select",
+ ),
+ PopupMenuItem(
+ child: Text(translate("Properties")),
+ value: "properties",
+ enabled: false,
+ )
+ ];
+ },
+ onSelected: (v) {
+ if (v == "delete") {
+ final items = SelectedItems(isLocal: isLocal);
+ items.add(entries[index]);
+ controller.removeAction(items);
+ } else if (v == "multi_select") {
+ _selectedItems.clear();
+ widget.selectMode.toggle(isLocal);
+ setState(() {});
+ }
+ }),
+ onTap: () {
+ if (showCheckBox) {
+ if (selected) {
+ _selectedItems.remove(entries[index]);
+ } else {
+ _selectedItems.add(entries[index]);
+ }
+ setState(() {});
+ return;
}
- setState(() {});
- return;
- }
- if (entries[index].isDirectory || entries[index].isDrive) {
- model.openDirectory(entries[index].path);
- } else {
- // Perform file-related tasks.
- }
- },
- onLongPress: entries[index].isDrive
- ? null
- : () {
- _selectedItems.clear();
- model.toggleSelectMode();
- if (model.selectMode) {
- _selectedItems.add(isLocal, entries[index]);
- }
- setState(() {});
- },
- ),
- );
- },
- ))
+ if (entries[index].isDirectory || entries[index].isDrive) {
+ controller.openDirectory(entries[index].path);
+ } else {
+ // Perform file-related tasks.
+ }
+ },
+ onLongPress: entries[index].isDrive
+ ? null
+ : () {
+ _selectedItems.clear();
+ widget.selectMode.toggle(isLocal);
+ if (widget.selectMode.value != SelectMode.none) {
+ _selectedItems.add(entries[index]);
+ }
+ setState(() {});
+ },
+ ),
+ );
+ },
+ );
+ }))
]);
}
- breadCrumbScrollToEnd() {
+ void breadCrumbScrollToEnd() {
Future.delayed(Duration(milliseconds: 200), () {
if (_breadCrumbScroller.hasClients) {
_breadCrumbScroller.animateTo(
@@ -342,35 +549,39 @@ class _FileManagerPageState extends State {
Widget headTools() => Container(
child: Row(
children: [
- Expanded(
- child: BreadCrumb(
- items: getPathBreadCrumbItems(() => model.goHome(), (list) {
- var path = "";
- if (model.currentHome.startsWith(list[0])) {
- // absolute path
- for (var item in list) {
- path = PathUtil.join(path, item, model.getCurrentIsWindows());
+ Expanded(child: Obx(() {
+ final home = controller.options.value.home;
+ final isWindows = controller.options.value.isWindows;
+ return BreadCrumb(
+ items: getPathBreadCrumbItems(controller.shortPath, isWindows,
+ () => controller.goToHomeDirectory(), (list) {
+ var path = "";
+ if (home.startsWith(list[0])) {
+ // absolute path
+ for (var item in list) {
+ path = PathUtil.join(path, item, isWindows);
+ }
+ } else {
+ path += home;
+ for (var item in list) {
+ path = PathUtil.join(path, item, isWindows);
+ }
}
- } else {
- path += model.currentHome;
- for (var item in list) {
- path = PathUtil.join(path, item, model.getCurrentIsWindows());
- }
- }
- model.openDirectory(path);
- }),
- divider: Icon(Icons.chevron_right),
- overflow: ScrollableOverflow(controller: _breadCrumbScroller),
- )),
+ controller.openDirectory(path);
+ }),
+ divider: Icon(Icons.chevron_right),
+ overflow: ScrollableOverflow(controller: _breadCrumbScroller),
+ );
+ })),
Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
- onPressed: model.goBack,
+ onPressed: controller.goBack,
),
IconButton(
icon: Icon(Icons.arrow_upward),
- onPressed: model.goToParentDirectory,
+ onPressed: controller.goToParentDirectory,
),
PopupMenuButton(
icon: Icon(Icons.sort),
@@ -382,123 +593,37 @@ class _FileManagerPageState extends State {
))
.toList();
},
- onSelected: model.changeSortStyle),
+ onSelected: controller.changeSortStyle),
],
)
],
));
- Widget listTail() {
- return Container(
- height: 100,
- child: Column(
- children: [
- Padding(
- padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
- child: Text(
- model.currentDir.path,
- style: TextStyle(color: MyTheme.darkGray),
- ),
- ),
- Padding(
- padding: EdgeInsets.all(2),
- child: Text(
- "${translate("Total")}: ${model.currentDir.entries.length} ${translate("items")}",
- style: TextStyle(color: MyTheme.darkGray),
- ),
- )
- ],
- ),
- );
- }
-
- Widget? bottomSheet() {
- final state = model.jobState;
- final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
- final selectedItemsLen = "${_selectedItems.length} ${translate("items")}";
- final local = _selectedItems.isLocal == null
- ? ""
- : " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
-
- if (model.selectMode) {
- if (_selectedItems.length == 0 || !isOtherPage) {
- return BottomSheetBody(
- leading: Icon(Icons.check),
- title: translate("Selected"),
- text: selectedItemsLen + local,
- onCanceled: () => model.toggleSelectMode(),
- actions: [
- IconButton(
- icon: Icon(Icons.compare_arrows),
- onPressed: model.togglePage,
+ Widget listTail() => Obx(() => Container(
+ height: 100,
+ child: Column(
+ children: [
+ Padding(
+ padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
+ child: Text(
+ controller.directory.value.path,
+ style: TextStyle(color: MyTheme.darkGray),
),
- IconButton(
- icon: Icon(Icons.delete_forever),
- onPressed: () {
- if (_selectedItems.length > 0) {
- model.removeAction(_selectedItems);
- }
- },
- )
- ]);
- } else {
- return BottomSheetBody(
- leading: Icon(Icons.input),
- title: translate("Paste here?"),
- text: selectedItemsLen + local,
- onCanceled: () => model.toggleSelectMode(),
- actions: [
- IconButton(
- icon: Icon(Icons.compare_arrows),
- onPressed: model.togglePage,
+ ),
+ Padding(
+ padding: EdgeInsets.all(2),
+ child: Text(
+ "${translate("Total")}: ${controller.directory.value.entries.length} ${translate("items")}",
+ style: TextStyle(color: MyTheme.darkGray),
),
- IconButton(
- icon: Icon(Icons.paste),
- onPressed: () {
- model.toggleSelectMode();
- model.sendFiles(_selectedItems);
- },
- )
- ]);
- }
- }
+ )
+ ],
+ ),
+ ));
- switch (state) {
- case JobState.inProgress:
- return BottomSheetBody(
- leading: CircularProgressIndicator(),
- title: translate("Waiting"),
- text:
- "${translate("Speed")}: ${readableFileSize(model.jobProgress.speed)}/s",
- onCanceled: () => model.cancelJob(model.jobProgress.id),
- );
- case JobState.done:
- return BottomSheetBody(
- leading: Icon(Icons.check),
- title: "${translate("Successful")}!",
- text: model.jobProgress.display(),
- onCanceled: () => model.jobReset(),
- );
- case JobState.error:
- return BottomSheetBody(
- leading: Icon(Icons.error),
- title: "${translate("Error")}!",
- text: "",
- onCanceled: () => model.jobReset(),
- );
- case JobState.none:
- break;
- case JobState.paused:
- // TODO: Handle this case.
- break;
- }
- return null;
- }
-
- List getPathBreadCrumbItems(
+ List getPathBreadCrumbItems(String shortPath, bool isWindows,
void Function() onHome, void Function(List) onPressed) {
- final path = model.currentShortPath;
- final list = PathUtil.split(path, model.getCurrentIsWindows());
+ final list = PathUtil.split(shortPath, isWindows);
final breadCrumbList = [
BreadCrumbItem(
content: IconButton(
diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart
index abccdf683..ae61c91a7 100644
--- a/flutter/lib/mobile/pages/server_page.dart
+++ b/flutter/lib/mobile/pages/server_page.dart
@@ -1,11 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
+import 'package:get/get.dart';
import 'package:provider/provider.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
+import '../../consts.dart';
import '../../models/platform_model.dart';
import '../../models/server_model.dart';
import 'home_page.dart';
@@ -40,14 +43,14 @@ class ServerPage extends StatefulWidget implements PageShape {
value: "setTemporaryPasswordLength",
enabled:
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
- child: Text(translate("Set temporary password length")),
+ child: Text(translate("One-time password length")),
),
const PopupMenuDivider(),
PopupMenuItem(
padding: const EdgeInsets.symmetric(horizontal: 0.0),
value: kUseTemporaryPassword,
child: ListTile(
- title: Text(translate("Use temporary password")),
+ title: Text(translate("Use one-time password")),
trailing: Icon(
Icons.check,
color: gFFI.serverModel.verificationMethod ==
@@ -138,9 +141,11 @@ class _ServerPageState extends State {
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
- ServerInfo(),
- const PermissionChecker(),
+ gFFI.serverModel.isStart
+ ? ServerInfo()
+ : ServiceNotRunningNotification(),
const ConnectionManager(),
+ const PermissionChecker(),
SizedBox.fromSize(size: const Size(0, 15.0)),
],
),
@@ -150,14 +155,42 @@ class _ServerPageState extends State {
}
void checkService() async {
- gFFI.invokeMethod("check_service"); // jvm
- // for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page
- if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
- PermissionManager.complete("file", await PermissionManager.check("file"));
+ gFFI.invokeMethod("check_service");
+ // for Android 10/11, request MANAGE_EXTERNAL_STORAGE permission from system setting page
+ if (AndroidPermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
+ AndroidPermissionManager.complete(kManageExternalStorage,
+ await AndroidPermissionManager.check(kManageExternalStorage));
debugPrint("file permission finished");
}
}
+class ServiceNotRunningNotification extends StatelessWidget {
+ ServiceNotRunningNotification({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final serverModel = Provider.of(context);
+
+ return PaddingCard(
+ title: translate("Service is not running"),
+ titleIcon:
+ const Icon(Icons.warning_amber_sharp, color: Colors.redAccent),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(translate("android_start_service_tip"),
+ style:
+ const TextStyle(fontSize: 12, color: MyTheme.darkGray))
+ .marginOnly(bottom: 8),
+ ElevatedButton.icon(
+ icon: const Icon(Icons.play_arrow),
+ onPressed: serverModel.toggleService,
+ label: Text(translate("Start Service")))
+ ],
+ ));
+ }
+}
+
class ServerInfo extends StatelessWidget {
final model = gFFI.serverModel;
final emptyController = TextEditingController(text: "-");
@@ -167,78 +200,104 @@ class ServerInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isPermanent = model.verificationMethod == kUsePermanentPassword;
- return model.isStart
- ? PaddingCard(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextFormField(
- readOnly: true,
- style: const TextStyle(
- fontSize: 25.0,
- fontWeight: FontWeight.bold,
- color: MyTheme.accent),
- controller: model.serverId,
- decoration: InputDecoration(
- icon: const Icon(Icons.perm_identity),
- labelText: translate("ID"),
- labelStyle: const TextStyle(
- fontWeight: FontWeight.bold, color: MyTheme.accent80),
- ),
- onSaved: (String? value) {},
+ final serverModel = Provider.of(context);
+
+ const Color colorPositive = Colors.green;
+ const Color colorNegative = Colors.red;
+ const double iconMarginRight = 15;
+ const double iconSize = 24;
+ const TextStyle textStyleHeading = TextStyle(
+ fontSize: 16.0, fontWeight: FontWeight.bold, color: Colors.grey);
+ const TextStyle textStyleValue =
+ TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold);
+
+ void copyToClipboard(String value) {
+ Clipboard.setData(ClipboardData(text: value));
+ showToast(translate('Copied'));
+ }
+
+ Widget ConnectionStateNotification() {
+ if (serverModel.connectStatus == -1) {
+ return Row(children: [
+ const Icon(Icons.warning_amber_sharp,
+ color: colorNegative, size: iconSize)
+ .marginOnly(right: iconMarginRight),
+ Expanded(child: Text(translate('not_ready_status')))
+ ]);
+ } else if (serverModel.connectStatus == 0) {
+ return Row(children: [
+ SizedBox(width: 20, height: 20, child: CircularProgressIndicator())
+ .marginOnly(left: 4, right: iconMarginRight),
+ Expanded(child: Text(translate('connecting_status')))
+ ]);
+ } else {
+ return Row(children: [
+ const Icon(Icons.check, color: colorPositive, size: iconSize)
+ .marginOnly(right: iconMarginRight),
+ Expanded(child: Text(translate('Ready')))
+ ]);
+ }
+ }
+
+ return PaddingCard(
+ title: translate('Your Device'),
+ child: Column(
+ // ID
+ children: [
+ Row(children: [
+ const Icon(Icons.perm_identity,
+ color: Colors.grey, size: iconSize)
+ .marginOnly(right: iconMarginRight),
+ Text(
+ translate('ID'),
+ style: textStyleHeading,
+ )
+ ]),
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Text(
+ model.serverId.value.text,
+ style: textStyleValue,
),
- TextFormField(
- readOnly: true,
- style: const TextStyle(
- fontSize: 25.0,
- fontWeight: FontWeight.bold,
- color: MyTheme.accent),
- controller: isPermanent ? emptyController : model.serverPasswd,
- decoration: InputDecoration(
- icon: const Icon(Icons.lock),
- labelText: translate("Password"),
- labelStyle: const TextStyle(
- fontWeight: FontWeight.bold, color: MyTheme.accent80),
- suffix: isPermanent
- ? null
- : IconButton(
- icon: const Icon(Icons.refresh),
- onPressed: () =>
- bind.mainUpdateTemporaryPassword())),
- onSaved: (String? value) {},
+ IconButton(
+ visualDensity: VisualDensity.compact,
+ icon: Icon(Icons.copy_outlined),
+ onPressed: () {
+ copyToClipboard(model.serverId.value.text.trim());
+ })
+ ]).marginOnly(left: 39, bottom: 10),
+ // Password
+ Row(children: [
+ const Icon(Icons.lock_outline, color: Colors.grey, size: iconSize)
+ .marginOnly(right: iconMarginRight),
+ Text(
+ translate('One-time Password'),
+ style: textStyleHeading,
+ )
+ ]),
+ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
+ Text(
+ isPermanent ? '-' : model.serverPasswd.value.text,
+ style: textStyleValue,
),
- ],
- ))
- : PaddingCard(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Center(
- child: Row(
- children: [
- const Icon(Icons.warning_amber_sharp,
- color: Colors.redAccent, size: 24),
- const SizedBox(width: 10),
- Expanded(
- child: Text(
- translate("Service is not running"),
- style: const TextStyle(
- fontFamily: 'WorkSans',
- fontWeight: FontWeight.bold,
- fontSize: 18,
- color: MyTheme.accent,
- ),
- ))
- ],
- )),
- const SizedBox(height: 5),
- Center(
- child: Text(
- translate("android_start_service_tip"),
- style: const TextStyle(fontSize: 12, color: MyTheme.darkGray),
- ))
- ],
- ));
+ isPermanent
+ ? SizedBox.shrink()
+ : Row(children: [
+ IconButton(
+ visualDensity: VisualDensity.compact,
+ icon: const Icon(Icons.refresh),
+ onPressed: () => bind.mainUpdateTemporaryPassword()),
+ IconButton(
+ visualDensity: VisualDensity.compact,
+ icon: Icon(Icons.copy_outlined),
+ onPressed: () {
+ copyToClipboard(
+ model.serverPasswd.value.text.trim());
+ })
+ ])
+ ]).marginOnly(left: 40, bottom: 15),
+ ConnectionStateNotification()
+ ],
+ ));
}
}
@@ -254,78 +313,37 @@ class _PermissionCheckerState extends State {
Widget build(BuildContext context) {
final serverModel = Provider.of(context);
final hasAudioPermission = androidVersion >= 30;
- final String status;
- if (serverModel.connectStatus == -1) {
- status = 'not_ready_status';
- } else if (serverModel.connectStatus == 0) {
- status = 'connecting_status';
- } else {
- status = 'Ready';
- }
return PaddingCard(
title: translate("Permissions"),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- PermissionRow(translate("Screen Capture"), serverModel.mediaOk,
- serverModel.toggleService),
- PermissionRow(translate("Input Control"), serverModel.inputOk,
- serverModel.toggleInput),
- PermissionRow(translate("Transfer File"), serverModel.fileOk,
- serverModel.toggleFile),
- hasAudioPermission
- ? PermissionRow(translate("Audio Capture"), serverModel.audioOk,
- serverModel.toggleAudio)
- : Text(
- "* ${translate("android_version_audio_tip")}",
+ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+ serverModel.mediaOk
+ ? ElevatedButton.icon(
+ style: ButtonStyle(
+ backgroundColor:
+ MaterialStateProperty.all(Colors.red)),
+ icon: const Icon(Icons.stop),
+ onPressed: serverModel.toggleService,
+ label: Text(translate("Stop service")))
+ .marginOnly(bottom: 8)
+ : SizedBox.shrink(),
+ PermissionRow(translate("Screen Capture"), serverModel.mediaOk,
+ serverModel.toggleService),
+ PermissionRow(translate("Input Control"), serverModel.inputOk,
+ serverModel.toggleInput),
+ PermissionRow(translate("Transfer File"), serverModel.fileOk,
+ serverModel.toggleFile),
+ hasAudioPermission
+ ? PermissionRow(translate("Audio Capture"), serverModel.audioOk,
+ serverModel.toggleAudio)
+ : Row(children: [
+ Icon(Icons.info_outline).marginOnly(right: 15),
+ Expanded(
+ child: Text(
+ translate("android_version_audio_tip"),
style: const TextStyle(color: MyTheme.darkGray),
- ),
- const SizedBox(height: 8),
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(
- flex: 0,
- child: serverModel.mediaOk
- ? ElevatedButton.icon(
- style: ButtonStyle(
- backgroundColor:
- MaterialStateProperty.all(Colors.red)),
- icon: const Icon(Icons.stop),
- onPressed: serverModel.toggleService,
- label: Text(translate("Stop service")))
- : ElevatedButton.icon(
- icon: const Icon(Icons.play_arrow),
- onPressed: serverModel.toggleService,
- label: Text(translate("Start Service")))),
- Expanded(
- child: serverModel.mediaOk
- ? Row(
- children: [
- Expanded(
- flex: 0,
- child: Padding(
- padding: const EdgeInsets.only(
- left: 20, right: 5),
- child: Icon(Icons.circle,
- color: serverModel.connectStatus > 0
- ? Colors.greenAccent
- : Colors.deepOrangeAccent,
- size: 10))),
- Expanded(
- child: Text(translate(status),
- softWrap: true,
- style: const TextStyle(
- fontSize: 14.0,
- fontWeight: FontWeight.w500,
- color: MyTheme.accent80)))
- ],
- )
- : const SizedBox.shrink())
- ],
- ),
- ],
- ));
+ ))
+ ])
+ ]));
}
}
@@ -339,37 +357,14 @@ class PermissionRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Row(
- children: [
- Expanded(
- flex: 5,
- child: FittedBox(
- fit: BoxFit.scaleDown,
- alignment: Alignment.centerLeft,
- child:
- Text(name, style: Theme.of(context).textTheme.labelLarge))),
- Expanded(
- flex: 2,
- child: FittedBox(
- fit: BoxFit.scaleDown,
- child: Text(isOk ? translate("ON") : translate("OFF"),
- style: TextStyle(
- fontSize: 16.0,
- color: isOk ? Colors.green : Colors.grey))),
- ),
- Expanded(
- flex: 3,
- child: FittedBox(
- fit: BoxFit.scaleDown,
- alignment: Alignment.centerRight,
- child: TextButton(
- onPressed: onPressed,
- child: Text(
- translate(isOk ? "CLOSE" : "OPEN"),
- style: const TextStyle(fontWeight: FontWeight.bold),
- )))),
- ],
- );
+ return SwitchListTile(
+ visualDensity: VisualDensity.compact,
+ contentPadding: EdgeInsets.all(0),
+ title: Text(name),
+ value: isOk,
+ onChanged: (bool value) {
+ onPressed();
+ });
}
}
@@ -386,70 +381,66 @@ class ConnectionManager extends StatelessWidget {
? "File Connection"
: "Screen Connection"),
titleIcon: client.isFileTransfer
- ? Icons.folder_outlined
- : Icons.mobile_screen_share,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(child: ClientInfo(client)),
- Expanded(
- flex: -1,
- child: client.isFileTransfer || !client.authorized
- ? const SizedBox.shrink()
- : IconButton(
- onPressed: () {
- gFFI.chatModel.changeCurrentID(client.id);
- final bar =
- navigationBarKey.currentWidget;
- if (bar != null) {
- bar as BottomNavigationBar;
- bar.onTap!(1);
- }
- },
- icon: const Icon(
- Icons.chat,
- color: MyTheme.accent,
- )))
- ],
- ),
- client.authorized
- ? const SizedBox.shrink()
- : Text(
- translate("android_new_connection_tip"),
- style: Theme.of(globalKey.currentContext!)
- .textTheme
- .bodyMedium,
- ),
- client.authorized
- ? ElevatedButton.icon(
- style: ButtonStyle(
- backgroundColor:
- MaterialStateProperty.all(Colors.red)),
- icon: const Icon(Icons.close),
- onPressed: () {
- bind.cmCloseConnection(connId: client.id);
- gFFI.invokeMethod(
- "cancel_notification", client.id);
- },
- label: Text(translate("Close")))
- : Row(children: [
- TextButton(
- child: Text(translate("Dismiss")),
- onPressed: () {
- serverModel.sendLoginResponse(client, false);
- }),
- const SizedBox(width: 20),
- ElevatedButton(
- child: Text(translate("Accept")),
- onPressed: () {
- serverModel.sendLoginResponse(client, true);
- }),
- ]),
- ],
- )))
+ ? Icon(Icons.folder_outlined)
+ : Icon(Icons.mobile_screen_share),
+ child: Column(children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(child: ClientInfo(client)),
+ Expanded(
+ flex: -1,
+ child: client.isFileTransfer || !client.authorized
+ ? const SizedBox.shrink()
+ : IconButton(
+ onPressed: () {
+ gFFI.chatModel.changeCurrentID(client.id);
+ final bar = navigationBarKey.currentWidget;
+ if (bar != null) {
+ bar as BottomNavigationBar;
+ bar.onTap!(1);
+ }
+ },
+ icon: const Icon(Icons.chat)))
+ ],
+ ),
+ client.authorized
+ ? const SizedBox.shrink()
+ : Text(
+ translate("android_new_connection_tip"),
+ style: Theme.of(context).textTheme.bodyMedium,
+ ).marginOnly(bottom: 5),
+ client.authorized
+ ? Container(
+ alignment: Alignment.centerRight,
+ child: ElevatedButton.icon(
+ style: ButtonStyle(
+ backgroundColor:
+ MaterialStatePropertyAll(Colors.red)),
+ icon: const Icon(Icons.close),
+ onPressed: () {
+ bind.cmCloseConnection(connId: client.id);
+ gFFI.invokeMethod(
+ "cancel_notification", client.id);
+ },
+ label: Text(translate("Disconnect"))))
+ : Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ TextButton(
+ child: Text(translate("Dismiss")),
+ onPressed: () {
+ serverModel.sendLoginResponse(
+ client, false);
+ }).marginOnly(right: 15),
+ ElevatedButton.icon(
+ icon: const Icon(Icons.check),
+ label: Text(translate("Accept")),
+ onPressed: () {
+ serverModel.sendLoginResponse(client, true);
+ }),
+ ]),
+ ])))
.toList());
}
}
@@ -459,7 +450,7 @@ class PaddingCard extends StatelessWidget {
: super(key: key);
final String? title;
- final IconData? titleIcon;
+ final Icon? titleIcon;
final Widget child;
@override
@@ -469,23 +460,16 @@ class PaddingCard extends StatelessWidget {
children.insert(
0,
Padding(
- padding: const EdgeInsets.symmetric(vertical: 5.0),
+ padding: const EdgeInsets.fromLTRB(0, 5, 0, 8),
child: Row(
children: [
- titleIcon != null
- ? Padding(
- padding: const EdgeInsets.only(right: 10),
- child:
- Icon(titleIcon, color: MyTheme.accent, size: 30))
- : const SizedBox.shrink(),
- Text(
- title!,
- style: const TextStyle(
- fontFamily: 'WorkSans',
- fontWeight: FontWeight.bold,
- fontSize: 20,
- color: MyTheme.accent,
- ),
+ titleIcon?.marginOnly(right: 10) ?? const SizedBox.shrink(),
+ Expanded(
+ child: Text(title!,
+ style: Theme.of(context)
+ .textTheme
+ .titleLarge
+ ?.merge(TextStyle(fontWeight: FontWeight.bold))),
)
],
)));
@@ -493,12 +477,14 @@ class PaddingCard extends StatelessWidget {
return SizedBox(
width: double.maxFinite,
child: Card(
- margin: const EdgeInsets.fromLTRB(15.0, 15.0, 15.0, 0),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(13),
+ ),
+ margin: const EdgeInsets.fromLTRB(12.0, 10.0, 12.0, 0),
child: Padding(
padding:
- const EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0),
+ const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0),
child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
@@ -514,7 +500,7 @@ class ClientInfo extends StatelessWidget {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+ child: Column(children: [
Row(
children: [
Expanded(
@@ -522,21 +508,19 @@ class ClientInfo extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.only(right: 12),
child: CircleAvatar(
- backgroundColor:
- str2color(client.name).withOpacity(0.7),
+ backgroundColor: str2color(
+ client.name,
+ Theme.of(context).brightness == Brightness.light
+ ? 255
+ : 150),
child: Text(client.name[0])))),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.center,
children: [
- Text(client.name,
- style: const TextStyle(
- color: MyTheme.idColor, fontSize: 18)),
+ Text(client.name, style: const TextStyle(fontSize: 18)),
const SizedBox(width: 8),
- Text(client.peerId,
- style: const TextStyle(
- color: MyTheme.idColor, fontSize: 10))
+ Text(client.peerId, style: const TextStyle(fontSize: 10))
]))
],
),
@@ -567,7 +551,7 @@ void androidChannelInit() {
{
var type = arguments["type"] as String;
var result = arguments["result"] as bool;
- PermissionManager.complete(type, result);
+ AndroidPermissionManager.complete(type, result);
break;
}
case "on_media_projection_canceled":
diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart
index c5f3b6935..c19601956 100644
--- a/flutter/lib/mobile/pages/settings_page.dart
+++ b/flutter/lib/mobile/pages/settings_page.dart
@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../common/widgets/login.dart';
+import '../../consts.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart';
@@ -31,18 +32,20 @@ class SettingsPage extends StatefulWidget implements PageShape {
}
const url = 'https://rustdesk.com/';
-final _hasIgnoreBattery = androidVersion >= 26;
-var _ignoreBatteryOpt = false;
-var _enableAbr = false;
-var _denyLANDiscovery = false;
-var _onlyWhiteList = false;
-var _enableDirectIPAccess = false;
-var _enableRecordSession = false;
-var _autoRecordIncomingSession = false;
-var _localIP = "";
-var _directAccessPort = "";
class _SettingsState extends State with WidgetsBindingObserver {
+ final _hasIgnoreBattery = androidVersion >= 26;
+ var _ignoreBatteryOpt = false;
+ var _enableStartOnBoot = false;
+ var _enableAbr = false;
+ var _denyLANDiscovery = false;
+ var _onlyWhiteList = false;
+ var _enableDirectIPAccess = false;
+ var _enableRecordSession = false;
+ var _autoRecordIncomingSession = false;
+ var _localIP = "";
+ var _directAccessPort = "";
+
@override
void initState() {
super.initState();
@@ -50,11 +53,34 @@ class _SettingsState extends State with WidgetsBindingObserver {
() async {
var update = false;
+
if (_hasIgnoreBattery) {
- update = await updateIgnoreBatteryStatus();
+ if (await checkAndUpdateIgnoreBatteryStatus()) {
+ update = true;
+ }
}
- final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N";
+ if (await checkAndUpdateStartOnBoot()) {
+ update = true;
+ }
+
+ // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
+ var enableStartOnBoot =
+ await gFFI.invokeMethod(AndroidChannel.kGetStartOnBootOpt);
+ if (enableStartOnBoot) {
+ if (!await canStartOnBoot()) {
+ enableStartOnBoot = false;
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
+ }
+ }
+
+ if (enableStartOnBoot != _enableStartOnBoot) {
+ update = true;
+ _enableStartOnBoot = enableStartOnBoot;
+ }
+
+ final enableAbrRes = option2bool(
+ "enable-abr", await bind.mainGetOption(key: "enable-abr"));
if (enableAbrRes != _enableAbr) {
update = true;
_enableAbr = enableAbrRes;
@@ -125,15 +151,18 @@ class _SettingsState extends State with WidgetsBindingObserver {
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
() async {
- if (await updateIgnoreBatteryStatus()) {
+ final ibs = await checkAndUpdateIgnoreBatteryStatus();
+ final sob = await checkAndUpdateStartOnBoot();
+ if (ibs || sob) {
setState(() {});
}
}();
}
}
- Future updateIgnoreBatteryStatus() async {
- final res = await PermissionManager.check("ignore_battery_optimizations");
+ Future checkAndUpdateIgnoreBatteryStatus() async {
+ final res = await AndroidPermissionManager.check(
+ kRequestIgnoreBatteryOptimizations);
if (_ignoreBatteryOpt != res) {
_ignoreBatteryOpt = res;
return true;
@@ -142,6 +171,18 @@ class _SettingsState extends State with WidgetsBindingObserver {
}
}
+ Future checkAndUpdateStartOnBoot() async {
+ if (!await canStartOnBoot() && _enableStartOnBoot) {
+ _enableStartOnBoot = false;
+ debugPrint(
+ "checkAndUpdateStartOnBoot and set _enableStartOnBoot -> false");
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
@override
Widget build(BuildContext context) {
Provider.of(context);
@@ -265,7 +306,8 @@ class _SettingsState extends State with WidgetsBindingObserver {
]),
onToggle: (v) async {
if (v) {
- PermissionManager.request("ignore_battery_optimizations");
+ await AndroidPermissionManager.request(
+ kRequestIgnoreBatteryOptimizations);
} else {
final res = await gFFI.dialogManager
.show((setState, close) => CustomAlertDialog(
@@ -282,11 +324,44 @@ class _SettingsState extends State with WidgetsBindingObserver {
],
));
if (res == true) {
- PermissionManager.request("application_details_settings");
+ AndroidPermissionManager.startAction(
+ kActionApplicationDetailsSettings);
}
}
}));
}
+ enhancementsTiles.add(SettingsTile.switchTile(
+ initialValue: _enableStartOnBoot,
+ title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
+ Text("${translate('Start on Boot')} (beta)"),
+ Text(
+ '* ${translate('Start the screen sharing service on boot, requires special permissions')}',
+ style: Theme.of(context).textTheme.bodySmall),
+ ]),
+ onToggle: (toValue) async {
+ if (toValue) {
+ // 1. request kIgnoreBatteryOptimizations
+ if (!await AndroidPermissionManager.check(
+ kRequestIgnoreBatteryOptimizations)) {
+ if (!await AndroidPermissionManager.request(
+ kRequestIgnoreBatteryOptimizations)) {
+ return;
+ }
+ }
+
+ // 2. request kSystemAlertWindow
+ if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
+ if (!await AndroidPermissionManager.request(kSystemAlertWindow)) {
+ return;
+ }
+ }
+
+ // (Optional) 3. request input permission
+ }
+ setState(() => _enableStartOnBoot = toValue);
+
+ gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
+ }));
return SettingsList(
sections: [
@@ -322,8 +397,13 @@ class _SettingsState extends State with WidgetsBindingObserver {
showLanguageSettings(gFFI.dialogManager);
}),
SettingsTile.navigation(
- title: Text(translate('Dark Theme')),
- leading: Icon(Icons.dark_mode),
+ title: Text(translate(
+ Theme.of(context).brightness == Brightness.light
+ ? 'Dark Theme'
+ : 'Light Theme')),
+ leading: Icon(Theme.of(context).brightness == Brightness.light
+ ? Icons.dark_mode
+ : Icons.light_mode),
onPressed: (context) {
showThemeSettings(gFFI.dialogManager);
},
@@ -387,6 +467,17 @@ class _SettingsState extends State with WidgetsBindingObserver {
],
);
}
+
+ Future canStartOnBoot() async {
+ // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW
+ if (_hasIgnoreBattery && !_ignoreBatteryOpt) {
+ return false;
+ }
+ if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
+ return false;
+ }
+ return true;
+ }
}
void showServerSettings(OverlayDialogManager dialogManager) async {
diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart
index 931999382..b14401795 100644
--- a/flutter/lib/mobile/widgets/dialog.dart
+++ b/flutter/lib/mobile/widgets/dialog.dart
@@ -25,19 +25,26 @@ void showRestartRemoteDevice(
final res =
await dialogManager.show((setState, close) => CustomAlertDialog(
title: Row(children: [
- Icon(Icons.warning_amber_sharp,
- color: Colors.redAccent, size: 28),
- SizedBox(width: 10),
- Text(translate("Restart Remote Device")),
+ Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
+ Text(translate("Restart Remote Device")).paddingOnly(left: 10),
]),
content: Text(
"${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
+ actions: [
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: close,
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: () => close(true),
+ ),
+ ],
onCancel: close,
onSubmit: () => close(true),
- actions: [
- dialogButton("Cancel", onPressed: close, isOutline: true),
- dialogButton("OK", onPressed: () => close(true)),
- ],
));
if (res == true) bind.sessionRestartRemoteDevice(id: id);
}
@@ -62,7 +69,13 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
}
return CustomAlertDialog(
- title: Text(translate('Set your own password')),
+ title: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.password_rounded, color: MyTheme.accent),
+ Text(translate('Set your own password')).paddingOnly(left: 10),
+ ],
+ ),
content: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(mainAxisSize: MainAxisSize.min, children: [
@@ -112,11 +125,13 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
actions: [
dialogButton(
'Cancel',
+ icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
'OK',
+ icon: Icon(Icons.done_rounded),
onPressed: (validateLength && validateSame) ? submit : null,
),
],
@@ -147,7 +162,7 @@ void setTemporaryPasswordLengthDialog(
}
return CustomAlertDialog(
- title: Text(translate("Set temporary password length")),
+ title: Text(translate("Set one-time password length")),
content: Column(
mainAxisSize: MainAxisSize.min,
children:
@@ -178,7 +193,13 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
}
return CustomAlertDialog(
- title: Text(translate('Password Required')),
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.password_rounded, color: MyTheme.accent),
+ Text(translate('Password Required')).paddingOnly(left: 10),
+ ],
+ ),
content: Column(mainAxisSize: MainAxisSize.min, children: [
PasswordWidget(controller: controller),
CheckboxListTile(
@@ -197,8 +218,17 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
),
]),
actions: [
- dialogButton('Cancel', onPressed: cancel, isOutline: true),
- dialogButton('OK', onPressed: submit),
+ dialogButton(
+ 'Cancel',
+ icon: Icon(Icons.close_rounded),
+ onPressed: cancel,
+ isOutline: true,
+ ),
+ dialogButton(
+ 'OK',
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
],
onSubmit: submit,
onCancel: cancel,
@@ -437,7 +467,7 @@ void showRequestElevationDialog(String id, OverlayDialogManager dialogManager) {
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.symmetric(vertical: 15),
- hintText: 'eg: admin',
+ hintText: translate('eg: admin'),
errorText: errUser.isEmpty ? null : errUser.value),
onChanged: (s) {
if (s.isNotEmpty) {
diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart
index 8666e13e4..9db5a1571 100644
--- a/flutter/lib/models/chat_model.dart
+++ b/flutter/lib/models/chat_model.dart
@@ -43,7 +43,7 @@ class ChatModel with ChangeNotifier {
final ChatUser me = ChatUser(
id: "",
- firstName: "Me",
+ firstName: translate("Me"),
);
late final Map _messages = {}..[clientModeID] =
diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart
index 5817e54fe..6efcc6f30 100644
--- a/flutter/lib/models/file_model.dart
+++ b/flutter/lib/models/file_model.dart
@@ -4,10 +4,11 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
-import 'package:flutter_hbb/consts.dart';
+import 'package:flutter_hbb/utils/event_loop.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as path;
+import '../consts.dart';
import 'model.dart';
import 'platform_model.dart';
@@ -24,298 +25,99 @@ enum SortBy {
}
}
-class FileModel extends ChangeNotifier {
- /// mobile, current selected page show on mobile screen
- var _isSelectedLocal = false;
-
- /// mobile, select mode state
- var _selectMode = false;
-
- final _localOption = DirectoryOption();
- final _remoteOption = DirectoryOption();
-
- List localHistory = [];
- List remoteHistory = [];
-
- var _jobId = 0;
-
- final _jobProgress = JobProgress(); // from rust update
-
- /// JobTable
- final _jobTable = List.empty(growable: true).obs;
-
- /// `isLocal` bool
- Function(bool)? onDirChanged;
-
- RxList get jobTable => _jobTable;
-
- bool get isLocal => _isSelectedLocal;
-
- bool get selectMode => _selectMode;
-
- JobProgress get jobProgress => _jobProgress;
-
- JobState get jobState => _jobProgress.state;
-
- SortBy _sortStyle = SortBy.name;
-
- SortBy get sortStyle => _sortStyle;
-
- SortBy _localSortStyle = SortBy.name;
-
- bool _localSortAscending = true;
-
- bool _remoteSortAscending = true;
-
- SortBy _remoteSortStyle = SortBy.name;
-
- bool get localSortAscending => _localSortAscending;
-
- SortBy getSortStyle(bool isLocal) {
- return isLocal ? _localSortStyle : _remoteSortStyle;
+class JobID {
+ int _count = 0;
+ int next() {
+ _count++;
+ return _count;
}
+}
- bool getSortAscending(bool isLocal) {
- return isLocal ? _localSortAscending : _remoteSortAscending;
- }
-
- FileDirectory _currentLocalDir = FileDirectory();
-
- FileDirectory get currentLocalDir => _currentLocalDir;
-
- FileDirectory _currentRemoteDir = FileDirectory();
-
- FileDirectory get currentRemoteDir => _currentRemoteDir;
-
- FileDirectory get currentDir =>
- _isSelectedLocal ? currentLocalDir : currentRemoteDir;
-
- FileDirectory getCurrentDir(bool isLocal) {
- return isLocal ? currentLocalDir : currentRemoteDir;
- }
-
- String getCurrentShortPath(bool isLocal) {
- final currentDir = getCurrentDir(isLocal);
- final currentHome = getCurrentHome(isLocal);
- if (currentDir.path.startsWith(currentHome)) {
- var path = currentDir.path.replaceFirst(currentHome, "");
- if (path.isEmpty) return "";
- if (path[0] == "/" || path[0] == "\\") {
- // remove more '/' or '\'
- path = path.replaceFirst(path[0], "");
- }
- return path;
- } else {
- return currentDir.path.replaceFirst(currentHome, "");
- }
- }
-
- String get currentHome =>
- _isSelectedLocal ? _localOption.home : _remoteOption.home;
-
- String getCurrentHome(bool isLocal) {
- return isLocal ? _localOption.home : _remoteOption.home;
- }
-
- int getJob(int id) {
- return jobTable.indexWhere((element) => element.id == id);
- }
-
- String get currentShortPath {
- if (currentDir.path.startsWith(currentHome)) {
- var path = currentDir.path.replaceFirst(currentHome, "");
- if (path.isEmpty) return "";
- if (path[0] == "/" || path[0] == "\\") {
- // remove more '/' or '\'
- path = path.replaceFirst(path[0], "");
- }
- return path;
- } else {
- return currentDir.path.replaceFirst(currentHome, "");
- }
- }
-
- String shortPath(bool isLocal) {
- final dir = isLocal ? currentLocalDir : currentRemoteDir;
- if (dir.path.startsWith(currentHome)) {
- var path = dir.path.replaceFirst(currentHome, "");
- if (path.isEmpty) return "";
- if (path[0] == "/" || path[0] == "\\") {
- // remove more '/' or '\'
- path = path.replaceFirst(path[0], "");
- }
- return path;
- } else {
- return dir.path.replaceFirst(currentHome, "");
- }
- }
-
- bool getCurrentShowHidden([bool? isLocal]) {
- final isLocal_ = isLocal ?? _isSelectedLocal;
- return isLocal_ ? _localOption.showHidden : _remoteOption.showHidden;
- }
-
- bool getCurrentIsWindows([bool? isLocal]) {
- final isLocal_ = isLocal ?? _isSelectedLocal;
- return isLocal_ ? _localOption.isWindows : _remoteOption.isWindows;
- }
-
- final _fileFetcher = FileFetcher();
-
- final _jobResultListener = JobResultListener>();
+typedef GetSessionID = String Function();
+class FileModel {
final WeakReference parent;
+ // late final String sessionID;
+ late final FileFetcher fileFetcher;
+ late final JobController jobController;
- FileModel(this.parent);
+ late final FileController localController;
+ late final FileController remoteController;
- toggleSelectMode() {
- if (jobState == JobState.inProgress) {
- return;
- }
- _selectMode = !_selectMode;
- notifyListeners();
+ late final GetSessionID getSessionID;
+ String get sessionID => getSessionID();
+ late final FileDialogEventLoop evtLoop;
+
+ FileModel(this.parent) {
+ getSessionID = () => parent.target?.id ?? "";
+ fileFetcher = FileFetcher(getSessionID);
+ jobController = JobController(getSessionID);
+ localController = FileController(
+ isLocal: true,
+ getSessionID: getSessionID,
+ rootState: parent,
+ jobController: jobController,
+ fileFetcher: fileFetcher,
+ getOtherSideDirectoryData: () => remoteController.directoryData());
+ remoteController = FileController(
+ isLocal: false,
+ getSessionID: getSessionID,
+ rootState: parent,
+ jobController: jobController,
+ fileFetcher: fileFetcher,
+ getOtherSideDirectoryData: () => localController.directoryData());
+ evtLoop = FileDialogEventLoop();
}
- togglePage() {
- _isSelectedLocal = !_isSelectedLocal;
- notifyListeners();
+ Future onReady() async {
+ await evtLoop.onReady();
+ await localController.onReady();
+ await remoteController.onReady();
}
- toggleShowHidden({bool? showHidden, bool? local}) {
- final isLocal = local ?? _isSelectedLocal;
- if (isLocal) {
- _localOption.showHidden = showHidden ?? !_localOption.showHidden;
- } else {
- _remoteOption.showHidden = showHidden ?? !_remoteOption.showHidden;
- }
- refresh(isLocal: local);
+ Future close() async {
+ await evtLoop.close();
+ parent.target?.dialogManager.dismissAll();
+ await localController.close();
+ await remoteController.close();
}
- tryUpdateJobProgress(Map evt) {
- try {
- int id = int.parse(evt['id']);
- if (!isDesktop) {
- _jobProgress.id = id;
- _jobProgress.fileNum = int.parse(evt['file_num']);
- _jobProgress.speed = double.parse(evt['speed']);
- _jobProgress.finishedSize = int.parse(evt['finished_size']);
- } else {
- // Desktop uses jobTable
- // id = index + 1
- final jobIndex = getJob(id);
- if (jobIndex >= 0 && _jobTable.length > jobIndex) {
- final job = _jobTable[jobIndex];
- job.fileNum = int.parse(evt['file_num']);
- job.speed = double.parse(evt['speed']);
- job.finishedSize = int.parse(evt['finished_size']);
- debugPrint("update job $id with $evt");
- }
- }
- notifyListeners();
- } catch (e) {
- debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
- }
+ Future refreshAll() async {
+ await localController.refresh();
+ await remoteController.refresh();
}
- receiveFileDir(Map evt) {
+ void receiveFileDir(Map evt) {
if (evt['is_local'] == "false") {
- // init remote home, the connection will automatic read remote home when established,
- try {
- final fd = FileDirectory.fromJson(jsonDecode(evt['value']));
- fd.format(_remoteOption.isWindows, sort: _sortStyle);
- if (fd.id > 0) {
- final jobIndex = getJob(fd.id);
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- var totalSize = 0;
- var fileCount = fd.entries.length;
- for (var element in fd.entries) {
- totalSize += element.size;
- }
- job.totalSize = totalSize;
- job.fileCount = fileCount;
- debugPrint("update receive details:${fd.path}");
- }
- } else if (_remoteOption.home.isEmpty) {
- _remoteOption.home = fd.path;
- debugPrint("init remote home:${fd.path}");
- _currentRemoteDir = fd;
- }
- } catch (e) {
- debugPrint("receiveFileDir err=$e");
- }
+ // init remote home, the remote connection will send one dir event when established. TODO opt
+ remoteController.initDirAndHome(evt);
}
- _fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
- notifyListeners();
+ fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
}
- jobDone(Map evt) async {
- if (_jobResultListener.isListening) {
- _jobResultListener.complete(evt);
- return;
- }
- if (!isDesktop) {
- _selectMode = false;
- _jobProgress.state = JobState.done;
- } else {
- int id = int.parse(evt['id']);
- final jobIndex = getJob(id);
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- job.finishedSize = job.totalSize;
- job.state = JobState.done;
- job.fileNum = int.parse(evt['file_num']);
- }
- }
- await Future.wait([
- refresh(isLocal: false),
- refresh(isLocal: true),
- ]);
+ Future postOverrideFileConfirm(Map evt) async {
+ evtLoop.pushEvent(
+ _FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
}
- jobError(Map evt) {
- final err = evt['err'].toString();
- if (!isDesktop) {
- if (_jobResultListener.isListening) {
- _jobResultListener.complete(evt);
- return;
- }
- _selectMode = false;
- _jobProgress.clear();
- _jobProgress.err = err;
- _jobProgress.state = JobState.error;
- _jobProgress.fileNum = int.parse(evt['file_num']);
- if (err == "skipped") {
- _jobProgress.state = JobState.done;
- _jobProgress.finishedSize = _jobProgress.totalSize;
- }
- } else {
- int jobIndex = getJob(int.parse(evt['id']));
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- job.state = JobState.error;
- job.err = err;
- job.fileNum = int.parse(evt['file_num']);
- if (err == "skipped") {
- job.state = JobState.done;
- job.finishedSize = job.totalSize;
- }
- }
- }
- debugPrint("jobError $evt");
- notifyListeners();
- }
-
- overrideFileConfirm(Map evt) async {
- final resp = await showFileConfirmDialog(
- translate("Overwrite"), "${evt['read_path']}", true);
+ Future overrideFileConfirm(Map evt,
+ {bool? overrideConfirm, bool skip = false}) async {
+ // If `skip == true`, it means to skip this file without showing dialog.
+ // Because `resp` may be null after the user operation or the last remembered operation,
+ // and we should distinguish them.
+ final resp = overrideConfirm ??
+ (!skip
+ ? await showFileConfirmDialog(translate("Overwrite"),
+ "${evt['read_path']}", true, evt['is_identical'] == "true")
+ : null);
final id = int.tryParse(evt['id']) ?? 0;
if (false == resp) {
- final jobIndex = getJob(id);
+ final jobIndex = jobController.getJob(id);
if (jobIndex != -1) {
- cancelJob(id);
- final job = jobTable[jobIndex];
+ await jobController.cancelJob(id);
+ final job = jobController.jobTable[jobIndex];
job.state = JobState.done;
+ jobController.jobTable.refresh();
}
} else {
var need_override = false;
@@ -326,362 +128,28 @@ class FileModel extends ChangeNotifier {
// overwrite
need_override = true;
}
- bind.sessionSetConfirmOverrideFile(
- id: parent.target?.id ?? "",
+ // Update the loop config.
+ if (fileConfirmCheckboxRemember) {
+ evtLoop.setSkip(!need_override);
+ }
+ await bind.sessionSetConfirmOverrideFile(
+ id: sessionID,
actId: id,
fileNum: int.parse(evt['file_num']),
needOverride: need_override,
remember: fileConfirmCheckboxRemember,
isUpload: evt['is_upload'] == "true");
}
- }
-
- jobReset() {
- _jobProgress.clear();
- notifyListeners();
- }
-
- onReady() async {
- _localOption.home = await bind.mainGetHomeDir();
- _localOption.showHidden = (await bind.sessionGetPeerOption(
- id: parent.target?.id ?? "", name: "local_show_hidden"))
- .isNotEmpty;
- _localOption.isWindows = Platform.isWindows;
-
- _remoteOption.showHidden = (await bind.sessionGetPeerOption(
- id: parent.target?.id ?? "", name: "remote_show_hidden"))
- .isNotEmpty;
- _remoteOption.isWindows =
- parent.target?.ffiModel.pi.platform == kPeerPlatformWindows;
-
- await Future.delayed(Duration(milliseconds: 100));
-
- final local = (await bind.sessionGetPeerOption(
- id: parent.target?.id ?? "", name: "local_dir"));
- final remote = (await bind.sessionGetPeerOption(
- id: parent.target?.id ?? "", name: "remote_dir"));
- openDirectory(local.isEmpty ? _localOption.home : local, isLocal: true);
- openDirectory(remote.isEmpty ? _remoteOption.home : remote, isLocal: false);
- await Future.delayed(Duration(seconds: 1));
- if (_currentLocalDir.path.isEmpty) {
- openDirectory(_localOption.home, isLocal: true);
+ // Update the loop config.
+ if (fileConfirmCheckboxRemember) {
+ evtLoop.setOverrideConfirm(resp);
}
- if (_currentRemoteDir.path.isEmpty) {
- openDirectory(_remoteOption.home, isLocal: false);
- }
- }
-
- Future onClose() async {
- parent.target?.dialogManager.dismissAll();
- jobReset();
-
- onDirChanged = null;
-
- // save config
- Map msgMap = {};
-
- msgMap["local_dir"] = _currentLocalDir.path;
- msgMap["local_show_hidden"] = _localOption.showHidden ? "Y" : "";
- msgMap["remote_dir"] = _currentRemoteDir.path;
- msgMap["remote_show_hidden"] = _remoteOption.showHidden ? "Y" : "";
- final id = parent.target?.id ?? "";
- for (final msg in msgMap.entries) {
- await bind.sessionPeerOption(id: id, name: msg.key, value: msg.value);
- }
- _currentLocalDir.clear();
- _currentRemoteDir.clear();
- _localOption.clear();
- _remoteOption.clear();
- }
-
- Future refresh({bool? isLocal}) async {
- if (isDesktop) {
- isLocal = isLocal ?? _isSelectedLocal;
- isLocal
- ? await openDirectory(currentLocalDir.path, isLocal: isLocal)
- : await openDirectory(currentRemoteDir.path, isLocal: isLocal);
- } else {
- await openDirectory(currentDir.path);
- }
- }
-
- openDirectory(String path, {bool? isLocal, bool isBack = false}) async {
- isLocal = isLocal ?? _isSelectedLocal;
- if (path == ".") {
- refresh(isLocal: isLocal);
- return;
- }
- if (path == "..") {
- goToParentDirectory(isLocal: isLocal);
- return;
- }
- if (!isBack) {
- pushHistory(isLocal);
- }
- final showHidden = getCurrentShowHidden(isLocal);
- final isWindows = getCurrentIsWindows(isLocal);
- // process /C:\ -> C:\ on Windows
- if (isWindows && path.length > 1 && path[0] == '/') {
- path = path.substring(1);
- if (path[path.length - 1] != '\\') {
- path = "$path\\";
- }
- }
- try {
- final fd = await _fileFetcher.fetchDirectory(path, isLocal, showHidden);
- fd.format(isWindows, sort: _sortStyle);
- if (isLocal) {
- _currentLocalDir = fd;
- } else {
- _currentRemoteDir = fd;
- }
- notifyListeners();
- onDirChanged?.call(isLocal);
- } catch (e) {
- debugPrint("Failed to openDirectory $path: $e");
- }
- }
-
- Future fetchDirectory(path, isLocal, showHidden) async {
- return await _fileFetcher.fetchDirectory(path, isLocal, showHidden);
- }
-
- void pushHistory(bool isLocal) {
- final history = isLocal ? localHistory : remoteHistory;
- final currPath = isLocal ? currentLocalDir.path : currentRemoteDir.path;
- if (history.isNotEmpty && history.last == currPath) {
- return;
- }
- history.add(currPath);
- }
-
- goHome({bool? isLocal}) {
- isLocal = isLocal ?? _isSelectedLocal;
- openDirectory(getCurrentHome(isLocal), isLocal: isLocal);
- }
-
- goBack({bool? isLocal}) {
- isLocal = isLocal ?? _isSelectedLocal;
- final history = isLocal ? localHistory : remoteHistory;
- if (history.isEmpty) return;
- final path = history.removeAt(history.length - 1);
- if (path.isEmpty) return;
- final currPath = isLocal ? currentLocalDir.path : currentRemoteDir.path;
- if (currPath == path) {
- goBack(isLocal: isLocal);
- return;
- }
- openDirectory(path, isLocal: isLocal, isBack: true);
- }
-
- goToParentDirectory({bool? isLocal}) {
- isLocal = isLocal ?? _isSelectedLocal;
- final isWindows =
- isLocal ? _localOption.isWindows : _remoteOption.isWindows;
- final currDir = isLocal ? currentLocalDir : currentRemoteDir;
- var parent = PathUtil.dirname(currDir.path, isWindows);
- // specially for C:\, D:\, goto '/'
- if (parent == currDir.path && isWindows) {
- openDirectory('/', isLocal: isLocal);
- return;
- }
- openDirectory(parent, isLocal: isLocal);
- }
-
- /// isRemote only for desktop now, [isRemote == true] means [remote -> local]
- sendFiles(SelectedItems items, {bool isRemote = false}) {
- if (isDesktop) {
- // desktop sendFiles
- final toPath = isRemote ? currentLocalDir.path : currentRemoteDir.path;
- final isWindows =
- isRemote ? _localOption.isWindows : _remoteOption.isWindows;
- final showHidden =
- isRemote ? _localOption.showHidden : _remoteOption.showHidden;
- for (var from in items.items) {
- final jobId = ++_jobId;
- _jobTable.add(JobProgress()
- ..jobName = from.path
- ..totalSize = from.size
- ..state = JobState.inProgress
- ..id = jobId
- ..isRemote = isRemote);
- bind.sessionSendFiles(
- id: '${parent.target?.id}',
- actId: _jobId,
- path: from.path,
- to: PathUtil.join(toPath, from.name, isWindows),
- fileNum: 0,
- includeHidden: showHidden,
- isRemote: isRemote);
- debugPrint(
- "path:${from.path}, toPath:$toPath, to:${PathUtil.join(toPath, from.name, isWindows)}");
- }
- } else {
- if (items.isLocal == null) {
- debugPrint("Failed to sendFiles ,wrong path state");
- return;
- }
- _jobProgress.state = JobState.inProgress;
- final toPath =
- items.isLocal! ? currentRemoteDir.path : currentLocalDir.path;
- final isWindows =
- items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
- final showHidden =
- items.isLocal! ? _localOption.showHidden : _remoteOption.showHidden;
- items.items.forEach((from) async {
- _jobId++;
- await bind.sessionSendFiles(
- id: '${parent.target?.id}',
- actId: _jobId,
- path: from.path,
- to: PathUtil.join(toPath, from.name, isWindows),
- fileNum: 0,
- includeHidden: showHidden,
- isRemote: !(items.isLocal!));
- });
- }
- }
-
- bool removeCheckboxRemember = false;
-
- removeAction(SelectedItems items, {bool? isLocal}) async {
- isLocal = isLocal ?? _isSelectedLocal;
- removeCheckboxRemember = false;
- if (items.isLocal == null) {
- debugPrint("Failed to removeFile, wrong path state");
- return;
- }
- final isWindows =
- items.isLocal! ? _localOption.isWindows : _remoteOption.isWindows;
- await Future.forEach(items.items, (Entry item) async {
- _jobId++;
- var title = "";
- var content = "";
- late final List entries;
- if (item.isFile) {
- title = translate("Are you sure you want to delete this file?");
- content = item.name;
- entries = [item];
- } else if (item.isDirectory) {
- title = translate("Not an empty directory");
- parent.target?.dialogManager.showLoading(translate("Waiting"));
- final fd = await _fileFetcher.fetchDirectoryRecursive(
- _jobId, item.path, items.isLocal!, true);
- if (fd.path.isEmpty) {
- fd.path = item.path;
- }
- fd.format(isWindows);
- parent.target?.dialogManager.dismissAll();
- if (fd.entries.isEmpty) {
- final confirm = await showRemoveDialog(
- translate(
- "Are you sure you want to delete this empty directory?"),
- item.name,
- false);
- if (confirm == true) {
- sendRemoveEmptyDir(item.path, 0, items.isLocal!);
- }
- return;
- }
- entries = fd.entries;
- } else {
- entries = [];
- }
-
- for (var i = 0; i < entries.length; i++) {
- final dirShow = item.isDirectory
- ? "${translate("Are you sure you want to delete the file of this directory?")}\n"
- : "";
- final count = entries.length > 1 ? "${i + 1}/${entries.length}" : "";
- content = "$dirShow$count \n${entries[i].path}";
- final confirm =
- await showRemoveDialog(title, content, item.isDirectory);
- try {
- if (confirm == true) {
- sendRemoveFile(entries[i].path, i, items.isLocal!);
- final res = await _jobResultListener.start();
- // handle remove res;
- if (item.isDirectory &&
- res['file_num'] == (entries.length - 1).toString()) {
- sendRemoveEmptyDir(item.path, i, items.isLocal!);
- }
- }
- if (removeCheckboxRemember) {
- if (confirm == true) {
- for (var j = i + 1; j < entries.length; j++) {
- sendRemoveFile(entries[j].path, j, items.isLocal!);
- final res = await _jobResultListener.start();
- if (item.isDirectory &&
- res['file_num'] == (entries.length - 1).toString()) {
- sendRemoveEmptyDir(item.path, i, items.isLocal!);
- }
- }
- }
- break;
- }
- } catch (e) {
- print("remove error: $e");
- }
- }
- });
- _selectMode = false;
- refresh(isLocal: isLocal);
- }
-
- Future showRemoveDialog(
- String title, String content, bool showCheckbox) async {
- return await parent.target?.dialogManager.show(
- (setState, Function(bool v) close) {
- cancel() => close(false);
- submit() => close(true);
- return CustomAlertDialog(
- title: Row(
- children: [
- const Icon(Icons.warning, color: Colors.red),
- const SizedBox(width: 20),
- Text(title)
- ],
- ),
- contentBoxConstraints:
- BoxConstraints(minHeight: 100, minWidth: 400, maxWidth: 400),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(content),
- const SizedBox(height: 5),
- Text(translate("This is irreversible!"),
- style: const TextStyle(fontWeight: FontWeight.bold)),
- showCheckbox
- ? CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- translate("Do this for all conflicts"),
- ),
- value: removeCheckboxRemember,
- onChanged: (v) {
- if (v == null) return;
- setState(() => removeCheckboxRemember = v);
- },
- )
- : const SizedBox.shrink()
- ]),
- actions: [
- dialogButton("Cancel", onPressed: cancel, isOutline: true),
- dialogButton("OK", onPressed: submit),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- }, useAnimation: false);
}
bool fileConfirmCheckboxRemember = false;
Future showFileConfirmDialog(
- String title, String content, bool showCheckbox) async {
+ String title, String content, bool showCheckbox, bool isIdentical) async {
fileConfirmCheckboxRemember = false;
return await parent.target?.dialogManager.show(
(setState, Function(bool? v) close) {
@@ -690,9 +158,10 @@ class FileModel extends ChangeNotifier {
return CustomAlertDialog(
title: Row(
children: [
- const Icon(Icons.warning, color: Colors.red),
- const SizedBox(width: 20),
- Text(title)
+ const Icon(Icons.warning_rounded, color: Colors.red),
+ Text(title).paddingOnly(
+ left: 10,
+ ),
],
),
contentBoxConstraints:
@@ -705,6 +174,17 @@ class FileModel extends ChangeNotifier {
style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 5),
Text(content),
+ Offstage(
+ offstage: !isIdentical,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(height: 12),
+ Text(translate("identical_file_tip"),
+ style: const TextStyle(fontWeight: FontWeight.w500))
+ ],
+ ),
+ ),
showCheckbox
? CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
@@ -722,9 +202,406 @@ class FileModel extends ChangeNotifier {
: const SizedBox.shrink()
]),
actions: [
- dialogButton("Cancel", onPressed: cancel, isOutline: true),
- dialogButton("Skip", onPressed: () => close(null), isOutline: true),
- dialogButton("OK", onPressed: submit),
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: cancel,
+ isOutline: true,
+ ),
+ dialogButton(
+ "Skip",
+ icon: Icon(Icons.navigate_next_rounded),
+ onPressed: () => close(null),
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
+ ],
+ onSubmit: submit,
+ onCancel: cancel,
+ );
+ }, useAnimation: false);
+ }
+}
+
+class DirectoryData {
+ final DirectoryOptions options;
+ final FileDirectory directory;
+ DirectoryData(this.directory, this.options);
+}
+
+class FileController {
+ final bool isLocal;
+ final GetSessionID getSessionID;
+ String get sessionID => getSessionID();
+
+ final FileFetcher fileFetcher;
+
+ final options = DirectoryOptions().obs;
+ final directory = FileDirectory().obs;
+
+ final history = RxList.empty(growable: true);
+ final sortBy = SortBy.name.obs;
+ var sortAscending = true;
+ final JobController jobController;
+ final WeakReference rootState;
+
+ final DirectoryData Function() getOtherSideDirectoryData;
+ late final SelectedItems selectedItems = SelectedItems(isLocal: isLocal);
+
+ FileController(
+ {required this.isLocal,
+ required this.getSessionID,
+ required this.rootState,
+ required this.jobController,
+ required this.fileFetcher,
+ required this.getOtherSideDirectoryData});
+
+ String get homePath => options.value.home;
+ OverlayDialogManager? get dialogManager => rootState.target?.dialogManager;
+
+ String get shortPath {
+ final dirPath = directory.value.path;
+ if (dirPath.startsWith(homePath)) {
+ var path = dirPath.replaceFirst(homePath, "");
+ if (path.isEmpty) return "";
+ if (path[0] == "/" || path[0] == "\\") {
+ // remove more '/' or '\'
+ path = path.replaceFirst(path[0], "");
+ }
+ return path;
+ } else {
+ return dirPath.replaceFirst(homePath, "");
+ }
+ }
+
+ DirectoryData directoryData() {
+ return DirectoryData(directory.value, options.value);
+ }
+
+ Future onReady() async {
+ if (isLocal) {
+ options.value.home = await bind.mainGetHomeDir();
+ }
+ options.value.showHidden = (await bind.sessionGetPeerOption(
+ id: sessionID,
+ name: isLocal ? "local_show_hidden" : "remote_show_hidden"))
+ .isNotEmpty;
+ options.value.isWindows = isLocal
+ ? Platform.isWindows
+ : rootState.target?.ffiModel.pi.platform == kPeerPlatformWindows;
+
+ await Future.delayed(Duration(milliseconds: 100));
+
+ final dir = (await bind.sessionGetPeerOption(
+ id: sessionID, name: isLocal ? "local_dir" : "remote_dir"));
+ openDirectory(dir.isEmpty ? options.value.home : dir);
+
+ await Future.delayed(Duration(seconds: 1));
+
+ if (directory.value.path.isEmpty) {
+ openDirectory(options.value.home);
+ }
+ }
+
+ Future close() async {
+ // save config
+ Map msgMap = {};
+ msgMap[isLocal ? "local_dir" : "remote_dir"] = directory.value.path;
+ msgMap[isLocal ? "local_show_hidden" : "remote_show_hidden"] =
+ options.value.showHidden ? "Y" : "";
+ for (final msg in msgMap.entries) {
+ await bind.sessionPeerOption(
+ id: sessionID, name: msg.key, value: msg.value);
+ }
+ directory.value.clear();
+ options.value.clear();
+ }
+
+ void toggleShowHidden({bool? showHidden}) {
+ options.value.showHidden = showHidden ?? !options.value.showHidden;
+ refresh();
+ }
+
+ void changeSortStyle(SortBy sort, {bool? isLocal, bool ascending = true}) {
+ sortBy.value = sort;
+ sortAscending = ascending;
+ directory.update((dir) {
+ dir?.changeSortStyle(sort, ascending: ascending);
+ });
+ }
+
+ Future refresh() async {
+ await openDirectory(directory.value.path);
+ }
+
+ Future openDirectory(String path, {bool isBack = false}) async {
+ if (path == ".") {
+ refresh();
+ return;
+ }
+ if (path == "..") {
+ goToParentDirectory();
+ return;
+ }
+ if (!isBack) {
+ pushHistory();
+ }
+ final showHidden = options.value.showHidden;
+ final isWindows = options.value.isWindows;
+ // process /C:\ -> C:\ on Windows
+ if (isWindows && path.length > 1 && path[0] == '/') {
+ path = path.substring(1);
+ if (path[path.length - 1] != '\\') {
+ path = "$path\\";
+ }
+ }
+ try {
+ final fd = await fileFetcher.fetchDirectory(path, isLocal, showHidden);
+ fd.format(isWindows, sort: sortBy.value);
+ directory.value = fd;
+ } catch (e) {
+ debugPrint("Failed to openDirectory $path: $e");
+ }
+ }
+
+ void pushHistory() {
+ if (history.isNotEmpty && history.last == directory.value.path) {
+ return;
+ }
+ history.add(directory.value.path);
+ }
+
+ void goToHomeDirectory() {
+ openDirectory(homePath);
+ }
+
+ void goBack() {
+ if (history.isEmpty) return;
+ final path = history.removeAt(history.length - 1);
+ if (path.isEmpty) return;
+ if (directory.value.path == path) {
+ goBack();
+ return;
+ }
+ openDirectory(path, isBack: true);
+ }
+
+ void goToParentDirectory() {
+ final isWindows = options.value.isWindows;
+ final dirPath = directory.value.path;
+ var parent = PathUtil.dirname(dirPath, isWindows);
+ // specially for C:\, D:\, goto '/'
+ if (parent == dirPath && isWindows) {
+ openDirectory('/');
+ return;
+ }
+ openDirectory(parent);
+ }
+
+ // TODO deprecated this
+ void initDirAndHome(Map evt) {
+ try {
+ final fd = FileDirectory.fromJson(jsonDecode(evt['value']));
+ fd.format(options.value.isWindows, sort: sortBy.value);
+ if (fd.id > 0) {
+ final jobIndex = jobController.getJob(fd.id);
+ if (jobIndex != -1) {
+ final job = jobController.jobTable[jobIndex];
+ var totalSize = 0;
+ var fileCount = fd.entries.length;
+ for (var element in fd.entries) {
+ totalSize += element.size;
+ }
+ job.totalSize = totalSize;
+ job.fileCount = fileCount;
+ debugPrint("update receive details: ${fd.path}");
+ jobController.jobTable.refresh();
+ }
+ } else if (options.value.home.isEmpty) {
+ options.value.home = fd.path;
+ debugPrint("init remote home: ${fd.path}");
+ directory.value = fd;
+ }
+ } catch (e) {
+ debugPrint("initDirAndHome err=$e");
+ }
+ }
+
+ /// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
+ void sendFiles(SelectedItems items, DirectoryData otherSideData) {
+ /// ignore wrong items side status
+ if (items.isLocal != isLocal) {
+ return;
+ }
+
+ // alias
+ final isRemoteToLocal = !isLocal;
+
+ final toPath = otherSideData.directory.path;
+ final isWindows = otherSideData.options.isWindows;
+ final showHidden = otherSideData.options.showHidden;
+ for (var from in items.items) {
+ final jobID = jobController.add(from, isRemoteToLocal);
+ bind.sessionSendFiles(
+ id: sessionID,
+ actId: jobID,
+ path: from.path,
+ to: PathUtil.join(toPath, from.name, isWindows),
+ fileNum: 0,
+ includeHidden: showHidden,
+ isRemote: isRemoteToLocal);
+ debugPrint(
+ "path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
+ }
+ }
+
+ bool _removeCheckboxRemember = false;
+
+ Future removeAction(SelectedItems items) async {
+ _removeCheckboxRemember = false;
+ if (items.isLocal != isLocal) {
+ debugPrint("Failed to removeFile, wrong files");
+ return;
+ }
+ final isWindows = options.value.isWindows;
+ await Future.forEach(items.items, (Entry item) async {
+ final jobID = JobController.jobID.next();
+ var title = "";
+ var content = "";
+ late final List entries;
+ if (item.isFile) {
+ title = translate("Are you sure you want to delete this file?");
+ content = item.name;
+ entries = [item];
+ } else if (item.isDirectory) {
+ title = translate("Not an empty directory");
+ dialogManager?.showLoading(translate("Waiting"));
+ final fd = await fileFetcher.fetchDirectoryRecursive(
+ jobID, item.path, items.isLocal, true);
+ if (fd.path.isEmpty) {
+ fd.path = item.path;
+ }
+ fd.format(isWindows);
+ dialogManager?.dismissAll();
+ if (fd.entries.isEmpty) {
+ final confirm = await showRemoveDialog(
+ translate(
+ "Are you sure you want to delete this empty directory?"),
+ item.name,
+ false);
+ if (confirm == true) {
+ sendRemoveEmptyDir(item.path, 0);
+ }
+ return;
+ }
+ entries = fd.entries;
+ } else {
+ entries = [];
+ }
+
+ for (var i = 0; i < entries.length; i++) {
+ final dirShow = item.isDirectory
+ ? "${translate("Are you sure you want to delete the file of this directory?")}\n"
+ : "";
+ final count = entries.length > 1 ? "${i + 1}/${entries.length}" : "";
+ content = "$dirShow\n\n${entries[i].path}".trim();
+ final confirm = await showRemoveDialog(
+ count.isEmpty ? title : "$title ($count)",
+ content,
+ item.isDirectory,
+ );
+ try {
+ if (confirm == true) {
+ sendRemoveFile(entries[i].path, i);
+ final res = await jobController.jobResultListener.start();
+ // handle remove res;
+ if (item.isDirectory &&
+ res['file_num'] == (entries.length - 1).toString()) {
+ sendRemoveEmptyDir(item.path, i);
+ }
+ }
+ if (_removeCheckboxRemember) {
+ if (confirm == true) {
+ for (var j = i + 1; j < entries.length; j++) {
+ sendRemoveFile(entries[j].path, j);
+ final res = await jobController.jobResultListener.start();
+ if (item.isDirectory &&
+ res['file_num'] == (entries.length - 1).toString()) {
+ sendRemoveEmptyDir(item.path, i);
+ }
+ }
+ }
+ break;
+ }
+ } catch (e) {
+ print("remove error: $e");
+ }
+ }
+ });
+ refresh();
+ }
+
+ Future showRemoveDialog(
+ String title, String content, bool showCheckbox) async {
+ return await dialogManager?.show((setState, Function(bool v) close) {
+ cancel() => close(false);
+ submit() => close(true);
+ return CustomAlertDialog(
+ title: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(Icons.warning_rounded, color: Colors.red),
+ Text(title).paddingOnly(
+ left: 10,
+ ),
+ ],
+ ),
+ contentBoxConstraints:
+ BoxConstraints(minHeight: 100, minWidth: 400, maxWidth: 400),
+ content: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(content),
+ Text(
+ translate("This is irreversible!"),
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.red,
+ ),
+ ).paddingOnly(top: 20),
+ showCheckbox
+ ? CheckboxListTile(
+ contentPadding: const EdgeInsets.all(0),
+ dense: true,
+ controlAffinity: ListTileControlAffinity.leading,
+ title: Text(
+ translate("Do this for all conflicts"),
+ ),
+ value: _removeCheckboxRemember,
+ onChanged: (v) {
+ if (v == null) return;
+ setState(() => _removeCheckboxRemember = v);
+ },
+ )
+ : const SizedBox.shrink()
+ ],
+ ),
+ actions: [
+ dialogButton(
+ "Cancel",
+ icon: Icon(Icons.close_rounded),
+ onPressed: cancel,
+ isOutline: true,
+ ),
+ dialogButton(
+ "OK",
+ icon: Icon(Icons.done_rounded),
+ onPressed: submit,
+ ),
],
onSubmit: submit,
onCancel: cancel,
@@ -732,64 +609,163 @@ class FileModel extends ChangeNotifier {
}, useAnimation: false);
}
- sendRemoveFile(String path, int fileNum, bool isLocal) {
+ void sendRemoveFile(String path, int fileNum) {
bind.sessionRemoveFile(
- id: '${parent.target?.id}',
- actId: _jobId,
+ id: sessionID,
+ actId: JobController.jobID.next(),
path: path,
isRemote: !isLocal,
fileNum: fileNum);
}
- sendRemoveEmptyDir(String path, int fileNum, bool isLocal) {
- final history = isLocal ? localHistory : remoteHistory;
+ void sendRemoveEmptyDir(String path, int fileNum) {
history.removeWhere((element) => element.contains(path));
bind.sessionRemoveAllEmptyDirs(
- id: '${parent.target?.id}',
- actId: _jobId,
+ id: sessionID,
+ actId: JobController.jobID.next(),
path: path,
isRemote: !isLocal);
}
- createDir(String path, {bool? isLocal}) async {
- isLocal = isLocal ?? this.isLocal;
- _jobId++;
+ Future createDir(String path) async {
bind.sessionCreateDir(
- id: '${parent.target?.id}',
- actId: _jobId,
+ id: sessionID,
+ actId: JobController.jobID.next(),
path: path,
isRemote: !isLocal);
}
+}
- cancelJob(int id) async {
- bind.sessionCancelJob(id: '${parent.target?.id}', actId: id);
- jobReset();
+class JobController {
+ static final JobID jobID = JobID();
+ final jobTable = List.empty(growable: true).obs;
+ final jobResultListener = JobResultListener>();
+ final GetSessionID getSessionID;
+ String get sessionID => getSessionID();
+
+ JobController(this.getSessionID);
+
+ int getJob(int id) {
+ return jobTable.indexWhere((element) => element.id == id);
}
- changeSortStyle(SortBy sort, {bool? isLocal, bool ascending = true}) {
- _sortStyle = sort;
- if (isLocal == null) {
- // compatible for mobile logic
- _currentLocalDir.changeSortStyle(sort, ascending: ascending);
- _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
- _localSortStyle = sort;
- _localSortAscending = ascending;
- _remoteSortStyle = sort;
- _remoteSortAscending = ascending;
- } else if (isLocal) {
- _currentLocalDir.changeSortStyle(sort, ascending: ascending);
- _localSortStyle = sort;
- _localSortAscending = ascending;
- } else {
- _currentRemoteDir.changeSortStyle(sort, ascending: ascending);
- _remoteSortStyle = sort;
- _remoteSortAscending = ascending;
+ // JobProgress? getJob(int id) {
+ // return jobTable.firstWhere((element) => element.id == id);
+ // }
+
+ // return jobID
+ int add(Entry from, bool isRemoteToLocal) {
+ final jobID = JobController.jobID.next();
+ jobTable.add(JobProgress()
+ ..fileName = path.basename(from.path)
+ ..jobName = from.path
+ ..totalSize = from.size
+ ..state = JobState.inProgress
+ ..id = jobID
+ ..isRemoteToLocal = isRemoteToLocal);
+ return jobID;
+ }
+
+ void tryUpdateJobProgress(Map evt) {
+ try {
+ int id = int.parse(evt['id']);
+ // id = index + 1
+ final jobIndex = getJob(id);
+ if (jobIndex >= 0 && jobTable.length > jobIndex) {
+ final job = jobTable[jobIndex];
+ job.fileNum = int.parse(evt['file_num']);
+ job.speed = double.parse(evt['speed']);
+ job.finishedSize = int.parse(evt['finished_size']);
+ debugPrint("update job $id with $evt");
+ jobTable.refresh();
+ }
+ } catch (e) {
+ debugPrint("Failed to tryUpdateJobProgress, evt: ${evt.toString()}");
}
- notifyListeners();
}
- initFileFetcher() {
- _fileFetcher.id = parent.target?.id;
+ void jobDone(Map evt) async {
+ if (jobResultListener.isListening) {
+ jobResultListener.complete(evt);
+ return;
+ }
+
+ int id = int.parse(evt['id']);
+ final jobIndex = getJob(id);
+ if (jobIndex != -1) {
+ final job = jobTable[jobIndex];
+ job.finishedSize = job.totalSize;
+ job.state = JobState.done;
+ job.fileNum = int.parse(evt['file_num']);
+ jobTable.refresh();
+ }
+ }
+
+ void jobError(Map