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..74f233276 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: @@ -330,13 +331,13 @@ jobs: - { arch: x86_64, target: aarch64-linux-android, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "", } # - { # arch: x86_64, # target: armv7-linux-androideabi, - # os: ubuntu-18.04, + # os: ubuntu-20.04, # extra-build-features: "", # } steps: @@ -354,7 +355,7 @@ 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 @@ -907,19 +908,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..6ec398c73 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 @@ -430,7 +431,7 @@ jobs: - { arch: x86_64, target: aarch64-linux-android, - os: ubuntu-18.04, + os: ubuntu-20.04, extra-build-features: "", } # - { @@ -454,7 +455,7 @@ 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 @@ -732,7 +733,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 +901,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 +911,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 +1512,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..7652f4430 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#102f2ec2cb2bbbd64413d20d28323e5e77e0fe71" 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 @@ DockerStructureSnapshot
- [Українська] | [č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/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/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/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 @@ DockerStructureSnapshot
- [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 @@ DockerStrukturaUká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 @@ DockerFilstrukturSkæ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 @@ DockerDateistrukturScreenshots
- [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) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](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). ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) -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 + +[![In Dev-Containern öffnen](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](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 @@ DockerStrukturoEkrankopio
- [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 @@ DockerEstructuraCapturas 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 @@ DockerRakenneTilannevedos
- [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 @@ +

+ RustDesk - Your remote desktop
+ Διακομιστές • + 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) + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +Ένα λογισμικό απομακρυσμένης επιφάνειας εργασίας, γραμμένο σε γλώσσα Rust. Δεν χρειάζεται κάποια παραμετροποίηση, λειτουργεί αμέσως μετά την εγκατάσταση. Έχετε τον πλήρη έλεγχο των δεδομένων σας, χωρίς να ανησυχείτε για την ασφάλειά τους. Μπορείτε να χρησιμοποιήσετε τους προκαθορισμένους διακομιστές rendezvous/αναμετάδοσης, [να εγκαταστήσετε τον δικό σας διακομιστή](https://rustdesk.com/server), ή [να αναπτύξετε ένα δικό σας διακομιστή rendezvous/αναμετάδοσης](https://github.com/rustdesk/rustdesk-server-demo). + +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) + +Το 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) + +[Get it on F-Droid](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 + +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](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 + +## Στιγμιότυπα + +![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) + +![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) + +![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) + +![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) 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 @@ DockerStruktúraKé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 @@ DockerStructureSnapshot
- [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 @@ DockerStrutturaScreenshots
- [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 @@ DockerStructureSnapshot
- [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 @@ DockerStructureSnapshot
- [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 @@ DockerStructureSnapshot
- [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-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 @@ DockerStrukturaSnapshot
- [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 @@ DockerEstruturaScreenshots
- [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 @@ DockerStructureSnapshot
- [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 @@

RustDesk - Ваш віддалений робочий стіл
- 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) -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) +[Get it on F-Droid](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 + +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](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 @@ DockerCấu trúc tệp tinSnapshot
- [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 UItà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/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/transfer.svg b/flutter/assets/transfer.svg new file mode 100644 index 000000000..24149bf58 --- /dev/null +++ b/flutter/assets/transfer.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index ff373cc9c..21dc427cc 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -109,29 +109,41 @@ class IconFont { class ColorThemeExtension extends ThemeExtension { const ColorThemeExtension({ required this.border, + required this.border2, required this.highlight, + required this.drag_indicator, }); final Color? border; + final Color? border2; final Color? highlight; + final Color? drag_indicator; - static const light = ColorThemeExtension( + static final light = ColorThemeExtension( border: Color(0xFFCCCCCC), + border2: Color(0xFFBBBBBB), highlight: Color(0xFFE5E5E5), + drag_indicator: Colors.grey[800], ); - static const dark = ColorThemeExtension( + static final dark = ColorThemeExtension( border: Color(0xFF555555), + border2: Color(0xFFE5E5E5), highlight: Color(0xFF3F3F3F), + drag_indicator: Colors.grey, ); @override ThemeExtension copyWith( - {Color? border, Color? highlight}) { + {Color? border, + Color? border2, + Color? highlight, + Color? drag_indicator}) { return ColorThemeExtension( - border: border ?? this.border, - highlight: highlight ?? this.highlight, - ); + border: border ?? this.border, + border2: border2 ?? this.border2, + highlight: highlight ?? this.highlight, + drag_indicator: drag_indicator ?? this.drag_indicator); } @override @@ -142,7 +154,9 @@ 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), ); } } @@ -150,8 +164,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 +180,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: InputDecorationTheme( + fillColor: grayBg, + filled: true, + isDense: true, + contentPadding: EdgeInsets.all(15), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(18), + borderSide: BorderSide.none, + ), + ), textTheme: const TextTheme( titleLarge: TextStyle(fontSize: 19, color: Colors.black87), titleSmall: TextStyle(fontSize: 14, color: Colors.black87), @@ -175,7 +209,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 +220,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 +275,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: InputDecorationTheme( + fillColor: Color(0xFF24252B), + filled: true, + isDense: true, + contentPadding: EdgeInsets.all(15), + border: UnderlineInputBorder( + borderRadius: BorderRadius.circular(18), + borderSide: BorderSide.none, + ), + ), textTheme: const TextTheme( titleLarge: TextStyle(fontSize: 19), titleSmall: TextStyle(fontSize: 14), @@ -215,23 +308,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 +384,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 +446,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 +468,7 @@ List supportedLocales = const [ Locale('vi'), Locale('pl'), Locale('kz'), - Locale('en', 'US'), + Locale('es'), ]; String formatDurationToTime(Duration duration) { @@ -456,7 +595,7 @@ class OverlayDialogManager { BackButtonInterceptor.removeByName(dialogTag); } - dialog.entry = OverlayEntry(builder: (_) { + dialog.entry = OverlayEntry(builder: (context) { bool innerClicked = false; return Listener( onPointerUp: (_) { @@ -466,7 +605,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 +789,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,18 +818,19 @@ 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), + actionsAlignment: MainAxisAlignment.center, ), ); } @@ -820,7 +962,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 +1044,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 +1060,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); @@ -1453,10 +1589,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 +1616,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; } } @@ -1706,28 +1844,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/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..83be522fa 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,7 @@ void changeIdDialog() { TextField( decoration: InputDecoration( labelText: translate('Your new ID'), - border: const OutlineInputBorder(), + border: isDesktop ? const OutlineInputBorder() : null, errorText: msg.isEmpty ? null : translate(msg), suffixText: '${rxId.value.length}/16', suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)), @@ -123,27 +126,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 +182,7 @@ void changeWhiteList({Function()? callback}) async { child: TextField( maxLines: null, decoration: InputDecoration( - border: const OutlineInputBorder(), + border: isDesktop ? const OutlineInputBorder() : null, errorText: msg.isEmpty ? null : translate(msg), ), controller: controller, 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..124057662 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,67 @@ 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'), + border: isDesktop ? const OutlineInputBorder() : null, + 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'), + border: isDesktop ? const OutlineInputBorder() : null), 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'), + border: + isDesktop ? const OutlineInputBorder() : null, suffixIcon: IconButton( onPressed: () => secure.value = !secure.value, icon: Icon(secure.value @@ -1135,7 +1211,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..43f65b4b4 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,18 @@ 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 _exit = false; late final mobileWidth = () { @@ -78,12 +112,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 +125,48 @@ 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 + ? 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) { + final body = ObxValue((filters) { return FutureBuilder>( builder: (context, snapshot) { if (snapshot.hasData) { @@ -109,16 +174,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 +196,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 +206,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 +216,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 +237,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 +284,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener { filteredList.add(peers[i]); } } + return filteredList; } } @@ -232,7 +320,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 +342,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 +364,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 +389,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..30fe07b95 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: [ @@ -203,6 +199,7 @@ class _ConnectionPageState extends State cursorColor: Theme.of(context).textTheme.titleLarge?.color, decoration: InputDecoration( + filled: false, counterText: '', hintText: _idInputFocused.value ? null diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index dfa5762b0..541ec6aa8 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, diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index e041b591d..4609d4f47 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, @@ -1722,7 +1729,6 @@ class _ComboBox extends StatelessWidget { required this.values, required this.initialKey, required this.onChanged, - // ignore: unused_element this.enabled = true, }) : super(key: key); @@ -1735,7 +1741,12 @@ class _ComboBox extends StatelessWidget { var ref = values[index].obs; current = keys[index]; return Container( - decoration: BoxDecoration(border: Border.all(color: MyTheme.border)), + decoration: BoxDecoration( + border: Border.all( + color: enabled + ? MyTheme.color(context).border2 ?? MyTheme.border + : MyTheme.border, + )), height: 30, child: Obx(() => DropdownButton( isExpanded: true, @@ -1744,6 +1755,10 @@ class _ComboBox extends StatelessWidget { underline: Container( height: 25, ), + style: TextStyle( + color: enabled + ? Theme.of(context).textTheme.titleMedium?.color + : _disabledTextColor(context, enabled)), icon: const Icon( Icons.expand_more_sharp, size: 20, 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..44def46c2 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,8 +1308,7 @@ class _FileManagerPageState extends State ) ]), proc: () { - openDirectory('${entry.name}\\', - isLocal: isLocal); + controller.openDirectory('${entry.name}\\'); }, dismissOnClicked: true)); } @@ -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_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..2099f2e95 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -308,6 +308,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 +333,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..d810650fd 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -205,11 +205,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, )); 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_menubar.dart index 37bbbd66a..d1189abe4 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.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(); @@ -365,10 +370,13 @@ 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, + child: _DraggableShowHide( + dragging: _dragging, + fractionX: _fractionX, + show: show, + ), ), ), ); @@ -402,22 +410,27 @@ class _RemoteMenubarState extends State { return Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - ), + Material( + elevation: _MenubarTheme.elevation, + 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), + ...menubarItems, + SizedBox(width: _MenubarTheme.buttonHMargin * 2) + ], + ), + ), + ), ), _buildDraggableShowHide(context), ], @@ -427,11 +440,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 +525,12 @@ class _MonitorMenu extends StatelessWidget { @override Widget build(BuildContext context) { - if (stateGlobal.displaysCount.value < 2) return Offstage(); + if (PrivacyModeState.find(id).isTrue || + stateGlobal.displaysCount.value < 2) { + return Offstage(); + } return _IconSubmenuButton( + tooltip: 'Select Monitor', icon: icon(), ffi: ffi, color: _MenubarTheme.blueColor, @@ -541,6 +569,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: "", @@ -593,6 +622,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 +681,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, @@ -896,6 +944,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 +965,7 @@ class _DisplayMenuState extends State<_DisplayMenu> { disableClipboard(), lockAfterSessionEnd(), privacyMode(), + swapKey(), ]); } @@ -949,12 +999,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 +1074,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); } @@ -1400,6 +1451,9 @@ class _DisplayMenuState extends State<_DisplayMenu> { } showRemoteCursor() { + if (widget.ffi.ffiModel.pi.platform == kPeerPlatformAndroid) { + return Offstage(); + } final visible = !widget.ffi.canvasModel.cursorEmbedded; if (!visible) return Offstage(); final state = ShowRemoteCursorState.find(widget.id); @@ -1417,6 +1471,9 @@ class _DisplayMenuState extends State<_DisplayMenu> { } 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'; @@ -1518,11 +1575,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,12 +1624,17 @@ 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, @@ -1555,13 +1644,13 @@ class _KeyboardMenu extends StatelessWidget { 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 = []; onChanged(String? value) async { @@ -1571,13 +1660,13 @@ class _KeyboardMenu extends StatelessWidget { for (KeyboardModeMenu mode in modes) { if (bind.sessionIsKeyboardModeSupported(id: id, mode: mode.key)) { - if (mode.key == 'translate') { + if (mode.key == _kKeyTranslateMode) { if (Platform.isLinux || pi.platform == kPeerPlatformLinux) { continue; } } var text = translate(mode.menu); - if (mode.key == 'translate') { + if (mode.key == _kKeyTranslateMode) { text = '$text beta'; } list.add(_RadioMenuButton( @@ -1633,6 +1722,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 +1841,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 +1878,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 +1924,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 +1949,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 +2116,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 +2168,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), ), 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..4170a5461 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -24,298 +24,83 @@ 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(); + + FileModel(this.parent) { + getSessionID = () => parent.target?.id ?? ""; + fileFetcher = FileFetcher(getSessionID); + jobController = JobController(getSessionID); + localController = FileController( + isLocal: true, + getSessionID: getSessionID, + dialogManager: parent.target?.dialogManager, + jobController: jobController, + fileFetcher: fileFetcher, + getOtherSideDirectoryData: () => remoteController.directoryData()); + remoteController = FileController( + isLocal: false, + getSessionID: getSessionID, + dialogManager: parent.target?.dialogManager, + jobController: jobController, + fileFetcher: fileFetcher, + getOtherSideDirectoryData: () => localController.directoryData()); } - togglePage() { - _isSelectedLocal = !_isSelectedLocal; - notifyListeners(); + Future onReady() async { + 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 { + 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), - ]); - } - - 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 { + void overrideFileConfirm(Map evt) async { final resp = await showFileConfirmDialog( translate("Overwrite"), "${evt['read_path']}", true); 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]; + jobController.cancelJob(id); + final job = jobController.jobTable[jobIndex]; job.state = JobState.done; + jobController.jobTable.refresh(); } } else { var need_override = false; @@ -327,7 +112,7 @@ class FileModel extends ChangeNotifier { need_override = true; } bind.sessionSetConfirmOverrideFile( - id: parent.target?.id ?? "", + id: sessionID, actId: id, fileNum: int.parse(evt['file_num']), needOverride: need_override, @@ -336,348 +121,6 @@ class FileModel extends ChangeNotifier { } } - 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); - } - 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( @@ -690,9 +133,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: @@ -722,9 +166,401 @@ 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 OverlayDialogManager? dialogManager; + + final DirectoryData Function() getOtherSideDirectoryData; + late final SelectedItems selectedItems = SelectedItems(isLocal: isLocal); + + FileController( + {required this.isLocal, + required this.getSessionID, + required this.dialogManager, + required this.jobController, + required this.fileFetcher, + required this.getOtherSideDirectoryData}); + + String get homePath => options.value.home; + + 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 { + 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 = Platform.isWindows; + + 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 +568,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 evt) { + final err = evt['err'].toString(); + 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; + } + jobTable.refresh(); + } + debugPrint("jobError $evt"); + } + + void cancelJob(int id) async { + bind.sessionCancelJob(id: sessionID, actId: id); + } + + void loadLastJob(Map evt) { + debugPrint("load last job: $evt"); + Map jobDetail = json.decode(evt['value']); + // int id = int.parse(jobDetail['id']); + String remote = jobDetail['remote']; + String to = jobDetail['to']; + bool showHidden = jobDetail['show_hidden']; + int fileNum = jobDetail['file_num']; + bool isRemote = jobDetail['is_remote']; + final currJobId = JobController.jobID.next(); + String fileName = path.basename(isRemote ? remote : to); + var jobProgress = JobProgress() + ..fileName = fileName + ..jobName = isRemote ? remote : to + ..id = currJobId + ..isRemoteToLocal = isRemote + ..fileNum = fileNum + ..remote = remote + ..to = to + ..showHidden = showHidden + ..state = JobState.paused; + jobTable.add(jobProgress); + bind.sessionAddJob( + id: sessionID, + isRemote: isRemote, + includeHidden: showHidden, + actId: currJobId, + path: isRemote ? remote : to, + to: isRemote ? to : remote, + fileNum: fileNum, + ); + } + + void resumeJob(int jobId) { + final jobIndex = getJob(jobId); + if (jobIndex != -1) { + final job = jobTable[jobIndex]; + bind.sessionResumeJob( + id: sessionID, actId: job.id, isRemote: job.isRemoteToLocal); + job.state = JobState.inProgress; + jobTable.refresh(); + } else { + debugPrint("jobId $jobId is not exists"); + } } void updateFolderFiles(Map evt) { @@ -803,55 +738,9 @@ class FileModel extends ChangeNotifier { final job = jobTable[jobIndex]; job.fileCount = num_entries; job.totalSize = total_size.toInt(); + jobTable.refresh(); } debugPrint("update folder files: $info"); - notifyListeners(); - } - - bool get remoteSortAscending => _remoteSortAscending; - - void loadLastJob(Map evt) { - debugPrint("load last job: $evt"); - Map jobDetail = json.decode(evt['value']); - // int id = int.parse(jobDetail['id']); - String remote = jobDetail['remote']; - String to = jobDetail['to']; - bool showHidden = jobDetail['show_hidden']; - int fileNum = jobDetail['file_num']; - bool isRemote = jobDetail['is_remote']; - final currJobId = _jobId++; - var jobProgress = JobProgress() - ..jobName = isRemote ? remote : to - ..id = currJobId - ..isRemote = isRemote - ..fileNum = fileNum - ..remote = remote - ..to = to - ..showHidden = showHidden - ..state = JobState.paused; - jobTable.add(jobProgress); - bind.sessionAddJob( - id: '${parent.target?.id}', - isRemote: isRemote, - includeHidden: showHidden, - actId: currJobId, - path: isRemote ? remote : to, - to: isRemote ? to : remote, - fileNum: fileNum, - ); - } - - resumeJob(int jobId) { - final jobIndex = getJob(jobId); - if (jobIndex != -1) { - final job = jobTable[jobIndex]; - bind.sessionResumeJob( - id: '${parent.target?.id}', actId: job.id, isRemote: job.isRemote); - job.state = JobState.inProgress; - } else { - debugPrint("jobId $jobId is not exists"); - } - notifyListeners(); } } @@ -900,10 +789,10 @@ class FileFetcher { Map> remoteTasks = {}; Map> readRecursiveTasks = {}; - String? id; + final GetSessionID getSessionID; + String get sessionID => getSessionID(); - // if id == null, means to fetch global FFI - FFI get _ffi => ffi(id ?? ""); + FileFetcher(this.getSessionID); Future registerReadTask(bool isLocal, String path) { // final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later @@ -922,16 +811,16 @@ class FileFetcher { return c.future; } - Future registerReadRecursiveTask(int id) { + Future registerReadRecursiveTask(int actID) { final tasks = readRecursiveTasks; - if (tasks.containsKey(id)) { + if (tasks.containsKey(actID)) { throw "Failed to registerRemoveTask, already have same ReadRecursive job"; } final c = Completer(); - tasks[id] = c; + tasks[actID] = c; Timer(Duration(seconds: 2), () { - tasks.remove(id); + tasks.remove(actID); if (c.isCompleted) return; c.completeError("Failed to read dir,timeout"); }); @@ -966,12 +855,12 @@ class FileFetcher { try { if (isLocal) { final res = await bind.sessionReadLocalDirSync( - id: id ?? "", path: path, showHidden: showHidden); + id: sessionID ?? "", path: path, showHidden: showHidden); final fd = FileDirectory.fromJson(jsonDecode(res)); return fd; } else { await bind.sessionReadRemoteDir( - id: id ?? "", path: path, includeHidden: showHidden); + id: sessionID ?? "", path: path, includeHidden: showHidden); return registerReadTask(isLocal, path); } } catch (e) { @@ -980,16 +869,16 @@ class FileFetcher { } Future fetchDirectoryRecursive( - int id, String path, bool isLocal, bool showHidden) async { + int actID, String path, bool isLocal, bool showHidden) async { // TODO test Recursive is show hidden default? try { await bind.sessionReadDirRecursive( - id: _ffi.id, - actId: id, + id: sessionID, + actId: actID, path: path, isRemote: !isLocal, showHidden: showHidden); - return registerReadRecursiveTask(id); + return registerReadRecursiveTask(actID); } catch (e) { return Future.error(e); } @@ -1086,8 +975,12 @@ class JobProgress { var finishedSize = 0; var totalSize = 0; var fileCount = 0; - var isRemote = false; + // [isRemote == true] means [remote -> local] + // var isRemote = false; + // to-do use enum + var isRemoteToLocal = false; var jobName = ""; + var fileName = ""; var remote = ""; var to = ""; var showHidden = false; @@ -1100,6 +993,7 @@ class JobProgress { speed = 0; finishedSize = 0; jobName = ""; + fileName = ""; fileCount = 0; remote = ""; to = ""; @@ -1141,12 +1035,12 @@ class PathUtil { } } -class DirectoryOption { +class DirectoryOptions { String home; bool showHidden; bool isWindows; - DirectoryOption( + DirectoryOptions( {this.home = "", this.showHidden = false, this.isWindows = false}); clear() { @@ -1157,53 +1051,37 @@ class DirectoryOption { } class SelectedItems { - bool? _isLocal; - final List _items = []; + final bool isLocal; + final items = RxList.empty(growable: true); - List get items => _items; + SelectedItems({required this.isLocal}); - int get length => _items.length; - - bool? get isLocal => _isLocal; - - add(bool isLocal, Entry e) { + void add(Entry e) { if (e.isDrive) return; - _isLocal ??= isLocal; - if (_isLocal != null && _isLocal != isLocal) { - return; - } - if (!_items.contains(e)) { - _items.add(e); + if (!items.contains(e)) { + items.add(e); } } - bool contains(Entry e) { - return _items.contains(e); + void remove(Entry e) { + items.remove(e); } - remove(Entry e) { - _items.remove(e); - if (_items.isEmpty) { - _isLocal = null; - } - } - - bool isOtherPage(bool currentIsLocal) { - if (_isLocal == null) { - return false; - } else { - return _isLocal != currentIsLocal; - } - } - - clear() { - _items.clear(); - _isLocal = null; + void clear() { + items.clear(); } void selectAll(List entries) { - _items.clear(); - _items.addAll(entries); + items.clear(); + items.addAll(entries); + } + + static bool valid(RxList items) { + if (items.isNotEmpty) { + // exclude DirDrive type + return items.any((item) => !item.isDrive); + } + return false; } } diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 9a5b06b14..9366d5b46 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -419,7 +419,50 @@ class InputModel { 'type': _kMouseEventMove, }); - void handleMouse(Map evt) { + void tryMoveEdgeOnExit(Offset pos) => handleMouse( + { + 'x': pos.dx, + 'y': pos.dy, + 'buttons': 0, + 'type': _kMouseEventMove, + }, + onExit: true, + ); + + int trySetNearestRange(int v, int min, int max, int n) { + if (v < min && v >= min - n) { + v = min; + } + if (v > max && v <= max + n) { + v = max; + } + return v; + } + + Offset setNearestEdge(double x, double y, Display d) { + double left = x - d.x; + double right = d.x + d.width - 1 - x; + double top = y - d.y; + double bottom = d.y + d.height - 1 - y; + if (left < right && left < top && left < bottom) { + x = d.x; + } + if (right < left && right < top && right < bottom) { + x = d.x + d.width - 1; + } + if (top < left && top < right && top < bottom) { + y = d.y; + } + if (bottom < left && bottom < right && bottom < top) { + y = d.y + d.height - 1; + } + return Offset(x, y); + } + + void handleMouse( + Map evt, { + bool onExit = false, + }) { double x = evt['x']; double y = max(0.0, evt['y']); final cursorModel = parent.target!.cursorModel; @@ -458,18 +501,21 @@ class InputModel { return; } evt['type'] = type; - if (isDesktop) { - y = y - stateGlobal.tabBarHeight; - } + y -= CanvasModel.topToEdge; + x -= CanvasModel.leftToEdge; final canvasModel = parent.target!.canvasModel; + final nearThr = 3; + var nearRight = (canvasModel.size.width - x) < nearThr; + var nearBottom = (canvasModel.size.height - y) < nearThr; + final ffiModel = parent.target!.ffiModel; if (isMove) { canvasModel.moveDesktopMouse(x, y); } final d = ffiModel.display; + final imageWidth = d.width * canvasModel.scale; + final imageHeight = d.height * canvasModel.scale; if (canvasModel.scrollStyle == ScrollStyle.scrollbar) { - final imageWidth = d.width * canvasModel.scale; - final imageHeight = d.height * canvasModel.scale; x += imageWidth * canvasModel.scrollX; y += imageHeight * canvasModel.scrollY; @@ -487,10 +533,42 @@ class InputModel { x /= canvasModel.scale; y /= canvasModel.scale; + if (canvasModel.scale > 0 && canvasModel.scale < 1) { + final step = 1.0 / canvasModel.scale - 1; + if (nearRight) { + x += step; + } + if (nearBottom) { + y += step; + } + } x += d.x; y += d.y; - if (x < d.x || y < d.y || x > (d.x + d.width) || y > (d.y + d.height)) { + if (onExit) { + final pos = setNearestEdge(x, y, d); + x = pos.dx; + y = pos.dy; + } + + var evtX = 0; + var evtY = 0; + try { + evtX = x.round(); + evtY = y.round(); + } catch (e) { + debugPrintStack( + label: 'canvasModel.scale value ${canvasModel.scale}, $e'); + return; + } + + int minX = d.x.toInt(); + int maxX = (d.x + d.width).toInt() - 1; + int minY = d.y.toInt(); + int maxY = (d.y + d.height).toInt() - 1; + evtX = trySetNearestRange(evtX, minX, maxX, 5); + evtY = trySetNearestRange(evtY, minY, maxY, 5); + if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) { // If left mouse up, no early return. if (evt['buttons'] != kPrimaryMouseButton || type != 'up') { return; @@ -498,12 +576,12 @@ class InputModel { } if (type != '') { - x = 0; - y = 0; + evtX = 0; + evtY = 0; } - evt['x'] = '${x.round()}'; - evt['y'] = '${y.round()}'; + evt['x'] = '$evtX'; + evt['y'] = '$evtY'; var buttons = ''; switch (evt['buttons']) { case kPrimaryMouseButton: diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index eae41679f..94e28ea21 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -156,7 +156,7 @@ class FfiModel with ChangeNotifier { } else if (name == 'clipboard') { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { - parent.target?.ffiModel.updatePermission(evt, peerId); + updatePermission(evt, peerId); } else if (name == 'chat_client_mode') { parent.target?.chatModel .receive(ChatModel.clientModeID, evt['text'] ?? ''); @@ -166,17 +166,18 @@ class FfiModel with ChangeNotifier { } else if (name == 'file_dir') { parent.target?.fileModel.receiveFileDir(evt); } else if (name == 'job_progress') { - parent.target?.fileModel.tryUpdateJobProgress(evt); + parent.target?.fileModel.jobController.tryUpdateJobProgress(evt); } else if (name == 'job_done') { - parent.target?.fileModel.jobDone(evt); + parent.target?.fileModel.jobController.jobDone(evt); + parent.target?.fileModel.refreshAll(); } else if (name == 'job_error') { - parent.target?.fileModel.jobError(evt); + parent.target?.fileModel.jobController.jobError(evt); } else if (name == 'override_file_confirm') { parent.target?.fileModel.overrideFileConfirm(evt); } else if (name == 'load_last_job') { - parent.target?.fileModel.loadLastJob(evt); + parent.target?.fileModel.jobController.loadLastJob(evt); } else if (name == 'update_folder_files') { - parent.target?.fileModel.updateFolderFiles(evt); + parent.target?.fileModel.jobController.updateFolderFiles(evt); } else if (name == 'add_connection') { parent.target?.serverModel.addConnection(evt); } else if (name == 'on_client_remove') { @@ -241,36 +242,33 @@ class FfiModel with ChangeNotifier { } } - handleSwitchDisplay(Map evt, String peerId) { - final oldOrientation = _display.width > _display.height; - var old = _pi.currentDisplay; - _pi.currentDisplay = int.parse(evt['display']); - _display.x = double.parse(evt['x']); - _display.y = double.parse(evt['y']); - _display.width = int.parse(evt['width']); - _display.height = int.parse(evt['height']); - _display.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1; - if (old != _pi.currentDisplay) { - parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y); + _updateCurDisplay(String peerId, Display newDisplay) { + if (newDisplay != _display) { + if (newDisplay.x != _display.x || newDisplay.y != _display.y) { + parent.target?.cursorModel + .updateDisplayOrigin(newDisplay.x, newDisplay.y); + } + _display = newDisplay; + _updateSessionWidthHeight(peerId); } + } - _updateSessionWidthHeight(peerId, display.width, display.height); + handleSwitchDisplay(Map evt, String peerId) { + _pi.currentDisplay = int.parse(evt['display']); + var newDisplay = Display(); + newDisplay.x = double.parse(evt['x']); + newDisplay.y = double.parse(evt['y']); + newDisplay.width = int.parse(evt['width']); + newDisplay.height = int.parse(evt['height']); + newDisplay.cursorEmbedded = int.parse(evt['cursor_embedded']) == 1; + + _updateCurDisplay(peerId, newDisplay); try { CurrentDisplayState.find(peerId).value = _pi.currentDisplay; } catch (e) { // } - - // remote is mobile, and orientation changed - if ((_display.width > _display.height) != oldOrientation) { - gFFI.canvasModel.updateViewStyle(); - } - if (_pi.platform == kPeerPlatformLinux || - _pi.platform == kPeerPlatformWindows || - _pi.platform == kPeerPlatformMacOS) { - parent.target?.canvasModel.updateViewStyle(); - } parent.target?.recordingModel.onSwitchDisplay(); handleResolutions(peerId, evt["resolutions"]); notifyListeners(); @@ -372,8 +370,13 @@ class FfiModel with ChangeNotifier { }); } - _updateSessionWidthHeight(String id, int width, int height) { - bind.sessionSetSize(id: id, width: display.width, height: display.height); + _updateSessionWidthHeight(String id) { + parent.target?.canvasModel.updateViewStyle(); + if (display.width <= 0 || display.height <= 0) { + debugPrintStack(label: 'invalid display size (${display.width},${display.height})'); + } else { + bind.sessionSetSize(id: id, width: display.width, height: display.height); + } } /// Handle the peer info event based on [evt]. @@ -429,7 +432,7 @@ class FfiModel with ChangeNotifier { stateGlobal.displaysCount.value = _pi.displays.length; if (_pi.currentDisplay < _pi.displays.length) { _display = _pi.displays[_pi.currentDisplay]; - _updateSessionWidthHeight(peerId, display.width, display.height); + _updateSessionWidthHeight(peerId); } if (displays.isNotEmpty) { parent.target?.dialogManager.showLoading( @@ -488,7 +491,7 @@ class FfiModel with ChangeNotifier { _pi.displays = newDisplays; stateGlobal.displaysCount.value = _pi.displays.length; if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) { - _display = _pi.displays[_pi.currentDisplay]; + _updateCurDisplay(peerId, _pi.displays[_pi.currentDisplay]); } } notifyListeners(); @@ -619,13 +622,28 @@ class ViewStyle { final int displayWidth; final int displayHeight; ViewStyle({ - this.style = '', - this.width = 0.0, - this.height = 0.0, - this.displayWidth = 0, - this.displayHeight = 0, + required this.style, + required this.width, + required this.height, + required this.displayWidth, + required this.displayHeight, }); + static defaultViewStyle() { + final desktop = (isDesktop || isWebDesktop); + final w = + desktop ? kDesktopDefaultDisplayWidth : kMobileDefaultDisplayWidth; + final h = + desktop ? kDesktopDefaultDisplayHeight : kMobileDefaultDisplayHeight; + return ViewStyle( + style: '', + width: w.toDouble(), + height: h.toDouble(), + displayWidth: w, + displayHeight: h, + ); + } + static int _double2Int(double v) => (v * 100).round().toInt(); @override @@ -654,9 +672,14 @@ class ViewStyle { double get scale { double s = 1.0; if (style == kRemoteViewStyleAdaptive) { - final s1 = width / displayWidth; - final s2 = height / displayHeight; - s = s1 < s2 ? s1 : s2; + if (width != 0 && + height != 0 && + displayWidth != 0 && + displayHeight != 0) { + final s1 = width / displayWidth; + final s2 = height / displayHeight; + s = s1 < s2 ? s1 : s2; + } } return s; } @@ -682,7 +705,7 @@ class CanvasModel with ChangeNotifier { // scroll offset y percent double _scrollY = 0.0; ScrollStyle _scrollStyle = ScrollStyle.scrollauto; - ViewStyle _lastViewStyle = ViewStyle(); + ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle(); final _imageOverflow = false.obs; @@ -709,12 +732,25 @@ class CanvasModel with ChangeNotifier { double get scrollX => _scrollX; double get scrollY => _scrollY; + static double get leftToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.left + : 0; + static double get rightToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.right + : 0; + static double get topToEdge => (isDesktop || isWebDesktop) + ? tabBarHeight + windowBorderWidth + kDragToResizeAreaPadding.top + : 0; + static double get bottomToEdge => (isDesktop || isWebDesktop) + ? windowBorderWidth + kDragToResizeAreaPadding.bottom + : 0; + updateViewStyle() async { Size getSize() { final size = MediaQueryData.fromWindow(ui.window).size; // If minimized, w or h may be negative here. - double w = size.width - windowBorderWidth * 2; - double h = size.height - tabBarHeight - windowBorderWidth * 2; + double w = size.width - leftToEdge - rightToEdge; + double h = size.height - topToEdge - bottomToEdge; return Size(w < 0 ? 0 : w, h < 0 ? 0 : h); } @@ -788,21 +824,33 @@ class CanvasModel with ChangeNotifier { return parent.target?.ffiModel.display.height ?? defaultHeight; } - double get windowBorderWidth => stateGlobal.windowBorderWidth.value; - double get tabBarHeight => stateGlobal.tabBarHeight; + static double get windowBorderWidth => stateGlobal.windowBorderWidth.value; + static double get tabBarHeight => stateGlobal.tabBarHeight; moveDesktopMouse(double x, double y) { + if (size.width == 0 || size.height == 0) { + return; + } + // On mobile platforms, move the canvas with the cursor. final dw = getDisplayWidth() * _scale; final dh = getDisplayHeight() * _scale; var dxOffset = 0; var dyOffset = 0; - if (dw > size.width) { - dxOffset = (x - dw * (x / size.width) - _x).toInt(); - } - if (dh > size.height) { - dyOffset = (y - dh * (y / size.height) - _y).toInt(); + try { + if (dw > size.width) { + dxOffset = (x - dw * (x / size.width) - _x).toInt(); + } + if (dh > size.height) { + dyOffset = (y - dh * (y / size.height) - _y).toInt(); + } + } catch (e) { + debugPrintStack( + label: + '(x,y) ($x,$y), (_x,_y) ($_x,$_y), _scale $_scale, display size (${getDisplayWidth()},${getDisplayHeight()}), size $size, , $e'); + return; } + _x += dxOffset; _y += dyOffset; if (dxOffset != 0 || dyOffset != 0) { @@ -1528,9 +1576,6 @@ class FFI { }(); // every instance will bind a stream this.id = id; - if (isFileTransfer) { - fileModel.initFileFetcher(); - } } /// Login with [password], choose if the client should [remember] it. @@ -1579,6 +1624,19 @@ class Display { ? kDesktopDefaultDisplayHeight : kMobileDefaultDisplayHeight; } + + @override + bool operator ==(Object other) => + other is Display && + other.runtimeType == runtimeType && + _innerEqual(other); + + bool _innerEqual(Display other) => + other.x == x && + other.y == y && + other.width == width && + other.height == height && + other.cursorEmbedded == cursorEmbedded; } class Resolution { diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 13f5b4587..2b99e1823 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -30,7 +30,7 @@ typedef F4Dart = int Function(Pointer); typedef F5 = Void Function(Pointer); typedef F5Dart = void Function(Pointer); typedef HandleEvent = Future Function(Map evt); -// pub fn session_register_texture(id: *const char, ptr: usize) +// pub fn session_register_texture(id: *const char, ptr: usize) typedef F6 = Void Function(Pointer, Uint64); typedef F6Dart = void Function(Pointer, int); @@ -56,7 +56,6 @@ class PlatformFFI { F4Dart? _session_get_rgba_size; F5Dart? _session_next_rgba; F6Dart? _session_register_texture; - static get localeName => Platform.localeName; @@ -162,7 +161,8 @@ class PlatformFFI { dylib.lookupFunction("session_get_rgba_size"); _session_next_rgba = dylib.lookupFunction("session_next_rgba"); - _session_register_texture = dylib.lookupFunction("session_register_texture"); + _session_register_texture = + dylib.lookupFunction("session_register_texture"); try { // SYSTEM user failed _dir = (await getApplicationDocumentsDirectory()).path; @@ -234,6 +234,9 @@ class PlatformFFI { debugPrint( '_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir'); } + if (desktopType == DesktopType.cm) { + await _ffiBind.cmStartListenIpcThread(); + } await _ffiBind.mainDeviceId(id: id); await _ffiBind.mainDeviceName(name: name); await _ffiBind.mainSetHomeDir(home: _homeDir); @@ -301,4 +304,8 @@ class PlatformFFI { if (!isAndroid) return Future(() => false); return await _toAndroidChannel.invokeMethod(method, arguments); } + + void syncAndroidServiceAppDirConfigPath() { + invokeMethod(AndroidChannel.kSyncAppDirConfigPath, _dir); + } } diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index ad5183ae3..1b71e382e 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -14,6 +14,13 @@ class Peer { String rdpUsername; bool online = false; + String getId() { + if (alias != '') { + return alias; + } + return id; + } + Peer.fromJson(Map json) : id = json['id'] ?? '', username = json['username'] ?? '', diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index b2043f3c2..216814cf6 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; @@ -127,10 +128,13 @@ class ServerModel with ChangeNotifier { _connectStatus = status; notifyListeners(); } - final res = await bind.cmCheckClientsLength(length: _clients.length); - if (res != null) { - debugPrint("clients not match!"); - updateClientState(res); + + if (desktopType == DesktopType.cm) { + final res = await bind.cmCheckClientsLength(length: _clients.length); + if (res != null) { + debugPrint("clients not match!"); + updateClientState(res); + } } updatePasswordModel(); @@ -154,7 +158,8 @@ class ServerModel with ChangeNotifier { /// file true by default (if permission on) checkAndroidPermission() async { // audio - if (androidVersion < 30 || !await PermissionManager.check("audio")) { + if (androidVersion < 30 || + !await AndroidPermissionManager.check(kRecordAudio)) { _audioOk = false; bind.mainSetOption(key: "enable-audio", value: "N"); } else { @@ -163,7 +168,7 @@ class ServerModel with ChangeNotifier { } // file - if (!await PermissionManager.check("file")) { + if (!await AndroidPermissionManager.check(kManageExternalStorage)) { _fileOk = false; bind.mainSetOption(key: "enable-file-transfer", value: "N"); } else { @@ -229,10 +234,13 @@ class ServerModel with ChangeNotifier { } toggleAudio() async { - if (!_audioOk && !await PermissionManager.check("audio")) { - final res = await PermissionManager.request("audio"); + if (clients.isNotEmpty) { + await showClientsMayNotBeChangedAlert(parent.target); + } + if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) { + final res = await AndroidPermissionManager.request(kRecordAudio); if (!res) { - // TODO handle fail + showToast(translate('Failed')); return; } } @@ -243,10 +251,15 @@ class ServerModel with ChangeNotifier { } toggleFile() async { - if (!_fileOk && !await PermissionManager.check("file")) { - final res = await PermissionManager.request("file"); + if (clients.isNotEmpty) { + await showClientsMayNotBeChangedAlert(parent.target); + } + if (!_fileOk && + !await AndroidPermissionManager.check(kManageExternalStorage)) { + final res = + await AndroidPermissionManager.request(kManageExternalStorage); if (!res) { - // TODO handle fail + showToast(translate('Failed')); return; } } @@ -256,11 +269,17 @@ class ServerModel with ChangeNotifier { notifyListeners(); } - toggleInput() { + toggleInput() async { + if (clients.isNotEmpty) { + await showClientsMayNotBeChangedAlert(parent.target); + } if (_inputOk) { parent.target?.invokeMethod("stop_input"); + bind.mainSetOption(key: "enable-keyboard", value: 'N'); } else { if (parent.target != null) { + /// the result of toggle-on depends on user actions in the settings page. + /// handle result, see [ServerModel.changeStatue] showInputWarnAlert(parent.target!); } } @@ -282,7 +301,7 @@ class ServerModel with ChangeNotifier { content: Text(translate("android_stop_service_tip")), actions: [ TextButton(onPressed: close, child: Text(translate("Cancel"))), - ElevatedButton(onPressed: submit, child: Text(translate("OK"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], onSubmit: submit, onCancel: close, @@ -344,10 +363,6 @@ class ServerModel with ChangeNotifier { } } - Future initInput() async { - await parent.target?.invokeMethod("init_input"); - } - Future setPermanentPassword(String newPW) async { await bind.mainSetPermanentPassword(password: newPW); await Future.delayed(Duration(milliseconds: 500)); @@ -456,7 +471,8 @@ class ServerModel with ChangeNotifier { Future.delayed(Duration.zero, () async { if (!hideCm) window_on_top(null); }); - if (client.authorized) { + // Only do the hidden task when on Desktop. + if (client.authorized && isDesktop) { cmHiddenTimer = Timer(const Duration(seconds: 3), () { if (!hideCm) windowManager.minimize(); cmHiddenTimer = null; @@ -561,7 +577,8 @@ class ServerModel with ChangeNotifier { } Future closeAll() async { - await Future.wait(_clients.map((client) => bind.cmCloseConnection(connId: client.id))); + await Future.wait( + _clients.map((client) => bind.cmCloseConnection(connId: client.id))); _clients.clear(); tabController.state.value.tabs.clear(); } @@ -684,7 +701,7 @@ String getLoginDialogTag(int id) { showInputWarnAlert(FFI ffi) { ffi.dialogManager.show((setState, close) { submit() { - ffi.serverModel.initInput(); + AndroidPermissionManager.startAction(kActionAccessibilitySettings); close(); } @@ -707,3 +724,22 @@ showInputWarnAlert(FFI ffi) { ); }); } + +Future showClientsMayNotBeChangedAlert(FFI? ffi) async { + await ffi?.dialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate("Permissions")), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(translate("android_permission_may_not_change_tip")), + ], + ), + actions: [ + dialogButton("OK", onPressed: close), + ], + onSubmit: close, + onCancel: close, + ); + }); +} diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index 761c95ded..187b1ffc5 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -9,8 +9,10 @@ import '../consts.dart'; class StateGlobal { int _windowId = -1; bool _fullscreen = false; + bool _maximize = false; bool grabKeyboard = false; final RxBool _showTabBar = true.obs; + final RxBool _showResizeEdge = true.obs; final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize); final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth); final RxBool showRemoteMenuBar = false.obs; @@ -18,18 +20,31 @@ class StateGlobal { int get windowId => _windowId; bool get fullscreen => _fullscreen; + bool get maximize => _maximize; double get tabBarHeight => fullscreen ? 0 : kDesktopRemoteTabBarHeight; RxBool get showTabBar => _showTabBar; RxDouble get resizeEdgeSize => _resizeEdgeSize; RxDouble get windowBorderWidth => _windowBorderWidth; setWindowId(int id) => _windowId = id; + setMaximize(bool v) { + if (_maximize != v && !_fullscreen) { + _maximize = v; + _resizeEdgeSize.value = _maximize ? kMaximizeEdgeSize : kWindowEdgeSize; + } + } setFullscreen(bool v) { if (_fullscreen != v) { _fullscreen = v; _showTabBar.value = !_fullscreen; _resizeEdgeSize.value = - fullscreen ? kFullScreenEdgeSize : kWindowEdgeSize; + fullscreen + ? kFullScreenEdgeSize + : _maximize + ? kMaximizeEdgeSize + : kWindowEdgeSize; + print( + "fullscreen: ${fullscreen}, resizeEdgeSize: ${_resizeEdgeSize.value}"); _windowBorderWidth.value = fullscreen ? 0 : kWindowBorderWidth; WindowController.fromWindowId(windowId) .setFullscreen(_fullscreen) diff --git a/flutter/macos/Runner.xcodeproj/project.pbxproj b/flutter/macos/Runner.xcodeproj/project.pbxproj index 0019335ef..c73e666c7 100644 --- a/flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter/macos/Runner.xcodeproj/project.pbxproj @@ -487,7 +487,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 5ffe805b8..c2e1f58fc 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -325,8 +325,8 @@ packages: dependency: "direct main" description: path: "." - ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a - resolved-ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a + ref: "980ae3b45837a4bc55d82231801ffcbb0806b0c1" + resolved-ref: "980ae3b45837a4bc55d82231801ffcbb0806b0c1" url: "https://github.com/Kingtous/rustdesk_desktop_multi_window" source: git version: "0.1.0" @@ -386,6 +386,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.2" + dropdown_button2: + dependency: "direct main" + description: + name: dropdown_button2 + sha256: "4458d81bfd24207f3d58f66f78097064e02f810f94cf1bc80bf20fe7685ebc80" + url: "https://pub.dev" + source: hosted + version: "2.0.0" event_bus: dependency: transitive description: @@ -1228,10 +1236,10 @@ packages: dependency: "direct main" description: name: texture_rgba_renderer - sha256: fbb09b2c6b4ce71261927f9e7e4ea339af3e2f3f2b175f6fb921de1c66ec848d + sha256: "52bc9f217b7b07a760ee837d5a17329ad1f78ae8ed1e3fa612c6f1bed3c77f79" url: "https://pub.dev" source: hosted - version: "0.0.8" + version: "0.0.13" timing: dependency: transitive description: @@ -1508,11 +1516,11 @@ packages: dependency: "direct main" description: path: "." - ref: "32b24c66151b72bba033ef8b954486aa9351d97b" - resolved-ref: "32b24c66151b72bba033ef8b954486aa9351d97b" + ref: c140f9685bead805c433c9f5a4618a5115f01fa8 + resolved-ref: c140f9685bead805c433c9f5a4618a5115f01fa8 url: "https://github.com/Kingtous/rustdesk_window_manager" source: git - version: "0.2.7" + version: "0.3.1" window_size: dependency: "direct main" description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 572b3e20a..3e301e107 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -55,11 +55,11 @@ dependencies: window_manager: git: url: https://github.com/Kingtous/rustdesk_window_manager - ref: 32b24c66151b72bba033ef8b954486aa9351d97b + ref: c140f9685bead805c433c9f5a4618a5115f01fa8 desktop_multi_window: git: url: https://github.com/Kingtous/rustdesk_desktop_multi_window - ref: f37357ed98a10717576eb9ed8413e92b2ec5d13a + ref: 980ae3b45837a4bc55d82231801ffcbb0806b0c1 freezed_annotation: ^2.0.3 flutter_custom_cursor: ^0.0.4 window_size: @@ -76,7 +76,7 @@ dependencies: file_picker: ^5.1.0 flutter_svg: ^1.1.5 flutter_improved_scrolling: - # currently, we use flutter 3.0.5 for windows build, latest for other builds. + # currently, we use flutter 3.7.0+. # # for flutter 3.0.5, please use official version(just comment code below). # if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below). @@ -92,8 +92,9 @@ dependencies: password_strength: ^0.2.0 flutter_launcher_icons: ^0.11.0 flutter_keyboard_visibility: ^5.4.0 - texture_rgba_renderer: ^0.0.8 + texture_rgba_renderer: ^0.0.13 percent_indicator: ^4.2.2 + dropdown_button2: ^2.0.0 dev_dependencies: icons_launcher: ^2.0.4 diff --git a/flutter/run.sh b/flutter/run.sh index f1066306a..78fd1e80c 100755 --- a/flutter/run.sh +++ b/flutter/run.sh @@ -4,5 +4,5 @@ dart pub global activate ffigen --version 5.0.1 flutter pub get # call `flutter clean` if cargo build fails # export LLVM_HOME=/Library/Developer/CommandLineTools/usr/ -cargo build --features flutter +cargo build --features "flutter,flutter_texture_render" flutter run $@ diff --git a/libs/hbb_common/Cargo.toml b/libs/hbb_common/Cargo.toml index a125078d2..ed9ec73be 100644 --- a/libs/hbb_common/Cargo.toml +++ b/libs/hbb_common/Cargo.toml @@ -7,16 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +flexi_logger = { version = "0.25", features = ["async"] } protobuf = { version = "3.1", features = ["with-bytes"] } tokio = { version = "1.20", features = ["full"] } tokio-util = { version = "0.7", features = ["full"] } futures = "0.3" bytes = { version = "1.2", features = ["serde"] } log = "0.4" -env_logger = "0.9" +env_logger = "0.10" socket2 = { version = "0.3", features = ["reuseport"] } zstd = "0.9" -quinn = {version = "0.8", optional = true } +quinn = {version = "0.9", optional = true } anyhow = "1.0" futures-util = "0.3" directories-next = "2.0" @@ -33,11 +34,11 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" } chrono = "0.4" backtrace = "0.3" libc = "0.2" -sysinfo = "0.24" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] mac_address = "1.1" machine-uid = "0.2" +sysinfo = "0.28" [features] quic = [] @@ -53,5 +54,5 @@ winapi = { version = "0.3", features = ["winuser"] } osascript = "0.3.0" [dev-dependencies] -toml = "0.5" +toml = "0.7" serde_json = "1.0" diff --git a/libs/hbb_common/examples/config.rs b/libs/hbb_common/examples/config.rs new file mode 100644 index 000000000..95169df8e --- /dev/null +++ b/libs/hbb_common/examples/config.rs @@ -0,0 +1,5 @@ +extern crate hbb_common; + +fn main() { + println!("{:?}", hbb_common::config::PeerConfig::load("455058072")); +} diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 3bfc885c5..4ce2cf07e 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -4,7 +4,7 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, path::{Path, PathBuf}, sync::{Arc, Mutex, RwLock}, - time::SystemTime, + time::{Duration, Instant, SystemTime}, }; use anyhow::Result; @@ -51,6 +51,7 @@ lazy_static::lazy_static! { pub static ref APP_NAME: Arc> = Arc::new(RwLock::new("RustDesk".to_owned())); static ref KEY_PAIR: Arc>> = Default::default(); static ref HW_CODEC_CONFIG: Arc> = Arc::new(RwLock::new(HwCodecConfig::load())); + static ref USER_DEFAULT_CONFIG: Arc> = Arc::new(RwLock::new((UserDefaultConfig::load(), Instant::now()))); } lazy_static::lazy_static! { @@ -110,10 +111,10 @@ macro_rules! serde_field_string { } macro_rules! serde_field_bool { - ($struct_name: ident, $field_name: literal, $func: ident) => { + ($struct_name: ident, $field_name: literal, $func: ident, $default: literal) => { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct $struct_name { - #[serde(rename = $field_name)] + #[serde(default = $default, rename = $field_name)] pub v: bool, } impl Default for $struct_name { @@ -123,7 +124,7 @@ macro_rules! serde_field_bool { } impl $struct_name { pub fn $func() -> bool { - UserDefaultConfig::load().get($field_name) == "Y" + UserDefaultConfig::read().get($field_name) == "Y" } } }; @@ -217,6 +218,8 @@ pub struct PeerConfig { pub lock_after_session_end: LockAfterSessionEnd, #[serde(flatten)] pub privacy_mode: PrivacyMode, + #[serde(flatten)] + pub allow_swap_key: AllowSwapKey, #[serde(default)] pub port_forwards: Vec<(i32, String, i32)>, #[serde(default)] @@ -978,21 +981,21 @@ impl PeerConfig { serde_field_string!( default_view_style, deserialize_view_style, - UserDefaultConfig::load().get("view_style") + UserDefaultConfig::read().get("view_style") ); serde_field_string!( default_scroll_style, deserialize_scroll_style, - UserDefaultConfig::load().get("scroll_style") + UserDefaultConfig::read().get("scroll_style") ); serde_field_string!( default_image_quality, deserialize_image_quality, - UserDefaultConfig::load().get("image_quality") + UserDefaultConfig::read().get("image_quality") ); fn default_custom_image_quality() -> Vec { - let f: f64 = UserDefaultConfig::load() + let f: f64 = UserDefaultConfig::read() .get("custom_image_quality") .parse() .unwrap_or(50.0); @@ -1018,15 +1021,15 @@ impl PeerConfig { let mut mp: HashMap = de::Deserialize::deserialize(deserializer)?; let mut key = "codec-preference"; if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); + mp.insert(key.to_owned(), UserDefaultConfig::read().get(key)); } key = "custom-fps"; if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); + mp.insert(key.to_owned(), UserDefaultConfig::read().get(key)); } key = "zoom-cursor"; if !mp.contains_key(key) { - mp.insert(key.to_owned(), UserDefaultConfig::load().get(key)); + mp.insert(key.to_owned(), UserDefaultConfig::read().get(key)); } Ok(mp) } @@ -1035,30 +1038,52 @@ impl PeerConfig { serde_field_bool!( ShowRemoteCursor, "show_remote_cursor", - default_show_remote_cursor + default_show_remote_cursor, + "ShowRemoteCursor::default_show_remote_cursor" ); serde_field_bool!( ShowQualityMonitor, "show_quality_monitor", - default_show_quality_monitor + default_show_quality_monitor, + "ShowQualityMonitor::default_show_quality_monitor" +); +serde_field_bool!( + DisableAudio, + "disable_audio", + default_disable_audio, + "DisableAudio::default_disable_audio" ); -serde_field_bool!(DisableAudio, "disable_audio", default_disable_audio); serde_field_bool!( EnableFileTransfer, "enable_file_transfer", - default_enable_file_transfer + default_enable_file_transfer, + "EnableFileTransfer::default_enable_file_transfer" ); serde_field_bool!( DisableClipboard, "disable_clipboard", - default_disable_clipboard + default_disable_clipboard, + "DisableClipboard::default_disable_clipboard" ); serde_field_bool!( LockAfterSessionEnd, "lock_after_session_end", - default_lock_after_session_end + default_lock_after_session_end, + "LockAfterSessionEnd::default_lock_after_session_end" +); +serde_field_bool!( + PrivacyMode, + "privacy_mode", + default_privacy_mode, + "PrivacyMode::default_privacy_mode" +); + +serde_field_bool!( + AllowSwapKey, + "allow_swap_key", + default_allow_swap_key, + "AllowSwapKey::default_allow_swap_key" ); -serde_field_bool!(PrivacyMode, "privacy_mode", default_privacy_mode); #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct LocalConfig { @@ -1273,6 +1298,14 @@ pub struct UserDefaultConfig { } impl UserDefaultConfig { + pub fn read() -> UserDefaultConfig { + let mut cfg = USER_DEFAULT_CONFIG.write().unwrap(); + if cfg.1.elapsed() > Duration::from_secs(1) { + *cfg = (Self::load(), Instant::now()); + } + cfg.0.clone() + } + pub fn load() -> UserDefaultConfig { Config::load_::("_default") } diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index bfb773908..799093d24 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -39,9 +39,10 @@ pub use tokio_socks::IntoTargetAddr; pub use tokio_socks::TargetAddr; pub mod password_security; pub use chrono; -pub use libc; pub use directories_next; +pub use libc; pub mod keyboard; +#[cfg(not(any(target_os = "android", target_os = "ios")))] pub use sysinfo; #[cfg(feature = "quic")] @@ -312,6 +313,44 @@ pub fn is_domain_port_str(id: &str) -> bool { .is_match(id) } +pub fn init_log(_is_async: bool, _name: &str) -> Option { + #[cfg(debug_assertions)] + { + use env_logger::*; + init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); + None + } + #[cfg(not(debug_assertions))] + { + // https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#write + // though async logger more efficient, but it also causes more problems, disable it for now + let mut logger_holder: Option = None; + let mut path = config::Config::log_path(); + if !_name.is_empty() { + path.push(_name); + } + use flexi_logger::*; + if let Ok(x) = Logger::try_with_env_or_str("debug") { + logger_holder = x + .log_to_file(FileSpec::default().directory(path)) + .write_mode(if _is_async { + WriteMode::Async + } else { + WriteMode::Direct + }) + .format(opt_format) + .rotate( + Criterion::Age(Age::Day), + Naming::Timestamps, + Cleanup::KeepLogFiles(6), + ) + .start() + .ok(); + } + logger_holder + } +} + #[cfg(test)] mod test { use super::*; diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs index 191ea2e6f..f6133415a 100644 --- a/libs/hbb_common/src/platform/linux.rs +++ b/libs/hbb_common/src/platform/linux.rs @@ -81,8 +81,10 @@ fn get_display_server_of_session(session: &str) -> String { display_server = sestype; } } - // If the session is not a tty, then just return the type as usual - display_server + if display_server == "" { + display_server = "x11".to_owned(); + } + display_server.to_lowercase() } pub fn get_values_of_seat0(indices: Vec) -> Vec { diff --git a/libs/scrap/Cargo.toml b/libs/scrap/Cargo.toml index 82cb88faf..99e15aeef 100644 --- a/libs/scrap/Cargo.toml +++ b/libs/scrap/Cargo.toml @@ -42,7 +42,7 @@ quest = "0.3" [build-dependencies] target_build_utils = "0.3" -bindgen = "0.59" +bindgen = "0.64" [target.'cfg(target_os = "linux")'.dependencies] dbus = { version = "0.9", optional = true } diff --git a/libs/scrap/examples/benchmark.rs b/libs/scrap/examples/benchmark.rs new file mode 100644 index 000000000..003830f95 --- /dev/null +++ b/libs/scrap/examples/benchmark.rs @@ -0,0 +1,292 @@ +use docopt::Docopt; +use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV}; +use scrap::{ + codec::{EncoderApi, EncoderCfg}, + Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig, + VpxVideoCodecId, STRIDE_ALIGN, +}; +use std::{io::Write, time::Instant}; + +// cargo run --package scrap --example benchmark --release --features hwcodec + +const USAGE: &'static str = " +Codec benchmark. + +Usage: + benchmark [--count=COUNT] [--bitrate=KBS] [--hw-pixfmt=PIXFMT] + benchmark (-h | --help) + +Options: + -h --help Show this screen. + --count=COUNT Capture frame count [default: 100]. + --bitrate=KBS Video bitrate in kilobits per second [default: 5000]. + --hw-pixfmt=PIXFMT Hardware codec pixfmt. [default: i420] + Valid values: i420, nv12. +"; + +#[derive(Debug, serde::Deserialize)] +struct Args { + flag_count: usize, + flag_bitrate: usize, + flag_hw_pixfmt: Pixfmt, +} + +#[derive(Debug, serde::Deserialize)] +enum Pixfmt { + I420, + NV12, +} + +fn main() { + init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.deserialize()) + .unwrap_or_else(|e| e.exit()); + let bitrate_k = args.flag_bitrate; + let yuv_count = args.flag_count; + let (yuvs, width, height) = capture_yuv(yuv_count); + println!( + "benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}", + width, height, bitrate_k, args.flag_hw_pixfmt + ); + test_vp9(&yuvs, width, height, bitrate_k, yuv_count); + #[cfg(feature = "hwcodec")] + { + use hwcodec::AVPixelFormat; + let hw_pixfmt = match args.flag_hw_pixfmt { + Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P, + Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12, + }; + let yuvs = hw::vp9_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt); + hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt); + } +} + +fn capture_yuv(yuv_count: usize) -> (Vec>, usize, usize) { + let mut index = 0; + let mut displays = Display::all().unwrap(); + for i in 0..displays.len() { + if displays[i].is_primary() { + index = i; + break; + } + } + let d = displays.remove(index); + let mut c = Capturer::new(d, true).unwrap(); + let mut v = vec![]; + loop { + if let Ok(frame) = c.frame(std::time::Duration::from_millis(30)) { + v.push(frame.0.to_vec()); + print!("\rcapture {}/{}", v.len(), yuv_count); + std::io::stdout().flush().ok(); + if v.len() == yuv_count { + println!(); + return (v, c.width(), c.height()); + } + } + } +} + +fn test_vp9(yuvs: &Vec>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) { + let config = EncoderCfg::VPX(VpxEncoderConfig { + width: width as _, + height: height as _, + timebase: [1, 1000], + bitrate: bitrate_k as _, + codec: VpxVideoCodecId::VP9, + num_threads: (num_cpus::get() / 2) as _, + }); + let mut encoder = VpxEncoder::new(config).unwrap(); + let start = Instant::now(); + for yuv in yuvs { + let _ = encoder + .encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN) + .unwrap(); + let _ = encoder.flush().unwrap(); + } + println!("vp9 encode: {:?}", start.elapsed() / yuv_count as _); + + // prepare data separately + let mut vp9s = vec![]; + let start = Instant::now(); + for yuv in yuvs { + for ref frame in encoder + .encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN) + .unwrap() + { + vp9s.push(frame.data.to_vec()); + } + for ref frame in encoder.flush().unwrap() { + vp9s.push(frame.data.to_vec()); + } + } + assert_eq!(vp9s.len(), yuv_count); + + let mut decoder = VpxDecoder::new(VpxDecoderConfig { + codec: VpxVideoCodecId::VP9, + num_threads: (num_cpus::get() / 2) as _, + }) + .unwrap(); + let start = Instant::now(); + for vp9 in vp9s { + let _ = decoder.decode(&vp9); + let _ = decoder.flush(); + } + println!("vp9 decode: {:?}", start.elapsed() / yuv_count as _); +} + +#[cfg(feature = "hwcodec")] +mod hw { + use super::*; + use hwcodec::{ + decode::{DecodeContext, Decoder}, + encode::{EncodeContext, Encoder}, + ffmpeg::{ffmpeg_linesize_offset_length, CodecInfo, CodecInfos}, + AVPixelFormat, + Quality::*, + RateControl::*, + }; + use scrap::{ + convert::{ + hw::{hw_bgra_to_i420, hw_bgra_to_nv12}, + i420_to_bgra, + }, + HW_STRIDE_ALIGN, + }; + + pub fn test( + yuvs: &Vec>, + width: usize, + height: usize, + bitrate_k: usize, + yuv_count: usize, + pixfmt: AVPixelFormat, + ) { + let ctx = EncodeContext { + name: String::from(""), + width: width as _, + height: height as _, + pixfmt, + align: 0, + bitrate: (bitrate_k * 1000) as _, + timebase: [1, 30], + gop: 60, + quality: Quality_Default, + rc: RC_DEFAULT, + }; + + let encoders = Encoder::available_encoders(ctx.clone()); + println!("hw encoders: {}", encoders.len()); + let best = CodecInfo::score(encoders.clone()); + for info in encoders { + test_encoder(info.clone(), ctx.clone(), yuvs, is_best(&best, &info)); + } + + let (h264s, h265s) = prepare_h26x(best, ctx.clone(), yuvs); + assert!(h264s.is_empty() || h264s.len() == yuv_count); + assert!(h265s.is_empty() || h265s.len() == yuv_count); + let decoders = Decoder::available_decoders(); + println!("hw decoders: {}", decoders.len()); + let best = CodecInfo::score(decoders.clone()); + for info in decoders { + let h26xs = if info.name.contains("h264") { + &h264s + } else { + &h265s + }; + if h26xs.len() == yuvs.len() { + test_decoder(info.clone(), h26xs, is_best(&best, &info)); + } + } + } + + fn test_encoder(info: CodecInfo, ctx: EncodeContext, yuvs: &Vec>, best: bool) { + let mut ctx = ctx; + ctx.name = info.name; + let mut encoder = Encoder::new(ctx.clone()).unwrap(); + let start = Instant::now(); + for yuv in yuvs { + let _ = encoder.encode(yuv).unwrap(); + } + println!( + "{}{}: {:?}", + if best { "*" } else { "" }, + ctx.name, + start.elapsed() / yuvs.len() as _ + ); + } + + fn test_decoder(info: CodecInfo, h26xs: &Vec>, best: bool) { + let ctx = DecodeContext { + name: info.name, + device_type: info.hwdevice, + }; + + let mut decoder = Decoder::new(ctx.clone()).unwrap(); + let start = Instant::now(); + let mut cnt = 0; + for h26x in h26xs { + let _ = decoder.decode(h26x).unwrap(); + cnt += 1; + } + let device = format!("{:?}", ctx.device_type).to_lowercase(); + let device = device.split("_").last().unwrap(); + println!( + "{}{} {}: {:?}", + if best { "*" } else { "" }, + ctx.name, + device, + start.elapsed() / cnt + ); + } + + fn prepare_h26x( + best: CodecInfos, + ctx: EncodeContext, + yuvs: &Vec>, + ) -> (Vec>, Vec>) { + let f = |info: Option| { + let mut h26xs = vec![]; + if let Some(info) = info { + let mut ctx = ctx.clone(); + ctx.name = info.name; + let mut encoder = Encoder::new(ctx).unwrap(); + for yuv in yuvs { + let h26x = encoder.encode(yuv).unwrap(); + for frame in h26x { + h26xs.push(frame.data.to_vec()); + } + } + } + h26xs + }; + (f(best.h264), f(best.h265)) + } + + fn is_best(best: &CodecInfos, info: &CodecInfo) -> bool { + Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265 + } + + pub fn vp9_yuv_to_hw_yuv( + yuvs: Vec>, + width: usize, + height: usize, + pixfmt: AVPixelFormat, + ) -> Vec> { + let yuvs = yuvs; + let mut bgra = vec![]; + let mut v = vec![]; + let (linesize, offset, length) = + ffmpeg_linesize_offset_length(pixfmt, width, height, HW_STRIDE_ALIGN).unwrap(); + for mut yuv in yuvs { + i420_to_bgra(width, height, &yuv, &mut bgra); + if pixfmt == AVPixelFormat::AV_PIX_FMT_YUV420P { + hw_bgra_to_i420(width, height, &linesize, &offset, length, &bgra, &mut yuv); + } else { + hw_bgra_to_nv12(width, height, &linesize, &offset, length, &bgra, &mut yuv); + } + v.push(yuv); + } + v + } +} diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index 3adc24a14..9e4b6fce4 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -306,7 +306,7 @@ impl Decoder { pub fn handle_video_frame( &mut self, frame: &video_frame::Union, - fmt: ImageFormat, + fmt: (ImageFormat, usize), rgb: &mut Vec, ) -> ResultType { match frame { @@ -352,7 +352,7 @@ impl Decoder { fn handle_vp9s_video_frame( decoder: &mut VpxDecoder, vp9s: &EncodedVideoFrames, - fmt: ImageFormat, + fmt: (ImageFormat, usize), rgb: &mut Vec, ) -> ResultType { let mut last_frame = Image::new(); @@ -369,7 +369,7 @@ impl Decoder { if last_frame.is_null() { Ok(false) } else { - last_frame.to(fmt, 1, rgb); + last_frame.to(fmt.0, fmt.1, rgb); Ok(true) } } @@ -378,7 +378,7 @@ impl Decoder { fn handle_hw_video_frame( decoder: &mut HwDecoder, frames: &EncodedVideoFrames, - fmt: ImageFormat, + fmt: (ImageFormat, usize), raw: &mut Vec, i420: &mut Vec, ) -> ResultType { @@ -398,7 +398,7 @@ impl Decoder { fn handle_mediacodec_video_frame( decoder: &mut MediaCodecDecoder, frames: &EncodedVideoFrames, - fmt: ImageFormat, + fmt: (ImageFormat, usize), raw: &mut Vec, ) -> ResultType { let mut ret = false; diff --git a/libs/scrap/src/common/convert.rs b/libs/scrap/src/common/convert.rs index f3ad51a21..65e3a16ad 100644 --- a/libs/scrap/src/common/convert.rs +++ b/libs/scrap/src/common/convert.rs @@ -190,6 +190,29 @@ pub fn i420_to_rgb(width: usize, height: usize, src: &[u8], dst: &mut Vec) { }; } +pub fn i420_to_bgra(width: usize, height: usize, src: &[u8], dst: &mut Vec) { + let (_, _, src_stride_y, src_stride_uv, u, v) = + get_vpx_i420_stride(width, height, super::STRIDE_ALIGN); + let src_y = src.as_ptr(); + let src_u = src[u..].as_ptr(); + let src_v = src[v..].as_ptr(); + dst.resize(width * height * 4, 0); + unsafe { + super::I420ToARGB( + src_y, + src_stride_y as _, + src_u, + src_stride_uv as _, + src_v, + src_stride_uv as _, + dst.as_mut_ptr(), + (width * 3) as _, + width as _, + height as _, + ); + }; +} + pub fn bgra_to_i420(width: usize, height: usize, src: &[u8], dst: &mut Vec) { let (_, h, dst_stride_y, dst_stride_uv, u, v) = get_vpx_i420_stride(width, height, super::STRIDE_ALIGN); @@ -269,8 +292,8 @@ pub unsafe fn nv12_to_i420( #[cfg(feature = "hwcodec")] pub mod hw { - use hbb_common::{anyhow::anyhow, ResultType}; use crate::ImageFormat; + use hbb_common::{anyhow::anyhow, ResultType}; #[cfg(target_os = "windows")] use hwcodec::{ffmpeg::ffmpeg_linesize_offset_length, AVPixelFormat}; @@ -466,9 +489,7 @@ pub mod hw { _ => Err(anyhow!("NV12ToABGR failed")), } } - _ => { - Err(anyhow!("unsupported image format")) - } + _ => Err(anyhow!("unsupported image format")), } } } @@ -519,8 +540,7 @@ pub mod hw { height as _, ); } - _ => { - } + _ => {} } }; } diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index d2b9f414f..3c86af94e 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -236,7 +236,13 @@ pub struct HwDecoderImage<'a> { } impl HwDecoderImage<'_> { - pub fn to_fmt(&self, fmt: ImageFormat, fmt_data: &mut Vec, i420: &mut Vec) -> ResultType<()> { + // take dst_stride into account when you convert + pub fn to_fmt( + &self, + (fmt, dst_stride): (ImageFormat, usize), + fmt_data: &mut Vec, + i420: &mut Vec, + ) -> ResultType<()> { let frame = self.frame; match frame.pixfmt { AVPixelFormat::AV_PIX_FMT_NV12 => hw::hw_nv12_to( @@ -270,11 +276,11 @@ impl HwDecoderImage<'_> { } pub fn bgra(&self, bgra: &mut Vec, i420: &mut Vec) -> ResultType<()> { - self.to_fmt(ImageFormat::ARGB, bgra, i420) + self.to_fmt((ImageFormat::ARGB, 1), bgra, i420) } pub fn rgba(&self, rgba: &mut Vec, i420: &mut Vec) -> ResultType<()> { - self.to_fmt(ImageFormat::ABGR, rgba, i420) + self.to_fmt((ImageFormat::ABGR, 1), rgba, i420) } } diff --git a/libs/scrap/src/common/mediacodec.rs b/libs/scrap/src/common/mediacodec.rs index 7bda0b69d..a0272d867 100644 --- a/libs/scrap/src/common/mediacodec.rs +++ b/libs/scrap/src/common/mediacodec.rs @@ -1,4 +1,4 @@ -use hbb_common::{log, anyhow::Error, bail, ResultType}; +use hbb_common::{anyhow::Error, bail, log, ResultType}; use ndk::media::media_codec::{MediaCodec, MediaCodecDirection, MediaFormat}; use std::ops::Deref; use std::{ @@ -50,7 +50,13 @@ impl MediaCodecDecoder { MediaCodecDecoders { h264, h265 } } - pub fn decode(&mut self, data: &[u8], fmt: ImageFormat, raw: &mut Vec) -> ResultType { + // take dst_stride into account please + pub fn decode( + &mut self, + data: &[u8], + (fmt, dst_stride): (ImageFormat, usize), + raw: &mut Vec, + ) -> ResultType { match self.dequeue_input_buffer(Duration::from_millis(10))? { Some(mut input_buffer) => { let mut buf = input_buffer.buffer_mut(); diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index c7da57734..9e15cf084 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -30,7 +30,7 @@ cfg_if! { } pub mod codec; -mod convert; +pub mod convert; #[cfg(feature = "hwcodec")] pub mod hwcodec; #[cfg(feature = "mediacodec")] diff --git a/libs/scrap/src/common/quartz.rs b/libs/scrap/src/common/quartz.rs index a02d55ebb..dc59edac1 100644 --- a/libs/scrap/src/common/quartz.rs +++ b/libs/scrap/src/common/quartz.rs @@ -83,7 +83,7 @@ impl crate::TraitCapturer for Capturer { } } -pub struct Frame<'a>(quartz::Frame, PhantomData<&'a [u8]>); +pub struct Frame<'a>(pub quartz::Frame, PhantomData<&'a [u8]>); impl<'a> ops::Deref for Frame<'a> { type Target = [u8]; diff --git a/libs/scrap/src/common/vpxcodec.rs b/libs/scrap/src/common/vpxcodec.rs index 7a65b193d..b91871c8f 100644 --- a/libs/scrap/src/common/vpxcodec.rs +++ b/libs/scrap/src/common/vpxcodec.rs @@ -539,15 +539,17 @@ impl Image { self.inner().stride[iplane] } - pub fn to(&self, fmt: ImageFormat, stride_align: usize, dst: &mut Vec) { + pub fn to(&self, fmt: ImageFormat, stride: usize, dst: &mut Vec) { let h = self.height(); - let mut w = self.width(); - let bps = match fmt { + let w = self.width(); + let bytes_per_pixel = match fmt { ImageFormat::Raw => 3, ImageFormat::ARGB | ImageFormat::ABGR => 4, }; - w = (w + stride_align - 1) & !(stride_align - 1); - dst.resize(h * w * bps, 0); + // https://github.com/lemenkov/libyuv/blob/6900494d90ae095d44405cd4cc3f346971fa69c9/source/convert_argb.cc#L128 + // https://github.com/lemenkov/libyuv/blob/6900494d90ae095d44405cd4cc3f346971fa69c9/source/convert_argb.cc#L129 + let bytes_per_row = (w * bytes_per_pixel + stride - 1) & !(stride - 1); + dst.resize(h * bytes_per_row, 0); let img = self.inner(); unsafe { match fmt { @@ -560,7 +562,7 @@ impl Image { img.planes[2], img.stride[2], dst.as_mut_ptr(), - (w * bps) as _, + bytes_per_row as _, self.width() as _, self.height() as _, ); @@ -574,7 +576,7 @@ impl Image { img.planes[2], img.stride[2], dst.as_mut_ptr(), - (w * bps) as _, + bytes_per_row as _, self.width() as _, self.height() as _, ); @@ -588,7 +590,7 @@ impl Image { img.planes[2], img.stride[2], dst.as_mut_ptr(), - (w * bps) as _, + bytes_per_row as _, self.width() as _, self.height() as _, ); diff --git a/libs/scrap/src/common/x11.rs b/libs/scrap/src/common/x11.rs index 6e3fc94fb..514608e4a 100644 --- a/libs/scrap/src/common/x11.rs +++ b/libs/scrap/src/common/x11.rs @@ -29,7 +29,7 @@ impl TraitCapturer for Capturer { } } -pub struct Frame<'a>(pub(crate) &'a [u8]); +pub struct Frame<'a>(pub &'a [u8]); impl<'a> ops::Deref for Frame<'a> { type Target = [u8]; diff --git a/libs/scrap/src/dxgi/mag.rs b/libs/scrap/src/dxgi/mag.rs index 0de86055e..62e90c08b 100644 --- a/libs/scrap/src/dxgi/mag.rs +++ b/libs/scrap/src/dxgi/mag.rs @@ -127,15 +127,6 @@ impl MagInterface { }; s.init_succeeded = false; unsafe { - if GetSystemMetrics(SM_CMONITORS) != 1 { - // Do not try to use the magnifier in multi-screen setup (where the API - // crashes sometimes). - return Err(Error::new( - ErrorKind::Other, - "Magnifier capturer cannot work on multi-screen system.", - )); - } - // load lib let lib_file_name = "Magnification.dll"; let lib_file_name_c = CString::new(lib_file_name).unwrap(); @@ -282,10 +273,10 @@ impl CapturerMag { let y = GetSystemMetrics(SM_YVIRTUALSCREEN); let w = GetSystemMetrics(SM_CXVIRTUALSCREEN); let h = GetSystemMetrics(SM_CYVIRTUALSCREEN); - if !(origin.0 == x as i32 - && origin.1 == y as i32 - && width == w as usize - && height == h as usize) + if !(origin.0 >= x as i32 + && origin.1 >= y as i32 + && width <= w as usize + && height <= h as usize) { return Err(Error::new( ErrorKind::Other, @@ -518,10 +509,10 @@ impl CapturerMag { let y = GetSystemMetrics(SM_YVIRTUALSCREEN); let w = GetSystemMetrics(SM_CXVIRTUALSCREEN); let h = GetSystemMetrics(SM_CYVIRTUALSCREEN); - if !(self.rect.left == x as i32 - && self.rect.top == y as i32 - && self.rect.right == (x + w) as i32 - && self.rect.bottom == (y + h) as i32) + if !(self.rect.left >= x as i32 + && self.rect.top >= y as i32 + && self.rect.right <= (x + w) as i32 + && self.rect.bottom <= (y + h) as i32) { return Err(Error::new( ErrorKind::Other, @@ -545,8 +536,8 @@ impl CapturerMag { HWND_TOP, self.rect.left, self.rect.top, - self.rect.right, - self.rect.bottom, + self.rect.right - self.rect.left, + self.rect.bottom - self.rect.top, 0, ) { @@ -556,8 +547,8 @@ impl CapturerMag { "Failed SetWindowPos (x, y, w , h) - ({}, {}, {}, {}), error {}", self.rect.left, self.rect.top, - self.rect.right, - self.rect.bottom, + self.rect.right - self.rect.left, + self.rect.bottom - self.rect.top, GetLastError() ), )); diff --git a/res/com.rustdesk.RustDesk.policy b/res/com.rustdesk.RustDesk.policy index a77223120..55f13629b 100644 --- a/res/com.rustdesk.RustDesk.policy +++ b/res/com.rustdesk.RustDesk.policy @@ -11,6 +11,7 @@ Authentication is required to change RustDesk options 要更改RustDesk选项, 需要您先通过身份验证 要變更RustDesk選項, 需要您先通過身份驗證 + Authentifizierung zum Ändern der RustDesk-Optionen /usr/share/rustdesk/files/polkit true diff --git a/res/lang.py b/res/lang.py index 481d65553..aa5f99f83 100644 --- a/res/lang.py +++ b/res/lang.py @@ -36,11 +36,11 @@ def main(): def expand(): for fn in glob.glob('./src/lang/*'): lang = os.path.basename(fn)[:-3] - if lang in ['en','cn']: continue + if lang in ['en','template']: continue print(lang) dict = get_lang(lang) fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8') - for line in open('./src/lang/cn.rs', encoding='utf8'): + for line in open('./src/lang/template.rs', encoding='utf8'): line_strip = line.strip() if line_strip.startswith('("'): k, v = line_split(line_strip) diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index 6c7055b4a..77c28a94e 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -4,7 +4,7 @@ Release: 0 Summary: RPM package License: GPL-3.0 Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libappindicator-gtk3 libvdpau1 libva2 -Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit) +Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) %description The best open-source remote desktop client software, written in Rust. diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index 73bb993aa..6124cbb70 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -4,7 +4,7 @@ Release: 0 Summary: RPM package License: GPL-3.0 Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator-gtk3 libvdpau libva -Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit) +Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) %description The best open-source remote desktop client software, written in Rust. diff --git a/src/client.rs b/src/client.rs index ebfda7283..705b387a8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -44,8 +44,7 @@ pub use helper::*; use scrap::{ codec::{Decoder, DecoderCfg}, record::{Recorder, RecorderContext}, - VpxDecoderConfig, VpxVideoCodecId, - ImageFormat, + ImageFormat, VpxDecoderConfig, VpxVideoCodecId, }; use crate::{ @@ -709,6 +708,7 @@ pub struct AudioHandler { audio_stream: Option>, channels: u16, latency_controller: Arc>, + ignore_count: i32, } impl AudioHandler { @@ -811,7 +811,11 @@ impl AudioHandler { .check_audio(frame.timestamp) .not() { - log::debug!("audio frame {} is ignored", frame.timestamp); + self.ignore_count += 1; + if self.ignore_count == 100 { + self.ignore_count = 0; + log::debug!("100 audio frames are ignored"); + } return; } } @@ -944,12 +948,11 @@ impl VideoHandler { } match &vf.union { Some(frame) => { - // windows && flutter_texture_render, fmt is ImageFormat::ABGR - #[cfg(all(target_os = "windows", feature = "flutter_texture_render"))] - let fmt = ImageFormat::ABGR; - #[cfg(not(all(target_os = "windows", feature = "flutter_texture_render")))] - let fmt = ImageFormat::ARGB; - let res = self.decoder.handle_video_frame(frame, fmt, &mut self.rgb); + let res = self.decoder.handle_video_frame( + frame, + (ImageFormat::ARGB, crate::DST_STRIDE_RGBA), + &mut self.rgb, + ); if self.record { self.recorder .lock() @@ -1230,6 +1233,8 @@ impl LoginConfigHandler { option.block_input = BoolOption::No.into(); } else if name == "show-quality-monitor" { config.show_quality_monitor.v = !config.show_quality_monitor.v; + } else if name == "allow_swap_key" { + config.allow_swap_key.v = !config.allow_swap_key.v; } else { let is_set = self .options @@ -1383,6 +1388,8 @@ impl LoginConfigHandler { self.config.disable_clipboard.v } else if name == "show-quality-monitor" { self.config.show_quality_monitor.v + } else if name == "allow_swap_key" { + self.config.allow_swap_key.v } else { !self.get_option(name).is_empty() } @@ -1807,6 +1814,7 @@ pub fn send_mouse( if check_scroll_on_mac(mask, x, y) { mouse_event.modifiers.push(ControlKey::Scroll.into()); } + interface.swap_modifier_mouse(&mut mouse_event); msg_out.set_mouse_event(mouse_event); interface.send(Data::Message(msg_out)); } @@ -2033,6 +2041,7 @@ pub trait Interface: Send + Clone + 'static + Sized { fn is_force_relay(&self) -> bool { self.get_login_config_handler().read().unwrap().force_relay } + fn swap_modifier_mouse(&self, _msg: &mut hbb_common::protos::message::MouseEvent) {} } /// Data used by the client interface. @@ -2229,7 +2238,7 @@ fn get_pk(pk: &[u8]) -> Option<[u8; 32]> { #[inline] fn get_rs_pk(str_base64: &str) -> Option { - if let Ok(pk) = base64::decode(str_base64) { + if let Ok(pk) = crate::decode64(str_base64) { get_pk(&pk).map(|x| sign::PublicKey(x)) } else { None diff --git a/src/common.rs b/src/common.rs index 5f24fd5c3..0b475805d 100644 --- a/src/common.rs +++ b/src/common.rs @@ -39,6 +39,13 @@ pub const CLIPBOARD_INTERVAL: u64 = 333; pub const SYNC_PEER_INFO_DISPLAYS: i32 = 1; +#[cfg(all(target_os = "macos", feature = "flutter_texture_render"))] +// https://developer.apple.com/forums/thread/712709 +// Memory alignment should be multiple of 64. +pub const DST_STRIDE_RGBA: usize = 64; +#[cfg(not(all(target_os = "macos", feature = "flutter_texture_render")))] +pub const DST_STRIDE_RGBA: usize = 1; + // the executable name of the portable version pub const PORTABLE_APPNAME_RUNTIME_ENV_KEY: &str = "RUSTDESK_APPNAME"; @@ -780,3 +787,15 @@ pub fn handle_url_scheme(url: String) { let _ = crate::run_me(vec![url]); } } + +#[inline] +pub fn encode64>(input: T) -> String { + #[allow(deprecated)] + base64::encode(input) +} + +#[inline] +pub fn decode64>(input: T) -> Result, base64::DecodeError> { + #[allow(deprecated)] + base64::decode(input) +} diff --git a/src/core_main.rs b/src/core_main.rs index 2619a1c07..76b630f88 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -8,9 +8,6 @@ use hbb_common::platform::register_breakdown_handler; /// If it returns [`None`], then the process will terminate, and flutter gui will not be started. /// If it returns [`Some`], then the process will continue, and flutter gui will be started. pub fn core_main() -> Option> { - // https://docs.rs/flexi_logger/latest/flexi_logger/error_info/index.html#write - // though async logger more efficient, but it also causes more problems, disable it for now - // let mut _async_logger_holder: Option = None; let mut args = Vec::new(); let mut flutter_args = Vec::new(); let mut i = 0; @@ -76,35 +73,14 @@ pub fn core_main() -> Option> { || (!click_setup && crate::platform::is_elevated(None).unwrap_or(false))); crate::portable_service::client::set_quick_support(_is_quick_support); } - #[cfg(debug_assertions)] - { - use hbb_common::env_logger::*; - init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); - } - #[cfg(not(debug_assertions))] - { - let mut path = hbb_common::config::Config::log_path(); - if args.len() > 0 && args[0].starts_with("--") { - let name = args[0].replace("--", ""); - if !name.is_empty() { - path.push(name); - } - } - use flexi_logger::*; - if let Ok(x) = Logger::try_with_env_or_str("debug") { - // _async_logger_holder = - x.log_to_file(FileSpec::default().directory(path)) - //.write_mode(WriteMode::Async) - .format(opt_format) - .rotate( - Criterion::Age(Age::Day), - Naming::Timestamps, - Cleanup::KeepLogFiles(6), - ) - .start() - .ok(); + let mut log_name = "".to_owned(); + if args.len() > 0 && args[0].starts_with("--") { + let name = args[0].replace("--", ""); + if !name.is_empty() { + log_name = name; } } + hbb_common::init_log(false, &log_name); #[cfg(windows)] if !crate::platform::is_installed() && args.is_empty() @@ -129,7 +105,7 @@ pub fn core_main() -> Option> { { use crate::platform; if args[0] == "--uninstall" { - if let Err(err) = platform::uninstall_me() { + if let Err(err) = platform::uninstall_me(true) { log::error!("Failed to uninstall: {}", err); } return None; @@ -147,7 +123,7 @@ pub fn core_main() -> Option> { hbb_common::allow_err!(platform::update_me()); return None; } else if args[0] == "--reinstall" { - hbb_common::allow_err!(platform::uninstall_me()); + hbb_common::allow_err!(platform::uninstall_me(false)); hbb_common::allow_err!(platform::install_me( "desktopicon startmenu", "".to_owned(), @@ -167,6 +143,10 @@ pub fn core_main() -> Option> { #[cfg(feature = "with_rc")] hbb_common::allow_err!(crate::rc::extract_resources(&args[1])); return None; + } else if args[0] == "--install-cert" { + #[cfg(windows)] + hbb_common::allow_err!(crate::platform::windows::install_cert(&args[1])); + return None; } else if args[0] == "--portable-service" { crate::platform::elevate_or_run_as_system( click_setup, @@ -227,6 +207,13 @@ pub fn core_main() -> Option> { } } return None; + } else if args[0] == "--get-id" { + if crate::platform::is_root() { + println!("{}", crate::ipc::get_id()); + } else { + println!("Permission denied!"); + } + return None; } else if args[0] == "--check-hwcodec-config" { #[cfg(feature = "hwcodec")] scrap::hwcodec::check_config(); diff --git a/src/flutter.rs b/src/flutter.rs index 2f660775f..354e418eb 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -26,8 +26,14 @@ use std::{ sync::{Arc, RwLock}, }; +/// tag "main" for [Desktop Main Page] and [Mobile (Client and Server)] (the mobile don't need multiple windows, only one global event stream is needed) +/// tag "cm" only for [Desktop CM Page] pub(super) const APP_TYPE_MAIN: &str = "main"; +#[cfg(not(any(target_os = "android", target_os = "ios")))] pub(super) const APP_TYPE_CM: &str = "cm"; +#[cfg(any(target_os = "android", target_os = "ios"))] +pub(super) const APP_TYPE_CM: &str = "main"; + pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote"; pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer"; pub(super) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward"; @@ -153,8 +159,14 @@ pub struct FlutterHandler { } #[cfg(feature = "flutter_texture_render")] -pub type FlutterRgbaRendererPluginOnRgba = - unsafe extern "C" fn(texture_rgba: *mut c_void, buffer: *const u8, width: c_int, height: c_int); +pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn( + texture_rgba: *mut c_void, + buffer: *const u8, + len: c_int, + width: c_int, + height: c_int, + dst_rgba_stride: c_int, +); // Video Texture Renderer in Flutter #[cfg(feature = "flutter_texture_render")] @@ -164,7 +176,6 @@ struct VideoRenderer { ptr: usize, width: i32, height: i32, - data_len: usize, on_rgba_func: Option>, } @@ -193,7 +204,6 @@ impl Default for VideoRenderer { ptr: 0, width: 0, height: 0, - data_len: 0, on_rgba_func, } } @@ -205,15 +215,10 @@ impl VideoRenderer { pub fn set_size(&mut self, width: i32, height: i32) { self.width = width; self.height = height; - self.data_len = if width > 0 && height > 0 { - (width * height * 4) as usize - } else { - 0 - }; } pub fn on_rgba(&self, rgba: &Vec) { - if self.ptr == usize::default() || rgba.len() != self.data_len { + if self.ptr == usize::default() { return; } if let Some(func) = &self.on_rgba_func { @@ -221,8 +226,10 @@ impl VideoRenderer { func( self.ptr as _, rgba.as_ptr() as _, + rgba.len() as _, self.width as _, self.height as _, + crate::DST_STRIDE_RGBA as _, ) }; } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 0866ff739..2a3baad95 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -10,7 +10,7 @@ use crate::{ }; use flutter_rust_bridge::{StreamSink, SyncReturn}; use hbb_common::{ - config::{self, LocalConfig, PeerConfig, ONLINE}, + config::{self, LocalConfig, PeerConfig, PeerInfoSerde, ONLINE}, fs, log, message_proto::KeyboardMode, ResultType, @@ -21,6 +21,7 @@ use std::{ ffi::{CStr, CString}, os::raw::c_char, str::FromStr, + time::SystemTime, }; // use crate::hbbs_http::account::AuthResult; @@ -726,10 +727,6 @@ pub fn main_peer_has_password(id: String) -> bool { peer_has_password(id) } -pub fn main_is_in_recent_peers(id: String) -> bool { - PeerConfig::peers().iter().any(|e| e.0 == id) -} - pub fn main_load_recent_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let peers: Vec> = PeerConfig::peers() @@ -756,7 +753,28 @@ pub fn main_load_recent_peers() { pub fn main_load_fav_peers() { if !config::APP_DIR.read().unwrap().is_empty() { let favs = get_fav(); - let peers: Vec> = PeerConfig::peers() + let mut recent = PeerConfig::peers(); + let mut lan = config::LanPeers::load() + .peers + .iter() + .filter(|d| recent.iter().all(|r| r.0 != d.id)) + .map(|d| { + ( + d.id.clone(), + SystemTime::UNIX_EPOCH, + PeerConfig { + info: PeerInfoSerde { + username: d.username.clone(), + hostname: d.hostname.clone(), + platform: d.platform.clone(), + }, + ..Default::default() + }, + ) + }) + .collect(); + recent.append(&mut lan); + let peers: Vec> = recent .into_iter() .filter_map(|(id, _, p)| { if favs.contains(&id) { @@ -840,6 +858,10 @@ pub fn main_get_user_default_option(key: String) -> SyncReturn { SyncReturn(get_user_default_option(key)) } +pub fn main_handle_relay_id(id: String) -> String { + handle_relay_id(id) +} + pub fn session_add_port_forward( id: String, local_port: i32, @@ -1357,7 +1379,7 @@ pub fn send_url_scheme(_url: String) { #[cfg(target_os = "android")] pub mod server_side { - use hbb_common::log; + use hbb_common::{config, log}; use jni::{ objects::{JClass, JString}, sys::jstring, @@ -1370,11 +1392,25 @@ pub mod server_side { pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startServer( env: JNIEnv, _class: JClass, + app_dir: JString, ) { - log::debug!("startServer from java"); + log::debug!("startServer from jvm"); + if let Ok(app_dir) = env.get_string(app_dir) { + *config::APP_DIR.write().unwrap() = app_dir.into(); + } std::thread::spawn(move || start_server(true)); } + #[no_mangle] + pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_startService( + env: JNIEnv, + _class: JClass, + ) { + log::debug!("startService from jvm"); + config::Config::set_option("stop-service".into(), "".into()); + crate::rendezvous_mediator::RendezvousMediator::restart(); + } + #[no_mangle] pub unsafe extern "system" fn Java_com_carriez_flutter_1hbb_MainService_translateLocale( env: JNIEnv, diff --git a/src/ipc.rs b/src/ipc.rs index b1b130340..5f415c6e8 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -251,7 +251,7 @@ pub async fn start(postfix: &str) -> ResultType<()> { pub async fn new_listener(postfix: &str) -> ResultType { let path = Config::ipc_path(postfix); - #[cfg(not(windows))] + #[cfg(not(any(windows, target_os = "android", target_os = "ios")))] check_pid(postfix).await; let mut endpoint = Endpoint::new(path.clone()); match SecurityAttributes::allow_everyone_create() { @@ -541,19 +541,19 @@ fn get_pid_file(postfix: &str) -> String { format!("{}.pid", path) } -#[cfg(not(windows))] +#[cfg(not(any(windows, target_os = "android", target_os = "ios")))] async fn check_pid(postfix: &str) { let pid_file = get_pid_file(postfix); if let Ok(mut file) = File::open(&pid_file) { let mut content = String::new(); file.read_to_string(&mut content).ok(); - let pid = content.parse::().unwrap_or(0); + let pid = content.parse::().unwrap_or(0); if pid > 0 { use hbb_common::sysinfo::{ProcessExt, System, SystemExt}; let mut sys = System::new(); sys.refresh_processes(); if let Some(p) = sys.process(pid.into()) { - if let Some(current) = sys.process((std::process::id() as i32).into()) { + if let Some(current) = sys.process((std::process::id() as usize).into()) { if current.name() == p.name() { // double check with connect if connect(1000, postfix).await.is_ok() { diff --git a/src/keyboard.rs b/src/keyboard.rs index 3f7ed6779..bfff9ff86 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -215,6 +215,7 @@ static mut IS_0X021D_DOWN: bool = false; static mut IS_LEFT_OPTION_DOWN: bool = false; pub fn start_grab_loop() { + std::env::set_var("KEYBOARD_ONLY", "y"); #[cfg(any(target_os = "windows", target_os = "macos"))] std::thread::spawn(move || { let try_handle_keyboard = move |event: Event, key: Key, is_press: bool| -> Option { diff --git a/src/lang.rs b/src/lang.rs index 6e758cc34..e0836ddc9 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,6 +1,6 @@ -use serde_json::{json, value::Value}; use std::ops::Deref; +mod ca; mod cn; mod cs; mod da; @@ -8,69 +8,65 @@ mod de; mod en; mod eo; mod es; +mod fa; mod fr; +mod el; mod hu; mod id; mod it; mod ja; mod ko; +mod kz; mod nl; mod pl; mod ptbr; mod ro; mod ru; mod sk; -mod tr; -mod tw; -mod vn; -mod kz; -mod ua; -mod fa; -mod ca; -mod gr; -mod sv; +mod sl; mod sq; mod sr; +mod sv; mod th; -mod sl; +mod tr; +mod tw; +mod ua; +mod vn; -lazy_static::lazy_static! { - pub static ref LANGS: Value = - json!(vec![ - ("en", "English"), - ("it", "Italiano"), - ("fr", "Français"), - ("de", "Deutsch"), - ("nl", "Nederlands"), - ("cn", "简体中文"), - ("tw", "繁體中文"), - ("pt", "Português"), - ("es", "Español"), - ("hu", "Magyar"), - ("ru", "Русский"), - ("sk", "Slovenčina"), - ("id", "Indonesia"), - ("cs", "Čeština"), - ("da", "Dansk"), - ("eo", "Esperanto"), - ("tr", "Türkçe"), - ("vn", "Tiếng Việt"), - ("pl", "Polski"), - ("ja", "日本語"), - ("ko", "한국어"), - ("kz", "Қазақ"), - ("ua", "Українська"), - ("fa", "فارسی"), - ("ca", "Català"), - ("gr", "Ελληνικά"), - ("sv", "Svenska"), - ("sq", "Shqip"), - ("sr", "Srpski"), - ("th", "ภาษาไทย"), - ("sl", "Slovenščina"), - ("ro", "Română"), - ]); -} +pub const LANGS: &[(&str, &str)] = &[ + ("en", "English"), + ("it", "Italiano"), + ("fr", "Français"), + ("de", "Deutsch"), + ("nl", "Nederlands"), + ("zh-cn", "简体中文"), + ("zh-tw", "繁體中文"), + ("pt", "Português"), + ("es", "Español"), + ("hu", "Magyar"), + ("ru", "Русский"), + ("sk", "Slovenčina"), + ("id", "Indonesia"), + ("cs", "Čeština"), + ("da", "Dansk"), + ("eo", "Esperanto"), + ("tr", "Türkçe"), + ("vn", "Tiếng Việt"), + ("pl", "Polski"), + ("ja", "日本語"), + ("ko", "한국어"), + ("kz", "Қазақ"), + ("ua", "Українська"), + ("fa", "فارسی"), + ("ca", "Català"), + ("el", "Ελληνικά"), + ("sv", "Svenska"), + ("sq", "Shqip"), + ("sr", "Srpski"), + ("th", "ภาษาไทย"), + ("sl", "Slovenščina"), + ("ro", "Română"), +]; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn translate(name: String) -> String { @@ -83,7 +79,12 @@ pub fn translate_locale(name: String, locale: &str) -> String { if lang.is_empty() { // zh_CN on Linux, zh-Hans-CN on mac, zh_CN_#Hans on Android if locale.starts_with("zh") { - lang = (if locale.contains("tw") { "tw" } else { "cn" }).to_owned(); + lang = (if locale.contains("tw") { + "zh-tw" + } else { + "zh-cn" + }) + .to_owned(); } } if lang.is_empty() { @@ -97,18 +98,17 @@ pub fn translate_locale(name: String, locale: &str) -> String { let lang = lang.to_lowercase(); let m = match lang.as_str() { "fr" => fr::T.deref(), - "cn" => cn::T.deref(), + "zh-cn" => cn::T.deref(), "it" => it::T.deref(), "nl" => nl::T.deref(), "tw" => tw::T.deref(), - "de" => de::T.deref(), - "nl" => nl::T.deref(), + "zh-tw" => tw::T.deref(), + "de" => de::T.deref(), "es" => es::T.deref(), "hu" => hu::T.deref(), "ru" => ru::T.deref(), "eo" => eo::T.deref(), "id" => id::T.deref(), - "ptbr" => ptbr::T.deref(), "br" => ptbr::T.deref(), "pt" => ptbr::T.deref(), "tr" => tr::T.deref(), @@ -123,7 +123,7 @@ pub fn translate_locale(name: String, locale: &str) -> String { "ua" => ua::T.deref(), "fa" => fa::T.deref(), "ca" => ca::T.deref(), - "gr" => gr::T.deref(), + "el" => el::T.deref(), "sv" => sv::T.deref(), "sq" => sq::T.deref(), "sr" => sr::T.deref(), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 71aa39337..93d9c76fe 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Avís"), ("Login screen using Wayland is not supported", "La pantalla d'inici de sessió amb Wayland no és compatible"), ("Reboot required", "Cal reiniciar"), - ("Unsupported display server ", "Servidor de visualització no compatible"), + ("Unsupported display server", "Servidor de visualització no compatible"), ("x11 expected", "x11 necessari"), ("Port", ""), ("Settings", "Ajustaments"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Nota"), ("Connection", "connexió"), ("Share Screen", "Compartir pantalla"), - ("CLOSE", "TANCAR"), - ("OPEN", "OBRIR"), ("Chat", "Xat"), ("Total", "Total"), ("items", "ítems"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Habilitar la captura de pantalla iniciarà el servei automàticament, i permetrà que altres dispositius sol·licitin una connexió des d'aquest dispositiu."), ("android_stop_service_tip", "Tancar el servei tancarà totes les connexions establertes."), ("android_version_audio_tip", "La versión actual de Android no admet la captura d'àudio, actualizi a Android 10 o superior."), - ("android_start_service_tip", "Toqui el permís [Iniciar servei] o OBRIR [Captura de pantalla] per iniciar el servei d'ús compartit de pantalla."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Compte"), ("Overwrite", "Sobreescriure"), ("This file exists, skip or overwrite this file?", "Aquest arxiu ja existeix, ometre o sobreescriure l'arxiu?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Mantenir RustDesk com a servei en segon pla"), ("Ignore Battery Optimizations", "Ignorar optimizacions de la bateria"), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Connexió no disponible"), ("Legacy mode", "Mode heretat"), ("Map mode", "Mode mapa"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Seguretat"), ("Theme", "Tema"), ("Dark Theme", "Tema Fosc"), + ("Light Theme", ""), ("Dark", "Fosc"), ("Light", "Clar"), ("Follow System", "Tema del sistema"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 818e63203..bdc2257b3 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -38,7 +38,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop service", "停止服务"), ("Change ID", "更改 ID"), ("Your new ID", "你的新 ID"), - ("length %min% to %max%", "长度在 %min 与 %max 之间"), + ("length %min% to %max%", "长度在 %min% 与 %max% 之间"), ("starts with a letter", "以字母开头"), ("allowed characters", "使用允许的字符"), ("id_change_tip", "只可以使用字母 a-z, A-Z, 0-9, _ (下划线)。首字母必须是 a-z, A-Z。长度在 6 与 16 之间。"), @@ -137,7 +137,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Failed to connect to rendezvous server", "连接注册服务器失败"), ("Please try later", "请稍后再试"), ("Remote desktop is offline", "远程电脑处于离线状态"), - ("Key mismatch", "密钥不匹配"), + ("Key mismatch", "Key 不匹配"), ("Timeout", "连接超时"), ("Failed to connect to relay server", "无法连接到中继服务器"), ("Failed to connect via rendezvous server", "无法通过注册服务器建立连接"), @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "警告"), ("Login screen using Wayland is not supported", "不支持使用 Wayland 登录界面"), ("Reboot required", "重启后才能生效"), - ("Unsupported display server ", "不支持当前显示服务器"), + ("Unsupported display server", "不支持当前显示服务器"), ("x11 expected", "请切换到 x11"), ("Port", "端口"), ("Settings", "设置"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "备注"), ("Connection", "连接"), ("Share Screen", "共享屏幕"), - ("CLOSE", "关闭"), - ("OPEN", "开启"), ("Chat", "聊天消息"), ("Total", "总计"), ("items", "个项目"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "开启录屏权限将自动开启服务,允许其他设备向此设备请求建立连接。"), ("android_stop_service_tip", "关闭服务将自动关闭所有已建立的连接。"), ("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓 10 或更高。"), - ("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", "对于已建立的连接,权限可能不会立即发生改变,除非重新建立连接。"), ("Account", "账户"), ("Overwrite", "覆盖"), ("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "保持 RustDesk 后台服务"), ("Ignore Battery Optimizations", "忽略电池优化"), ("android_open_battery_optimizations_tip", "如需关闭此功能,请在接下来的 RustDesk 应用设置页面中,找到并进入 [电源] 页面,取消勾选 [不受限制]"), + ("Start on Boot", "开机自启动"), + ("Start the screen sharing service on boot, requires special permissions", "开机自动启动屏幕共享服务,此功能需要一些特殊权限。"), ("Connection not allowed", "对方不允许连接"), ("Legacy mode", "传统模式"), ("Map mode", "1:1 传输"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "安全"), ("Theme", "主题"), ("Dark Theme", "暗黑主题"), + ("Light Theme", "明亮主题"), ("Dark", "黑暗"), ("Light", "明亮"), ("Follow System", "跟随系统"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", "重连"), ("Codec", "编解码"), ("Resolution", "分辨率"), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", "安装虚拟显示器驱动,以便在没有连接显示器的情况下启动虚拟显示器进行控制。"), + ("confirm_idd_driver_tip", "安装虚拟显示器驱动的选项已勾选。请注意,测试证书将被安装以信任虚拟显示器驱动。测试证书仅会用于信任Rustdesk的驱动。"), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index be0ffa7f4..651043850 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Upozornení"), ("Login screen using Wayland is not supported", "Přihlašovací obrazovka prostřednictvím Wayland není podporována"), ("Reboot required", "Je třeba restartovat"), - ("Unsupported display server ", "Nepodporovaný zobrazovací server"), + ("Unsupported display server", "Nepodporovaný zobrazovací server"), ("x11 expected", "očekávány x11"), ("Port", ""), ("Settings", "Nastavení"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Poznámka"), ("Connection", "Připojení"), ("Share Screen", "Nasdílet obrazovku"), - ("CLOSE", "ZAVŘÍT"), - ("OPEN", "OTEVŘÍT"), ("Chat", "Chat"), ("Total", "Celkem"), ("items", "Položek"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Zapnutí „Zachytávání obsahu obrazovky“ automaticky spustí službu, což umožní ostatním zařízením žádat o připojení k vašemu zařízení."), ("android_stop_service_tip", "Zastavení služby automaticky ukončí veškerá navázaná spojení."), ("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku – přejděte na Android 10 nebo novější."), - ("android_start_service_tip", "Službu pro sdílení obrazovky spustíte klepnutím na [Spustit službu] nebo UDĚLTE pověření pro [Zachytávání obsahu obrazovky]."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "Přepsat"), ("This file exists, skip or overwrite this file?", "Tento soubor existuje – přeskočit ho nebo přepsat?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", ""), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 150a57715..0235df8fe 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Advarsel"), ("Login screen using Wayland is not supported", "Registreringsskærm med Wayland understøttes ikke"), ("Reboot required", "Genstart krævet"), - ("Unsupported display server ", "Ikke-understøttet displayserver"), + ("Unsupported display server", "Ikke-understøttet displayserver"), ("x11 expected", "X11 Forventet"), ("Port", "Port"), ("Settings", "Indstillinger"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Note"), ("Connection", "Forbindelse"), ("Share Screen", "Del skærmen"), - ("CLOSE", "LUK"), - ("OPEN", "ÅBEN"), ("Chat", "Chat"), ("Total", "Total"), ("items", "artikel"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Ved at tænde for skærmoptagelsen startes tjenesten automatisk, så andre enheder kan anmode om en forbindelse fra denne enhed."), ("android_stop_service_tip", "Ved at lukke tjenesten lukkes alle fremstillede forbindelser automatisk."), ("android_version_audio_tip", "Den aktuelle Android -version understøtter ikke lydoptagelse, skal du opdatere om Android 10 eller højere."), - ("android_start_service_tip", "Tryk på [Start Service] eller åbn autorisationen [skærmoptagelse] for at starte skærmudgivelsen."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Konto"), ("Overwrite", "Overskriv"), ("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Behold RustDesk baggrundstjeneste"), ("Ignore Battery Optimizations", "Ignorer betteri optimeringer"), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Forbindelse ikke tilladt"), ("Legacy mode", "Bagudkompatibilitetstilstand"), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Sikkerhed"), ("Theme", "Thema"), ("Dark Theme", "Mørk Tema"), + ("Light Theme", ""), ("Dark", "Mørk"), ("Light", "Lys"), ("Follow System", "Følg System"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index c9c25df2b..09fad80eb 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -7,7 +7,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Password", "Passwort"), ("Ready", "Bereit"), ("Established", "Verbunden"), - ("connecting_status", "Verbinden mit dem RustDesk-Netzwerk..."), + ("connecting_status", "Verbinden mit dem RustDesk-Netzwerk …"), ("Enable Service", "Vermittlungsdienst aktivieren"), ("Start Service", "Vermittlungsdienst starten"), ("Service is running", "Vermittlungsdienst aktiv"), @@ -76,12 +76,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Connection Error", "Verbindungsfehler"), ("Error", "Fehler"), ("Reset by the peer", "Verbindung wurde von der Gegenstelle zurückgesetzt."), - ("Connecting...", "Verbindung wird hergestellt..."), - ("Connection in progress. Please wait.", "Die Verbindung wird hergestellt. Bitte warten..."), + ("Connecting...", "Verbindung wird hergestellt …"), + ("Connection in progress. Please wait.", "Die Verbindung wird hergestellt. Bitte warten …"), ("Please try 1 minute later", "Bitte versuchen Sie es später erneut"), ("Login Error", "Anmeldefehler"), ("Successful", "Erfolgreich"), - ("Connected, waiting for image...", "Verbindung hergestellt. Warten auf Bild..."), + ("Connected, waiting for image...", "Verbindung hergestellt. Warte auf anderen Bildschirm …"), ("Name", "Name"), ("Type", "Typ"), ("Modified", "Geändert"), @@ -125,7 +125,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Good image quality", "Hohe Bildqualität"), ("Balanced", "Ausgeglichen"), ("Optimize reaction time", "Geschwindigkeit"), - ("Custom", "Individuell"), + ("Custom", "Benutzerdefiniert"), ("Show remote cursor", "Entfernten Cursor anzeigen"), ("Show quality monitor", "Qualitätsüberwachung anzeigen"), ("Disable clipboard", "Zwischenablage deaktivieren"), @@ -152,7 +152,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Configure", "Konfigurieren"), ("config_acc", "Um Ihren PC aus der Ferne zu steuern, müssen Sie RustDesk Zugriffsrechte erteilen."), ("config_screen", "Um aus der Ferne auf Ihren PC zugreifen zu können, müssen Sie RustDesk die Berechtigung \"Bildschirmaufnahme\" erteilen."), - ("Installing ...", "Installieren..."), + ("Installing ...", "Wird installiert …"), ("Install", "Installieren"), ("Installation", "Installation"), ("Installation Path", "Installationspfad"), @@ -161,10 +161,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("agreement_tip", "Durch die Installation akzeptieren Sie die Lizenzvereinbarung."), ("Accept and Install", "Akzeptieren und Installieren"), ("End-user license agreement", "Lizenzvereinbarung für Endbenutzer"), - ("Generating ...", "Wird generiert..."), + ("Generating ...", "Wird generiert …"), ("Your installation is lower version.", "Ihre Version ist veraltet."), ("not_close_tcp_tip", "Schließen Sie dieses Fenster nicht, solange Sie den Tunnel benutzen."), - ("Listening ...", "Lauschen..."), + ("Listening ...", "Lauschen …"), ("Remote Host", "Entfernter PC"), ("Remote Port", "Entfernter Port"), ("Action", "Aktion"), @@ -190,7 +190,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relayed and unencrypted connection", "Vermittelte und unverschlüsselte Verbindung"), ("Enter Remote ID", "Remote-ID eingeben"), ("Enter your password", "Geben Sie Ihr Passwort ein"), - ("Logging in...", "Anmelden..."), + ("Logging in...", "Anmelden …"), ("Enable RDP session sharing", "RDP-Sitzungsfreigabe aktivieren"), ("Auto Login", "Automatisch anmelden (nur gültig, wenn Sie \"Nach Sitzungsende sperren\" aktiviert haben)"), ("Enable Direct IP Access", "Direkten IP-Zugang aktivieren"), @@ -204,16 +204,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Warnung"), ("Login screen using Wayland is not supported", "Anmeldebildschirm mit Wayland wird nicht unterstützt."), ("Reboot required", "Neustart erforderlich"), - ("Unsupported display server ", "Nicht unterstützter Anzeigeserver"), + ("Unsupported display server", "Nicht unterstützter Anzeigeserver"), ("x11 expected", "X11 erwartet"), ("Port", "Port"), ("Settings", "Einstellungen"), ("Username", "Benutzername"), ("Invalid port", "Ungültiger Port"), - ("Closed manually by the peer", "Von der Gegenstelle manuell geschlossen"), + ("Closed manually by the peer", "Von der Gegenstelle manuell geschlossen."), ("Enable remote configuration modification", "Änderung der Konfiguration aus der Ferne zulassen"), ("Run without install", "Ohne Installation ausführen"), - ("Connect via relay", "Verbindung über Relay-Server"), + ("Connect via relay", "Über Relay-Server verbinden"), ("Always connect via relay", "Immer über Relay-Server verbinden"), ("whitelist_tip", "Nur IPs auf der Whitelist können zugreifen."), ("Login", "Anmelden"), @@ -221,17 +221,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remember me", "Login speichern"), ("Trust this device", "Diesem Gerät vertrauen"), ("Verification code", "Verifizierungscode"), - ("verification_tip", "Es wurde ein neues Gerät erkannt und ein Verifizierungscode an die registrierte E-Mail-Adresse gesendet. Geben Sie den Verifizierungscode ein, um sich weiter anzumelden."), + ("verification_tip", "Es wurde ein neues Gerät erkannt und ein Verifizierungscode an die registrierte E-Mail-Adresse gesendet. Geben Sie den Verifizierungscode ein, um sich erneut anzumelden."), ("Logout", "Abmelden"), ("Tags", "Schlagworte"), - ("Search ID", "Suche ID"), + ("Search ID", "ID suchen"), ("whitelist_sep", "Getrennt durch Komma, Semikolon, Leerzeichen oder Zeilenumbruch"), ("Add ID", "ID hinzufügen"), ("Add Tag", "Stichwort hinzufügen"), ("Unselect all tags", "Alle Stichworte abwählen"), ("Network error", "Netzwerkfehler"), - ("Username missed", "Benutzernamen vergessen"), - ("Password missed", "Passwort vergessen"), + ("Username missed", "Benutzername fehlt"), + ("Password missed", "Passwort fehlt"), ("Wrong credentials", "Falsche Anmeldedaten"), ("Edit Tag", "Schlagwort bearbeiten"), ("Unremember Password", "Gespeichertes Passwort löschen"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Hinweis"), ("Connection", "Verbindung"), ("Share Screen", "Bildschirm freigeben"), - ("CLOSE", "SCHLIEẞEN"), - ("OPEN", "ÖFFNEN"), ("Chat", "Chat"), ("Total", "Gesamt"), ("items", "Einträge"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Durch das Aktivieren der Bildschirmfreigabe wird der Dienst automatisch gestartet, sodass andere Geräte dieses Android-Gerät steuern können."), ("android_stop_service_tip", "Durch das Deaktivieren des Dienstes werden automatisch alle hergestellten Verbindungen getrennt."), ("android_version_audio_tip", "Ihre Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher, falls möglich."), - ("android_start_service_tip", "Tippen Sie auf \"Dienst aktivieren\" oder aktivieren Sie die Berechtigung \"Bildschirmaufnahme\", um den Bildschirmfreigabedienst zu starten."), + ("android_start_service_tip", "Tippen Sie auf \"Vermittlungsdienst starten\" oder aktivieren Sie die Berechtigung \"Bildschirmaufnahme\", um den Bildschirmfreigabedienst zu starten."), + ("android_permission_may_not_change_tip", "Die Berechtigungen für bestehende Verbindungen können nicht sofort geändert werden, bis die Verbindung wiederhergestellt ist."), ("Account", "Konto"), ("Overwrite", "Überschreiben"), ("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"), @@ -299,7 +298,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Help", "Hilfe"), ("Failed", "Fehlgeschlagen"), ("Succeeded", "Erfolgreich"), - ("Someone turns on privacy mode, exit", "Jemand hat den Datenschutzmodus aktiviert, beende..."), + ("Someone turns on privacy mode, exit", "Jemand hat den Datenschutzmodus aktiviert, wird beendet …"), ("Unsupported", "Nicht unterstützt"), ("Peer denied", "Die Gegenstelle hat die Verbindung abgelehnt."), ("Please install plugins", "Bitte installieren Sie Plugins"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk im Hintergrund ausführen"), ("Ignore Battery Optimizations", "Akkuoptimierung ignorieren"), ("android_open_battery_optimizations_tip", "Möchten Sie die Einstellungen zur Akkuoptimierung öffnen?"), + ("Start on Boot", "Beim Booten starten"), + ("Start the screen sharing service on boot, requires special permissions", "Bildschirmfreigabedienst beim Booten starten, erfordert zusätzliche Berechtigungen"), ("Connection not allowed", "Verbindung abgelehnt"), ("Legacy mode", "Kompatibilitätsmodus"), ("Map mode", "Kartenmodus"), @@ -338,7 +339,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Show Menubar", "Menüleiste anzeigen"), ("Hide Menubar", "Menüleiste ausblenden"), ("Direct Connection", "Direkte Verbindung"), - ("Relay Connection", "Relaisverbindung"), + ("Relay Connection", "Relay-Verbindung"), ("Secure Connection", "Sichere Verbindung"), ("Insecure Connection", "Unsichere Verbindung"), ("Scale original", "Keine Skalierung"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Sicherheit"), ("Theme", "Farbgebung"), ("Dark Theme", "Dunkle Farbgebung"), + ("Light Theme", "Helle Farbgebung"), ("Dark", "Dunkel"), ("Light", "Hell"), ("Follow System", "Systemstandard"), @@ -379,8 +381,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Deny LAN Discovery", "LAN-Erkennung verbieten"), ("Write a message", "Nachricht schreiben"), ("Prompt", "Meldung"), - ("Please wait for confirmation of UAC...", "Bitte auf die Bestätigung des Nutzers warten..."), - ("elevated_foreground_window_tip", "Das aktuell geöffnete Fenster des ferngesteuerten Computers benötigt höhere Rechte. Deshalb ist es derzeit nicht möglich, die Maus und die Tastatur zu verwenden. Bitten Sie den Nutzer, dessen Computer Sie fernsteuern, das Fenster zu minimieren oder die Rechte zu erhöhen. Um dieses Problem zukünftig zu vermeiden, wird empfohlen, die Software auf dem ferngesteuerten Computer zu installieren."), + ("Please wait for confirmation of UAC...", "Bitte auf die Bestätigung des Nutzers warten …"), + ("elevated_foreground_window_tip", "Das aktuell geöffnete Fenster des ferngesteuerten Computers erfordert höhere Rechte. Deshalb ist es derzeit nicht möglich, die Maus und die Tastatur zu verwenden. Bitten Sie den Nutzer, dessen Computer Sie fernsteuern, das Fenster zu minimieren oder die Rechte zu erhöhen. Um dieses Problem zukünftig zu vermeiden, wird empfohlen, die Software auf dem ferngesteuerten Computer zu installieren."), ("Disconnected", "Verbindung abgebrochen"), ("Other", "Weitere Einstellungen"), ("Confirm before closing multiple tabs", "Nachfragen, wenn mehrere Tabs geschlossen werden"), @@ -395,16 +397,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "Dieser PC"), ("or", "oder"), ("Continue with", "Fortfahren mit"), - ("Elevate", "Erheben"), + ("Elevate", "Zugriff gewähren"), ("Zoom cursor", "Cursor vergrößern"), ("Accept sessions via password", "Sitzung mit Passwort bestätigen"), ("Accept sessions via click", "Sitzung mit einem Klick bestätigen"), ("Accept sessions via both", "Sitzung mit Klick und Passwort bestätigen"), - ("Please wait for the remote side to accept your session request...", "Bitte warten Sie, bis die Gegenseite Ihre Sitzungsanfrage akzeptiert hat..."), + ("Please wait for the remote side to accept your session request...", "Bitte warten Sie, bis die Gegenseite Ihre Sitzungsanfrage akzeptiert hat …"), ("One-time Password", "Einmalpasswort"), ("Use one-time password", "Einmalpasswort verwenden"), ("One-time password length", "Länge des Einmalpassworts"), - ("Request access to your device", "Zugriff zu Ihrem Gerät erbitten"), + ("Request access to your device", "Zugriff auf Ihr Gerät anfordern"), ("Hide connection management window", "Fenster zur Verwaltung der Verbindung verstecken"), ("hide_cm_tip", "Dies ist nur möglich, wenn der Zugriff über ein permanentes Passwort erfolgt."), ("wayland_experiment_tip", "Die Unterstützung von Wayland ist nur experimentell. Bitte nutzen Sie X11, wenn Sie einen unbeaufsichtigten Zugriff benötigen."), @@ -424,7 +426,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Wait", "Warten"), ("Elevation Error", "Berechtigungsfehler"), ("Ask the remote user for authentication", "Den entfernten Benutzer zur Authentifizierung auffordern"), - ("Choose this if the remote account is administrator", "Wählen Sie dies, wenn das entfernte Konto Administrator ist"), + ("Choose this if the remote account is administrator", "Wählen Sie dies, wenn das entfernte Konto Administrator ist."), ("Transmit the username and password of administrator", "Übermitteln Sie den Benutzernamen und das Passwort des Administrators"), ("still_click_uac_tip", "Der entfernte Benutzer muss immer noch im UAC-Fenster von RustDesk auf OK klicken."), ("Request Elevation", "Erhöhte Rechte anfordern"), @@ -439,7 +441,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Medium", "Mittel"), ("Strong", "Stark"), ("Switch Sides", "Seiten wechseln"), - ("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, ob Sie Ihren Desktop freigeben möchten."), + ("Please confirm if you want to share your desktop?", "Bitte bestätigen Sie, wenn Sie Ihren Desktop freigeben möchten."), ("Display", "Anzeige"), ("Default View Style", "Standard-Ansichtsstil"), ("Default Scroll Style", "Standard-Scroll-Stil"), @@ -454,7 +456,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop voice call", "Sprachanruf beenden"), ("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."), ("Reconnect", "Erneut verbinden"), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Codec", "Codec"), + ("Resolution", "Auflösung"), + ("No transfers in progress", "Keine Übertragungen im Gange"), + ("Set one-time password length", "Länge des Einmalpassworts festlegen"), + ("idd_driver_tip", "Installieren Sie den virtuellen Anzeigetreiber, der verwendet wird, wenn Sie keine physischen Anzeigen haben."), + ("confirm_idd_driver_tip", "Die Option zur Installation des virtuellen Anzeigetreibers ist aktiviert. Beachten Sie, dass ein Testzertifikat installiert wird, um dem virtuellen Anzeigetreiber zu vertrauen. Dieses Testzertifikat wird nur verwendet, um Rustdesk-Treibern zu vertrauen."), + ("RDP Settings", "RDP-Einstellungen"), + ("Sort by", "Sortieren nach"), + ("New Connection", "Neue Verbindung"), + ("Restore", "Verkleinern"), + ("Minimize", "Minimieren"), + ("Maximize", "Maximieren"), + ("Your Device", "Ihr Gerät"), + ("empty_recent_tip", "Ups, keine aktuellen Sitzungen!\nZeit, eine neue zu planen."), + ("empty_favorite_tip", "Noch keine favorisierte Gegenstelle?\nLassen Sie uns jemanden finden, mit dem wir uns verbinden können und fügen Sie ihn zu Ihren Favoriten hinzu!"), + ("empty_lan_tip", "Oh nein, es sieht so aus, als hätten wir noch keine Gegenstelle entdeckt."), + ("empty_address_book_tip", "Oh je, es scheint, dass in Ihrem Adressbuch derzeit keine Gegenstellen aufgeführt sind."), + ("eg: admin", "z. B.: admin"), + ("Empty Username", "Leerer Benutzername"), + ("Empty Password", "Leeres Passwort"), + ("Me", "Ich"), + ].iter().cloned().collect(); } diff --git a/src/lang/gr.rs b/src/lang/el.rs similarity index 81% rename from src/lang/gr.rs rename to src/lang/el.rs index c18e6c07b..923d4b64e 100644 --- a/src/lang/gr.rs +++ b/src/lang/el.rs @@ -12,7 +12,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start Service", "Έναρξη υπηρεσίας"), ("Service is running", "Η υπηρεσία εκτελείται"), ("Service is not running", "Η υπηρεσία δεν εκτελείται"), - ("not_ready_status", "Δεν είναι έτοιμο. Ελέγξτε τη σύνδεσή σας"), + ("not_ready_status", "Δεν είναι έτοιμο. Ελέγξτε τη σύνδεσή σας στο δίκτυο"), ("Control Remote Desktop", "Έλεγχος απομακρυσμένου σταθμού εργασίας"), ("Transfer File", "Μεταφορά αρχείου"), ("Connect", "Σύνδεση"), @@ -24,7 +24,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Refresh random password", "Νέος τυχαίος κωδικός πρόσβασης"), ("Set your own password", "Ορίστε τον δικό σας κωδικό πρόσβασης"), ("Enable Keyboard/Mouse", "Ενεργοποίηση πληκτρολογίου/ποντικιού"), - ("Enable Clipboard", "Ενεργοποίηση Προχείρου"), + ("Enable Clipboard", "Ενεργοποίηση προχείρου"), ("Enable File Transfer", "Ενεργοποίηση μεταφοράς αρχείων"), ("Enable TCP Tunneling", "Ενεργοποίηση TCP Tunneling"), ("IP Whitelisting", "Λίστα επιτρεπόμενων IP"), @@ -37,19 +37,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "Το πρόχειρο είναι κενό"), ("Stop service", "Διακοπή υπηρεσίας"), ("Change ID", "Αλλαγή αναγνωριστικού ID"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "Το νέο σας ID"), + ("length %min% to %max%", "μέγεθος από %min% έως %max%"), + ("starts with a letter", "ξεκινά με γράμμα"), + ("allowed characters", "επιτρεπόμενοι χαρακτήρες"), ("id_change_tip", "Επιτρέπονται μόνο οι χαρακτήρες a-z, A-Z, 0-9 και _ (υπογράμμιση). Το πρώτο γράμμα πρέπει να είναι a-z, A-Z και το μήκος πρέπει να είναι μεταξύ 6 και 16 χαρακτήρων."), ("Website", "Ιστότοπος"), ("About", "Πληροφορίες"), - ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Slogan_tip", "Φτιαγμένο με πάθος - σε έναν κόσμο που βυθίζεται στο χάος!"), + ("Privacy Statement", "Πολιτική απορρήτου"), ("Mute", "Σίγαση"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), + ("Build Date", "Ημερομηνία δημιουργίας"), + ("Version", "Έκδοση"), + ("Home", "Αρχική"), ("Audio Input", "Είσοδος ήχου"), ("Enhancements", "Βελτιώσεις"), ("Hardware Codec", "Κωδικοποιητής υλικού"), @@ -60,14 +60,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("invalid_http", "Πρέπει να ξεκινά με http:// ή https://"), ("Invalid IP", "Μη έγκυρη διεύθυνση IP"), ("Invalid format", "Μη έγκυρη μορφή"), - ("server_not_support", "Αυτή η δυνατότητα δεν υποστηρίζεται ακόμη από τον διακομιστή"), + ("server_not_support", "Αυτή η δυνατότητα δεν υποστηρίζεται από τον διακομιστή"), ("Not available", "Μη διαθέσιμο"), ("Too frequent", "Πολύ συχνά"), ("Cancel", "Ακύρωση"), ("Skip", "Παράλειψη"), ("Close", "Κλείσιμο"), ("Retry", "Δοκίμασε ξανά"), - ("OK", "Εντάξει"), + ("OK", "ΟΚ"), ("Password Required", "Απαιτείται κωδικός πρόσβασης"), ("Please enter your password", "Παρακαλώ εισάγετε τον κωδικό πρόσβασης"), ("Remember password", "Απομνημόνευση κωδικού πρόσβασης"), @@ -108,7 +108,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Do this for all conflicts", "Κάνε αυτό για όλες τις διενέξεις"), ("This is irreversible!", "Αυτό είναι μη αναστρέψιμο!"), ("Deleting", "Διαγραφή"), - ("files", "αρχεία"), + ("files", "αρχείων"), ("Waiting", "Αναμονή"), ("Finished", "Ολοκληρώθηκε"), ("Speed", "Ταχύτητα"), @@ -120,17 +120,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Original", "Πρωτότυπο"), ("Shrink", "Συρρίκνωση"), ("Stretch", "Προσαρμογή"), - ("Scrollbar", "Γραμμή κύλισης"), + ("Scrollbar", "Μπάρα κύλισης"), ("ScrollAuto", "Αυτόματη κύλιση"), ("Good image quality", "Καλή ποιότητα εικόνας"), - ("Balanced", "Ισορροπημένο"), - ("Optimize reaction time", "Βελτιστοποίηση χρόνου αντίδρασης"), - ("Custom", "Προσαρμογή ποιότητας εικόνας"), + ("Balanced", "Ισορροπημένη"), + ("Optimize reaction time", "Βελτιστοποίηση απόκρισης"), + ("Custom", "Προσαρμοσμένη ποιότητας εικόνας"), ("Show remote cursor", "Εμφάνιση απομακρυσμένου κέρσορα"), ("Show quality monitor", "Εμφάνιση παρακολούθησης ποιότητας σύνδεσης"), ("Disable clipboard", "Απενεργοποίηση προχείρου"), ("Lock after session end", "Κλείδωμα μετά το τέλος της συνεδρίας"), - ("Insert", ""), + ("Insert", "Εισαγωγή"), ("Insert Lock", "Κλείδωμα απομακρυσμένου σταθμού"), ("Refresh", "Ανανέωση"), ("ID does not exist", "Το αναγνωριστικό ID δεν υπάρχει"), @@ -139,22 +139,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remote desktop is offline", "Ο απομακρυσμένος σταθμός εργασίας είναι εκτός σύνδεσης"), ("Key mismatch", "Μη έγκυρο κλειδί"), ("Timeout", "Τέλος χρόνου"), - ("Failed to connect to relay server", "Αποτυχία σύνδεσης με διακομιστή αναμετάδοσης"), + ("Failed to connect to relay server", "Αποτυχία σύνδεσης με τον διακομιστή αναμετάδοσης"), ("Failed to connect via rendezvous server", "Απέτυχε η σύνδεση μέσω διακομιστή"), ("Failed to connect via relay server", "Απέτυχε η σύνδεση μέσω διακομιστή αναμετάδοσης"), ("Failed to make direct connection to remote desktop", "Απέτυχε η απευθείας σύνδεση με τον απομακρυσμένο σταθμό εργασίας"), - ("Set Password", "Ορίστε κωδικό"), + ("Set Password", "Ορίστε κωδικό πρόσβασης"), ("OS Password", "Κωδικός πρόσβασης λειτουργικού συστήματος"), ("install_tip", "Λόγω UAC, το RustDesk ενδέχεται να μην λειτουργεί σωστά σε ορισμένες περιπτώσεις. Για να αποφύγετε το UAC, κάντε κλικ στο κουμπί παρακάτω για να εγκαταστήσετε το RustDesk στο σύστημα"), - ("Click to upgrade", "Κάντε κλικ για αναβάθμιση"), - ("Click to download", "Κάντε κλικ για λήψη"), - ("Click to update", "Κάντε κλικ για ενημέρωση"), + ("Click to upgrade", "Πιέστε για αναβάθμιση"), + ("Click to download", "Πιέστε για λήψη"), + ("Click to update", "Πιέστε για ενημέρωση"), ("Configure", "Διαμόρφωση"), ("config_acc", "Για τον απομακρυσμένο έλεγχο του υπολογιστή σας, πρέπει να εκχωρήσετε δικαιώματα πρόσβασης στο RustDesk."), ("config_screen", "Για να αποκτήσετε απομακρυσμένη πρόσβαση στον υπολογιστή σας, πρέπει να εκχωρήσετε το δικαίωμα RustDesk \"Screen Capture\"."), - ("Installing ...", "Εγκατάσταση ..."), + ("Installing ...", "Γίνεται εγκατάσταση ..."), ("Install", "Εγκατάσταση"), - ("Installation", "Εγκατάσταση"), + ("Installation", "Η εγκατάσταση"), ("Installation Path", "Διαδρομή εγκατάστασης"), ("Create start menu shortcuts", "Δημιουργία συντομεύσεων μενού έναρξης"), ("Create desktop icon", "Δημιουργία εικονιδίου επιφάνειας εργασίας"), @@ -182,7 +182,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Να επιτρέπεται η χρήση πληκτρολογίου και ποντικιού"), ("Allow using clipboard", "Να επιτρέπεται η χρήση του προχείρου"), ("Allow hearing sound", "Να επιτρέπεται η αναπαραγωγή ήχου"), - ("Allow file copy and paste", "Να επιτρέπεται η αντιγραφή και επικόλληση αρχείου"), + ("Allow file copy and paste", "Να επιτρέπεται η αντιγραφή και επικόλληση αρχείων"), ("Connected", "Συνδεδεμένο"), ("Direct and encrypted connection", "Άμεση και κρυπτογραφημένη σύνδεση"), ("Relayed and encrypted connection", "Κρυπτογραφημένη σύνδεση με αναμετάδοση"), @@ -190,21 +190,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relayed and unencrypted connection", "Μη κρυπτογραφημένη σύνδεση με αναμετάδοση"), ("Enter Remote ID", "Εισαγωγή απομακρυσμένου ID"), ("Enter your password", "Εισάγετε τον κωδικό σας"), - ("Logging in...", "Σύνδεση..."), + ("Logging in...", "Γίνεται σύνδεση..."), ("Enable RDP session sharing", "Ενεργοποίηση κοινής χρήσης RDP"), ("Auto Login", "Αυτόματη είσοδος"), ("Enable Direct IP Access", "Ενεργοποίηση άμεσης πρόσβασης IP"), ("Rename", "Μετονομασία"), ("Space", "Χώρος"), ("Create Desktop Shortcut", "Δημιουργία συντόμευσης στην επιφάνεια εργασίας"), - ("Change Path", "Αλλαγή διαδρομής"), + ("Change Path", "Αλλαγή διαδρομής δίσκου"), ("Create Folder", "Δημιουργία φακέλου"), ("Please enter the folder name", "Παρακαλώ εισάγετε το όνομα του φακέλου"), ("Fix it", "Επιδιόρθωσε το"), ("Warning", "Προειδοποίηση"), ("Login screen using Wayland is not supported", "Η οθόνη εισόδου με χρήση του Wayland δεν υποστηρίζεται"), ("Reboot required", "Απαιτείται επανεκκίνηση"), - ("Unsupported display server ", "Μη υποστηριζόμενος διακομιστής εμφάνισης "), + ("Unsupported display server", "Μη υποστηριζόμενος διακομιστής εμφάνισης "), ("x11 expected", "απαιτείται X11"), ("Port", "Θύρα"), ("Settings", "Ρυθμίσεις"), @@ -213,15 +213,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "Έκλεισε από τον απομακρυσμένο σταθμό"), ("Enable remote configuration modification", "Ενεργοποίηση απομακρυσμένης τροποποίησης ρυθμίσεων"), ("Run without install", "Εκτέλεση χωρίς εγκατάσταση"), - ("Connect via relay", ""), - ("Always connect via relay", "Σύνδεση πάντα μέσω αναμετάδοσης"), + ("Connect via relay", "Πραγματοποίηση σύνδεση μέσω αναμεταδότη"), + ("Always connect via relay", "Σύνδεση πάντα μέσω αναμεταδότη"), ("whitelist_tip", "Μόνο οι IP της λίστας επιτρεπόμενων έχουν πρόσβαση"), ("Login", "Σύνδεση"), - ("Verify", ""), - ("Remember me", ""), - ("Trust this device", ""), - ("Verification code", ""), - ("verification_tip", ""), + ("Verify", "Επαλήθευση"), + ("Remember me", "Να με θυμάσαι"), + ("Trust this device", "Εμπιστεύομαι αυτή την συσκευή"), + ("Verification code", "Κωδικός επαλήθευσης"), + ("verification_tip", "Εντοπίστηκε νέα συσκευή και εστάλη ένας κωδικός επαλήθευσης στην καταχωρισμένη διεύθυνση email. Εισαγάγετε τον κωδικό επαλήθευσης για να συνδεθείτε ξανά."), ("Logout", "Αποσύνδεση"), ("Tags", "Ετικέτες"), ("Search ID", "Αναζήτηση ID"), @@ -242,7 +242,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Invalid folder name", "Μη έγκυρο όνομα φακέλου"), ("Socks5 Proxy", "Διαμεσολαβητής Socks5"), ("Hostname", "Όνομα υπολογιστή"), - ("Discovered", "Ανακαλύφθηκε"), + ("Discovered", "Ανακαλύφθηκαν"), ("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος"), ("Remote ID", "Απομακρυσμένο ID"), ("Paste", "Επικόλληση"), @@ -270,15 +270,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Σημείωση"), ("Connection", "Σύνδεση"), ("Share Screen", "Κοινή χρήση οθόνης"), - ("CLOSE", "Απενεργοποίηση"), - ("OPEN", "Ενεργοποίηση"), ("Chat", "Κουβέντα"), ("Total", "Σύνολο"), ("items", "στοιχεία"), ("Selected", "Επιλεγμένο"), ("Screen Capture", "Αποτύπωση οθόνης"), ("Input Control", "Έλεγχος εισόδου"), - ("Audio Capture", "Λήψη ήχου"), + ("Audio Capture", "Εγγραφή ήχου"), ("File Connection", "Σύνδεση αρχείου"), ("Screen Connection", "Σύνδεση οθόνης"), ("Do you accept?", "Δέχεσαι;"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Η ενεργοποίηση της κοινής χρήσης οθόνης θα ξεκινήσει αυτόματα την υπηρεσία, ώστε άλλες συσκευές να μπορούν να ελέγχουν αυτήν τη συσκευή Android."), ("android_stop_service_tip", "Η απενεργοποίηση της υπηρεσίας θα αποσυνδέσει αυτόματα όλες τις εγκατεστημένες συνδέσεις."), ("android_version_audio_tip", "Η έκδοση Android που διαθέτετε δεν υποστηρίζει εγγραφή ήχου, ενημερώστε το σε Android 10 ή νεότερη έκδοση, εάν είναι δυνατόν."), - ("android_start_service_tip", "Πατήστε [Ενεργοποίηση υπηρεσίας] ή ενεργοποιήστε την άδεια [Πρόσβαση στην οθόνη] για να ξεκινήσετε την υπηρεσία κοινής χρήσης οθόνης."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Λογαριασμός"), ("Overwrite", "Αντικατάσταση"), ("This file exists, skip or overwrite this file?", "Αυτό το αρχείο υπάρχει, παράβλεψη ή αντικατάσταση αυτού του αρχείου;"), @@ -302,7 +301,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Someone turns on privacy mode, exit", "Κάποιος ενεργοποιεί τη λειτουργία απορρήτου, έξοδος"), ("Unsupported", "Δεν υποστηρίζεται"), ("Peer denied", "Ο απομακρυσμένος σταθμός απέρριψε τη σύνδεση"), - ("Please install plugins", "Παρακαλώ εγκαταστήστε πρόσθετα"), + ("Please install plugins", "Παρακαλώ εγκαταστήστε τα πρόσθετα"), ("Peer exit", "Ο απομακρυσμένος σταθμός έχει αποσυνδεθεί"), ("Failed to turn off", "Αποτυχία απενεργοποίησης"), ("Turned off", "Απενεργοποιημένο"), @@ -312,7 +311,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Εκτέλεση του RustDesk στο παρασκήνιο"), ("Ignore Battery Optimizations", "Παράβλεψη βελτιστοποιήσεων μπαταρίας"), ("android_open_battery_optimizations_tip", "Θέλετε να ανοίξετε τις ρυθμίσεις βελτιστοποίησης μπαταρίας;"), - ("Connection not allowed", "Η σύνδεση απορρίφθηκε"), + ("Start on Boot", "Έναρξη κατά την εκκίνηση"), + ("Start the screen sharing service on boot, requires special permissions", "Η έναρξη της υπηρεσίας κοινής χρήσης οθόνης κατά την εκκίνηση, απαιτεί ειδικά δικαιώματα"), + ("Connection not allowed", "Η σύνδεση δεν επιτρέπεται"), ("Legacy mode", "Λειτουργία συμβατότητας"), ("Map mode", "Map mode"), ("Translate mode", "Λειτουργία μετάφρασης"), @@ -323,7 +324,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow remote restart", "Να επιτρέπεται η απομακρυσμένη επανεκκίνηση"), ("Restart Remote Device", "Επανεκκίνηση απομακρυσμένης συσκευής"), ("Are you sure you want to restart", "Είστε βέβαιοι ότι θέλετε να κάνετε επανεκκίνηση"), - ("Restarting Remote Device", "Επανεκκίνηση απομακρυσμένης συσκευής"), + ("Restarting Remote Device", "Γίνεται επανεκκίνηση της απομακρυσμένης συσκευής"), ("remote_restarting_tip", "Η απομακρυσμένη συσκευή επανεκκινείται, κλείστε αυτό το μήνυμα και επανασυνδεθείτε χρησιμοποιώντας τον μόνιμο κωδικό πρόσβασης."), ("Copied", "Αντιγράφηκε"), ("Exit Fullscreen", "Έξοδος από πλήρη οθόνη"), @@ -342,11 +343,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Secure Connection", "Ασφαλής σύνδεση"), ("Insecure Connection", "Μη ασφαλής σύνδεση"), ("Scale original", "Κλιμάκωση πρωτότυπου"), - ("Scale adaptive", "Προσαρμοστική κλίμακα"), + ("Scale adaptive", "Προσαρμοσμένη κλίμακα"), ("General", "Γενικά"), ("Security", "Ασφάλεια"), ("Theme", "Θέμα"), ("Dark Theme", "Σκούρο θέμα"), + ("Light Theme", "Φωτεινό θέμα"), ("Dark", "Σκούρο"), ("Light", "Φωτεινό"), ("Follow System", "Από το σύστημα"), @@ -355,7 +357,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable Audio", "Ενεργοποίηση ήχου"), ("Unlock Network Settings", "Ξεκλείδωμα ρυθμίσεων δικτύου"), ("Server", "Διακομιστής"), - ("Direct IP Access", "Άμεση πρόσβαση IP"), + ("Direct IP Access", "Πρόσβαση με χρήση IP"), ("Proxy", "Διαμεσολαβητής"), ("Apply", "Εφαρμογή"), ("Disconnect all devices?", "Αποσύνδεση όλων των συσκευών;"), @@ -374,11 +376,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Start session recording", "Έναρξη εγγραφής συνεδρίας"), ("Stop session recording", "Διακοπή εγγραφής συνεδρίας"), ("Enable Recording Session", "Ενεργοποίηση εγγραφής συνεδρίας"), - ("Allow recording session", "Να επιτρέπεται η εγγραφή"), + ("Allow recording session", "Να επιτρέπεται η εγγραφή συνεδρίας"), ("Enable LAN Discovery", "Ενεργοποίηση εντοπισμού LAN"), ("Deny LAN Discovery", "Απαγόρευση εντοπισμού LAN"), ("Write a message", "Γράψτε ένα μήνυμα"), - ("Prompt", "Προτροπή"), + ("Prompt", "Υπενθυμίζω"), ("Please wait for confirmation of UAC...", "Παρακαλώ περιμένετε για επιβεβαίωση του UAC..."), ("elevated_foreground_window_tip", "Το τρέχον παράθυρο της απομακρυσμένης επιφάνειας εργασίας απαιτεί υψηλότερα δικαιώματα για να λειτουργήσει, επομένως δεν μπορεί να χρησιμοποιήσει προσωρινά το ποντίκι και το πληκτρολόγιο. Μπορείτε να ζητήσετε από τον απομακρυσμένο χρήστη να ελαχιστοποιήσει το τρέχον παράθυρο ή να κάνετε κλικ στο κουμπί ανύψωσης στο παράθυρο διαχείρισης σύνδεσης. Για να αποφύγετε αυτό το πρόβλημα, συνιστάται η εγκατάσταση του λογισμικού στην απομακρυσμένη συσκευή."), ("Disconnected", "Αποσυνδέθηκε"), @@ -396,10 +398,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("or", "ή"), ("Continue with", "Συνέχεια με"), ("Elevate", "Ανύψωση"), - ("Zoom cursor", "Μεγέθυνση στον κέρσορα"), - ("Accept sessions via password", "Αποδοχή συνεδριών μέσω κωδικού πρόσβασης"), - ("Accept sessions via click", "Αποδοχή συνεδριών μέσω κλικ"), - ("Accept sessions via both", "Αποδοχή συνεδριών και από τα δύο"), + ("Zoom cursor", "Kέρσορας μεγέθυνσης"), + ("Accept sessions via password", "Αποδοχή συνεδριών με κωδικό πρόσβασης"), + ("Accept sessions via click", "Αποδοχή συνεδριών με κλικ"), + ("Accept sessions via both", "Αποδοχή συνεδριών και με τα δύο"), ("Please wait for the remote side to accept your session request...", "Παρακαλώ περιμένετε μέχρι η απομακρυσμένη πλευρά να αποδεχτεί το αίτημα συνεδρίας σας..."), ("One-time Password", "Κωδικός μίας χρήσης"), ("Use one-time password", "Χρήση κωδικού πρόσβασης μίας χρήσης"), @@ -419,42 +421,61 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Εάν έχετε κάρτα γραφικών Nvidia και το παράθυρο σύνδεσης κλείνει αμέσως μετά τη σύνδεση, η εγκατάσταση του προγράμματος οδήγησης nouveau και η επιλογή χρήσης της επιτάχυνσης γραφικών μέσω λογισμικού μπορεί να βοηθήσει. Απαιτείται επανεκκίνηση."), ("Always use software rendering", "Επιτάχυνση γραφικών μέσω λογισμικού"), ("config_input", "Για να ελέγξετε την απομακρυσμένη επιφάνεια εργασίας με πληκτρολόγιο, πρέπει να εκχωρήσετε δικαιώματα στο RustDesk"), - ("config_microphone", ""), + ("config_microphone", "Ρύθμιση μικροφώνου"), ("request_elevation_tip", "αίτημα ανύψωσης δικαιωμάτων χρήστη"), ("Wait", "Περιμένετε"), ("Elevation Error", "Σφάλμα ανύψωσης δικαιωμάτων χρήστη"), ("Ask the remote user for authentication", "Ζητήστε από τον απομακρυσμένο χρήστη έλεγχο ταυτότητας"), ("Choose this if the remote account is administrator", "Επιλέξτε αυτό εάν ο απομακρυσμένος λογαριασμός είναι διαχειριστής"), - ("Transmit the username and password of administrator", "Μεταβίβαση του ονόματος χρήστη και του κωδικού πρόσβασης του διαχειριστή"), + ("Transmit the username and password of administrator", "Αποστολή του ονόματος χρήστη και του κωδικού πρόσβασης του διαχειριστή"), ("still_click_uac_tip", "Εξακολουθεί να απαιτεί από τον απομακρυσμένο χρήστη να κάνει κλικ στο OK στο παράθυρο UAC όπου εκτελείται το RustDesk."), ("Request Elevation", "Αίτημα ανύψωσης δικαιωμάτων χρήστη"), ("wait_accept_uac_tip", "Περιμένετε να αποδεχτεί ο απομακρυσμένος χρήστης το παράθυρο διαλόγου UAC."), ("Elevate successfully", "Επιτυχής ανύψωση δικαιωμάτων χρήστη"), ("uppercase", "κεφαλαία γράμματα"), ("lowercase", "πεζά γράμματα"), - ("digit", "Αριθμός"), + ("digit", "αριθμός"), ("special character", "ειδικός χαρακτήρας"), ("length>=8", "μήκος>=8"), ("Weak", "Αδύναμο"), ("Medium", "Μέτριο"), ("Strong", "Δυνατό"), - ("Switch Sides", ""), - ("Please confirm if you want to share your desktop?", ""), - ("Display", ""), - ("Default View Style", ""), - ("Default Scroll Style", ""), - ("Default Image Quality", ""), - ("Default Codec", ""), - ("Bitrate", ""), - ("FPS", ""), - ("Auto", ""), - ("Other Default Options", ""), - ("Voice call", ""), - ("Text chat", ""), - ("Stop voice call", ""), - ("relay_hint_tip", ""), - ("Reconnect", ""), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Switch Sides", "Εναλλαγή πλευράς"), + ("Please confirm if you want to share your desktop?", "Παρακαλώ επιβεβαιώστε αν επιθυμείτε την κοινή χρήση της επιφάνειας εργασίας;"), + ("Display", "Εμφάνιση"), + ("Default View Style", "Προκαθορισμένος τρόπος εμφάνισης"), + ("Default Scroll Style", "Προκαθορισμένος τρόπος κύλισης"), + ("Default Image Quality", "Προκαθορισμένη ποιότητα εικόνας"), + ("Default Codec", "Προκαθορισμένη κωδικοποίηση"), + ("Bitrate", "Bitrate"), + ("FPS", "FPS"), + ("Auto", "Αυτόματο"), + ("Other Default Options", "Άλλες προκαθορισμένες ρυθμίσεις"), + ("Voice call", "Φωνητική κλήση"), + ("Text chat", "Συνομιλία κειμένου"), + ("Stop voice call", "Διακοπή φωνητικής κλήσης"), + ("relay_hint_tip", "Εάν δεν είναι δυνατή η απευθείας σύνδεση, μπορείτε να δοκιμάσετε να συνδεθείτε μέσω διακομιστή αναμετάδοσης"), + ("Reconnect", "Επανασύνδεση"), + ("Codec", "Κωδικοποίηση"), + ("Resolution", "Ανάλυση"), + ("No transfers in progress", "Δεν υπάρχει μεταφορά σε εξέλιξη"), + ("Set one-time password length", "Μέγεθος κωδικού μιας χρήσης"), + ("idd_driver_tip", "Εγκαταστήστε το πρόγραμμα οδήγησης εικονικής οθόνης που χρησιμοποιείται όταν δεν έχετε φυσικές οθόνες."), + ("confirm_idd_driver_tip", "Είναι ενεργοποιημένη η επιλογή εγκατάστασης του προγράμματος οδήγησης εικονικής οθόνης. Λάβετε υπόψη ότι θα εγκατασταθεί ένα δοκιμαστικό πιστοποιητικό για το πρόγραμμα οδήγησης εικονικής οθόνης. Αυτό το πιστοποιητικό θα χρησιμοποιηθεί μόνο για την πιστοποίηση των προγραμμάτων οδήγησης του Rustdesk."), + ("RDP Settings", "Ρυθμίσεις RDP"), + ("Sort by", "Ταξινόμηση κατά"), + ("New Connection", "Νέα σύνδεση"), + ("Restore", "Επαναφορά"), + ("Minimize", "Ελαχιστοποίηση"), + ("Maximize", "Μεγιστοποίηση"), + ("Your Device", "Η συσκευή σας"), + ("empty_recent_tip", "Δεν υπάρχουν πρόσφατες συνεδρίες!\nΔοκιμάστε να ξεκινήσετε μια νέα."), + ("empty_favorite_tip", "Δεν υπάρχουν ακόμη αγαπημένες συνδέσεις;\nΑφού πραγματοποιήστε σύνδεση με κάποιο απομακρυσμένο σταθμό, μπορείτε να τον προσθέσετε στα αγαπημένα σας!"), + ("empty_lan_tip", "Δεν έχουμε ανακαλυφθεί ακόμη απομακρυσμένοι σταθμοί."), + ("empty_address_book_tip", "Φαίνεται ότι αυτή τη στιγμή δεν υπάρχουν αγαπημένες συνδέσεις στο βιβλίο διευθύνσεών σας."), + ("eg: admin", "π.χ. admin"), + ("Empty Username", "Κενό όνομα χρήστη"), + ("Empty Password", "Κενός κωδικός πρόσβασης"), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index 4bfa86349..b44ff2e40 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -23,7 +23,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Turning on \"Screen Capture\" will automatically start the service, allowing other devices to request a connection to your device."), ("android_stop_service_tip", "Closing the service will automatically close all established connections."), ("android_version_audio_tip", "The current Android version does not support audio capture, please upgrade to Android 10 or higher."), - ("android_start_service_tip", "Tap [Start Service] or OPEN [Screen Capture] permission to start the screen sharing service."), + ("android_start_service_tip", "Tap [Start Service] or enable [Screen Capture] permission to start the screen sharing service."), + ("android_permission_may_not_change_tip", "Permissions for established connections may not be changed instantly until reconnected."), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), ("server_not_support", "Not yet supported by the server"), @@ -39,10 +40,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("verification_tip", "A new device has been detected, and a verification code has been sent to the registered email address, enter the verification code to continue logging in."), ("software_render_tip", "If you have an Nvidia graphics card and the remote window closes immediately after connecting, installing the nouveau driver and choosing to use software rendering may help. A software restart is required."), ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."), - ("request_elevation_tip","You can also request elevation if there is someone on the remote side."), - ("wait_accept_uac_tip","Please wait for the remote user to accept the UAC dialog."), + ("request_elevation_tip", "You can also request elevation if there is someone on the remote side."), + ("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."), ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."), ("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."), ("relay_hint_tip", "It may not be possible to connect directly, you can try to connect via relay. \nIn addition, if you want to use relay on your first try, you can add the \"/r\" suffix to the ID, or select the option \"Always connect via relay\" in the peer card."), + ("No transfers in progress", ""), + ("idd_driver_tip", "Install virtual display driver which is used when you have no physical displays."), + ("confirm_idd_driver_tip", "The option to install the virtual display driver is checked. Note that a test certificate will be installed to trust the virtual display driver. This test certificate will only be used to trust Rustdesk drivers."), + ("empty_recent_tip", "Oops, no recent sessions!\nTime to plan a new one."), + ("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"), + ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."), + ("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index bb2615efc..57a519338 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Averto"), ("Login screen using Wayland is not supported", "Konektajn ekranojn uzantajn Wayland ne estas subtenitaj"), ("Reboot required", "Restarto deviga"), - ("Unsupported display server ", "La aktuala bilda servilo ne estas subtenita"), + ("Unsupported display server", "La aktuala bilda servilo ne estas subtenita"), ("x11 expected", "Bonvolu uzi x11"), ("Port", ""), ("Settings", "Agordoj"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Notu"), ("Connection", ""), ("Share Screen", ""), - ("CLOSE", ""), - ("OPEN", ""), ("Chat", ""), ("Total", ""), ("items", ""), @@ -291,6 +289,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_stop_service_tip", ""), ("android_version_audio_tip", ""), ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", ""), ("This file exists, skip or overwrite this file?", ""), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", ""), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index d7e43b6bf..3ee8fcb51 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Aviso"), ("Login screen using Wayland is not supported", "La pantalla de inicio de sesión con Wayland no es compatible"), ("Reboot required", "Reinicio requerido"), - ("Unsupported display server ", "Servidor de visualización no compatible"), + ("Unsupported display server", "Servidor de visualización no compatible"), ("x11 expected", "x11 necesario"), ("Port", "Puerto"), ("Settings", "Ajustes"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Nota"), ("Connection", "Conexión"), ("Share Screen", "Compartir pantalla"), - ("CLOSE", "CERRAR"), - ("OPEN", "ABRIR"), ("Chat", "Chat"), ("Total", "Total"), ("items", "items"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Habilitar la captura de pantalla iniciará automáticamente el servicio, lo que permitirá que otros dispositivos soliciten una conexión desde este dispositivo."), ("android_stop_service_tip", "Cerrar el servicio cerrará automáticamente todas las conexiones establecidas."), ("android_version_audio_tip", "La versión actual de Android no admite la captura de audio, actualice a Android 10 o posterior."), - ("android_start_service_tip", "Toque el permiso [Iniciar servicio] o ABRIR [Captura de pantalla] para iniciar el servicio de uso compartido de pantalla."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Cuenta"), ("Overwrite", "Sobrescribir"), ("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"), ("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), ("android_open_battery_optimizations_tip", "Si deseas deshabilitar esta característica, por favor, ve a la página siguiente de ajustes, busca y entra en [Batería] y desmarca [Sin restricción]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Conexión no disponible"), ("Legacy mode", "Modo heredado"), ("Map mode", "Modo mapa"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Seguridad"), ("Theme", "Tema"), ("Dark Theme", "Tema Oscuro"), + ("Light Theme", ""), ("Dark", "Oscuro"), ("Light", "Claro"), ("Follow System", "Tema del sistema"), @@ -454,7 +456,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop voice call", "Detener llamada de voz"), ("relay_hint_tip", "Puede que no sea posible conectar directamente. Puedes tratar de conectar a través de relay. \nAdicionalmente, si quieres usar relay en el primer intento, puedes añadir el sufijo \"/r\" a la ID o seleccionar la opción \"Conectar siempre a través de relay\" en la tarjeta del par."), ("Reconnect", "Reconectar"), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Codec", "Códec"), + ("Resolution", "Resolución"), + ("No transfers in progress", "No hay transferencias en curso"), + ("Set one-time password length", "Establecer contraseña de un solo uso"), + ("idd_driver_tip", "Instalar controlador virtual de pantalla a usar cuando no hay pantalla física."), + ("confirm_idd_driver_tip", "La opción de instalar el controlador de pantalla virtual está marcada. Hay que tener en cuenta que se instalará un certificado de prueba para confirar en el controlador de pantalla. Este certificado solo se usará para confiar en controladores Rustdesk."), + ("RDP Settings", "Ajustes RDP"), + ("Sort by", "Ordenar por"), + ("New Connection", "Nueva conexión"), + ("Restore", "Restaurar"), + ("Minimize", "Minimizar"), + ("Maximize", "Maximizar"), + ("Your Device", "Tu dispositivo"), + ("empty_recent_tip", "Vaya, no hay conexiones recientes!\nBuen momento para planificar una."), + ("empty_favorite_tip", "¿Sin pares favoritos aún?\nEncuentra uno al que conectarte y añádelo a favoritos!"), + ("empty_lan_tip", "Oh no, parece que aún no has descubierto ningún par."), + ("empty_address_book_tip", "Parece que actualmente no hay pares en tu libreta de direcciones."), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index d8fcff436..be9821083 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -37,23 +37,23 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "کلیپبورد خالی است"), ("Stop service", "توقف سرویس"), ("Change ID", "تعویض شناسه"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "جدید ID"), + ("length %min% to %max%", "%max% تا %min% طول از"), + ("starts with a letter", "با حرف شروع می شود"), + ("allowed characters", "کارکترهای مجاز"), ("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"), ("Website", "وب سایت"), ("About", "درباره"), - ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Slogan_tip", "ساخته شده با قلب(عشق) در این دنیای پر هرج و مرج!"), + ("Privacy Statement", "بیانیه حریم خصوصی"), ("Mute", "بستن صدا"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), + ("Build Date", "تاریخ ساخت"), + ("Version", "نسخه"), + ("Home", "صفحه اصلی"), ("Audio Input", "ورودی صدا"), ("Enhancements", "بهبودها"), ("Hardware Codec", "کدک سخت افزاری"), - ("Adaptive Bitrate", ""), + ("Adaptive Bitrate", "سازگار Bitrate"), ("ID Server", "شناسه سرور"), ("Relay Server", "Relay سرور"), ("API Server", "API سرور"), @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "هشدار"), ("Login screen using Wayland is not supported", "پشتیبانی نمی شود Wayland ورود به سیستم با استفاده از "), ("Reboot required", "راه اندازی مجدد مورد نیاز است"), - ("Unsupported display server ", "سرور تصویر پشتیبانی نشده است"), + ("Unsupported display server", "سرور تصویر پشتیبانی نشده است"), ("x11 expected", ""), ("Port", "پورت"), ("Settings", "تنظیمات"), @@ -213,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "به صورت دستی توسط میزبان بسته شد"), ("Enable remote configuration modification", "فعال بودن اعمال تغییرات پیکربندی از راه دور"), ("Run without install", "بدون نصب اجرا شود"), - ("Connect via relay", ""), + ("Connect via relay", "اتصال با رله"), ("Always connect via relay", "برای اتصال استفاده شود Relay از"), ("whitelist_tip", "های مجاز می توانند به این دسکتاپ متصل شوند IP فقط"), ("Login", "ورود"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "یادداشت"), ("Connection", "ارتباط"), ("Share Screen", "اشتراک گذاری صفحه"), - ("CLOSE", "بستن"), - ("OPEN", "باز کردن"), ("Chat", "چت"), ("Total", "مجموع"), ("items", "آیتم ها"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "فعال کردن ضبط صفحه به طور خودکار سرویس را راه اندازی می کند و به دستگاه های دیگر امکان می دهد درخواست اتصال به آن دستگاه را داشته باشند."), ("android_stop_service_tip", "با بستن سرویس، تمام اتصالات برقرار شده به طور خودکار بسته می شود"), ("android_version_audio_tip", "نسخه فعلی اندروید از ضبط صدا پشتیبانی نمی‌کند، لطفاً به اندروید 10 یا بالاتر به‌روزرسانی کنید"), - ("android_start_service_tip", "برای شروع سرویس اشتراک‌گذاری صفحه، روی مجوز \"شروع مرحله‌بندی سرور\" یا OPEN \"Screen Capture\" کلیک کنید."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "حساب کاربری"), ("Overwrite", "بازنویسی"), ("This file exists, skip or overwrite this file?", "این فایل وجود دارد، از فایل رد شود یا آن را بازنویسی کند؟"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "را در پس زمینه نگه دارید RustDesk سرویس"), ("Ignore Battery Optimizations", "بهینه سازی باتری نادیده گرفته شود"), ("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"), + ("Start on Boot", "در هنگام بوت شروع شود"), + ("Start the screen sharing service on boot, requires special permissions", "سرویس اشتراک‌گذاری صفحه را در بوت راه‌اندازی کنید، به مجوزهای خاصی نیاز دارد"), ("Connection not allowed", "اتصال مجاز نیست"), ("Legacy mode", "legacy حالت"), ("Map mode", "map حالت"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "امنیت"), ("Theme", "نمایه"), ("Dark Theme", "نمایه تیره"), + ("Light Theme", "نمایه روشن"), ("Dark", "تیره"), ("Light", "روشن"), ("Follow System", "پیروی از سیستم"), @@ -379,8 +381,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Deny LAN Discovery", "غیر فعالسازی جستجو در شبکه"), ("Write a message", "یک پیام بنویسید"), ("Prompt", ""), - ("Please wait for confirmation of UAC...", ""), - ("elevated_foreground_window_tip", ""), + ("Please wait for confirmation of UAC...", "باشید UAC لطفا منتظر تایید"), + ("elevated_foreground_window_tip", "پنجره فعلی دسکتاپ راه دور برای کار کردن به دسترسی بالاتری نیاز دارد، بنابراین نمی‌تواند به طور موقت از ماوس و صفحه کلید استفاده کند. می توانید از کاربر راه دور درخواست کنید که پنجره فعلی را به پایین منتقل کند یا روی دکمه ارتقاء دسترسی در پنجره مدیریت اتصال کلیک کنید. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی دستگاه از راه دور نصب کنید."), ("Disconnected", "قطع ارتباط"), ("Other", "سایر"), ("Confirm before closing multiple tabs", "تایید بستن دسته ای برگه ها"), @@ -395,7 +397,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This PC", "This PC"), ("or", "یا"), ("Continue with", "ادامه با"), - ("Elevate", "افزایش سطح"), + ("Elevate", "ارتقاء"), ("Zoom cursor", " بزرگنمایی نشانگر ماوس"), ("Accept sessions via password", "قبول درخواست با رمز عبور"), ("Accept sessions via click", "قبول درخواست با کلیک موس"), @@ -419,17 +421,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "اگر کارت گرافیک Nvidia دارید و پنجره راه دور بلافاصله پس از اتصال بسته می شود، درایور nouveau را نصب نمایید و انتخاب گزینه استفاده از رندر نرم افزار می تواند کمک کننده باشد. راه اندازی مجدد نرم افزار مورد نیاز است."), ("Always use software rendering", "همیشه از رندر نرم افزاری استفاده کنید"), ("config_input", "برای کنترل دسکتاپ از راه دور با صفحه کلید، باید مجوز RustDesk \"Input Monitoring\" را بدهید."), - ("config_microphone", ""), - ("request_elevation_tip", "همچنین می توانید در صورت وجود شخصی در سمت راه دور درخواست ارتفاع دهید."), + ("config_microphone", "را بدهید. RustDesk \"Record Audio\" برای صحبت از راه دور، باید مجوز"), + ("request_elevation_tip", "همچنین می توانید در صورت وجود شخصی در سمت راه دور درخواست ارتقاء دسترسی دهید."), ("Wait", "صبر کنید"), - ("Elevation Error", "خطای ارتفاع"), + ("Elevation Error", "خطای ارتقاء دسترسی"), ("Ask the remote user for authentication", "درخواست احراز هویت از یک کاربر راه دور"), ("Choose this if the remote account is administrator", "اگر حساب راه دور یک مدیر است، این را انتخاب کنید"), ("Transmit the username and password of administrator", "نام کاربری و رمز عبور مدیر را منتقل کنید"), ("still_click_uac_tip", "همچنان کاربر از راه دور نیاز دارد که روی OK در پنجره UAC اجرای RustDesk کلیک کند."), - ("Request Elevation", "درخواست ارتفاع"), + ("Request Elevation", "درخواست ارتقاء دسترسی"), ("wait_accept_uac_tip", "لطفاً منتظر بمانید تا کاربر راه دور درخواست پنجره UAC را بپذیرد."), - ("Elevate successfully", "با موفقیت بالا ببرید"), + ("Elevate successfully", "ارتقاء دسترسی با موفقیت انجام شد"), ("uppercase", "حروف بزرگ"), ("lowercase", "حروف کوچک"), ("digit", "عدد"), @@ -454,7 +456,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop voice call", "توقف تماس صوتی"), ("relay_hint_tip", " را به شناسه اضافه کنید یا گزینه \"همیشه از طریق رله متصل شوید\" را در کارت همتا انتخاب کنید. همچنین، اگر می‌خواهید فوراً از سرور رله استفاده کنید، می‌توانید پسوند \"/r\".\n اتصال مستقیم ممکن است امکان پذیر نباشد. در این صورت می توانید سعی کنید از طریق سرور رله متصل شوید"), ("Reconnect", "اتصال مجدد"), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Codec", "کدک"), + ("Resolution", "وضوح"), + ("No transfers in progress", "هیچ انتقالی در حال انجام نیست"), + ("Set one-time password length", "طول رمز یکبار مصرف را تعیین کنید"), + ("idd_driver_tip", "درایور صفحه نمایش مجازی را نصب کنید این برای زمانیست که هیچ نمایشگر فیزیکی ندارید."), + ("confirm_idd_driver_tip", "گزینه نصب درایور نمایش مجازی تیک خورده است. توجه داشته باشید که یک گواهی آزمایشی برای اعتماد به درایور نمایش مجازی نصب خواهد شد. این گواهی آزمایشی فقط برای اعتماد به درایورهای Rustdesk استفاده خواهد شد."), + ("RDP Settings", "RDP تنظیمات"), + ("Sort by", "مرتب سازی بر اساس"), + ("New Connection", "اتصال جدید"), + ("Restore", "بازیابی"), + ("Minimize", "کوچک کردن پنجره"), + ("Maximize", "بزرک کردن پنجره"), + ("Your Device", "دستگاه شما"), + ("empty_recent_tip", "اوه، هیچ جلسه اخیری وجود ندارد!\nزمان برنامه ریزی جلسه جدید است"), + ("empty_favorite_tip", "هنوز همتای مورد علاقه‌ای ندارید؟\nبیایید فردی را برای ارتباط پیدا کنیم و آن را به موارد دلخواه خود اضافه کنیم!"), + ("empty_lan_tip", "اوه نه، به نظر می رسد که ما هنوز همتای خود را پیدا نکرده ایم"), + ("empty_address_book_tip", "اوه ، به نظر می رسد که در حال حاضر هیچ همتایی در دفترچه آدرس شما وجود ندارد"), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 37ee42e41..5798253ad 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Avertissement"), ("Login screen using Wayland is not supported", "L'écran de connexion utilisant Wayland n'est pas pris en charge"), ("Reboot required", "Redémarrage requis"), - ("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"), + ("Unsupported display server", "Le serveur d'affichage actuel n'est pas pris en charge"), ("x11 expected", "x11 requis"), ("Port", "Port"), ("Settings", "Paramètres"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Noter"), ("Connection", "Connexion"), ("Share Screen", "Partager l'écran"), - ("CLOSE", "FERMER"), - ("OPEN", "OUVRIR"), ("Chat", "Discuter"), ("Total", "Total"), ("items", "éléments"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "L'activation de la capture d'écran démarrera automatiquement le service, permettant à d'autres appareils de demander une connexion à partir de cet appareil."), ("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."), ("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."), - ("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Compte"), ("Overwrite", "Écraser"), ("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Gardez le service RustDesk en arrière plan"), ("Ignore Battery Optimizations", "Ignorer les optimisations batterie"), ("android_open_battery_optimizations_tip", "Conseil android d'optimisation de batterie"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Connexion non autorisée"), ("Legacy mode", "Mode hérité"), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Sécurité"), ("Theme", "Thème"), ("Dark Theme", "Thème somble"), + ("Light Theme", ""), ("Dark", "Sombre"), ("Light", "Clair"), ("Follow System", "Suivi système"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 557e3faf0..672c65bbb 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Figyelmeztetés"), ("Login screen using Wayland is not supported", "Bejelentkezéskori Wayland használata nem támogatott"), ("Reboot required", "Újraindítás szükséges"), - ("Unsupported display server ", "Nem támogatott megjelenítő szerver"), + ("Unsupported display server", "Nem támogatott megjelenítő szerver"), ("x11 expected", "x11-re számítottt"), ("Port", "Port"), ("Settings", "Beállítások"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Megyjegyzés"), ("Connection", "Kapcsolat"), ("Share Screen", "Képernyőmegosztás"), - ("CLOSE", "BEZÁRÁS"), - ("OPEN", "MEGNYITÁS"), ("Chat", "Chat"), ("Total", "Összes"), ("items", "elemek"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "A \"Képernyőrögzítés\" bekapcsolásával automatikus elindul a szolgáltatás, lehetővé téve, hogy más eszközök csatlakozási kérelmet küldhessenek"), ("android_stop_service_tip", "A szolgáltatás leállítása automatikusan szétkapcsol minden létező kapcsolatot."), ("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, frissítsen legalább Android 10-re, vagy egy újabb verzióra."), - ("android_start_service_tip", "Nyomjon a [Szolgáltatás indítása] gombra, vagy adjon [Képernyőrözítési] engedélyt az applikációnak hogy elindítsa a képernyőmegosztó szolgáltatást."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Fiók"), ("Overwrite", "Felülírás"), ("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, kihagyja vagy felülírja ezt a fájlt?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk futtatása a háttérben"), ("Ignore Battery Optimizations", "Akkumulátorkímélő figyelmen kívűl hagyása"), ("android_open_battery_optimizations_tip", "Ha le szeretné tiltani ezt a funkciót, lépjen a RustDesk alkalmazás beállítási oldalára, keresse meg az [Akkumulátorkímélő] lehetőséget és válassza a nincs korlátozás lehetőséget."), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "A csatlakozás nem engedélyezett"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Biztonság"), ("Theme", "Téma"), ("Dark Theme", "Sötét téma"), + ("Light Theme", ""), ("Dark", "Sötét"), ("Light", "Világos"), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 1a34e6fea..a6272dbd3 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Peringatan"), ("Login screen using Wayland is not supported", "Layar masuk menggunakan Wayland tidak didukung"), ("Reboot required", "Diperlukan boot ulang"), - ("Unsupported display server ", "Server tampilan tidak didukung "), + ("Unsupported display server", "Server tampilan tidak didukung "), ("x11 expected", "x11 diharapkan"), ("Port", "Port"), ("Settings", "Pengaturan"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Catatan"), ("Connection", "Koneksi"), ("Share Screen", "Bagikan Layar"), - ("CLOSE", "TUTUP"), - ("OPEN", "BUKA"), ("Chat", "Obrolan"), ("Total", "Total"), ("items", "item"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Mengaktifkan \"Tangkapan Layar\" akan memulai layanan secara otomatis, memungkinkan perangkat lain untuk meminta sambungan ke perangkat Anda."), ("android_stop_service_tip", "Menutup layanan akan secara otomatis menutup semua koneksi yang dibuat."), ("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."), - ("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Akun"), ("Overwrite", "Timpa"), ("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"), ("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Koneksi tidak dijinkan"), ("Legacy mode", "Mode lama"), ("Map mode", "Mode peta"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Keamanan"), ("Theme", "Tema"), ("Dark Theme", "Tema gelap"), + ("Light Theme", ""), ("Dark", "Gelap"), ("Light", "Terang"), ("Follow System", "Ikuti sistem"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 7256b13d8..2333e23dd 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -204,11 +204,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Avviso"), ("Login screen using Wayland is not supported", "La schermata di accesso non è supportata utilizzando Wayland"), ("Reboot required", "Riavvio necessario"), - ("Unsupported display server ", "Display server non supportato"), + ("Unsupported display server", "Display server non supportato"), ("x11 expected", "x11 necessario"), ("Port", "Porta"), ("Settings", "Impostazioni"), - ("Username", " Nome utente"), + ("Username", "Nome utente"), ("Invalid port", "Numero di porta non valido"), ("Closed manually by the peer", "Chiuso manualmente dal peer"), ("Enable remote configuration modification", "Abilita la modifica remota della configurazione"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Nota"), ("Connection", "Connessione"), ("Share Screen", "Condividi schermo"), - ("CLOSE", "CHIUDERE"), - ("OPEN", "APRIRE"), ("Chat", "Chat"), ("Total", "Totale"), ("items", "Oggetti"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "L'attivazione di Cattura schermo avvierà automaticamente il servizio, consentendo ad altri dispositivi di richiedere una connessione da questo dispositivo."), ("android_stop_service_tip", "La chiusura del servizio chiuderà automaticamente tutte le connessioni stabilite."), ("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."), - ("android_start_service_tip", "Toccare [Avvia servizio] o APRI l'autorizzazione [Cattura schermo] per avviare il servizio di condivisione dello schermo."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Account"), ("Overwrite", "Sovrascrivi"), ("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Mantieni il servizio di RustDesk in background"), ("Ignore Battery Optimizations", "Ignora le ottimizzazioni della batteria"), ("android_open_battery_optimizations_tip", "Se si desidera disabilitare questa funzione, andare nelle impostazioni dell'applicazione RustDesk, aprire la sezione [Batteria] e deselezionare [Senza restrizioni]."), + ("Start on Boot", "Avvia all'accensione"), + ("Start the screen sharing service on boot, requires special permissions", "L'avvio del servizio di condivisione dello schermo all'accensione, richiede autorizzazioni speciali"), ("Connection not allowed", "Connessione non consentita"), ("Legacy mode", "Modalità legacy"), ("Map mode", "Modalità mappa"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Sicurezza"), ("Theme", "Tema"), ("Dark Theme", "Tema Scuro"), + ("Light Theme", ""), ("Dark", "Scuro"), ("Light", "Chiaro"), ("Follow System", "Segui il sistema"), @@ -454,7 +456,26 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop voice call", "Interrompi la chiamata vocale"), ("relay_hint_tip", "Se non è possibile connettersi direttamente, si può provare a farlo tramite relay.\nInoltre, se si desidera utilizzare il relay al primo tentativo, è possibile aggiungere il suffisso \"/r\" all'ID o selezionare l'opzione \"Collegati sempre tramite relay\" nella scheda peer."), ("Reconnect", "Riconnetti"), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Codec", "Codec"), + ("Resolution", "Risoluzione"), + ("No transfers in progress", "Nessun trasferimento in corso"), + ("Set one-time password length", "Imposta la lunghezza della password monouso"), + ("idd_driver_tip", "Installa il driver per lo schermo virtuale che sarà utilizzato quando non si dispone di schermi fisici."), + ("confirm_idd_driver_tip", "L'opzione per installare il driver per lo schermo virtuale è selezionata. Nota che un certificato di test sarà installato per l'attendibilità del driver dello schermo virtuale. Questo certificato di test verrà utilizzato solo per l'attendibilità dei driver di RustDesk."), + ("RDP Settings", "Impostazioni RDP"), + ("Sort by", "Ordina per"), + ("New Connection", "Nuova connessione"), + ("Restore", "Ripristina"), + ("Minimize", "Minimizza"), + ("Maximize", "Massimizza"), + ("Your Device", "Il tuo dispositivo"), + ("empty_recent_tip", "Oops, non c'è nessuna sessione recente!\nTempo di pianificarne una."), + ("empty_favorite_tip", "Ancora nessun peer?\nTrova qualcuno con cui connetterti e aggiungilo ai tuoi preferiti!"), + ("empty_lan_tip", "Oh no, sembra proprio che non abbiamo ancora rilevato nessun peer."), + ("empty_address_book_tip", "Oh diamine, sembra che per ora non ci siano peer nella tua rubrica."), + ("eg: admin", "es: admin"), + ("Empty Username", "Nome Utente Vuoto"), + ("Empty Password", "Password Vuota"), + ("Me", "Io"), + ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index d6354c1c9..5b1bcec4c 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "注意"), ("Login screen using Wayland is not supported", "Waylandを使用したログインスクリーンはサポートされていません"), ("Reboot required", "再起動が必要"), - ("Unsupported display server ", "サポートされていないディスプレイサーバー"), + ("Unsupported display server", "サポートされていないディスプレイサーバー"), ("x11 expected", "X11 が必要です"), ("Port", ""), ("Settings", "設定"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "ノート"), ("Connection", "接続"), ("Share Screen", "画面を共有"), - ("CLOSE", "閉じる"), - ("OPEN", "開く"), ("Chat", "チャット"), ("Total", "計"), ("items", "個のアイテム"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "「画面キャプチャ」をオンにするとサービスが自動的に開始され、他の端末がこの端末への接続をリクエストできるようになります。"), ("android_stop_service_tip", "サービスを停止すると、現在確立されている接続が全て自動的に閉じられます。"), ("android_version_audio_tip", "現在のAndroidバージョンでは音声キャプチャはサポートされていません。Android 10以降にアップグレードしてください。"), - ("android_start_service_tip", "「サービスを開始」をタップするか「画面キャプチャ」を開くと、画面共有サービスが開始されます。"), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "上書き"), ("This file exists, skip or overwrite this file?", "このファイルは存在しています。スキップするか上書きしますか?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk バックグラウンドサービスを維持"), ("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"), ("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "接続が許可されていません"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index dc57c8bf9..3b31b631b 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "경고"), ("Login screen using Wayland is not supported", "Wayland를 사용한 로그인 화면이 지원되지 않습니다"), ("Reboot required", "재부팅이 필요합니다"), - ("Unsupported display server ", "지원하지 않는 디스플레이 서버"), + ("Unsupported display server", "지원하지 않는 디스플레이 서버"), ("x11 expected", "x11 예상됨"), ("Port", ""), ("Settings", "설정"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "노트"), ("Connection", "연결"), ("Share Screen", "화면 공유"), - ("CLOSE", "종료"), - ("OPEN", "열기"), ("Chat", "채팅"), ("Total", "총합"), ("items", "개체"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "\"화면 캡처\"를 켜면 서비스가 자동으로 시작되어 다른 장치에서 사용자 장치에 대한 연결을 요청할 수 있습니다."), ("android_stop_service_tip", "서비스를 종료하면 모든 연결이 자동으로 닫힙니다."), ("android_version_audio_tip", "현재 Android 버전은 오디오 캡처를 지원하지 않습니다. Android 10 이상으로 업그레이드하십시오."), - ("android_start_service_tip", "[서비스 시작] 또는 [화면 캡처] 권한을 눌러 화면 공유 서비스를 시작합니다."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "덮어쓰기"), ("This file exists, skip or overwrite this file?", "해당 파일이 이미 존재합니다, 넘어가거나 덮어쓰시겠습니까?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk 백그라운드 서비스로 유지하기"), ("Ignore Battery Optimizations", "배터리 최적화 무시하기"), ("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "연결이 허용되지 않음"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 6698b2c5f..ccce435fc 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Ескерту"), ("Login screen using Wayland is not supported", "Wayland қолданған Кіру екіреніне қолдау көрсетілмейді"), ("Reboot required", "Қайта-қосу қажет"), - ("Unsupported display server ", "Қолдаусыз дисплей сербері"), + ("Unsupported display server", "Қолдаусыз дисплей сербері"), ("x11 expected", "x11 күтілген"), ("Port", "Порт"), ("Settings", "Орнатпалар"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Нота"), ("Connection", "Қосылым"), ("Share Screen", "Екіренді Бөлісу"), - ("CLOSE", "ЖАБУ"), - ("OPEN", "АШУ"), ("Chat", "Чат"), ("Total", "Барлығы"), ("items", "зат"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "\"Екіренді Тұсіру\" қосылған кезде сербес аутыматты іске қосылып, басқа құрылғыларға сіздің құрылғыға қосылым сұраныстауға мүмкіндің береді."), ("android_stop_service_tip", "Сербесті жабу аутыматты түрде барлық орнатылған қосылымдарды жабады."), ("android_version_audio_tip", "Ағымдағы Android нұсқасы аудионы түсіруді қолдамайды, Android 10 не жоғарғысына жаңғыртуды өтінеміз."), - ("android_start_service_tip", "[Сербесті Іске қосу]'ды түртіңіз не [Екіренді Түсіру] рұқсатын АШУ арқылы екіренді бөлісу сербесін іске қосыңыз."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Есепкі"), ("Overwrite", "Үстінен қайта жазу"), ("This file exists, skip or overwrite this file?", "Бұл файыл бар, өткізіп жіберу әлде үстінен қайта жазу керек пе?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Артжақтағы RustDesk сербесін сақтап тұру"), ("Ignore Battery Optimizations", "Бәтері Оңтайландыруларын Елемеу"), ("android_open_battery_optimizations_tip", "Егер де бұл ерекшелікті өшіруді қаласаңыз, келесі RustDesk апылқат орнатпалары бетіне барып, [Бәтері]'ні тауып кіріңіз де [Шектеусіз]'ден құсбелгіні алып тастауды өтінеміз"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Қосылу рұқсат етілмеген"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 545e1ec2e..9574581e5 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -37,14 +37,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "Klembord is leeg"), ("Stop service", "Stop service"), ("Change ID", "Wijzig ID"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "Uw nieuw ID"), + ("length %min% to %max%", "lengte %min% tot %max%"), + ("starts with a letter", "begint met een letter"), + ("allowed characters", "toegestane tekens"), ("id_change_tip", "Alleen de letters a-z, A-Z, 0-9, _ (underscore) kunnen worden gebruikt. De eerste letter moet a-z, A-Z zijn. De lengte moet tussen 6 en 16 liggen."), ("Website", "Website"), ("About", "Over"), - ("Slogan_tip", "Gedaan met het hart in deze chaotische wereld!"), + ("Slogan_tip", "Ontwikkeld met het hart voor deze chaotische wereld!"), ("Privacy Statement", "Privacyverklaring"), ("Mute", "Geluid uit"), ("Build Date", "Versie datum"), @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Waarschuwing"), ("Login screen using Wayland is not supported", "Aanmeldingsscherm via Wayland wordt niet ondersteund"), ("Reboot required", "Opnieuw opstarten vereist"), - ("Unsupported display server ", "Niet-ondersteunde weergaveserver"), + ("Unsupported display server", "Niet-ondersteunde weergaveserver"), ("x11 expected", "x11 verwacht"), ("Port", "Poort"), ("Settings", "Instellingen"), @@ -213,7 +213,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "Handmatig gesloten door de peer"), ("Enable remote configuration modification", "Wijziging configuratie op afstand inschakelen"), ("Run without install", "Uitvoeren zonder installatie"), - ("Connect via relay", ""), + ("Connect via relay", "Verbinden via relais"), ("Always connect via relay", "Altijd verbinden via relay"), ("whitelist_tip", "Alleen een IP-adres op de witte lijst krijgt toegang tot mijn toestel"), ("Login", "Log In"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Opmerking"), ("Connection", "Verbinding"), ("Share Screen", "Scherm Delen"), - ("CLOSE", "SLUITEN"), - ("OPEN", "OPEN"), ("Chat", "Chat"), ("Total", "Totaal"), ("items", "items"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Als u \"Schermopname\" inschakelt, wordt de service automatisch gestart, zodat andere apparaten een verbinding met uw apparaat kunnen aanvragen."), ("android_stop_service_tip", "Het sluiten van de service zal automatisch alle gemaakte verbindingen sluiten."), ("android_version_audio_tip", "De huidige versie van Android ondersteunt geen audio-opname, upgrade naar Android 10 of hoger."), - ("android_start_service_tip", "Druk op [Start Service] of op de permissie OPEN [Screenshot] om de service voor het overnemen van het scherm te starten."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Account"), ("Overwrite", "Overschrijven"), ("This file exists, skip or overwrite this file?", "Dit bestand bestaat reeds, overslaan of overschrijven?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk achtergronddienst behouden"), ("Ignore Battery Optimizations", "Negeer Batterij Optimalisaties"), ("android_open_battery_optimizations_tip", "Ga naar de volgende pagina met instellingen"), + ("Start on Boot", "Starten bij Opstarten"), + ("Start the screen sharing service on boot, requires special permissions", "Start de schermdelings service bij het opstarten, vereist speciale rechten"), ("Connection not allowed", "Verbinding niet toegestaan"), ("Legacy mode", "Verouderde modus"), ("Map mode", "Map mode"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Beveiliging"), ("Theme", "Thema"), ("Dark Theme", "Donker Thema"), + ("Light Theme", "Lichte Thema"), ("Dark", "Donker"), ("Light", "Licht"), ("Follow System", "Volg Systeem"), @@ -444,7 +446,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Default View Style", "Standaard Weergave Stijl"), ("Default Scroll Style", "Standaard Scroll Stijl"), ("Default Image Quality", "Standaard Beeldkwaliteit"), - ("Default Codec", "tandaard Codec"), + ("Default Codec", "Standaard Codec"), ("Bitrate", "Bitrate"), ("FPS", "FPS"), ("Auto", "Auto"), @@ -452,9 +454,28 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Voice call", "Spraakoproep"), ("Text chat", "Tekst chat"), ("Stop voice call", "Stop spraakoproep"), - ("relay_hint_tip", ""), - ("Reconnect", ""), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("relay_hint_tip", "Indien een directe verbinding niet mogelijk is, kunt u proberen verbinding te maken via een Relay Server. \nAls u bij de eerste poging een relaisverbinding tot stand wilt brengen, kunt u het achtervoegsel \"/r\" toevoegen aan het ID of de optie \"Altijd verbinden via relaisserver\" selecteren op de externe terminal."), + ("Reconnect", "Herverbinden"), + ("Codec", "Codec"), + ("Resolution", "Resolutie"), + ("No transfers in progress", "Geen overdrachten in uitvoering"), + ("Set one-time password length", "Stel de lengte van het eenmalige wachtwoord in"), + ("idd_driver_tip", "Installeer het virtuele beeldschermstuurprogramma dat wordt gebruikt wanneer u geen fysieke beeldschermen hebt."), + ("confirm_idd_driver_tip", "De optie om het virtuele displaystuurprogramma te installeren is ingeschakeld. Er wordt een testcertificaat geplaatst om het virtuele displaystuurprogramma te vertrouwen. Dit testcertificaat wordt alleen gebruikt om RustDesk-stuurprogramma's te vertrouwen."), + ("RDP Settings", "RDP Instellingen"), + ("Sort by", "Sorteren op"), + ("New Connection", "Nieuwe Verbinding"), + ("Restore", "Herstel"), + ("Minimize", "Minimaliseren"), + ("Maximize", "Maximaliseren"), + ("Your Device", "Uw Apparaat"), + ("empty_recent_tip", "Oeps, geen actuele situatie!\nTijd om een nieuwe te plannen."), + ("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan je favorieten!"), + ("empty_lan_tip", "Oh nee, het lijkt erop dat we nog geen extern station hebben ontdekt."), + ("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in je adresboek staan."), + ("eg: admin", "bijv: admin"), + ("Empty Username", "Gebruikersnaam Leeg"), + ("Empty Password", "Wachtwoord Leeg"), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index eea46accb..5534bbe1b 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Ostrzeżenie"), ("Login screen using Wayland is not supported", "Ekran logowania korzystający z Wayland nie jest obsługiwany"), ("Reboot required", "Wymagany ponowne uruchomienie"), - ("Unsupported display server ", "Nieobsługiwany serwer wyświetlania"), + ("Unsupported display server", "Nieobsługiwany serwer wyświetlania"), ("x11 expected", "Wymagany jest X11"), ("Port", "Port"), ("Settings", "Ustawienia"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Notatka"), ("Connection", "Połączenie"), ("Share Screen", "Udostępnij ekran"), - ("CLOSE", "Zamknij"), - ("OPEN", "Otwórz"), ("Chat", "Czat"), ("Total", "Łącznie"), ("items", "elementów"), @@ -284,13 +282,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Do you accept?", "Akceptujesz?"), ("Open System Setting", "Otwórz ustawienia systemowe"), ("How to get Android input permission?", "Jak uzyskać uprawnienia do wprowadzania danych w systemie Android?"), - ("android_input_permission_tip1", "android_input_permission_tip1"), - ("android_input_permission_tip2", "android_input_permission_tip2"), - ("android_new_connection_tip", "android_new_connection_tip"), - ("android_service_will_start_tip", "android_service_will_start_tip"), - ("android_stop_service_tip", "android_stop_service_tip"), - ("android_version_audio_tip", "android_version_audio_tip"), - ("android_start_service_tip", "android_start_service_tip"), + ("android_input_permission_tip1", "Aby można było sterować Twoim urządzeniem za pomocą myszy lub dotyku, musisz zezwolić RustDesk na korzystanie z usługi \"Ułatwienia dostępu\"."), + ("android_input_permission_tip2", "Przejdź do następnej strony ustawień systemowych, znajdź i wejdź w [Zainstalowane usługi], włącz usługę [RustDesk Input]."), + ("android_new_connection_tip", "Otrzymano nowe żądanie zdalnego dostępu, które chce przejąć kontrolę nad Twoim urządzeniem."), + ("android_service_will_start_tip", "Włączenie opcji „Przechwytywanie ekranu” spowoduje automatyczne uruchomienie usługi, umożliwiając innym urządzeniom żądanie połączenia z Twoim urządzeniem."), + ("android_stop_service_tip", "Zamknięcie usługi spowoduje automatyczne zamknięcie wszystkich nawiązanych połączeń."), + ("android_version_audio_tip", "Bieżąca wersja systemu Android nie obsługuje przechwytywania dźwięku, zaktualizuj system do wersji Android 10 lub nowszej."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Konto"), ("Overwrite", "Nadpisz"), ("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"), @@ -311,7 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Language", "Język"), ("Keep RustDesk background service", "Zachowaj usługę RustDesk w tle"), ("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"), - ("android_open_battery_optimizations_tip", "android_open_battery_optimizations_tip"), + ("android_open_battery_optimizations_tip", "Jeśli chcesz wyłączyć tę funkcję, przejdź do następnej strony ustawień aplikacji RustDesk, znajdź i wprowadź [Bateria], odznacz [Bez ograniczeń]"), + ("Start on Boot", "Autostart"), + ("Start the screen sharing service on boot, requires special permissions", "Uruchom usługę udostępniania ekranu podczas startu, wymaga specjalnych uprawnień"), ("Connection not allowed", "Połączenie niedozwolone"), ("Legacy mode", "Tryb kompatybilności wstecznej (legacy)"), ("Map mode", "Tryb mapowania"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Zabezpieczenia"), ("Theme", "Motyw"), ("Dark Theme", "Ciemny motyw"), + ("Light Theme", ""), ("Dark", "Ciemny"), ("Light", "Jasny"), ("Follow System", "Zgodny z systemem"), @@ -449,12 +451,31 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("FPS", "FPS"), ("Auto", "Auto"), ("Other Default Options", "Inne opcje domyślne"), - ("Voice call", ""), - ("Text chat", ""), - ("Stop voice call", ""), - ("relay_hint_tip", ""), - ("Reconnect", ""), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Voice call", "Rozmowa głosowa"), + ("Text chat", "Chat tekstowy"), + ("Stop voice call", "Rozłącz"), + ("relay_hint_tip", "Bezpośrednie połączenie może nie być możliwe, możesz spróbować połączyć się przez serwer przekazujący. \nDodatkowo, jeśli chcesz użyć serwera przekazującego przy pierwszej próbie, możesz dodać sufiks \"/r\" do identyfikatora lub wybrać opcję \"Zawsze łącz przez serwer przekazujący\" na karcie peer-ów."), + ("Reconnect", "Połącz ponownie"), + ("Codec", "Kodek"), + ("Resolution", "Rozdzielczość"), + ("No transfers in progress", "Brak transferów w toku"), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index ee1561123..780bc46c6 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Aviso"), ("Login screen using Wayland is not supported", "Tela de Login com Wayland não é suportada"), ("Reboot required", "Reinicialização necessária"), - ("Unsupported display server ", "Servidor de display não suportado"), + ("Unsupported display server", "Servidor de display não suportado"), ("x11 expected", "x11 em falha"), ("Port", ""), ("Settings", "Configurações"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Nota"), ("Connection", "Ligação"), ("Share Screen", "Partilhar ecrã"), - ("CLOSE", "FECHAR"), - ("OPEN", "ABRIR"), ("Chat", "Conversar"), ("Total", "Total"), ("items", "itens"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Activar a Captura de Ecran irá automaticamente inicializar o serviço, permitindo que outros dispositivos solicitem uma ligação deste dispositivo."), ("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as ligações estabelecidas."), ("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor actualize para o Android 10 ou maior."), - ("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Ecran] para iniciar o serviço de partilha de ecran."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "Substituir"), ("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Manter o serviço RustDesk em funcionamento"), ("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Ligação não autorizada"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 7b16bdf34..5cfc2e5a3 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Aviso"), ("Login screen using Wayland is not supported", "Tela de Login utilizando Wayland não é suportada"), ("Reboot required", "Reinicialização necessária"), - ("Unsupported display server ", "Servidor de display não suportado"), + ("Unsupported display server", "Servidor de display não suportado"), ("x11 expected", "x11 esperado"), ("Port", "Porta"), ("Settings", "Configurações"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Nota"), ("Connection", "Conexão"), ("Share Screen", "Compartilhar Tela"), - ("CLOSE", "FECHAR"), - ("OPEN", "ABRIR"), ("Chat", "Chat"), ("Total", "Total"), ("items", "itens"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Habilitar a Captura de Tela irá automaticamente inicalizar o serviço, permitindo que outros dispositivos solicitem uma conexão deste dispositivo."), ("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as conexões estabelecidas."), ("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou superior."), - ("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Tela] para iniciar o serviço de compartilhamento de tela."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Conta"), ("Overwrite", "Substituir"), ("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"), ("Ignore Battery Optimizations", "Ignorar otimizações de bateria"), ("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Conexão não permitida"), ("Legacy mode", "Modo legado"), ("Map mode", "Modo mapa"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Segurança"), ("Theme", "Tema"), ("Dark Theme", "Tema escuro"), + ("Light Theme", ""), ("Dark", "Escuro"), ("Light", "Claro"), ("Follow System", "Seguir sistema"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 315eadd2a..c354c4685 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Avertisment"), ("Login screen using Wayland is not supported", "Ecranele de conectare care folosesc Wayland nu sunt acceptate"), ("Reboot required", "Repornire necesară"), - ("Unsupported display server ", "Tipul de server de afișaj nu este acceptat"), + ("Unsupported display server", "Tipul de server de afișaj nu este acceptat"), ("x11 expected", "E necesar X11"), ("Port", "Port"), ("Settings", "Setări"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Reține"), ("Connection", "Conexiune"), ("Share Screen", "Partajează ecran"), - ("CLOSE", "ÎNCHIDE"), - ("OPEN", "DESCHIDE"), ("Chat", "Discută"), ("Total", "Total"), ("items", "elemente"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Activarea setării Captură ecran va porni automat serviciul, permițând altor dispozitive să solicite conectarea la dispozitivul tău."), ("android_stop_service_tip", "Închiderea serviciului va închide automat toate conexiunile stabilite."), ("android_version_audio_tip", "Versiunea actuală de Android nu suportă captura audio. Fă upgrade la Android 10 sau la o versiune superioară."), - ("android_start_service_tip", "Apasă [Pornește serviciu] sau DESCHIDE [Captură ecran] pentru a porni serviciul de partajare a ecranului."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Cont"), ("Overwrite", "Suprascrie"), ("This file exists, skip or overwrite this file?", "Fișier deja existent. Omite sau suprascrie?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Rulează serviciul RustDesk în fundal"), ("Ignore Battery Optimizations", "Ignoră optimizările de baterie"), ("android_open_battery_optimizations_tip", "Pentru dezactivarea acestei funcții, accesează setările aplicației RustDesk, deschide secțiunea [Baterie] și deselectează [Fără restricții]."), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Conexiune neautoriztă"), ("Legacy mode", "Mod legacy"), ("Map mode", "Mod hartă"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Securitate"), ("Theme", "Temă"), ("Dark Theme", "Temă întunecată"), + ("Light Theme", ""), ("Dark", "Întunecat"), ("Light", "Luminos"), ("Follow System", "Urmărește sistem"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 6d212490b..3abea8029 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Предупреждение"), ("Login screen using Wayland is not supported", "Вход в систему с использованием Wayland не поддерживается"), ("Reboot required", "Требуется перезагрузка"), - ("Unsupported display server ", "Неподдерживаемый сервер отображения"), + ("Unsupported display server", "Неподдерживаемый сервер отображения"), ("x11 expected", "Ожидается X11"), ("Port", "Порт"), ("Settings", "Настройки"), @@ -269,9 +269,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No permission of file transfer", "Нет разрешения на передачу файлов"), ("Note", "Примечание"), ("Connection", "Соединение"), - ("Share Screen", "Поделиться экраном"), - ("CLOSE", "ЗАКРЫТЬ"), - ("OPEN", "ОТКРЫТЬ"), + ("Share Screen", "Демонстрация экрана"), ("Chat", "Чат"), ("Total", "Всего"), ("items", "элементы"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Включение захвата экрана автоматически запускает службу, позволяя другим устройствам запрашивать соединение с этим устройством."), ("android_stop_service_tip", "Закрытие службы автоматически закроет все установленные соединения."), ("android_version_audio_tip", "Текущая версия Android не поддерживает захват звука, обновите её до Android 10 или выше."), - ("android_start_service_tip", "Нажмите \"Запуск промежуточного сервера\" или ОТКРЫТЬ разрешение \"Захват экрана\", чтобы запустить службу демонстрации экрана."), + ("android_start_service_tip", "Нажмите [Запустить службу] или разрешите [Захват экрана], чтобы запустить службу демонстрации экрана."), + ("android_permission_may_not_change_tip", ""), ("Account", "Аккаунт"), ("Overwrite", "Перезаписать"), ("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Держать в фоне службу RustDesk"), ("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"), ("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек"), + ("Start on Boot", "Начинать при загрузке"), + ("Start the screen sharing service on boot, requires special permissions", "Запускать службу демонстрации экрана при загрузке (требуются специальные разрешения)"), ("Connection not allowed", "Подключение не разрешено"), ("Legacy mode", "Устаревший режим"), ("Map mode", "Режим сопоставления"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Безопасность"), ("Theme", "Тема"), ("Dark Theme", "Тёмная тема"), + ("Light Theme", "Светлая тема"), ("Dark", "Тёмная"), ("Light", "Светлая"), ("Follow System", "Системная"), @@ -386,11 +388,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Confirm before closing multiple tabs", "Подтверждать закрытие несколько вкладок"), ("Keyboard Settings", "Настройки клавиатуры"), ("Full Access", "Полный доступ"), - ("Screen Share", "Поделиться экраном"), + ("Screen Share", "Демонстрация экрана"), ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland требует Ubuntu 21.04 или более позднюю версию."), ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Для Wayland требуется более поздняя версия дистрибутива Linux. Пожалуйста, попробуйте рабочий стол X11 или смените ОС."), ("JumpLink", "Просмотр"), - ("Please Select the screen to be shared(Operate on the peer side).", "Выберите экран для совместного использования (работайте на одноранговой стороне)."), + ("Please Select the screen to be shared(Operate on the peer side).", "Выберите экран для демонстрации (работайте на одноранговой стороне)."), ("Show RustDesk", "Показать RustDesk"), ("This PC", "Этот компьютер"), ("or", "или"), @@ -439,7 +441,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Medium", "Средний"), ("Strong", "Стойкий"), ("Switch Sides", "Переключить стороны"), - ("Please confirm if you want to share your desktop?", "Подтверждаете, что хотите поделиться своим рабочим столом?"), + ("Please confirm if you want to share your desktop?", "Подтверждаете, что разрешаете демонстрацию рабочего стола?"), ("Display", "Отображение"), ("Default View Style", "Стиль отображения по умолчанию"), ("Default Scroll Style", "Стиль прокрутки по умолчанию"), @@ -452,9 +454,28 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Voice call", "Голосовой вызов"), ("Text chat", "Текстовый чат"), ("Stop voice call", "Завершить голосовой вызов"), - ("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."), + ("relay_hint_tip", "Прямое подключение может оказаться невозможным. В этом случае можно попытаться подключиться через сервер ретрансляции. \nКроме того, если вы хотите сразу использовать сервер ретрансляции, можно добавить к ID суффикс \"/r\" или включить \"Всегда подключаться через ретранслятор\" в настройках удалённого узла."), ("Reconnect", "Переподключить"), - ("Codec", ""), - ("Resolution", ""), - ].iter().cloned().collect(); + ("Codec", "Кодек"), + ("Resolution", "Разрешение"), + ("No transfers in progress", "Передача не осуществляется"), + ("Set one-time password length", "Установить длину одноразового пароля"), + ("idd_driver_tip", "Установите драйвер виртуального дисплея, который используется при отсутствии физических дисплеев."), + ("confirm_idd_driver_tip", "Включена функция установки драйвера виртуального дисплея. Обратите внимание, что для доверия к драйверу будет установлен тестовый сертификат. Этот сертификат будет использоваться только для подтверждения доверия драйверам Rustdesk."), + ("RDP Settings", "Настройки RDP"), + ("Sort by", "Сортировка"), + ("New Connection", "Новое подключение"), + ("Restore", "Восстановить"), + ("Minimize", "Свернуть"), + ("Maximize", "Развернуть"), + ("Your Device", "Ваше устройство"), + ("empty_recent_tip", "Нет последних сеансов!\nПора спланировать новый."), + ("empty_favorite_tip", "Ещё нет избранных удалённых узлов?\nДавайте найдём, кого можно добавить в избранное!"), + ("empty_lan_tip", "Не найдено удалённых узлов."), + ("empty_address_book_tip", "В адресной книге нет удалённых узлов."), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 462a78ab6..820b7a7db 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Upozornenie"), ("Login screen using Wayland is not supported", "Prihlasovacia obrazovka prostredníctvom Wayland nie je podporovaná"), ("Reboot required", "Vyžaduje sa reštart"), - ("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"), + ("Unsupported display server", "Nepodporovaný zobrazovací (display) server"), ("x11 expected", "očakáva sa x11"), ("Port", ""), ("Settings", "Nastavenia"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Poznámka"), ("Connection", "Pripojenie"), ("Share Screen", "Zdielať obrazovku"), - ("CLOSE", "ZATVORIŤ"), - ("OPEN", "OTVORIŤ"), ("Chat", "Chat"), ("Total", "Celkom"), ("items", "položiek"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Zapnutie \"Zachytávanie obsahu obrazovky\" automaticky spistí službu, čo iným zariadeniam umožní požiadať o pripojenie k tomuto zariadeniu."), ("android_stop_service_tip", "Zastavenie služby automaticky ukončí všetky naviazané spojenia."), ("android_version_audio_tip", "Vaša verzia Androidu neumožňuje zaznamenávanie zvuku. Prejdite na verziu Android 10 alebo vyššiu."), - ("android_start_service_tip", "Klepnite na [Spustiť službu] alebo OTVORTE oprávnenie [Zachytávanie obsahu obrazovky], aby sa aktivovala služba zdieľania obrazovky."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "Prepísať"), ("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", ""), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 0eb1949fe..e81124d5c 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Opozorilo"), ("Login screen using Wayland is not supported", "Prijava z Waylandom ni podprta"), ("Reboot required", "Potreben je ponovni zagon"), - ("Unsupported display server ", "Nepodprt zaslonski strežnik"), + ("Unsupported display server", "Nepodprt zaslonski strežnik"), ("x11 expected", "Pričakovan X11"), ("Port", "Vrata"), ("Settings", "Nastavitve"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Opomba"), ("Connection", "Povezava"), ("Share Screen", "Deli zaslon"), - ("CLOSE", "ZAPRI"), - ("OPEN", "ODPRI"), ("Chat", "Pogovor"), ("Total", "Skupaj"), ("items", "elementi"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Z vklopom zajema zaslona se bo samodejno zagnala storitev, ki omogoča da oddaljene naprave pošljejo zahtevo za povezavo na vašo napravo."), ("android_stop_service_tip", "Z zaustavitvijo storitve bodo samodejno prekinjene vse oddaljene povezave."), ("android_version_audio_tip", "Trenutna različica Androida ne omogoča zajema zvoka. Za zajem zvoka nadgradite na Android 10 ali novejši."), - ("android_start_service_tip", "Tapnite »Zaženi storitev« ali »ODPRI« pri dovoljenju za zajem zaslona da zaženete storitev deljenja zaslona."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Račun"), ("Overwrite", "Prepiši"), ("This file exists, skip or overwrite this file?", "Datoteka obstaja, izpusti ali prepiši?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Ohrani RustDeskovo storitev v ozadju"), ("Ignore Battery Optimizations", "Prezri optimizacije baterije"), ("android_open_battery_optimizations_tip", "Če želite izklopiti to možnost, pojdite v nastavitve aplikacije RustDesk, poiščite »Baterija« in izklopite »Neomejeno«"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Povezava ni dovoljena"), ("Legacy mode", "Stari način"), ("Map mode", "Način preslikave"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Varnost"), ("Theme", "Tema"), ("Dark Theme", "Temna tema"), + ("Light Theme", ""), ("Dark", "Temna"), ("Light", "Svetla"), ("Follow System", "Sistemska"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 2fc5dfe0d..d237b8895 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Dicka po shkon keq"), ("Login screen using Wayland is not supported", "Hyrja në ekran duke përdorur Wayland muk suportohet"), ("Reboot required", "Kërkohet rinisja"), - ("Unsupported display server ", "Nuk supurtohet severi ekranit"), + ("Unsupported display server", "Nuk supurtohet severi ekranit"), ("x11 expected", "Pritet x11"), ("Port", "Port"), ("Settings", "Cilësimet"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Shënime"), ("Connection", "Lidhja"), ("Share Screen", "Ndaj ekranin"), - ("CLOSE", "Mbyll"), - ("OPEN", "Hap"), ("Chat", "Biseda"), ("Total", "Total"), ("items", "artikuj"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Aktivizimi i \"Regjistrimi i ekranit\" do të nisë automatikisht shërbimin, duke lejuar pajisjet e tjera të kërkojnë një lidhje me pajisjen tuaj."), ("android_stop_service_tip", "Mbyllja e shërbimit do të mbyllë automatikisht të gjitha lidhjet e vendosura."), ("android_version_audio_tip", "Versioni aktual i Android nuk mbështet regjistrimin e audios, ju lutemi përmirësoni në Android 10 ose më të lartë."), - ("android_start_service_tip", "Shtyp [Fillo Shërbimin] ose HAP lejen e [Kapjen e Ekranit] për të nisur shërbimin e ndarjes së ekranit."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Llogaria"), ("Overwrite", "Përshkruaj"), ("This file exists, skip or overwrite this file?", "Ky skedar ekziston , tejkalo ose përshkruaj këtë skedarë"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Mbaje shërbimin e sfondit të RustDesk"), ("Ignore Battery Optimizations", "Injoro optimizimet e baterisë"), ("android_open_battery_optimizations_tip", "Nëse dëshironi ta çaktivizoni këtë veçori, ju lutemi shkoni te faqja tjetër e cilësimeve të aplikacionit RustDesk, gjeni dhe shtypni [Batteri], hiqni zgjedhjen [Te pakufizuara]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Lidhja nuk lejohet"), ("Legacy mode", "Modaliteti i trashëgimisë"), ("Map mode", "Modaliteti i hartës"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Siguria"), ("Theme", "Theme"), ("Dark Theme", "Theme e errët"), + ("Light Theme", ""), ("Dark", "E errët"), ("Light", "Drita"), ("Follow System", "Ndiq sistemin"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 17882094c..8e7ecf824 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Upozorenje"), ("Login screen using Wayland is not supported", "Ekran za prijavu koji koristi Wayland nije podržan"), ("Reboot required", "Potreban je restart"), - ("Unsupported display server ", "Nepodržan server za prikaz"), + ("Unsupported display server", "Nepodržan server za prikaz"), ("x11 expected", "x11 očekivan"), ("Port", "Port"), ("Settings", "Postavke"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Primedba"), ("Connection", "Konekcija"), ("Share Screen", "Podeli ekran"), - ("CLOSE", "ZATVORI"), - ("OPEN", "OTVORI"), ("Chat", "Dopisivanje"), ("Total", "Ukupno"), ("items", "stavki"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Uključenje \"Screen Capture\" automatski će pokrenuti servis, dozvoljavajući drugim uređajima da zahtevaju spajanje na vaš uređaj."), ("android_stop_service_tip", "Zatvaranje servisa automatski će zatvoriti sve uspostavljene konekcije."), ("android_version_audio_tip", "Tekuća Android verzija ne podržava audio snimanje, molimo nadogradite na Android 10 ili veći."), - ("android_start_service_tip", "Kliknite [Start Service] ili OPEN [Screen Capture] dozvolu da pokrenete servis deljenja ekrana."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Nalog"), ("Overwrite", "Prepiši preko"), ("This file exists, skip or overwrite this file?", "Ova datoteka postoji, preskoči ili prepiši preko?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Zadrži RustDesk kao pozadinski servis"), ("Ignore Battery Optimizations", "Zanemari optimizacije baterije"), ("android_open_battery_optimizations_tip", "Ako želite da onemogućite ovu funkciju, molimo idite na sledeću stranicu za podešavanje RustDesk aplikacije, pronađite i uđite u [Battery], isključite [Unrestricted]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Konekcija nije dozvoljena"), ("Legacy mode", "Zastareli mod"), ("Map mode", "Mod mapiranja"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Bezbednost"), ("Theme", "Tema"), ("Dark Theme", "Tamna tema"), + ("Light Theme", ""), ("Dark", "Tamno"), ("Light", "Svetlo"), ("Follow System", "Prema sistemu"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 250cf3405..e0807d892 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Varning"), ("Login screen using Wayland is not supported", "Login med Wayland stöds inte"), ("Reboot required", "Omstart krävs"), - ("Unsupported display server ", "Displayserver stöds inte "), + ("Unsupported display server", "Displayserver stöds inte "), ("x11 expected", "x11 förväntades"), ("Port", "Port"), ("Settings", "Inställningar"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Notering"), ("Connection", "Anslutning"), ("Share Screen", "Dela skärm"), - ("CLOSE", "STÄNG"), - ("OPEN", "ÖPPNA"), ("Chat", "Chatt"), ("Total", "Totalt"), ("items", "föremål"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Sätter du på \"skärminspelning\" kommer tjänsten automatiskt att starta. Detta tillåter andra enheter att kontrollera din enhet."), ("android_stop_service_tip", "Genom att stänga av tjänsten kommer alla enheter att kopplas ifrån."), ("android_version_audio_tip", "Din version av Android stödjer inte ljudinspelning, Android 10 eller nyare krävs"), - ("android_start_service_tip", "Tryck på [Starta tjänsten] eller tillåt [skärminspelning] för att starta skärmdelning."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Konto"), ("Overwrite", "Skriv över"), ("This file exists, skip or overwrite this file?", "Filen finns redan, hoppa över eller skriv över filen?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Behåll RustDesk i bakgrunden"), ("Ignore Battery Optimizations", "Ignorera batterioptimering"), ("android_open_battery_optimizations_tip", "Om du vill stänga av denna funktion, gå till nästa RustDesk programs inställningar, hitta [Batteri], Checka ur [Obegränsad]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Anslutning ej tillåten"), ("Legacy mode", "Legacy mode"), ("Map mode", "Kartläge"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Säkerhet"), ("Theme", "Tema"), ("Dark Theme", "Mörkt tema"), + ("Light Theme", ""), ("Dark", "Mörk"), ("Light", "Ljus"), ("Follow System", "Följ system"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index dcdcc1289..085ed025d 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", ""), ("Login screen using Wayland is not supported", ""), ("Reboot required", ""), - ("Unsupported display server ", ""), + ("Unsupported display server", ""), ("x11 expected", ""), ("Port", ""), ("Settings", ""), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", ""), ("Connection", ""), ("Share Screen", ""), - ("CLOSE", ""), - ("OPEN", ""), ("Chat", ""), ("Total", ""), ("items", ""), @@ -291,6 +289,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_stop_service_tip", ""), ("android_version_audio_tip", ""), ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", ""), ("This file exists, skip or overwrite this file?", ""), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", ""), ("Ignore Battery Optimizations", ""), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", ""), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index a1eb34c54..6b3e34e48 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "คำเตือน"), ("Login screen using Wayland is not supported", "หน้าจอการเข้าสู่ระบบโดยใช้ Wayland ยังไม่ถูกรองรับ"), ("Reboot required", "จำเป็นต้องเริ่มต้นระบบใหม่"), - ("Unsupported display server ", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"), + ("Unsupported display server", "เซิร์ฟเวอร์การแสดงผลที่ไม่รองรับ"), ("x11 expected", "ต้องใช้งาน x11"), ("Port", "พอร์ท"), ("Settings", "ตั้งค่า"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "บันทึกข้อความ"), ("Connection", "การเชื่อมต่อ"), ("Share Screen", "แชร์หน้าจอ"), - ("CLOSE", "ปิด"), - ("OPEN", "เปิด"), ("Chat", "แชท"), ("Total", "รวม"), ("items", "รายการ"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "การเปิดการใช้งาน \"การบันทึกหน้าจอ\" จะเป็นการเริ่มต้นการทำงานของเซอร์วิสโดยอัตโนมัติ ที่จะอนุญาตให้อุปกรณ์อื่นๆ ส่งคำขอเข้าถึงมายังอุปกรณ์ของคุณได้"), ("android_stop_service_tip", "การปิดการใช้งานเซอร์วิสจะปิดการเชื่อมต่อทั้งหมดโดยอัตโนมัติ"), ("android_version_audio_tip", "เวอร์ชั่นแอนดรอยด์ปัจจุบันของคุณไม่รองรับการบันทึกข้อมูลเสียง กรุณาอัปเกรดเป็นแอนดรอยด์เวอร์ชั่น 10 หรือสูงกว่า"), - ("android_start_service_tip", "แตะ [เริ่มต้นใช้งานเซอร์วิส] หรือเปิดสิทธิ์ [การบันทึกหน้าจอ] เพื่อเริ่มเซอร์วิสการแชร์หน้าจอ"), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "บัญชี"), ("Overwrite", "เขียนทับ"), ("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "คงสถานะการทำงานเบื้องหลังของเซอร์วิส RustDesk"), ("Ignore Battery Optimizations", "เพิกเฉยการตั้งค่าการใช้งาน Battery Optimization"), ("android_open_battery_optimizations_tip", "หากคุณต้องการปิดการใช้งานฟีเจอร์นี้ กรุณาไปยังหน้าตั้งค่าในแอปพลิเคชัน RustDesk ค้นหาหัวข้อ [Battery] และยกเลิกการเลือกรายการ [Unrestricted]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "การเชื่อมต่อไม่อนุญาต"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "ความปลอดภัย"), ("Theme", "ธีม"), ("Dark Theme", "ธีมมืด"), + ("Light Theme", ""), ("Dark", "มืด"), ("Light", "สว่าง"), ("Follow System", "ตามระบบ"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 09c40a83f..c92044e39 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Uyarı"), ("Login screen using Wayland is not supported", "Wayland kullanan giriş ekranı desteklenmiyor"), ("Reboot required", "Yeniden başlatma gerekli"), - ("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"), + ("Unsupported display server", "Desteklenmeyen görüntü sunucusu"), ("x11 expected", "x11 bekleniyor"), ("Port", "Port"), ("Settings", "Ayarlar"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Not"), ("Connection", "Bağlantı"), ("Share Screen", "Ekranı Paylaş"), - ("CLOSE", "KAPAT"), - ("OPEN", "AÇ"), ("Chat", "Mesajlaş"), ("Total", "Toplam"), ("items", "öğeler"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Ekran Yakalamanın etkinleştirilmesi, hizmeti otomatik olarak başlatacak ve diğer cihazların bu cihazdan bağlantı talep etmesine izin verecektir."), ("android_stop_service_tip", "Hizmetin kapatılması, kurulan tüm bağlantıları otomatik olarak kapatacaktır."), ("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."), - ("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Hesap"), ("Overwrite", "üzerine yaz"), ("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "RustDesk arka plan hizmetini sürdürün"), ("Ignore Battery Optimizations", "Pil Optimizasyonlarını Yoksay"), ("android_open_battery_optimizations_tip", ""), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "bağlantıya izin verilmedi"), ("Legacy mode", "Eski mod"), ("Map mode", "Haritalama modu"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Güvenlik"), ("Theme", "Tema"), ("Dark Theme", "Koyu Tema"), + ("Light Theme", ""), ("Dark", "Koyu"), ("Light", "Açık"), ("Follow System", "Sisteme Uy"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index ca1193eaa..a16bf26b8 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -37,19 +37,19 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Clipboard is empty", "剪貼簿是空的"), ("Stop service", "停止服務"), ("Change ID", "更改 ID"), - ("Your new ID", ""), - ("length %min% to %max%", ""), - ("starts with a letter", ""), - ("allowed characters", ""), + ("Your new ID", "你的新 ID"), + ("length %min% to %max%", "長度在 %min% 與 %max% 之間"), + ("starts with a letter", "以字母開頭"), + ("allowed characters", "使用允許的字元"), ("id_change_tip", "僅能使用以下字元:a-z、A-Z、0-9、_ (底線)。首字元必須為 a-z 或 A-Z。長度介於 6 到 16 之間。"), ("Website", "網站"), ("About", "關於"), ("Slogan_tip", ""), - ("Privacy Statement", ""), + ("Privacy Statement", "隱私聲明"), ("Mute", "靜音"), - ("Build Date", ""), - ("Version", ""), - ("Home", ""), + ("Build Date", "建構日期"), + ("Version", "版本"), + ("Home", "主頁"), ("Audio Input", "音訊輸入"), ("Enhancements", "增強功能"), ("Hardware Codec", "硬件編解碼"), @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "警告"), ("Login screen using Wayland is not supported", "不支援使用 Wayland 的登入畫面"), ("Reboot required", "需要重新啟動"), - ("Unsupported display server ", "不支援顯示伺服器"), + ("Unsupported display server", "不支援顯示伺服器"), ("x11 expected", "預期 x11"), ("Port", "端口"), ("Settings", "設定"), @@ -213,15 +213,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Closed manually by the peer", "由對方手動關閉"), ("Enable remote configuration modification", "啟用遠端更改設定"), ("Run without install", "跳過安裝直接執行"), - ("Connect via relay", ""), + ("Connect via relay", "中繼連線"), ("Always connect via relay", "一律透過轉送連線"), ("whitelist_tip", "只有白名單中的 IP 可以存取"), ("Login", "登入"), - ("Verify", ""), - ("Remember me", ""), - ("Trust this device", ""), - ("Verification code", ""), - ("verification_tip", ""), + ("Verify", "驗證"), + ("Remember me", "記住我"), + ("Trust this device", "信任此設備"), + ("Verification code", "驗證碼"), + ("verification_tip", "檢測到新設備登錄,已向註冊郵箱發送了登入驗證碼,請輸入驗證碼繼續登錄"), ("Logout", "登出"), ("Tags", "標籤"), ("Search ID", "搜尋 ID"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "備註"), ("Connection", "連接"), ("Share Screen", "共享畫面"), - ("CLOSE", "關閉"), - ("OPEN", "開啟"), ("Chat", "聊天消息"), ("Total", "總計"), ("items", "個項目"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "開啟畫面錄製權限將自動開啟服務,允許其他裝置向此裝置請求建立連接。"), ("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連接。"), ("android_version_audio_tip", "目前的 Android 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"), - ("android_start_service_tip", "點擊 「啟動服務」 或啟用 「畫面錄製」 權限以開啟手機畫面共享服務。"), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", "對於已經建立的連接,權限可能不會立即發生改變,除非重新建立連接。"), ("Account", "賬戶"), ("Overwrite", "覆寫"), ("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "保持RustDesk後台服務"), ("Ignore Battery Optimizations", "忽略電池優化"), ("android_open_battery_optimizations_tip", "如需關閉此功能,請在接下來的RustDesk應用設置頁面中,找到並進入 [電源] 頁面,取消勾選 [不受限制]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "對方不允許連接"), ("Legacy mode", "傳統模式"), ("Map mode", "1:1傳輸"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "安全"), ("Theme", "主題"), ("Dark Theme", "暗黑主題"), + ("Light Theme", "明亮主題"), ("Dark", "黑暗"), ("Light", "明亮"), ("Follow System", "跟隨系統"), @@ -391,12 +393,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland 需要更高版本的 linux 發行版。 請嘗試 X11 桌面或更改您的操作系統。"), ("JumpLink", "查看"), ("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"), - ("Show RustDesk", ""), - ("This PC", ""), - ("or", ""), - ("Continue with", ""), + ("Show RustDesk", "顯示 RustDesk"), + ("This PC", "此電腦"), + ("or", "或"), + ("Continue with", "使用"), ("Elevate", "提權"), - ("Zoom cursor", ""), + ("Zoom cursor", "縮放游標"), ("Accept sessions via password", "只允許密碼訪問"), ("Accept sessions via click", "只允許點擊訪問"), ("Accept sessions via both", "允許密碼或點擊訪問"), @@ -407,9 +409,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Request access to your device", "請求訪問你的設備"), ("Hide connection management window", "隱藏連接管理窗口"), ("hide_cm_tip", "在只允許密碼連接並且只用固定密碼的情況下才允許隱藏"), - ("wayland_experiment_tip", ""), + ("wayland_experiment_tip", "Wayland 支持處於實驗階段,如果你需要使用無人值守訪問,請使用 X11。"), ("Right click to select tabs", "右鍵選擇選項卡"), - ("Skipped", ""), + ("Skipped", "已略過"), ("Add to Address Book", "添加到地址簿"), ("Group", "小組"), ("Search", "搜索"), @@ -418,8 +420,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Select local keyboard type", "請選擇本地鍵盤類型"), ("software_render_tip", "如果你使用英偉達顯卡, 並且遠程窗口在會話建立後會立刻關閉, 那麼安裝nouveau驅動並且選擇使用軟件渲染可能會有幫助。重啟軟件後生效。"), ("Always use software rendering", "使用軟件渲染"), - ("config_input", ""), - ("config_microphone", ""), + ("config_input", "為了能夠通過鍵盤控制遠程桌面, 請給予 RustDesk \"輸入監控\" 權限。"), + ("config_microphone", "為了支持通過麥克風進行音訊傳輸,請給予 RustDesk \"錄音\"權限。"), ("request_elevation_tip", "如果對面有人, 也可以請求提升權限。"), ("Wait", "等待"), ("Elevation Error", "提權失敗"), @@ -438,8 +440,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Weak", "弱"), ("Medium", "中"), ("Strong", "強"), - ("Switch Sides", ""), - ("Please confirm if you want to share your desktop?", ""), + ("Switch Sides", "反轉訪問方向"), + ("Please confirm if you want to share your desktop?", "請確認是否要讓對方訪問你的桌面?"), ("Display", "顯示"), ("Default View Style", "默認顯示方式"), ("Default Scroll Style", "默認滾動方式"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", "重連"), ("Codec", "編解碼"), ("Resolution", "分辨率"), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index b48385e6e..302dd9166 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Попередження"), ("Login screen using Wayland is not supported", "Вхід у систему з використанням Wayland не підтримується"), ("Reboot required", "Потрібне перезавантаження"), - ("Unsupported display server ", "Графічний сервер не підтримується"), + ("Unsupported display server", "Графічний сервер не підтримується"), ("x11 expected", "Очікується X11"), ("Port", "Порт"), ("Settings", "Налаштування"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Примітка"), ("Connection", "З'єднання"), ("Share Screen", "Поділитися екраном"), - ("CLOSE", "ЗАКРИТИ"), - ("OPEN", "ВІДКРИТИ"), ("Chat", "Чат"), ("Total", "Всього"), ("items", "елементи"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Увімкнення захоплення екрана автоматично запускає службу, дозволяючи іншим пристроям запитувати з'єднання з цього пристрою."), ("android_stop_service_tip", "Закриття служби автоматично закриє всі встановлені з'єднання."), ("android_version_audio_tip", "Поточна версія Android не підтримує захоплення звуку, оновіть її до Android 10 або вище."), - ("android_start_service_tip", "Натисніть [Запуск проміжного сервера] або ВІДКРИТИ роздільну здатність [Захоплення екрана], щоб запустити службу демонстрації екрана."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", "Акаунт"), ("Overwrite", "Перезаписати"), ("This file exists, skip or overwrite this file?", "Цей файл існує, пропустити або перезаписати файл?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Зберегти фонову службу RustDesk"), ("Ignore Battery Optimizations", "Ігнорувати оптимізацію батареї"), ("android_open_battery_optimizations_tip", "Перейдіть на наступну сторінку налаштувань"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Підключення не дозволено"), ("Legacy mode", "Застарілий режим"), ("Map mode", "Режим карти"), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", "Безпека"), ("Theme", "Тема"), ("Dark Theme", "Темна тема"), + ("Light Theme", ""), ("Dark", "Темна"), ("Light", "Світла"), ("Follow System", "Як у системі"), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 61d7c0b8a..e72be8ea5 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -204,7 +204,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Warning", "Cảnh báo"), ("Login screen using Wayland is not supported", "Màn hình đăng nhập sử dụng Wayland không đựoc hỗ trợ"), ("Reboot required", "Yêu cầu khởi động lại"), - ("Unsupported display server ", "Máy chủ hiển thị không đuợc hỗ trọ"), + ("Unsupported display server", "Máy chủ hiển thị không đuợc hỗ trọ"), ("x11 expected", "Cần x11"), ("Port", ""), ("Settings", "Cài đặt"), @@ -270,8 +270,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Note", "Ghi nhớ"), ("Connection", "Kết nối"), ("Share Screen", "Chia sẻ màn hình"), - ("CLOSE", "ĐÓNG"), - ("OPEN", "MỞ"), ("Chat", "Chat"), ("Total", "Tổng"), ("items", "items"), @@ -290,7 +288,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_service_will_start_tip", "Bật \"Ghi màn hình\" sẽ tự động khởi động dịch vụ, cho phép các thiết bị khác yêu cầu kết nối với thiết bị của bạn."), ("android_stop_service_tip", "Đóng dịch vụ sẽ tự động đóng tất cả các kết nối đã thiết lập."), ("android_version_audio_tip", "Phiên bản Android hiện tại không hỗ trợ ghi âm, vui lòng nâng cấp lên Android 10 trở lên."), - ("android_start_service_tip", "Nhấn vào [Bắt đầu dịch vụ] hoặc MỞ quyền [Ghi màn hình] để bắt đầu dịch vụ chia sẻ màn hình."), + ("android_start_service_tip", ""), + ("android_permission_may_not_change_tip", ""), ("Account", ""), ("Overwrite", "Ghi đè"), ("This file exists, skip or overwrite this file?", "Tệp tin này đã tồn tại, bạn có muốn bỏ qua hay ghi đè lên tệp tin này?"), @@ -312,6 +311,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Keep RustDesk background service", "Giữ dịch vụ nền RustDesk"), ("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"), ("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"), + ("Start on Boot", ""), + ("Start the screen sharing service on boot, requires special permissions", ""), ("Connection not allowed", "Kết nối không đuợc phép"), ("Legacy mode", ""), ("Map mode", ""), @@ -347,6 +348,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Security", ""), ("Theme", ""), ("Dark Theme", ""), + ("Light Theme", ""), ("Dark", ""), ("Light", ""), ("Follow System", ""), @@ -456,5 +458,24 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Reconnect", ""), ("Codec", ""), ("Resolution", ""), - ].iter().cloned().collect(); + ("No transfers in progress", ""), + ("Set one-time password length", ""), + ("idd_driver_tip", ""), + ("confirm_idd_driver_tip", ""), + ("RDP Settings", ""), + ("Sort by", ""), + ("New Connection", ""), + ("Restore", ""), + ("Minimize", ""), + ("Maximize", ""), + ("Your Device", ""), + ("empty_recent_tip", ""), + ("empty_favorite_tip", ""), + ("empty_lan_tip", ""), + ("empty_address_book_tip", ""), + ("eg: admin", ""), + ("Empty Username", ""), + ("Empty Password", ""), + ("Me", ""), + ].iter().cloned().collect(); } diff --git a/src/license.rs b/src/license.rs index f8f5d27d5..8875d2b64 100644 --- a/src/license.rs +++ b/src/license.rs @@ -1,3 +1,4 @@ +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use hbb_common::{bail, sodiumoxide::crypto::sign, ResultType}; use serde_derive::{Deserialize, Serialize}; @@ -18,7 +19,7 @@ fn get_license_from_string_(s: &str) -> ResultType { 12, 46, 129, 83, 17, 84, 193, 119, 197, 130, 103, ]; let pk = sign::PublicKey(*PK); - let data = base64::decode_config(tmp, base64::URL_SAFE_NO_PAD)?; + let data = URL_SAFE_NO_PAD.decode(tmp)?; if let Ok(lic) = serde_json::from_slice::(&data) { return Ok(lic); } diff --git a/src/main.rs b/src/main.rs index 169515425..3759f6056 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ -// Specify the Windows subsystem to eliminate console window. -// Requires Rust 1.18. -//#![windows_subsystem = "windows"] + #![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" + )] use librustdesk::*; diff --git a/src/naming.rs b/src/naming.rs index 38b514f86..53e675d92 100644 --- a/src/naming.rs +++ b/src/naming.rs @@ -1,10 +1,11 @@ mod license; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use hbb_common::ResultType; use license::*; fn gen_name(lic: &License) -> ResultType { let tmp = serde_json::to_vec::(lic)?; - let tmp = base64::encode_config(tmp, base64::URL_SAFE_NO_PAD); + let tmp = URL_SAFE_NO_PAD.encode(&tmp); let tmp: String = tmp.chars().rev().collect(); Ok(tmp) } diff --git a/src/ui/macos.rs b/src/platform/delegate.rs similarity index 97% rename from src/ui/macos.rs rename to src/platform/delegate.rs index cd0e5871b..01855536e 100644 --- a/src/ui/macos.rs +++ b/src/platform/delegate.rs @@ -26,7 +26,7 @@ const SHOW_SETTINGS_TAG: u32 = 2; const RUN_ME_TAG: u32 = 3; const AWAKE: u32 = 4; -trait AppHandler { +pub trait AppHandler { fn command(&mut self, cmd: u32); } @@ -63,9 +63,12 @@ impl AppHandler for Rc { } // https://github.com/xi-editor/druid/blob/master/druid-shell/src/platform/mac/application.rs -unsafe fn set_delegate(handler: Option>) { - let mut decl = - ClassDecl::new("AppDelegate", class!(NSObject)).expect("App Delegate definition failed"); +pub unsafe fn set_delegate(handler: Option>) { + let decl = ClassDecl::new("AppDelegate", class!(NSObject)); + if decl.is_none() { + return; + } + let mut decl = decl.unwrap(); decl.add_ivar::<*mut c_void>(APP_HANDLER_IVAR); decl.add_method( diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 08e343d49..47184e796 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,14 +1,22 @@ use super::{CursorData, ResultType}; -use hbb_common::libc::{c_char, c_int, c_long, c_void}; pub use hbb_common::platform::linux::*; -use hbb_common::{allow_err, anyhow::anyhow, bail, log, message_proto::Resolution}; +use hbb_common::{ + allow_err, + anyhow::anyhow, + bail, + libc::{c_char, c_int, c_long, c_void}, + log, + message_proto::Resolution, +}; use std::{ cell::RefCell, - path::PathBuf, + path::{Path, PathBuf}, + process::{Child, Command}, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, + time::{Duration, Instant}, }; use xrandr_parser::Parser; @@ -162,10 +170,29 @@ fn start_uinput_service() { }); } -fn stop_server(server: &mut Option) { +#[inline] +fn try_start_server_(user: Option<(String, String)>) -> ResultType> { + if user.is_some() { + run_as_user(vec!["--server"], user) + } else { + Ok(Some(crate::run_me(vec!["--server"])?)) + } +} + +#[inline] +fn start_server(user: Option<(String, String)>, server: &mut Option) { + match try_start_server_(user) { + Ok(ps) => *server = ps, + Err(err) => { + log::error!("Failed to start server: {}", err); + } + } +} + +fn stop_server(server: &mut Option) { if let Some(mut ps) = server.take() { allow_err!(ps.kill()); - std::thread::sleep(std::time::Duration::from_millis(30)); + std::thread::sleep(Duration::from_millis(30)); match ps.try_wait() { Ok(Some(_status)) => {} Ok(None) => { @@ -182,7 +209,7 @@ fn set_x11_env(uid: &str) { let mut auth = get_env_tries("XAUTHORITY", uid, 10); // auth is another user's when uid = 0, https://github.com/rustdesk/rustdesk/issues/2468 if auth.is_empty() || uid == "0" { - auth = if std::path::Path::new(&gdm).exists() { + auth = if Path::new(&gdm).exists() { gdm } else { let username = get_active_username(); @@ -190,7 +217,7 @@ fn set_x11_env(uid: &str) { format!("/{}/.Xauthority", username) } else { let tmp = format!("/home/{}/.Xauthority", username); - if std::path::Path::new(&tmp).exists() { + if Path::new(&tmp).exists() { tmp } else { format!("/var/lib/{}/.Xauthority", username) @@ -223,8 +250,8 @@ fn should_start_server( uid: &mut String, cur_uid: String, cm0: &mut bool, - last_restart: &mut std::time::Instant, - server: &mut Option, + last_restart: &mut Instant, + server: &mut Option, ) -> bool { let cm = get_cm(); let mut start_new = false; @@ -235,8 +262,8 @@ fn should_start_server( } if let Some(ps) = server.as_mut() { allow_err!(ps.kill()); - std::thread::sleep(std::time::Duration::from_millis(30)); - *last_restart = std::time::Instant::now(); + std::thread::sleep(Duration::from_millis(30)); + *last_restart = Instant::now(); } } else if !cm && ((*cm0 && last_restart.elapsed().as_secs() > 60) @@ -247,8 +274,8 @@ fn should_start_server( // and x server get displays failure issue if let Some(ps) = server.as_mut() { allow_err!(ps.kill()); - std::thread::sleep(std::time::Duration::from_millis(30)); - *last_restart = std::time::Instant::now(); + std::thread::sleep(Duration::from_millis(30)); + *last_restart = Instant::now(); log::info!("restart server"); } } @@ -267,6 +294,13 @@ fn should_start_server( start_new } +// to-do: stop_server(&mut user_server); may not stop child correctly +// stop_rustdesk_servers() is just a temp solution here. +fn force_stop_server() { + stop_rustdesk_servers(); + std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL)); +} + pub fn start_os_service() { stop_rustdesk_servers(); start_uinput_service(); @@ -274,8 +308,8 @@ pub fn start_os_service() { let running = Arc::new(AtomicBool::new(true)); let r = running.clone(); let mut uid = "".to_owned(); - let mut server: Option = None; - let mut user_server: Option = None; + let mut server: Option = None; + let mut user_server: Option = None; if let Err(err) = ctrlc::set_handler(move || { r.store(false, Ordering::SeqCst); }) { @@ -283,12 +317,13 @@ pub fn start_os_service() { } let mut cm0 = false; - let mut last_restart = std::time::Instant::now(); + let mut last_restart = Instant::now(); while running.load(Ordering::SeqCst) { let (cur_uid, cur_user) = get_active_user_id_name(); let is_wayland = current_is_wayland(); if cur_user == "root" || !is_wayland { + // try kill subprocess "--server" stop_server(&mut user_server); // try start subprocess "--server" if should_start_server( @@ -299,16 +334,8 @@ pub fn start_os_service() { &mut last_restart, &mut server, ) { - // to-do: stop_server(&mut user_server); may not stop child correctly - // stop_rustdesk_servers() is just a temp solution here. - stop_rustdesk_servers(); - std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL)); - match crate::run_me(vec!["--server"]) { - Ok(ps) => server = Some(ps), - Err(err) => { - log::error!("Failed to start server: {}", err); - } - } + force_stop_server(); + start_server(None, &mut server); } } else if cur_user != "" { if cur_user != "gdm" { @@ -324,23 +351,16 @@ pub fn start_os_service() { &mut last_restart, &mut user_server, ) { - stop_rustdesk_servers(); - std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL)); - match run_as_user(vec!["--server"], Some((cur_uid, cur_user))) { - Ok(ps) => user_server = ps, - Err(err) => { - log::error!("Failed to start server: {}", err); - } - } + force_stop_server(); + start_server(Some((cur_uid, cur_user)), &mut user_server); } } } else { - stop_rustdesk_servers(); - std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL)); + force_stop_server(); stop_server(&mut user_server); stop_server(&mut server); } - std::thread::sleep(std::time::Duration::from_millis(super::SERVICE_INTERVAL)); + std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL)); } if let Some(ps) = user_server.take().as_mut() { @@ -362,7 +382,7 @@ pub fn get_active_userid() -> String { } fn get_cm() -> bool { - if let Ok(output) = std::process::Command::new("ps").args(vec!["aux"]).output() { + if let Ok(output) = Command::new("ps").args(vec!["aux"]).output() { for line in String::from_utf8_lossy(&output.stdout).lines() { if line.contains(&format!( "{} --cm", @@ -380,7 +400,7 @@ fn get_cm() -> bool { fn get_display() -> String { let user = get_active_username(); log::debug!("w {}", &user); - if let Ok(output) = std::process::Command::new("w").arg(&user).output() { + if let Ok(output) = Command::new("w").arg(&user).output() { for line in String::from_utf8_lossy(&output.stdout).lines() { log::debug!(" {}", line); let mut iter = line.split_whitespace(); @@ -395,7 +415,7 @@ fn get_display() -> String { // above not work for gdm user log::debug!("ls -l /tmp/.X11-unix/"); let mut last = "".to_owned(); - if let Ok(output) = std::process::Command::new("ls") + if let Ok(output) = Command::new("ls") .args(vec!["-l", "/tmp/.X11-unix/"]) .output() { @@ -474,10 +494,7 @@ fn is_opensuse() -> bool { false } -pub fn run_as_user( - arg: Vec<&str>, - user: Option<(String, String)>, -) -> ResultType> { +pub fn run_as_user(arg: Vec<&str>, user: Option<(String, String)>) -> ResultType> { let (uid, username) = match user { Some(id_name) => id_name, None => get_active_user_id_name(), @@ -491,7 +508,7 @@ pub fn run_as_user( args.insert(0, "-E"); } - let task = std::process::Command::new("sudo").args(args).spawn()?; + let task = Command::new("sudo").args(args).spawn()?; Ok(Some(task)) } @@ -553,10 +570,7 @@ pub fn get_default_pa_source() -> Option<(String, String)> { } pub fn lock_screen() { - std::process::Command::new("xdg-screensaver") - .arg("lock") - .spawn() - .ok(); + Command::new("xdg-screensaver").arg("lock").spawn().ok(); } pub fn toggle_blank_screen(_v: bool) { @@ -577,7 +591,7 @@ fn get_env_tries(name: &str, uid: &str, n: usize) -> String { if !x.is_empty() { return x; } - std::thread::sleep(std::time::Duration::from_millis(300)); + std::thread::sleep(Duration::from_millis(300)); } "".to_owned() } @@ -604,12 +618,12 @@ pub fn quit_gui() { pub fn check_super_user_permission() -> ResultType { let file = "/usr/share/rustdesk/files/polkit"; let arg; - if std::path::Path::new(file).is_file() { + if Path::new(file).is_file() { arg = file; } else { arg = "echo"; } - let status = std::process::Command::new("pkexec").arg(arg).status()?; + let status = Command::new("pkexec").arg(arg).status()?; Ok(status.success() && status.code() == Some(0)) } @@ -684,7 +698,7 @@ pub fn current_resolution(name: &str) -> ResultType { } pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType<()> { - std::process::Command::new("xrandr") + Command::new("xrandr") .args(vec![ "--output", name, diff --git a/src/platform/macos.mm b/src/platform/macos.mm index 443351469..a252a9a8f 100644 --- a/src/platform/macos.mm +++ b/src/platform/macos.mm @@ -1,10 +1,16 @@ #import #import #import +#include +#include + // https://github.com/codebytere/node-mac-permissions/blob/main/permissions.mm extern "C" bool InputMonitoringAuthStatus(bool prompt) { + #ifdef NO_InputMonitoringAuthStatus + return true; + #else if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_15) { IOHIDAccessType theType = IOHIDCheckAccess(kIOHIDRequestTypeListenEvent); NSLog(@"IOHIDCheckAccess = %d, kIOHIDAccessTypeGranted = %d", theType, kIOHIDAccessTypeGranted); @@ -33,6 +39,34 @@ extern "C" bool InputMonitoringAuthStatus(bool prompt) { return true; } return false; + #endif +} + +extern "C" bool MacCheckAdminAuthorization() { + AuthorizationRef authRef; + OSStatus status; + + status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults, &authRef); + if (status != errAuthorizationSuccess) { + printf("Failed to create AuthorizationRef\n"); + return false; + } + + AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights authRights = {1, &authItem}; + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); + if (status != errAuthorizationSuccess) { + printf("Failed to authorize\n"); + return false; + } + + AuthorizationFree(authRef, kAuthorizationFlagDefaults); + return true; } extern "C" float BackingScaleFactor() { @@ -44,6 +78,33 @@ extern "C" float BackingScaleFactor() { // https://github.com/jhford/screenresolution/blob/master/cg_utils.c // https://github.com/jdoupe/screenres/blob/master/setgetscreen.m +size_t bitDepth(CGDisplayModeRef mode) { + size_t depth = 0; + // Deprecated, same display same bpp? + // https://stackoverflow.com/questions/8210824/how-to-avoid-cgdisplaymodecopypixelencoding-to-get-bpp + // https://github.com/libsdl-org/SDL/pull/6628 + CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode); + // my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels + // are made up and possibly non-sensical + if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO32BitFloatPixels), kCFCompareCaseInsensitive)) { + depth = 96; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO64BitDirectPixels), kCFCompareCaseInsensitive)) { + depth = 64; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO16BitFloatPixels), kCFCompareCaseInsensitive)) { + depth = 48; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive)) { + depth = 32; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive)) { + depth = 30; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive)) { + depth = 16; + } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive)) { + depth = 8; + } + CFRelease(pixelEncoding); + return depth; +} + extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) { CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); if (allModes == NULL) { @@ -55,16 +116,28 @@ extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) { } extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) { - CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); - if (allModes == NULL) { + CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display); + if (currentMode == NULL) { return false; } - *numModes = CFArrayGetCount(allModes); - for (int i = 0; i < *numModes && i < max; i++) { - CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); - widths[i] = (uint32_t)CGDisplayModeGetWidth(mode); - heights[i] = (uint32_t)CGDisplayModeGetHeight(mode); + CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + if (allModes == NULL) { + CGDisplayModeRelease(currentMode); + return false; } + uint32_t allModeCount = CFArrayGetCount(allModes); + uint32_t realNum = 0; + for (uint32_t i = 0; i < allModeCount && realNum < max; i++) { + CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); + if (CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) && + bitDepth(currentMode) == bitDepth(mode)) { + widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode); + heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode); + realNum++; + } + } + *numModes = realNum; + CGDisplayModeRelease(currentMode); CFRelease(allModes); return true; } @@ -80,31 +153,8 @@ extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t return true; } -size_t bitDepth(CGDisplayModeRef mode) { - size_t depth = 0; - CFStringRef pixelEncoding = CGDisplayModeCopyPixelEncoding(mode); - // my numerical representation for kIO16BitFloatPixels and kIO32bitFloatPixels - // are made up and possibly non-sensical - if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO32BitFloatPixels), kCFCompareCaseInsensitive)) { - depth = 96; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO64BitDirectPixels), kCFCompareCaseInsensitive)) { - depth = 64; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO16BitFloatPixels), kCFCompareCaseInsensitive)) { - depth = 48; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive)) { - depth = 32; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(kIO30BitDirectPixels), kCFCompareCaseInsensitive)) { - depth = 30; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive)) { - depth = 16; - } else if (kCFCompareEqualTo == CFStringCompare(pixelEncoding, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive)) { - depth = 8; - } - CFRelease(pixelEncoding); - return depth; -} -bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { +static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { CGError rc; CGDisplayConfigRef config; rc = CGBeginDisplayConfiguration(&config); @@ -122,7 +172,6 @@ bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { return true; } - extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height) { bool ret = false; @@ -136,13 +185,12 @@ extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t h return ret; } int numModes = CFArrayGetCount(allModes); - CGDisplayModeRef bestMode = NULL; for (int i = 0; i < numModes; i++) { CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); if (width == CGDisplayModeGetWidth(mode) && height == CGDisplayModeGetHeight(mode) && - bitDepth(currentMode) == bitDepth(mode) && - CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode)) { + CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) && + bitDepth(currentMode) == bitDepth(mode)) { ret = setDisplayToMode(display, mode); break; } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 025274840..251211c29 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -34,6 +34,7 @@ extern "C" { static kAXTrustedCheckOptionPrompt: CFStringRef; fn AXIsProcessTrustedWithOptions(options: CFDictionaryRef) -> BOOL; fn InputMonitoringAuthStatus(_: BOOL) -> BOOL; + fn MacCheckAdminAuthorization() -> BOOL; fn MacGetModeNum(display: u32, numModes: *mut u32) -> BOOL; fn MacGetModes( display: u32, @@ -580,7 +581,7 @@ fn check_main_window() -> bool { sys.refresh_processes(); let app = format!("/Applications/{}.app", crate::get_app_name()); let my_uid = sys - .process((std::process::id() as i32).into()) + .process((std::process::id() as usize).into()) .map(|x| x.user_id()) .unwrap_or_default(); for (_, p) in sys.processes().iter() { @@ -612,18 +613,18 @@ pub fn resolutions(name: &str) -> Vec { unsafe { if YES == MacGetModeNum(display, &mut num) { let (mut widths, mut heights) = (vec![0; num as _], vec![0; num as _]); - let mut realNum = 0; + let mut real_num = 0; if YES == MacGetModes( display, widths.as_mut_ptr(), heights.as_mut_ptr(), num, - &mut realNum, + &mut real_num, ) { - if realNum <= num { - for i in 0..realNum { + if real_num <= num { + for i in 0..real_num { let resolution = Resolution { width: widths[i as usize] as _, height: heights[i as usize] as _, @@ -665,3 +666,7 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType< } Ok(()) } + +pub fn check_super_user_permission() -> ResultType { + unsafe { Ok(MacCheckAdminAuthorization() == YES) } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ad058d4c0..f2b609d3f 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,6 +11,9 @@ pub mod windows; #[cfg(target_os = "macos")] pub mod macos; +#[cfg(target_os = "macos")] +pub mod delegate; + #[cfg(target_os = "linux")] pub mod linux; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 6b3f8013c..696a18ab9 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -976,7 +976,7 @@ fn get_after_install(exe: &str) -> String { } pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> { - let uninstall_str = get_uninstall(); + let uninstall_str = get_uninstall(false); let mut path = path.trim_end_matches('\\').to_owned(); let (subkey, _path, start_menu, exe) = get_default_install_info(); let mut exe = exe; @@ -1108,6 +1108,12 @@ if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name} ); let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_string(); + let install_cert = if options.contains("driverCert") { + format!("\"{}\" --install-cert \"RustDeskIddDriver.cer\"", src_exe) + } else { + "".to_owned() + }; + let cmds = format!( " {uninstall_str} @@ -1139,6 +1145,7 @@ sc create {app_name} binpath= \"\\\"{exe}\\\" --import-config \\\"{config_path}\ sc start {app_name} sc stop {app_name} sc delete {app_name} +{install_cert} {after_install} {sleep} ", @@ -1159,6 +1166,7 @@ sc delete {app_name} shortcuts=shortcuts, config_path=Config::file().to_str().unwrap_or(""), lic=register_licence(), + install_cert=install_cert, after_install=get_after_install(&exe), sleep=if debug { "timeout 300" @@ -1188,30 +1196,35 @@ pub fn run_after_install() -> ResultType<()> { } pub fn run_before_uninstall() -> ResultType<()> { - run_cmds(get_before_uninstall(), true, "before_install") + run_cmds(get_before_uninstall(true), true, "before_install") } -fn get_before_uninstall() -> String { +fn get_before_uninstall(kill_self: bool) -> String { let app_name = crate::get_app_name(); let ext = app_name.to_lowercase(); + let filter = if kill_self { + "".to_string() + } else { + format!(" /FI \"PID ne {}\"", get_current_pid()) + }; format!( " chcp 65001 sc stop {app_name} sc delete {app_name} taskkill /F /IM {broker_exe} - taskkill /F /IM {app_name}.exe /FI \"PID ne {cur_pid}\" + taskkill /F /IM {app_name}.exe{filter} reg delete HKEY_CLASSES_ROOT\\.{ext} /f netsh advfirewall firewall delete rule name=\"{app_name} Service\" ", app_name = app_name, broker_exe = crate::win_privacy::INJECTED_PROCESS_EXE, ext = ext, - cur_pid = get_current_pid(), + filter = filter, ) } -fn get_uninstall() -> String { +fn get_uninstall(kill_self: bool) -> String { let (subkey, path, start_menu, _) = get_install_info(); format!( " @@ -1222,7 +1235,7 @@ fn get_uninstall() -> String { if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\" if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" ", - before_uninstall=get_before_uninstall(), + before_uninstall=get_before_uninstall(kill_self), subkey=subkey, app_name = crate::get_app_name(), path = path, @@ -1230,12 +1243,22 @@ fn get_uninstall() -> String { ) } -pub fn uninstall_me() -> ResultType<()> { - run_cmds(get_uninstall(), true, "uninstall") +pub fn uninstall_me(kill_self: bool) -> ResultType<()> { + allow_err!(cert::uninstall_certs()); + run_cmds(get_uninstall(kill_self), true, "uninstall") } fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType { let mut tmp = std::env::temp_dir(); + // When dir contains these characters, the bat file will not execute in elevated mode. + if vec!["&", "@", "^"] + .drain(..) + .any(|s| tmp.to_string_lossy().to_string().contains(s)) + { + if let Ok(dir) = user_accessible_folder() { + tmp = dir; + } + } tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext)); let mut file = std::fs::File::create(&tmp)?; // in case cmds mixed with \r\n and \n, make sure all ending with \r\n @@ -1872,3 +1895,193 @@ pub fn change_resolution(name: &str, width: usize, height: usize) -> ResultType< Ok(()) } } + +pub fn user_accessible_folder() -> ResultType { + let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string()); + let dir1 = PathBuf::from(format!("{}\\ProgramData", disk)); + // NOTICE: "C:\Windows\Temp" requires permanent authorization. + let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk)); + let dir; + if dir1.exists() { + dir = dir1; + } else if dir2.exists() { + dir = dir2; + } else { + bail!("no vaild user accessible folder"); + } + Ok(dir) +} + +#[inline] +pub fn install_cert(cert_file: &str) -> ResultType<()> { + let exe_file = std::env::current_exe()?; + if let Some(cur_dir) = exe_file.parent() { + allow_err!(cert::install_cert(cur_dir.join(cert_file))); + } else { + bail!( + "Invalid exe parent for {}", + exe_file.to_string_lossy().as_ref() + ); + } + Ok(()) +} + +mod cert { + use hbb_common::{allow_err, bail, log, ResultType}; + use std::{path::Path, str::from_utf8}; + use winapi::shared::{ + minwindef::{BYTE, DWORD, TRUE}, + ntdef::NULL, + }; + use winapi::um::{ + errhandlingapi::GetLastError, + wincrypt::{ + CertCloseStore, CertEnumCertificatesInStore, CertNameToStrA, CertOpenSystemStoreA, + CryptHashCertificate, ALG_ID, CALG_SHA1, CERT_ID_SHA1_HASH, CERT_X500_NAME_STR, + PCCERT_CONTEXT, + }, + winreg::HKEY_LOCAL_MACHINE, + }; + use winreg::{ + enums::{KEY_WRITE, REG_BINARY}, + RegKey, + }; + + const ROOT_CERT_STORE_PATH: &str = "SOFTWARE\\Microsoft\\SystemCertificates\\ROOT\\Certificates\\"; + const THUMBPRINT_ALG: ALG_ID = CALG_SHA1; + const THUMBPRINT_LEN: DWORD = 20; + + #[inline] + unsafe fn compute_thumbprint(pb_encoded: *const BYTE, cb_encoded: DWORD) -> (Vec, String) { + let mut size = THUMBPRINT_LEN; + let mut thumbprint = [0u8; THUMBPRINT_LEN as usize]; + if CryptHashCertificate( + 0, + THUMBPRINT_ALG, + 0, + pb_encoded, + cb_encoded, + thumbprint.as_mut_ptr(), + &mut size, + ) == TRUE + { + (thumbprint.to_vec(), hex::encode(thumbprint).to_ascii_uppercase()) + } else { + (thumbprint.to_vec(), "".to_owned()) + } + } + + #[inline] + unsafe fn open_reg_cert_store() -> ResultType { + let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); + Ok(hklm.open_subkey_with_flags(ROOT_CERT_STORE_PATH, KEY_WRITE)?) + } + + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpef/6a9e35fa-2ac7-4c10-81e1-eabe8d2472f1 + fn create_cert_blob(thumbprint: Vec, encoded: Vec) -> Vec { + let mut blob = Vec::new(); + + let mut property_id = (CERT_ID_SHA1_HASH as u32).to_le_bytes().to_vec(); + let mut pro_reserved = [0x01, 0x00, 0x00, 0x00].to_vec(); + let mut pro_length = (THUMBPRINT_LEN as u32).to_le_bytes().to_vec(); + let mut pro_val = thumbprint; + blob.append(&mut property_id); + blob.append(&mut pro_reserved); + blob.append(&mut pro_length); + blob.append(&mut pro_val); + + let mut blob_reserved = [0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00].to_vec(); + let mut blob_length = (encoded.len() as u32).to_le_bytes().to_vec(); + let mut blob_val = encoded; + blob.append(&mut blob_reserved); + blob.append(&mut blob_length); + blob.append(&mut blob_val); + + blob + } + + pub fn install_cert>(path: P) -> ResultType<()> { + let mut cert_bytes = std::fs::read(path)?; + unsafe { + let thumbprint = compute_thumbprint(cert_bytes.as_mut_ptr(), cert_bytes.len() as _); + log::debug!("Thumbprint of cert {}", &thumbprint.1); + + let reg_cert_key = open_reg_cert_store()?; + let (cert_key, _) = reg_cert_key.create_subkey(&thumbprint.1)?; + let data = winreg::RegValue { + vtype: REG_BINARY, + bytes: create_cert_blob(thumbprint.0, cert_bytes), + }; + cert_key.set_raw_value("Blob", &data)?; + } + Ok(()) + } + + fn get_thumbprints_to_rm() -> ResultType> { + let issuers_to_rm = ["CN=\"WDKTestCert admin,133225435702113567\""]; + + let mut thumbprints = Vec::new(); + let mut buf = [0u8; 1024]; + + unsafe { + let store_handle = CertOpenSystemStoreA(0 as _, "ROOT\0".as_ptr() as _); + if store_handle.is_null() { + bail!("Error opening certificate store: {}", GetLastError()); + } + + let mut cert_ctx: PCCERT_CONTEXT = CertEnumCertificatesInStore(store_handle, NULL as _); + while !cert_ctx.is_null() { + // https://stackoverflow.com/a/66432736 + let cb_size = CertNameToStrA( + (*cert_ctx).dwCertEncodingType, + &mut ((*(*cert_ctx).pCertInfo).Issuer) as _, + CERT_X500_NAME_STR, + buf.as_mut_ptr() as _, + buf.len() as _, + ); + if cb_size != 1 { + if let Ok(issuer) = from_utf8(&buf[..cb_size as _]) { + for iss in issuers_to_rm.iter() { + if issuer.contains(iss) { + let (_, thumbprint) = compute_thumbprint( + (*cert_ctx).pbCertEncoded, + (*cert_ctx).cbCertEncoded, + ); + if !thumbprint.is_empty() { + thumbprints.push(thumbprint); + } + } + } + } + } + cert_ctx = CertEnumCertificatesInStore(store_handle, cert_ctx); + } + CertCloseStore(store_handle, 0); + } + + Ok(thumbprints) + } + + pub fn uninstall_certs() -> ResultType<()> { + let thumbprints = get_thumbprints_to_rm()?; + let reg_cert_key = unsafe { open_reg_cert_store()? }; + for thumbprint in thumbprints.iter() { + allow_err!(reg_cert_key.delete_subkey(thumbprint)); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_install_cert() { + println!("install driver cert: {:?}", cert::install_cert("RustDeskIddDriver.cer")); + } + + #[test] + fn test_uninstall_cert() { + println!("uninstall driver certs: {:?}", cert::uninstall_certs()); + } +} diff --git a/src/server/connection.rs b/src/server/connection.rs index 898939b62..7899009c4 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -541,7 +541,10 @@ impl Connection { conn.reset_resolution(); ALIVE_CONNS.lock().unwrap().retain(|&c| c != id); if let Some(s) = conn.server.upgrade() { - s.write().unwrap().remove_connection(&conn.inner); + let mut s = s.write().unwrap(); + s.remove_connection(&conn.inner); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + try_stop_record_cursor_pos(); } log::info!("#{} connection loop exited", id); } @@ -735,7 +738,7 @@ impl Connection { let url = self.server_audit_conn.clone(); let mut v = v; v["id"] = json!(Config::get_id()); - v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); + v["uuid"] = json!(crate::encode64(hbb_common::get_uuid())); v["conn_id"] = json!(self.inner.id); tokio::spawn(async move { allow_err!(Self::post_audit_async(url, v).await); @@ -765,7 +768,7 @@ impl Connection { info["files"] = json!(files); let v = json!({ "id":json!(Config::get_id()), - "uuid":json!(base64::encode(hbb_common::get_uuid())), + "uuid":json!(crate::encode64(hbb_common::get_uuid())), "peer_id":json!(self.lr.my_id), "type": r#type as i8, "path":path, @@ -788,7 +791,7 @@ impl Connection { } let mut v = Value::default(); v["id"] = json!(Config::get_id()); - v["uuid"] = json!(base64::encode(hbb_common::get_uuid())); + v["uuid"] = json!(crate::encode64(hbb_common::get_uuid())); v["typ"] = json!(typ as i8); v["from_remote"] = json!(from_remote); v["info"] = serde_json::Value::String(info.to_string()); @@ -948,9 +951,10 @@ impl Connection { if !self.audio_enabled() { noperms.push(super::audio_service::NAME); } - s.write() - .unwrap() - .add_connection(self.inner.clone(), &noperms); + let mut s = s.write().unwrap(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + try_start_record_cursor_pos(); + s.add_connection(self.inner.clone(), &noperms); } } } @@ -1738,6 +1742,7 @@ impl Connection { self.lock_after_session_end = q == BoolOption::Yes; } } + #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Ok(q) = o.show_remote_cursor.enum_value() { if q != BoolOption::NotSet { self.show_remote_cursor = q == BoolOption::Yes; @@ -2008,10 +2013,12 @@ async fn start_ipc( for _ in 0..10 { #[cfg(not(target_os = "linux"))] { + log::debug!("Start cm"); res = crate::platform::run_as_user(args.clone()); } #[cfg(target_os = "linux")] { + log::debug!("Start cm"); res = crate::platform::run_as_user(args.clone(), None); } if res.is_ok() { @@ -2027,12 +2034,13 @@ async fn start_ipc( run_done = false; } if !run_done { + log::debug!("Start cm"); super::CHILD_PROCESS .lock() .unwrap() .push(crate::run_me(args)?); } - for _ in 0..10 { + for _ in 0..20 { sleep(0.3).await; if let Ok(s) = crate::ipc::connect(1000, "_cm").await { stream = Some(s); diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 917a815bb..f205e3f02 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -13,7 +13,8 @@ use std::{ convert::TryFrom, ops::Sub, sync::atomic::{AtomicBool, Ordering}, - time::Instant, + thread, + time::{self, Instant}, }; const INVALID_CURSOR_POS: i32 = i32::MIN; @@ -128,6 +129,7 @@ pub fn new_pos() -> GenericService { sp } +#[inline] fn update_last_cursor_pos(x: i32, y: i32) { let mut lock = LATEST_SYS_CURSOR_POS.lock().unwrap(); if lock.1 .0 != x || lock.1 .1 != y { @@ -136,29 +138,31 @@ fn update_last_cursor_pos(x: i32, y: i32) { } fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> { - if let Some((x, y)) = crate::get_cursor_pos() { - update_last_cursor_pos(x, y); - if state.is_moved(x, y) { - let mut msg_out = Message::new(); - msg_out.set_cursor_position(CursorPosition { - x, - y, - ..Default::default() - }); - let exclude = { - let now = get_time(); - let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap(); - if now - lock.time < 300 { - lock.conn - } else { - 0 - } - }; - sp.send_without(msg_out, exclude); - } - state.cursor_pos = (x, y); + let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap(); + if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS { + return Ok(()); } + if state.is_moved(x, y) { + let mut msg_out = Message::new(); + msg_out.set_cursor_position(CursorPosition { + x, + y, + ..Default::default() + }); + let exclude = { + let now = get_time(); + let lock = LATEST_PEER_INPUT_CURSOR.lock().unwrap(); + if now - lock.time < 300 { + lock.conn + } else { + 0 + } + }; + sp.send_without(msg_out, exclude); + } + state.cursor_pos = (x, y); + sp.snapshot(|sps| { let mut msg_out = Message::new(); msg_out.set_cursor_position(CursorPosition { @@ -213,7 +217,7 @@ lazy_static::lazy_static! { }; static ref KEYS_DOWN: Arc>> = Default::default(); static ref LATEST_PEER_INPUT_CURSOR: Arc> = Default::default(); - static ref LATEST_SYS_CURSOR_POS: Arc> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0)))); + static ref LATEST_SYS_CURSOR_POS: Arc> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (INVALID_CURSOR_POS, INVALID_CURSOR_POS)))); } static EXITING: AtomicBool = AtomicBool::new(false); @@ -221,6 +225,42 @@ const MOUSE_MOVE_PROTECTION_TIMEOUT: Duration = Duration::from_millis(1_000); // Actual diff of (x,y) is (1,1) here. But 5 may be tolerant. const MOUSE_ACTIVE_DISTANCE: i32 = 5; +static RECORD_CURSOR_POS_RUNNING: AtomicBool = AtomicBool::new(false); + +pub fn try_start_record_cursor_pos() { + if RECORD_CURSOR_POS_RUNNING.load(Ordering::SeqCst) { + return; + } + + RECORD_CURSOR_POS_RUNNING.store(true, Ordering::SeqCst); + thread::spawn(|| { + let interval = time::Duration::from_millis(33); + loop { + if !RECORD_CURSOR_POS_RUNNING.load(Ordering::SeqCst) { + break; + } + + let now = time::Instant::now(); + if let Some((x, y)) = crate::get_cursor_pos() { + update_last_cursor_pos(x, y); + } + let elapsed = now.elapsed(); + if elapsed < interval { + thread::sleep(interval - elapsed); + } + } + update_last_cursor_pos(INVALID_CURSOR_POS, INVALID_CURSOR_POS); + }); +} + +pub fn try_stop_record_cursor_pos() { + let count_lock = CONN_COUNT.lock().unwrap(); + if *count_lock > 0 { + return; + } + RECORD_CURSOR_POS_RUNNING.store(false, Ordering::SeqCst); +} + // mac key input must be run in main thread, otherwise crash on >= osx 10.15 #[cfg(target_os = "macos")] lazy_static::lazy_static! { @@ -717,11 +757,8 @@ pub fn handle_key(evt: &KeyEvent) { fn reset_input() { unsafe { let _lock = VIRTUAL_INPUT_MTX.lock(); - VIRTUAL_INPUT = VirtualInput::new( - CGEventSourceStateID::Private, - CGEventTapLocation::Session, - ) - .ok(); + VIRTUAL_INPUT = + VirtualInput::new(CGEventSourceStateID::Private, CGEventTapLocation::Session).ok(); } } @@ -1095,8 +1132,7 @@ fn translate_keyboard_mode(evt: &KeyEvent) { Some(key_event::Union::Seq(seq)) => { ENIGO.lock().unwrap().key_sequence(seq); } - Some(key_event::Union::Chr(..)) => - { + Some(key_event::Union::Chr(..)) => { #[cfg(target_os = "windows")] translate_process_code(evt.chr(), evt.down); #[cfg(not(target_os = "windows"))] diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs index 7514ead38..c49f974a7 100644 --- a/src/server/portable_service.rs +++ b/src/server/portable_service.rs @@ -117,17 +117,7 @@ impl SharedMemory { } fn flink(name: String) -> ResultType { - let disk = std::env::var("SystemDrive").unwrap_or("C:".to_string()); - let dir1 = PathBuf::from(format!("{}\\ProgramData", disk)); - let dir2 = PathBuf::from(format!("{}\\Windows\\Temp", disk)); - let mut dir; - if dir1.exists() { - dir = dir1; - } else if dir2.exists() { - dir = dir2; - } else { - bail!("no vaild flink directory"); - } + let mut dir = crate::platform::user_accessible_folder()?; dir = dir.join(hbb_common::config::APP_NAME.read().unwrap().clone()); if !dir.exists() { std::fs::create_dir(&dir)?; diff --git a/src/server/video_service.rs b/src/server/video_service.rs index a9a9fd9ab..affb5eb17 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -577,6 +577,14 @@ fn run(sp: GenericService) -> ResultType<()> { if last_check_displays.elapsed().as_millis() > 1000 { last_check_displays = now; + // Capturer on macos does not return Err event the solution is changed. + #[cfg(target_os = "macos")] + if check_display_changed(c.ndisplay, c.current, c.width, c.height) { + log::info!("Displays changed"); + *SWITCH.lock().unwrap() = true; + bail!("SWITCH"); + } + if let Some(msg_out) = check_displays_changed() { sp.send(msg_out); } diff --git a/src/tray.rs b/src/tray.rs index 617ec2c93..38ed9b0cb 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -131,6 +131,10 @@ pub fn make_tray() -> hbb_common::ResultType<()> { let event_loop = EventLoopBuilder::new().build(); + unsafe { + crate::platform::delegate::set_delegate(None); + } + let tray_menu = Menu::new(); let quit_i = MenuItem::new(crate::client::translate("Exit".to_owned()), true, None); tray_menu.append_items(&[&quit_i]); diff --git a/src/ui.rs b/src/ui.rs index a197cb257..f7419cd34 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -9,7 +9,7 @@ use sciter::Value; use hbb_common::{ allow_err, - config::{self, LocalConfig, PeerConfig}, + config::{LocalConfig, PeerConfig}, log, }; @@ -20,8 +20,6 @@ use crate::{common::get_app_name, ipc, ui_interface::*}; mod cm; #[cfg(feature = "inline")] pub mod inline; -#[cfg(target_os = "macos")] -pub mod macos; pub mod remote; pub type Children = Arc)>>; @@ -43,7 +41,7 @@ struct UIHostHandler; pub fn start(args: &mut [String]) { #[cfg(target_os = "macos")] - macos::show_dock(); + crate::platform::delegate::show_dock(); #[cfg(all(target_os = "linux", feature = "inline"))] { #[cfg(feature = "appimage")] @@ -75,7 +73,7 @@ pub fn start(args: &mut [String]) { allow_err!(sciter::set_options(sciter::RuntimeOptions::UxTheming(true))); frame.set_title(&crate::get_app_name()); #[cfg(target_os = "macos")] - macos::make_menubar(frame.get_host(), args.is_empty()); + crate::platform::delegate::make_menubar(frame.get_host(), args.is_empty()); let page; if args.len() > 1 && args[0] == "--play" { args[0] = "--connect".to_owned(); @@ -420,8 +418,8 @@ impl UI { crate::lan::send_wol(id) } - fn new_remote(&mut self, id: String, remote_type: String) { - new_remote(id, remote_type) + fn new_remote(&mut self, id: String, remote_type: String, force_relay: bool) { + new_remote(id, remote_type, force_relay) } fn is_process_trusted(&mut self, _prompt: bool) -> bool { @@ -571,6 +569,10 @@ impl UI { fn default_video_save_directory(&self) -> String { default_video_save_directory() } + + fn handle_relay_id(&self, id: String) -> String { + handle_relay_id(id) + } } impl sciter::EventHandler for UI { @@ -588,7 +590,7 @@ impl sciter::EventHandler for UI { fn set_remote_id(String); fn closing(i32, i32, i32, i32); fn get_size(); - fn new_remote(String, bool); + fn new_remote(String, String, bool); fn send_wol(String); fn remove_peer(String); fn remove_discovered(String); @@ -653,6 +655,7 @@ impl sciter::EventHandler for UI { fn has_hwcodec(); fn get_langs(); fn default_video_save_directory(); + fn handle_relay_id(String); } } @@ -718,9 +721,13 @@ pub fn value_crash_workaround(values: &[Value]) -> Arc> { } #[inline] -pub fn new_remote(id: String, remote_type: String) { +pub fn new_remote(id: String, remote_type: String, force_relay: bool) { let mut lock = CHILDREN.lock().unwrap(); - let args = vec![format!("--{}", remote_type), id.clone()]; + let mut args = vec![format!("--{}", remote_type), id.clone()]; + if force_relay { + args.push("".to_string()); // password + args.push("--relay".to_string()); + } let key = (id.clone(), remote_type.clone()); if let Some(c) = lock.1.get_mut(&key) { if let Ok(Some(_)) = c.try_wait() { diff --git a/src/ui/header.tis b/src/ui/header.tis index e25c0d544..257ba417e 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -198,6 +198,7 @@ class Header: Reactor.Component { {keyboard_enabled && clipboard_enabled ?
  • {svg_checkmark}{translate('Disable clipboard')}
  • : ""} {keyboard_enabled ?
  • {svg_checkmark}{translate('Lock after session end')}
  • : ""} {keyboard_enabled && pi.platform == "Windows" ?
  • {svg_checkmark}{translate('Privacy mode')}
  • : ""} + {keyboard_enabled && ((is_osx && pi.platform != "Mac OS") || (!is_osx && pi.platform == "Mac OS")) ?
  • {svg_checkmark}{translate('Swap control-command key')}
  • : ""} ; } @@ -440,7 +441,7 @@ function toggleMenuState() { for (var el in $$(menu#keyboard-options>li)) { el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); } - for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) { + for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "allow_swap_key"]) { var el = self.select('#' + id); if (el) { var value = handler.get_toggle_option(id); diff --git a/src/ui/index.tis b/src/ui/index.tis index ec2e0a748..0e2247070 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -62,12 +62,15 @@ function createNewConnect(id, type) { id = id.replace(/\s/g, ""); app.remote_id.value = formatId(id); if (!id) return; + var old_id = id; + id = handler.handle_relay_id(id); + var force_relay = old_id != id; if (id == my_id) { msgbox("custom-error", "Error", "You cannot connect to your own computer"); return; } handler.set_remote_id(id); - handler.new_remote(id, type); + handler.new_remote(id, type, force_relay); } class ShareRdp: Reactor.Component { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index c6e0229b2..ed16f1e0e 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -462,6 +462,7 @@ impl sciter::EventHandler for SciterSession { impl SciterSession { pub fn new(cmd: String, id: String, password: String, args: Vec) -> Self { + let force_relay = args.contains(&"--relay".to_string()); let session: Session = Session { id: id.clone(), password: password.clone(), @@ -486,7 +487,7 @@ impl SciterSession { .lc .write() .unwrap() - .initialize(id, conn_type, None, false); + .initialize(id, conn_type, None, force_relay); Self(session) } diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index f5c575d43..bd6eab3bf 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -848,7 +848,7 @@ pub fn elevate_portable(_id: i32) { #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn handle_incoming_voice_call(id: i32, accept: bool) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + if let Some(client) = CLIENTS.read().unwrap().get(&id) { allow_err!(client.tx.send(Data::VoiceCallResponse(accept))); }; } @@ -856,7 +856,7 @@ pub fn handle_incoming_voice_call(id: i32, accept: bool) { #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn close_voice_call(id: i32) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { + if let Some(client) = CLIENTS.read().unwrap().get(&id) { allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); }; } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 3b2ba0897..17b4fafca 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -511,7 +511,7 @@ pub fn get_error() -> String { if dtype != "x11" { return format!( "{} {}, {}", - crate::client::translate("Unsupported display server ".to_owned()), + crate::client::translate("Unsupported display server".to_owned()), dtype, crate::client::translate("x11 expected".to_owned()), ); @@ -605,7 +605,7 @@ pub fn remove_discovered(id: String) { #[inline] pub fn get_uuid() -> String { - base64::encode(hbb_common::get_uuid()) + crate::encode64(hbb_common::get_uuid()) } #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] @@ -636,26 +636,62 @@ pub fn get_async_job_status() -> String { #[inline] pub fn get_langs() -> String { - crate::lang::LANGS.to_string() + use serde_json::json; + let mut x: Vec<(&str, String)> = crate::lang::LANGS + .iter() + .map(|a| (a.0, format!("{} ({})", a.1, a.0))) + .collect(); + x.sort_by(|a, b| a.0.cmp(b.0)); + json!(x).to_string() } #[inline] pub fn default_video_save_directory() -> String { let appname = crate::get_app_name(); + // ui process can show it correctly Once vidoe process created it. + let try_create = |path: &std::path::Path| { + if !path.exists() { + std::fs::create_dir_all(path).ok(); + } + if path.exists() { + path.to_string_lossy().to_string() + } else { + "".to_string() + } + }; #[cfg(any(target_os = "android", target_os = "ios"))] if let Ok(home) = config::APP_HOME_DIR.read() { let mut path = home.to_owned(); path.push_str("/RustDesk/ScreenRecord"); - return path; + let dir = try_create(&std::path::Path::new(&path)); + if !dir.is_empty() { + return dir; + } } if let Some(user) = directories_next::UserDirs::new() { if let Some(video_dir) = user.video_dir() { - return video_dir.join(appname).to_string_lossy().to_string(); + let dir = try_create(&video_dir.join(&appname)); + if !dir.is_empty() { + return dir; + } + if video_dir.exists() { + return video_dir.to_string_lossy().to_string(); + } + } + if let Some(desktop_dir) = user.desktop_dir() { + if desktop_dir.exists() { + return desktop_dir.to_string_lossy().to_string(); + } + } + let home = user.home_dir(); + if home.exists() { + return home.to_string_lossy().to_string(); } } + // same order as above #[cfg(not(any(target_os = "android", target_os = "ios")))] if let Some(home) = crate::platform::get_active_user_home() { let name = if cfg!(target_os = "macos") { @@ -663,12 +699,31 @@ pub fn default_video_save_directory() -> String { } else { "Videos" }; - return home.join(name).join(appname).to_string_lossy().to_string(); + let video_dir = home.join(name); + let dir = try_create(&video_dir.join(&appname)); + if !dir.is_empty() { + return dir; + } + if video_dir.exists() { + return video_dir.to_string_lossy().to_string(); + } + let desktop_dir = home.join("Desktop"); + if desktop_dir.exists() { + return desktop_dir.to_string_lossy().to_string(); + } + if home.exists() { + return home.to_string_lossy().to_string(); + } } if let Ok(exe) = std::env::current_exe() { - if let Some(dir) = exe.parent() { - return dir.join("videos").to_string_lossy().to_string(); + if let Some(parent) = exe.parent() { + let dir = try_create(&parent.join("videos")); + if !dir.is_empty() { + return dir; + } + // basically exist + return parent.to_string_lossy().to_string(); } } "".to_owned() @@ -707,10 +762,10 @@ pub fn is_root() -> bool { pub fn check_super_user_permission() -> bool { #[cfg(feature = "flatpak")] return true; - #[cfg(any(windows, target_os = "linux"))] + #[cfg(any(windows, target_os = "linux", target_os = "macos"))] return crate::platform::check_super_user_permission().unwrap_or(false); - #[cfg(not(any(windows, target_os = "linux")))] - true + #[cfg(not(any(windows, target_os = "linux", target_os = "macos")))] + return true; } #[allow(dead_code)] @@ -870,7 +925,7 @@ pub async fn change_id_shared(id: String, old_id: String) -> &'static str { #[cfg(not(any(target_os = "android", target_os = "ios")))] let uuid = machine_uid::get().unwrap_or("".to_owned()); #[cfg(any(target_os = "android", target_os = "ios"))] - let uuid = base64::encode(hbb_common::get_uuid()); + let uuid = crate::encode64(hbb_common::get_uuid()); if uuid.is_empty() { log::error!("Failed to change id, uuid is_empty"); @@ -970,3 +1025,12 @@ async fn check_id( } "" } + +// if it's relay id, return id processed, otherwise return original id +pub fn handle_relay_id(id: String) -> String { + if id.ends_with(r"\r") || id.ends_with(r"/r") { + id[0..id.len() - 2].to_string() + } else { + id + } +} diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index f726ed526..11bcff925 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -373,10 +373,87 @@ impl Session { return "".to_owned(); } + pub fn swab_modifier_key(&self, msg: &mut KeyEvent) { + + let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string()); + if allow_swap_key { + if let Some(key_event::Union::ControlKey(ck)) = msg.union { + let ck = ck.enum_value_or_default(); + let ck = match ck { + ControlKey::Control => ControlKey::Meta, + ControlKey::Meta => ControlKey::Control, + ControlKey::RControl => ControlKey::Meta, + ControlKey::RWin => ControlKey::Control, + _ => ck, + }; + msg.set_control_key(ck); + } + msg.modifiers = msg.modifiers.iter().map(|ck| { + let ck = ck.enum_value_or_default(); + let ck = match ck { + ControlKey::Control => ControlKey::Meta, + ControlKey::Meta => ControlKey::Control, + ControlKey::RControl => ControlKey::Meta, + ControlKey::RWin => ControlKey::Control, + _ => ck, + }; + hbb_common::protobuf::EnumOrUnknown::new(ck) + }).collect(); + + + let code = msg.chr(); + if code != 0 { + let mut peer = self.peer_platform().to_lowercase(); + peer.retain(|c| !c.is_whitespace()); + + let key = match peer.as_str() { + "windows" => { + let key = rdev::win_key_from_scancode(code); + let key = match key { + rdev::Key::ControlLeft => rdev::Key::MetaLeft, + rdev::Key::MetaLeft => rdev::Key::ControlLeft, + rdev::Key::ControlRight => rdev::Key::MetaLeft, + rdev::Key::MetaRight => rdev::Key::ControlLeft, + _ => key, + }; + rdev::win_scancode_from_key(key).unwrap_or_default() + } + "macos" => { + let key = rdev::macos_key_from_code(code); + let key = match key { + rdev::Key::ControlLeft => rdev::Key::MetaLeft, + rdev::Key::MetaLeft => rdev::Key::ControlLeft, + rdev::Key::ControlRight => rdev::Key::MetaLeft, + rdev::Key::MetaRight => rdev::Key::ControlLeft, + _ => key, + }; + rdev::macos_keycode_from_key(key).unwrap_or_default() + } + _ => { + let key = rdev::linux_key_from_code(code); + let key = match key { + rdev::Key::ControlLeft => rdev::Key::MetaLeft, + rdev::Key::MetaLeft => rdev::Key::ControlLeft, + rdev::Key::ControlRight => rdev::Key::MetaLeft, + rdev::Key::MetaRight => rdev::Key::ControlLeft, + _ => key, + }; + rdev::linux_keycode_from_key(key).unwrap_or_default() + } + }; + msg.set_chr(key); + } + } + + } + pub fn send_key_event(&self, evt: &KeyEvent) { // mode: legacy(0), map(1), translate(2), auto(3) + + let mut msg = evt.clone(); + self.swab_modifier_key(&mut msg); let mut msg_out = Message::new(); - msg_out.set_key_event(evt.clone()); + msg_out.set_key_event(msg); self.send(Data::Message(msg_out)); } @@ -934,6 +1011,23 @@ impl Interface for Session { handle_test_delay(t, peer).await; } } + + fn swap_modifier_mouse(&self, msg : &mut hbb_common::protos::message::MouseEvent) { + let allow_swap_key = self.get_toggle_option("allow_swap_key".to_string()); + if allow_swap_key { + msg.modifiers = msg.modifiers.iter().map(|ck| { + let ck = ck.enum_value_or_default(); + let ck = match ck { + ControlKey::Control => ControlKey::Meta, + ControlKey::Meta => ControlKey::Control, + ControlKey::RControl => ControlKey::Meta, + ControlKey::RWin => ControlKey::Control, + _ => ck, + }; + hbb_common::protobuf::EnumOrUnknown::new(ck) + }).collect(); + }; + } } impl Session { diff --git a/vdi/README.md b/vdi/README.md new file mode 100644 index 000000000..85e6ff194 --- /dev/null +++ b/vdi/README.md @@ -0,0 +1 @@ +# WIP diff --git a/vdi/host/.devcontainer/Dockerfile b/vdi/host/.devcontainer/Dockerfile new file mode 100644 index 000000000..f02042771 --- /dev/null +++ b/vdi/host/.devcontainer/Dockerfile @@ -0,0 +1,16 @@ +FROM rockylinux:9.1 +ENV HOME=/home/vscode +ENV WORKDIR=$HOME/rustdesk/vdi/host + +# https://ciq.co/blog/top-10-things-to-do-after-rocky-linux-9-install/ also gpu driver install +WORKDIR $HOME +RUN dnf -y install epel-release +RUN dnf config-manager --set-enabled crb +RUN dnf -y install cargo libvpx-devel opus-devel usbredir-devel git cmake gcc-c++ pkg-config nasm yasm ninja-build automake libtool libva-devel libvdpau-devel llvm-devel +WORKDIR / + +RUN git clone https://chromium.googlesource.com/libyuv/libyuv +WORKDIR /libyuv +RUN cmake . -DCMAKE_INSTALL_PREFIX=/user +RUN make -j4 && make install +WORKDIR / \ No newline at end of file diff --git a/vdi/host/.devcontainer/devcontainer.json b/vdi/host/.devcontainer/devcontainer.json new file mode 100644 index 000000000..f0016b5b1 --- /dev/null +++ b/vdi/host/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +{ + "name": "rustdesk", + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "workspaceMount": "source=${localWorkspaceFolder}/../..,target=/home/vscode/rustdesk,type=bind,consistency=cache", + "workspaceFolder": "/home/vscode/rustdesk/vdi/host", + "customizations": { + "vscode": { + "extensions": [ + "vadimcn.vscode-lldb", + "mutantdino.resourcemonitor", + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + "serayuzgur.crates", + "mhutchie.git-graph", + "formulahendry.terminal", + "eamodio.gitlens" + ], + "settings": { + "files.watcherExclude": { + "**/target/**": true + } + } + } + } +} \ No newline at end of file diff --git a/vdi/host/.gitignore b/vdi/host/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/vdi/host/.gitignore @@ -0,0 +1 @@ +/target diff --git a/vdi/host/Cargo.lock b/vdi/host/Cargo.lock new file mode 100644 index 000000000..7b7cf26bd --- /dev/null +++ b/vdi/host/Cargo.lock @@ -0,0 +1,2147 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "async-broadcast" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b" +dependencies = [ + "easy-parallel", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-broadcast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2 0.4.7", + "waker-fn", + "windows-sys 0.42.0", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + +[[package]] +name = "async-trait" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "confy" +version = "0.4.0" +source = "git+https://github.com/open-trade/confy#630cc28a396cb7d01eefdd9f3824486fe4d8554b" +dependencies = [ + "directories-next", + "serde", + "thiserror", + "toml", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.7.1", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cxx" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "easy-parallel" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946" + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "filetime" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.45.0", +] + +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hbb_common" +version = "0.1.0" +dependencies = [ + "anyhow", + "backtrace", + "bytes", + "chrono", + "confy", + "directories-next", + "dirs-next", + "env_logger", + "filetime", + "futures", + "futures-util", + "lazy_static", + "libc", + "log", + "mac_address", + "machine-uid", + "osascript", + "protobuf", + "protobuf-codegen", + "rand", + "regex", + "serde", + "serde_derive", + "socket2 0.3.19", + "sodiumoxide", + "sysinfo", + "tokio", + "tokio-socks", + "tokio-util", + "winapi", + "zstd", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + +[[package]] +name = "libusb1-sys" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mac_address" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b238e3235c8382b7653c6408ed1b08dd379bdb9fdf990fb0bbae3db2cc0ae963" +dependencies = [ + "nix 0.23.2", + "winapi", +] + +[[package]] +name = "machine-uid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1595709b0a7386bcd56ba34d250d626e5503917d05d32cdccddcd68603e212" +dependencies = [ + "winreg", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "ordered-stream" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "osascript" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "windows-sys 0.42.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protobuf" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +dependencies = [ + "bytes", + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-codegen" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] + +[[package]] +name = "protobuf-parse" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49" +dependencies = [ + "anyhow", + "indexmap", + "log", + "protobuf", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +dependencies = [ + "thiserror", +] + +[[package]] +name = "qemu-display" +version = "0.1.0" +source = "git+https://gitlab.com/marcandre.lureau/qemu-display#544a4075615702abf414cd2d63bbb6a9ca10d0ea" +dependencies = [ + "async-broadcast 0.3.4", + "async-lock", + "async-trait", + "cfg-if", + "derivative", + "enumflags2", + "futures", + "futures-util", + "libc", + "log", + "once_cell", + "serde", + "serde_bytes", + "serde_repr", + "uds_windows", + "usbredirhost", + "windows", + "zbus", + "zvariant", +] + +[[package]] +name = "qemu-rustdesk" +version = "0.1.0" +dependencies = [ + "hbb_common", + "qemu-display", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +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", +] + +[[package]] +name = "rusb" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703aa035c21c589b34fb5136b12e68fc8dcf7ea46486861381361dd8ebf5cee0" +dependencies = [ + "libc", + "libusb1-sys", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-xml-rs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0bf1ba0696ccf0872866277143ff1fd14d22eec235d2b23702f95e6660f7dfa" +dependencies = [ + "log", + "serde", + "thiserror", + "xml-rs", +] + +[[package]] +name = "serde_bytes" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "sodiumoxide" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26be3acb6c2d9a7aac28482586a7856436af4cfe7100031d219de2d2ecb0028" +dependencies = [ + "ed25519", + "libc", + "libsodium-sys", + "serde", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb4ebf3d49308b99e6e9dc95e989e2fdbdc210e4f67c39db0bb89ba927001c" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.4.7", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-socks" +version = "0.5.1-1" +source = "git+https://github.com/open-trade/tokio-socks#7034e79263ce25c348be072808d7601d82cd892d" +dependencies = [ + "bytes", + "either", + "futures-core", + "futures-sink", + "futures-util", + "pin-project", + "thiserror", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "usbredirhost" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87485e4dfeb0176203afd1086f11ed2ead837053143b12b6eed55c598e9393d5" +dependencies = [ + "libc", + "rusb", + "usbredirhost-sys", + "usbredirparser", +] + +[[package]] +name = "usbredirhost-sys" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b27c305da1f7601b665d68948bcfaf9909d443bec94510ab776118ab8afc2c7d" +dependencies = [ + "libusb1-sys", + "pkg-config", + "usbredirparser-sys", +] + +[[package]] +name = "usbredirparser" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f8b5241d7cbb3e08b4677212a9ac001f116f50731c2737d16129a84ecf6a56" +dependencies = [ + "libc", + "usbredirparser-sys", +] + +[[package]] +name = "usbredirparser-sys" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b0e834e187916fc762bccdc9d64e454a0ee58b134f8f7adab321141e8e0d91" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[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 = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "zbus" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ce2de393c874ba871292e881bf3c13a0d5eb38170ebab2e50b4c410eaa222b" +dependencies = [ + "async-broadcast 0.4.1", + "async-channel", + "async-executor", + "async-io", + "async-lock", + "async-recursion", + "async-task", + "async-trait", + "byteorder", + "derivative", + "dirs", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.24.3", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde-xml-rs", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13d08f5dc6cf725b693cb6ceacd43cd430ec0664a879188f29e7d7dcd98f96d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34f314916bd89bdb9934154627fab152f4f28acdda03e7c4c68181b214fe7e3" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zstd" +version = "0.9.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.3+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "zvariant" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903169c05b9ab948ee93fefc9127d08930df4ce031d46c980784274439803e51" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "serde_bytes", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce76636e8fab7911be67211cf378c252b115ee7f2bae14b18b84821b39260b5" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/vdi/host/Cargo.toml b/vdi/host/Cargo.toml new file mode 100644 index 000000000..6a67813a2 --- /dev/null +++ b/vdi/host/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "qemu-rustdesk" +version = "0.1.0" +authors = ["rustdesk "] +edition = "2021" + +[dependencies] +qemu-display = { git = "https://gitlab.com/marcandre.lureau/qemu-display" } +hbb_common = { path = "../../libs/hbb_common" } diff --git a/vdi/host/README.md b/vdi/host/README.md new file mode 100644 index 000000000..3b29a10e3 --- /dev/null +++ b/vdi/host/README.md @@ -0,0 +1 @@ +# RustDesk protocol on QEMU D-Bus display diff --git a/vdi/host/src/main.rs b/vdi/host/src/main.rs new file mode 100644 index 000000000..f79c691f0 --- /dev/null +++ b/vdi/host/src/main.rs @@ -0,0 +1,2 @@ +fn main() { +}