diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 42a4adb55..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,16 +0,0 @@ -[target.x86_64-pc-windows-msvc] -rustflags = ["-Ctarget-feature=+crt-static"] -[target.i686-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static", "-C", "link-args=/NODEFAULTLIB:MSVCRT"] -[target.'cfg(target_os="macos")'] -rustflags = [ - "-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null", -] -#[target.'cfg(target_os="linux")'] -# glibc-static required, this may fix https://github.com/rustdesk/rustdesk/issues/9103, but I do not want this big change -# this is unlikely to help also, because the other so files still use libc dynamically -#rustflags = [ -# "-C", "link-args=-Wl,-Bstatic -lc -Wl,-Bdynamic" -#] -[net] -git-fetch-with-cli = true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..5ba29c8b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug Report +about: Report a bug (English only, Please). +title: "" +labels: bug +assignees: '' + +--- + + + +**Describe the bug you encountered:** + +... + +**What did you expect to happen instead?** + +... + + +**How did you install `RustDesk`?** + + + +--- + +**RustDesk version and environment** + + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml deleted file mode 100644 index dbb8ed8a9..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ /dev/null @@ -1,55 +0,0 @@ -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 (if it's a keyboard issue, provide the keyboard mode you're using. e.g. legacy, map, translate) - validations: - required: true - - type: textarea - id: reproduce - attributes: - label: How to Reproduce - description: What steps can we take to reproduce this behavior? - validations: - required: true - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: A clear and concise description of what you expected to happen - validations: - required: true - - type: input - id: os - attributes: - label: Operating system(s) on local (controlling) side and remote (controlled) side - description: What operating system(s) do you see this bug on? local (controlling) side -> remote (controlled) side. - placeholder: | - Windows 10 -> osx - validations: - required: true - - type: input - id: version - attributes: - label: RustDesk Version(s) on local (controlling) side and remote (controlled) side - description: What RustDesk version(s) do you see this bug on? local (controlling) side -> remote (controlled) side. - placeholder: | - 1.1.9 -> 1.1.8 - validations: - required: true - - type: textarea - id: screenshots - attributes: - label: Screenshots - description: Please add screenshots to help explain your problem, if applicable, please upload video. - validations: - required: true - - type: textarea - id: context - attributes: - label: Additional Context - description: Add any additonal context about the problem here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8a5429be7..b92c70cbb 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,2 @@ -blank_issues_enabled: false -contact_links: - - name: Feature Request - url: https://github.com/rustdesk/rustdesk/discussions/categories/feature-request - about: Discuss ideas for new features or enhancements, it will be converted to GitHub issue when we commit to building those changes or are helping a community member contribute their own changes - - name: Ask a question - url: https://github.com/rustdesk/rustdesk/discussions/category_choices - about: Ask questions and discuss with other community members. +blank_issues_enabled: true + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..9e02f555b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,10 @@ +--- +name: Feature Request +about: Suggest an idea for this project ((English only, Please). +title: '' +labels: feature-request +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..435b58e48 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,10 @@ +--- +name: Question +about: Ask a question about 'RustDesk' (English only, Please). +title: '' +labels: question +assignees: '' + +--- + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 56258e4e0..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "gitsubmodule" - directory: "/" - target-branch: "master" - schedule: - interval: "daily" - commit-message: - prefix: "Git submodule" - labels: - - "dependencies" diff --git a/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff b/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff deleted file mode 100644 index 9b8ea2690..000000000 --- a/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart -index 7e634cd2aa..c1e9acc295 100644 ---- a/packages/flutter/lib/src/material/dropdown_menu.dart -+++ b/packages/flutter/lib/src/material/dropdown_menu.dart -@@ -475,7 +475,7 @@ class _DropdownMenuState extends State> { - final GlobalKey _leadingKey = GlobalKey(); - late List buttonItemKeys; - final MenuController _controller = MenuController(); -- late bool _enableFilter; -+ bool _enableFilter = false; - late List> filteredEntries; - List? _initialMenu; - int? currentHighlight; -@@ -524,6 +524,11 @@ class _DropdownMenuState extends State> { - } - _localTextEditingController = widget.controller ?? TextEditingController(); - } -+ if (oldWidget.enableFilter != widget.enableFilter) { -+ if (!widget.enableFilter) { -+ _enableFilter = false; -+ } -+ } - if (oldWidget.enableSearch != widget.enableSearch) { - if (!widget.enableSearch) { - currentHighlight = null; -@@ -663,6 +668,7 @@ class _DropdownMenuState extends State> { - ); - currentHighlight = widget.enableSearch ? i : null; - widget.onSelected?.call(entry.value); -+ _enableFilter = false; - } - : null, - requestFocusOnHover: false, -@@ -735,6 +741,8 @@ class _DropdownMenuState extends State> { - if (_enableFilter) { - filteredEntries = widget.filterCallback?.call(filteredEntries, _localTextEditingController!.text) - ?? filter(widget.dropdownMenuEntries, _localTextEditingController!); -+ } else { -+ filteredEntries = widget.dropdownMenuEntries; - } - - if (widget.enableSearch) { diff --git a/.github/workflows/bridge.yml b/.github/workflows/bridge.yml deleted file mode 100644 index 1913132e2..000000000 --- a/.github/workflows/bridge.yml +++ /dev/null @@ -1,98 +0,0 @@ -# This yaml shares the build bridge steps with ci and nightly. -name: Build flutter-rust-bridge -# 2023-11-23 18:00:00+00:00 - -on: - workflow_call: - -env: - CARGO_EXPAND_VERSION: "1.0.95" - FLUTTER_VERSION: "3.22.3" - FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" - RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503 - -jobs: - generate_bridge: - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { - target: x86_64-unknown-linux-gnu, - os: ubuntu-22.04, - extra-build-args: "", - } - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install prerequisites - run: | - sudo apt-get install ca-certificates -y - sudo apt-get update -y - sudo apt-get install -y \ - clang \ - cmake \ - curl \ - gcc \ - git \ - g++ \ - libclang-dev \ - libgtk-3-dev \ - llvm-dev \ - nasm \ - ninja-build \ - pkg-config \ - wget - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: bridge-${{ matrix.job.os }} - - - name: Cache Bridge - id: cache-bridge - uses: actions/cache@v3 - with: - path: /tmp/flutter_rust_bridge - key: vcpkg-${{ matrix.job.arch }} - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true - - - name: Install flutter rust bridge deps - shell: bash - run: | - cargo install cargo-expand --version ${{ env.CARGO_EXPAND_VERSION }} --locked - cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked - pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd - - - name: Run flutter rust bridge - run: | - ~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h - cp ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h - - - name: Upload Artifact - uses: actions/upload-artifact@master - with: - name: bridge-artifact - path: | - ./src/bridge_generated.rs - ./src/bridge_generated.io.rs - ./flutter/lib/generated_bridge.dart - ./flutter/lib/generated_bridge.freezed.dart - ./flutter/macos/Runner/bridge_generated.h - ./flutter/ios/Runner/bridge_generated.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a7d21d7e..cd8282187 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,28 +1,17 @@ name: CI -env: +# env: # MIN_SUPPORTED_RUST_VERSION: "1.46.0" # CICD_INTERMEDIATES_DIR: "_cicd-intermediates" - VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" - # for multiarch gcc compatibility - VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b" on: workflow_dispatch: pull_request: - paths-ignore: - - "docs/**" - - "README.md" push: branches: - master - paths-ignore: - - ".github/**" - - "docs/**" - - "README.md" - - "res/**" - - "appimage/**" - - "flatpak/**" + tags: + - '*' jobs: # ensure_cargo_fmt: @@ -35,7 +24,7 @@ jobs: # default: true # profile: minimal # components: rustfmt - # - uses: actions/checkout@v3 + # - uses: actions/checkout@v2 # - run: cargo fmt -- --check # min_version: @@ -43,9 +32,7 @@ jobs: # runs-on: ubuntu-20.04 # steps: # - name: Checkout source code - # uses: actions/checkout@v3 - # with: - # submodules: recursive + # uses: actions/checkout@v2 # - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) # uses: actions-rs/toolchain@v1 @@ -75,92 +62,45 @@ jobs: # - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } # - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } # - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } - # - { target: i686-pc-windows-msvc , os: windows-2022 } + # - { target: i686-pc-windows-msvc , os: windows-2019 } # - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } # - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } # - { target: x86_64-apple-darwin , os: macos-10.15 } - # - { target: x86_64-pc-windows-gnu , os: windows-2022 } - # - { target: x86_64-pc-windows-msvc , os: windows-2022 } - - { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04 } + # - { target: x86_64-pc-windows-gnu , os: windows-2019 } + # - { target: x86_64-pc-windows-msvc , os: windows-2019 } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 } # - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } steps: - - name: Free Disk Space (Ubuntu) - if: runner.os == 'Linux' - # jlumbroso/free-disk-space@main is used in .github\workflows\flutter-build.yml - # But pinning to a specific version to avoid unexpected issues is preferred. - uses: jlumbroso/free-disk-space@v1.3.1 - with: - tool-cache: false - android: true - dotnet: true - haskell: true - large-packages: false - docker-images: true - swap-storage: false - - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive + uses: actions/checkout@v2 - name: Install prerequisites shell: bash run: | case ${{ matrix.job.target }} in - x86_64-unknown-linux-gnu) - sudo apt-get -y update - sudo apt-get install -y \ - clang \ - cmake \ - curl \ - gcc \ - git \ - g++ \ - libpam0g-dev \ - libasound2-dev \ - libunwind-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - libpulse-dev \ - libva-dev \ - libvdpau-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - nasm \ - wget - ;; + x86_64-unknown-linux-gnu) sudo apt-get -y update ; 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 ;; # arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; # aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; esac - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 + - name: Restore from cache and install vcpkg + uses: lukka/run-vcpkg@v7 with: - vcpkgDirectory: /opt/artifacts/vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - + setupOnly: true + vcpkgGitCommitId: '1d4128f08e30cec31b94500840c7eca8ebc579cb' + - name: Install vcpkg dependencies run: | - $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed" - shell: bash + $VCPKG_ROOT/vcpkg install libvpx libyuv opus + shell: bash - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 + uses: actions-rs/toolchain@v1 with: toolchain: stable - targets: ${{ matrix.job.target }} - components: '' + target: ${{ matrix.job.target }} + override: true + profile: minimal # minimal component installation (ie, no documentation) - name: Show version information (Rust, cargo, GCC) shell: bash @@ -172,19 +112,14 @@ jobs: cargo -V rustc -V - - uses: Swatinem/rust-cache@v2 - + - uses: Swatinem/rust-cache@v1 + - name: Build uses: actions-rs/cargo@v1 with: use-cross: ${{ matrix.job.use-cross }} command: build - args: --locked --target=${{ matrix.job.target }} - - - name: clean - shell: bash - run: | - cargo clean + args: --locked --release --target=${{ matrix.job.target }} # - name: Strip debug information from executable # id: strip @@ -228,23 +163,19 @@ jobs: run: | # test only library unit tests and binary for arm-type targets unset CARGO_TEST_OPTIONS + unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" ;; esac; + echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - case ${{ matrix.job.target }} in - arm-* | aarch64-*) - CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" - ;; - *) - CARGO_TEST_OPTIONS="--workspace --no-fail-fast -- --skip test_get_cursor_pos --skip test_get_key_state" - ;; - esac; - - #deprecated echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} - echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_ENV - echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT - - - name: Run tests + - name: Build tests uses: actions-rs/cargo@v1 with: use-cross: ${{ matrix.job.use-cross }} - command: test - args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + command: build + args: --locked --tests --target=${{ matrix.job.target }} + + # - name: Run tests + # uses: actions-rs/cargo@v1 + # with: + # use-cross: ${{ matrix.job.use-cross }} + # command: test + # args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml deleted file mode 100644 index cd94cab68..000000000 --- a/.github/workflows/clear-cache.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Clear cache - -on: - workflow_dispatch: - -permissions: - actions: write - -jobs: - clear-cache: - runs-on: ubuntu-latest - steps: - - name: Clear cache - uses: actions/github-script@v7 - with: - script: | - console.log("About to clear") - const caches = await github.rest.actions.getActionsCacheList({ - owner: context.repo.owner, - repo: context.repo.repo, - }) - for (const cache of caches.data.actions_caches) { - console.log(cache) - github.rest.actions.deleteActionsCacheById({ - owner: context.repo.owner, - repo: context.repo.repo, - cache_id: cache.id, - }) - } - console.log("Clear completed") - - - name: Purge cache # Above seems not clear thouroughly, so add this to double clear - uses: MyAlbum/purge-cache@v2 - with: - accessed: true # Purge caches by their last accessed time (default) - created: false # Purge caches by their created time (default) - max-age: 1 # in seconds diff --git a/.github/workflows/fdroid.yml b/.github/workflows/fdroid.yml deleted file mode 100644 index 94f8d3d7d..000000000 --- a/.github/workflows/fdroid.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Fdroid version file generation - -on: - workflow_dispatch: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+-[0-9]+' - -jobs: - # https://gitlab.com/fdroid/fdroiddata/-/blob/master/metadata/com.carriez.flutter_hbb.yml - # Finds latest release and transforms F-Droid version code from version as follows: - # X.Y.Z-A => X * 1e6 + Y * 1e4 + Z * 1e2 + A - update-fdroid-version-file: - name: Publish RustDesk version file for F-Droid updater - runs-on: ubuntu-latest - steps: - - name: Generate RustDesk version file - run: | - if [ "${GITHUB_REF_TYPE}" = "tag" ]; then - UPSTREAM_VERNAME="${GITHUB_REF##refs/tags/}" - UPSTREAM_VERNAME="${UPSTREAM_VERNAME##v}" - else - UPSTREAM_VERNAME="$(curl https://api.github.com/repos/rustdesk/rustdesk/releases/latest | jq -r .tag_name | sed 's/^v//')" - fi - UPSTREAM_VERCODE="$(echo "$UPSTREAM_VERNAME" | tr '.' ' ' | tr '-' ' ' | while read -r MAJOR MINOR PATCH REV; do [ -z "$MAJOR" ] && MAJOR=0; [ -z "$MINOR" ] && MINOR=0; [ -z "$PATCH" ] && PATCH=0; [ -z "$REV" ] && REV=0; echo "$(( 1000000 * $MAJOR + 10000 * $MINOR + 100 * $PATCH + $REV ))"; done)" - echo "versionName=$UPSTREAM_VERNAME" > rustdesk-version.txt - echo "versionCode=$UPSTREAM_VERCODE" >> rustdesk-version.txt - shell: bash - - - name: Publish RustDesk version file - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: "fdroid-version" - files: | - ./rustdesk-version.txt diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml deleted file mode 100644 index 263bd67dc..000000000 --- a/.github/workflows/flutter-build.yml +++ /dev/null @@ -1,2062 +0,0 @@ -name: Build the flutter version of the RustDesk - -on: - workflow_call: - inputs: - upload-artifact: - type: boolean - default: true - upload-tag: - type: string - default: "nightly" - -# NOTE: F-Droid builder script 'flutter/build_fdroid.sh' reads environment -# variables from this workflow! -# -# It does NOT read build steps, however, so please fix 'flutter/build_fdroid.sh -# whenever you add changes to Android CI build action ('build-rustdesk-android') -# in this file! - -env: - SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html - RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html - MAC_RUST_VERSION: "1.81" # 1.81 is requred for macos, because of https://github.com/yury/cidre requires 1.81 - CARGO_NDK_VERSION: "3.1.2" - SCITER_ARMV7_CMAKE_VERSION: "3.29.7" - SCITER_NASM_DEBVERSION: "2.15.05-1" - LLVM_VERSION: "15.0.6" - FLUTTER_VERSION: "3.24.5" - ANDROID_FLUTTER_VERSION: "3.24.5" - # for arm64 linux because official Dart SDK does not work - FLUTTER_ELINUX_VERSION: "3.16.9" - TAG_NAME: "${{ inputs.upload-tag }}" - VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" - # vcpkg version: 2025.08.27 - # If we change the `VCPKG COMMIT_ID`, please remember: - # 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`. - # Or we may face build issue like - # https://github.com/rustdesk/rustdesk/actions/runs/14414119794/job/40427970174 - # 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`. - VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b" - ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version - VERSION: "1.4.6" - NDK_VERSION: "r28c" - #signing keys env variable checks - ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" - MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}" - UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" - SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}-2" - -jobs: - generate-bridge: - uses: ./.github/workflows/bridge.yml - - build-RustDeskTempTopMostWindow: - uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml - with: - upload-artifact: ${{ inputs.upload-artifact }} - target: windows-2022 - configuration: Release - platform: x64 - target_version: Windows10 - strategy: - fail-fast: false - - build-for-windows-flutter: - name: ${{ matrix.job.target }} - needs: [build-RustDeskTempTopMostWindow, generate-bridge] - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - # - { target: i686-pc-windows-msvc , os: windows-2022 } - # - { target: x86_64-pc-windows-gnu , os: windows-2022 } - - { - target: x86_64-pc-windows-msvc, - os: windows-2022, - arch: x86_64, - vcpkg-triplet: x64-windows-static, - } - # - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Restore bridge files - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Install LLVM and Clang - uses: KyleMayes/install-llvm-action@v1 - with: - version: ${{ env.LLVM_VERSION }} - - - name: Install flutter - uses: subosito/flutter-action@v2.12.0 #https://github.com/subosito/flutter-action/issues/277 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - - # https://github.com/flutter/flutter/issues/155685 - - name: Replace engine with rustdesk custom flutter engine - run: | - flutter doctor -v - flutter precache --windows - Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip - Expand-Archive -Path windows-x64-release.zip -DestinationPath windows-x64-release - mv -Force windows-x64-release/*  C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/ - - - name: Patch flutter - shell: bash - run: | - cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter))) - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.SCITER_RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.job.os }} - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgDirectory: C:\vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - env: - VCPKG_DEFAULT_HOST_TRIPLET: ${{ matrix.job.vcpkg-triplet }} - run: | - if ! $VCPKG_ROOT/vcpkg \ - install \ - --triplet ${{ matrix.job.vcpkg-triplet }} \ - --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - shell: bash - - - name: Build rustdesk - run: | - # Windows: build RustDesk - python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack - mv ./flutter/build/windows/x64/runner/Release ./rustdesk - - # Download usbmmidd_v2.zip and extract it to ./rustdesk - Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip - Expand-Archive usbmmidd_v2.zip -DestinationPath . - Remove-Item -Path usbmmidd_v2\Win32 -Recurse - Remove-Item -Path "usbmmidd_v2\deviceinstaller64.exe", "usbmmidd_v2\deviceinstaller.exe", "usbmmidd_v2\usbmmidd.bat" - mv -Force .\usbmmidd_v2 ./rustdesk - - # Download printer driver files and extract them to ./rustdesk - try { - Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/rustdesk_printer_driver_v4-1.4.zip -OutFile rustdesk_printer_driver_v4-1.4.zip - Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/printer_driver_adapter.zip -OutFile printer_driver_adapter.zip - Invoke-WebRequest -Uri https://github.com/rustdesk/hbb_common/releases/download/driver/sha256sums -OutFile sha256sums - - # Check and move the files - $checksum_driver = (Select-String -Path .\sha256sums -Pattern '^([a-fA-F0-9]{64}) \*rustdesk_printer_driver_v4-1.4\.zip$').Matches.Groups[1].Value - $downloadsum_driver = Get-FileHash -Path rustdesk_printer_driver_v4-1.4.zip -Algorithm SHA256 - $checksum_adapter = (Select-String -Path .\sha256sums -Pattern '^([a-fA-F0-9]{64}) \*printer_driver_adapter\.zip$').Matches.Groups[1].Value - $downloadsum_adapter = Get-FileHash -Path printer_driver_adapter.zip -Algorithm SHA256 - if ($checksum_driver -eq $downloadsum_driver.Hash -and $checksum_adapter -eq $downloadsum_adapter.Hash) { - Write-Output "rustdesk_printer_driver_v4-1.4, checksums match, extract the file." - Expand-Archive rustdesk_printer_driver_v4-1.4.zip -DestinationPath . - mkdir ./rustdesk/drivers - mv -Force .\rustdesk_printer_driver_v4-1.4 ./rustdesk/drivers/RustDeskPrinterDriver - Expand-Archive printer_driver_adapter.zip -DestinationPath . - mv -Force .\printer_driver_adapter.dll ./rustdesk - } elseif ($checksum_driver -ne $downloadsum_driver.Hash) { - Write-Output "rustdesk_printer_driver_v4-1.4, checksums do not match, ignore the file." - } else { - Write-Output "printer_driver_adapter.dll, checksums do not match, ignore the file." - } - } catch { - Write-Host "Ingore the printer driver error." - } - - - name: find Runner.res - # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res - # Runner.rc does not contain actual version, but Runner.res does - continue-on-error: true - shell: bash - run: | - runner_res=$(find . -name "Runner.res"); - if [ "$runner_res" == "" ]; then - echo "Runner.res: not found"; - else - echo "Runner.res: $runner_res"; - cp $runner_res ./libs/portable/Runner.res; - echo "list ./libs/portable/Runner.res"; - ls -l ./libs/portable/Runner.res; - fi - - - name: Download RustDeskTempTopMostWindow artifacts - uses: actions/download-artifact@master - if: ${{ inputs.upload-artifact }} - with: - name: topmostwindow-artifacts - path: "./rustdesk" - - - name: Upload unsigned - if: env.UPLOAD_ARTIFACT == 'true' - uses: actions/upload-artifact@master - with: - name: rustdesk-unsigned-windows-${{ matrix.job.arch }} - path: rustdesk - - - name: Sign rustdesk files - if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' - shell: bash - run: | - pip3 install requests argparse - BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./rustdesk/ - - - name: Build self-extracted executable - shell: bash - if: env.UPLOAD_ARTIFACT == 'true' - run: | - sed -i '/dpiAware/d' res/manifest.xml - pushd ./libs/portable - pip3 install -r requirements.txt - python3 ./generate.py -f ../../rustdesk/ -o . -e ../../rustdesk/rustdesk.exe - popd - mkdir -p ./SignOutput - mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.exe - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v2 - - - name: Build msi - if: env.UPLOAD_ARTIFACT == 'true' - run: | - pushd ./res/msi - python preprocess.py --arp -d ../../rustdesk - nuget restore msi.sln - msbuild msi.sln -p:Configuration=Release -p:Platform=x64 /p:TargetVersion=Windows10 - mv ./Package/bin/x64/Release/en-us/Package.msi ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi - sha256sum ../../SignOutput/rustdesk-*.msi - - - name: Sign rustdesk self-extracted file - if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' - shell: bash - run: | - BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput - - - name: Publish Release - uses: softprops/action-gh-release@v1 - if: env.UPLOAD_ARTIFACT == 'true' - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ./SignOutput/rustdesk-*.msi - ./SignOutput/rustdesk-*.exe - - # The fallback for the flutter version, we use Sciter for 32bit Windows. - build-for-windows-sciter: - name: ${{ matrix.job.target }} (${{ matrix.job.os }}) - runs-on: ${{ matrix.job.os }} - # Temporarily disable this action due to additional test is needed. - # if: false - strategy: - fail-fast: false - matrix: - job: - # - { target: i686-pc-windows-msvc , os: windows-2022 } - # - { target: x86_64-pc-windows-gnu , os: windows-2022 } - - { - target: i686-pc-windows-msvc, - os: windows-2022, - arch: x86, - vcpkg-triplet: x86-windows-static, - } - # - { target: aarch64-pc-windows-msvc, os: windows-2022 } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install LLVM and Clang - uses: rustdesk-org/install-llvm-action-32bit@master - with: - version: ${{ env.LLVM_VERSION }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: nightly-2023-10-13-${{ matrix.job.target }} # must use nightly here, because of abi_thiscall feature required - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.job.os }}-sciter - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgDirectory: C:\vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - env: - VCPKG_DEFAULT_HOST_TRIPLET: ${{ matrix.job.vcpkg-triplet }} - run: | - if ! $VCPKG_ROOT/vcpkg \ - install \ - --triplet ${{ matrix.job.vcpkg-triplet }} \ - --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - shell: bash - - - name: Build rustdesk - id: build - shell: bash - run: | - python3 res/inline-sciter.py - # Patch sciter x86 - sed -i 's/branch = "dyn"/branch = "dyn_x86"/g' ./Cargo.toml - cargo build --features inline,vram,hwcodec --release --bins - mkdir -p ./Release - mv ./target/release/rustdesk.exe ./Release/rustdesk.exe - curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll - echo "output_folder=./Release" >> $GITHUB_OUTPUT - curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip - unzip usbmmidd_v2.zip - # Do not remove x64 files, because the user may run the 32bit version on a 64bit system. - # Do not remove ./usbmmidd_v2/deviceinstaller64.exe, as x86 exe cannot install and uninstall drivers when running on x64, - # we need the x64 exe to install and uninstall the driver. - rm -rf ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/usbmmidd.bat - mv ./usbmmidd_v2 ./Release || true - - - name: find Runner.res - # Windows: find Runner.res (compiled from ./flutter/windows/runner/Runner.rc), copy to ./Runner.res - # Runner.rc does not contain actual version, but Runner.res does - continue-on-error: true - shell: bash - run: | - runner_res=$(find . -name "Runner.res"); - if [ "$runner_res" == "" ]; then - echo "Runner.res: not found"; - else - echo "Runner.res: $runner_res"; - cp $runner_res ./libs/portable/Runner.res; - echo "list ./libs/portable/Runner.res"; - ls -l ./libs/portable/Runner.res; - fi - - - name: Upload unsigned - if: env.UPLOAD_ARTIFACT == 'true' - uses: actions/upload-artifact@master - with: - name: rustdesk-unsigned-windows-${{ matrix.job.arch }} - path: Release - - - name: Sign rustdesk files - if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' - shell: bash - run: | - pip3 install requests argparse - BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./Release/ - - - name: Build self-extracted executable - shell: bash - run: | - sed -i '/dpiAware/d' res/manifest.xml - pushd ./libs/portable - pip3 install -r requirements.txt - python3 ./generate.py -f ../../Release/ -o . -e ../../Release/rustdesk.exe - popd - mkdir -p ./SignOutput - mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.exe - - - name: Sign rustdesk self-extracted file - if: env.UPLOAD_ARTIFACT == 'true' && env.SIGN_BASE_URL != '-2' - shell: bash - run: | - BASE_URL=${{ env.SIGN_BASE_URL }} SECRET_KEY=${{ secrets.SIGN_SECRET_KEY }} python3 res/job.py sign_files ./SignOutput/ - - - name: Publish Release - uses: softprops/action-gh-release@v1 - if: env.UPLOAD_ARTIFACT == 'true' - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ./SignOutput/rustdesk-*.exe - - build-rustdesk-ios: - if: ${{ inputs.upload-artifact }} - name: build rustdesk ios ipa - runs-on: ${{ matrix.job.os }} - needs: [generate-bridge] - strategy: - fail-fast: false - matrix: - job: - - { - arch: aarch64, - target: aarch64-apple-ios, - os: macos-latest, - vcpkg-triplet: arm64-ios, - } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Install dependencies - run: | - brew install nasm yasm - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - - - name: Patch flutter - run: | - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - run: | - if ! $VCPKG_ROOT/vcpkg \ - install \ - --triplet ${{ matrix.job.vcpkg-triplet }} \ - --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - shell: bash - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: rustdesk-lib-cache-ios - key: ${{ matrix.job.target }} - - - name: Restore bridge files - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Build rustdesk lib - run: | - rustup target add ${{ matrix.job.target }} - cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib - - - name: Upload liblibrustdesk.a Artifacts - uses: actions/upload-artifact@master - with: - name: liblibrustdesk.a - path: target/aarch64-apple-ios/release/liblibrustdesk.a - - - name: Build rustdesk - shell: bash - run: | - pushd flutter - # flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info --no-codesign - # for easy debugging - flutter build ipa --release --no-codesign - - # - name: Upload Artifacts - # # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - # uses: actions/upload-artifact@master - # with: - # name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - # path: flutter/build/ios/ipa/*.ipa - - # - name: Publish ipa package - # # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - # uses: softprops/action-gh-release@v1 - # with: - # prerelease: true - # tag_name: ${{ env.TAG_NAME }} - # files: | - # flutter/build/ios/ipa/*.ipa - - - build-for-macOS: - name: ${{ matrix.job.target }} - runs-on: ${{ matrix.job.os }} - needs: [generate-bridge] - strategy: - fail-fast: false - matrix: - job: - - { - target: x86_64-apple-darwin, - os: macos-15-intel, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel - extra-build-args: "", - arch: x86_64, - vcpkg-triplet: x64-osx, - } - - { - target: aarch64-apple-darwin, - os: macos-14, - # extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296 - extra-build-args: "--screencapturekit", - arch: aarch64, - vcpkg-triplet: arm64-osx, - } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Import the codesign cert - if: env.MACOS_P12_BASE64 != null - uses: apple-actions/import-codesign-certs@v1 - with: - p12-file-base64: ${{ secrets.MACOS_P12_BASE64 }} - p12-password: ${{ secrets.MACOS_P12_PASSWORD }} - keychain: rustdesk - - - name: Check sign and import sign key - if: env.MACOS_P12_BASE64 != null - run: | - security default-keychain -s rustdesk.keychain - security find-identity -v - - - name: Import notarize key - if: env.MACOS_P12_BASE64 != null - uses: timheuer/base64-to-file@v1.2 - with: - # https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_rcodesign.html#notarizing-and-stapling - fileName: rustdesk.json - fileDir: ${{ github.workspace }} - encodedString: ${{ secrets.MACOS_NOTARIZE_JSON }} - - - name: Install rcodesign tool - if: env.MACOS_P12_BASE64 != null - shell: bash - run: | - pushd /tmp - wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-macos-universal.tar.gz - tar -zxvf apple-codesign-0.22.0-macos-universal.tar.gz - mv apple-codesign-0.22.0-macos-universal/rcodesign /usr/local/bin - popd - - - name: Install build runtime - run: | - brew install llvm create-dmg - # pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner - if command -v pkg-config &>/dev/null; then - echo "pkg-config is already installed" - else - brew install pkg-config - fi - - - name: Install NASM - run: | - # Install NASM 2.16.x from official release. - # Do NOT use `brew install nasm` which installs NASM 3.x. - # NASM 3.x is a complete rewrite with incompatible CLI options and removed features. - # aom and other multimedia libraries require NASM 2.x for x86/x86_64 assembly. - wget https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/macosx/nasm-2.16.03-macosx.zip - unzip nasm-2.16.03-macosx.zip - sudo cp nasm-2.16.03/nasm /usr/local/bin/nasm - nasm --version - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - - - name: Patch flutter - run: | - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - - - name: Workaround for flutter issue - shell: bash - run: | - cd "$(dirname "$(which flutter)")" - # https://github.com/flutter/flutter/issues/133533 - sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart - grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.MAC_RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.job.os }} - - - name: Restore bridge files - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - run: | - if ! $VCPKG_ROOT/vcpkg \ - install \ - --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - - - name: Show version information (Rust, cargo, Clang) - shell: bash - run: | - clang --version || true - rustup -V - rustup toolchain list - rustup default - cargo -V - rustc -V - - - name: Build rustdesk - run: | - if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then - MIN_MACOS_VERSION="12.3" - sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py - sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile - sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml - sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj - fi - ./build.py --flutter --hwcodec --unix-file-copy-paste ${{ matrix.job.extra-build-args }} - - - name: create unsigned dmg - if: env.UPLOAD_ARTIFACT == 'true' - run: | - CREATE_DMG="$(command -v create-dmg)" - CREATE_DMG="$(readlink -f "$CREATE_DMG")" - sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" - create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app - - - name: Upload unsigned macOS app - if: env.UPLOAD_ARTIFACT == 'true' - uses: actions/upload-artifact@master - with: - name: rustdesk-unsigned-macos-${{ matrix.job.arch }} - path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg # can not upload the directory directly or tar.gz, which destroy the link structure, causing the codesign failed - - - name: Codesign app and create signed dmg - if: env.MACOS_P12_BASE64 != null && env.UPLOAD_ARTIFACT == 'true' - run: | - # Patch create-dmg to give more attempts to unmount image - CREATE_DMG="$(command -v create-dmg)" - CREATE_DMG="$(readlink -f "$CREATE_DMG")" - sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" - # Unlock keychain - security default-keychain -s rustdesk.keychain - security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain - # start sign the rustdesk.app and dmg - rm -rf *.dmg || true - codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv - create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app - codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict rustdesk-${{ env.VERSION }}.dmg -vvv - # notarize the rustdesk-${{ env.VERSION }}.dmg - rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple rustdesk-${{ env.VERSION }}.dmg - - - name: Rename rustdesk - if: env.UPLOAD_ARTIFACT == 'true' - run: | - for name in rustdesk*??.dmg; do - mv "$name" "${name%%.dmg}-${{ matrix.job.arch }}.dmg" - done - - - name: Publish DMG package - if: env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - rustdesk*-${{ matrix.job.arch }}.dmg - - publish_unsigned: - needs: - - build-for-macOS - - build-for-windows-flutter - - build-for-windows-sciter - runs-on: ubuntu-latest - if: ${{ inputs.upload-artifact }} - steps: - - name: Download artifacts - uses: actions/download-artifact@master - with: - name: rustdesk-unsigned-macos-x86_64 - path: ./ - - - name: Download Artifacts - uses: actions/download-artifact@master - with: - name: rustdesk-unsigned-macos-aarch64 - path: ./ - - - name: Download Artifacts - uses: actions/download-artifact@master - with: - name: rustdesk-unsigned-windows-x86_64 - path: ./windows-x86_64/ - - - name: Download Artifacts - uses: actions/download-artifact@master - with: - name: rustdesk-unsigned-windows-x86 - path: ./windows-x86/ - - - name: Combine unsigned app - run: | - tar czf rustdesk-${{ env.VERSION }}-unsigned.tar.gz *.dmg windows-x86_64 windows-x86 - - - name: Publish unsigned app - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: rustdesk-${{ env.VERSION }}-unsigned.tar.gz - - build-rustdesk-android: - needs: [generate-bridge] - name: build rustdesk android apk ${{ matrix.job.target }} - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { - arch: aarch64, - target: aarch64-linux-android, - os: ubuntu-24.04, - reltype: release, - suffix: "", - } - - { - arch: armv7, - target: armv7-linux-androideabi, - os: ubuntu-24.04, - reltype: release, - suffix: "", - } - - { - arch: x86_64, - target: x86_64-linux-android, - os: ubuntu-24.04, - reltype: release, - suffix: "", - } - steps: - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - tool-cache: false - android: false - dotnet: true - haskell: true - large-packages: false - docker-images: true - swap-storage: false - - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - clang \ - cmake \ - curl \ - gcc-multilib \ - git \ - g++ \ - g++-multilib \ - libayatana-appindicator3-dev \ - libasound2-dev \ - libc6-dev \ - libclang-dev \ - libunwind-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - libpam0g-dev \ - libpulse-dev \ - libva-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - llvm-dev \ - nasm \ - ninja-build \ - openjdk-17-jdk-headless \ - pkg-config \ - tree \ - wget - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }} - - - name: Patch flutter - run: | - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - - - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: ${{ env.NDK_VERSION }} - add-to-path: true - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgDirectory: /opt/artifacts/vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - run: | - case ${{ matrix.job.target }} in - aarch64-linux-android) - ANDROID_TARGET=arm64-v8a - ;; - armv7-linux-androideabi) - ANDROID_TARGET=armeabi-v7a - ;; - x86_64-linux-android) - ANDROID_TARGET=x86_64 - ;; - i686-linux-android) - ANDROID_TARGET=x86 - ;; - esac - if ! ./flutter/build_android_deps.sh "${ANDROID_TARGET}"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - shell: bash - - - name: Restore bridge files - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_VERSION }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: rustdesk-lib-cache-android # TODO: drop '-android' part after caches are invalidated - key: ${{ matrix.job.target }} - - - name: Build rustdesk lib - env: - ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} - run: | - rustup target add ${{ matrix.job.target }} - cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked - case ${{ matrix.job.target }} in - aarch64-linux-android) - ./flutter/ndk_arm64.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so - ;; - armv7-linux-androideabi) - ./flutter/ndk_arm.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so - ;; - x86_64-linux-android) - ./flutter/ndk_x64.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/x86_64 - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so - ;; - i686-linux-android) - ./flutter/ndk_x86.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/x86 - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so - ;; - esac - - - name: Upload Rustdesk library to Artifacts - uses: actions/upload-artifact@master - with: - name: librustdesk.so.${{ matrix.job.target }} - path: ./target/${{ matrix.job.target }}/release/liblibrustdesk.so - - - name: Build rustdesk - shell: bash - env: - JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 - run: | - export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH - # Increase Gradle JVM memory for CI builds - sed -i "s/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g" ./flutter/android/gradle.properties - # temporary use debug sign config - sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle - case ${{ matrix.job.target }} in - aarch64-linux-android) - mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so - # build flutter - pushd flutter - flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-arm64 --split-per-abi - mv build/app/outputs/flutter-apk/app-arm64-v8a-${{ matrix.job.reltype }}.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk - ;; - armv7-linux-androideabi) - mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so - # build flutter - pushd flutter - flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-arm --split-per-abi - mv build/app/outputs/flutter-apk/app-armeabi-v7a-${{ matrix.job.reltype }}.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk - ;; - x86_64-linux-android) - mkdir -p ./flutter/android/app/src/main/jniLibs/x86_64 - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86_64/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so - # build flutter - pushd flutter - flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-x64 --split-per-abi - mv build/app/outputs/flutter-apk/app-x86_64-${{ matrix.job.reltype }}.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk - ;; - i686-linux-android) - mkdir -p ./flutter/android/app/src/main/jniLibs/x86 - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so - # build flutter - pushd flutter - flutter build apk "--${{ matrix.job.reltype }}" --target-platform android-x86 --split-per-abi - mv build/app/outputs/flutter-apk/app-x86-${{ matrix.job.reltype }}.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk - ;; - esac - popd - mkdir -p signed-apk; pushd signed-apk - mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk . - - # https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075 - - name: Setup sign tool version variable - shell: bash - run: | - BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1) - echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV - echo Last build tool version is: $BUILD_TOOL_VERSION - - - uses: r0adkll/sign-android-release@v1 - name: Sign app APK - if: env.ANDROID_SIGNING_KEY != null - id: sign-rustdesk - with: - releaseDirectory: ./signed-apk - signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} - alias: ${{ secrets.ANDROID_ALIAS }} - keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} - keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} - env: - # env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable" - BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }} - - - name: Upload Artifacts - if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - uses: actions/upload-artifact@master - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - path: ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - - - name: Publish signed apk package - if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - - - name: Publish unsigned apk package - if: env.ANDROID_SIGNING_KEY == null && env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - signed-apk/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - - build-rustdesk-android-universal: - needs: [build-rustdesk-android] - name: build rustdesk android universal apk - if: ${{ inputs.upload-artifact }} - runs-on: ubuntu-24.04 - env: - reltype: release - x86_target: "" # can be ",android-x86" - suffix: "" - steps: - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - tool-cache: false - android: false - dotnet: true - haskell: true - large-packages: false - docker-images: true - swap-storage: false - - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - clang \ - cmake \ - curl \ - gcc-multilib \ - git \ - g++ \ - g++-multilib \ - libayatana-appindicator3-dev \ - libasound2-dev \ - libc6-dev \ - libclang-dev \ - libunwind-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - libpam0g-dev \ - libpulse-dev \ - libva-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - llvm-dev \ - nasm \ - ninja-build \ - openjdk-17-jdk-headless \ - pkg-config \ - tree \ - wget - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }} - - - name: Patch flutter - run: | - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - - - name: Restore bridge files - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Download Rustdesk library from Artifacts - uses: actions/download-artifact@master - with: - name: librustdesk.so.aarch64-linux-android - path: ./flutter/android/app/src/main/jniLibs/arm64-v8a - - - name: Download Rustdesk library from Artifacts - uses: actions/download-artifact@master - with: - name: librustdesk.so.armv7-linux-androideabi - path: ./flutter/android/app/src/main/jniLibs/armeabi-v7a - - - name: Download Rustdesk library from Artifacts - uses: actions/download-artifact@master - with: - name: librustdesk.so.x86_64-linux-android - path: ./flutter/android/app/src/main/jniLibs/x86_64 - - - name: Download Rustdesk library from Artifacts - if: ${{ env.reltype == 'debug' }} - uses: actions/download-artifact@master - with: - name: librustdesk.so.i686-linux-android - path: ./flutter/android/app/src/main/jniLibs/x86 - - - name: Build rustdesk - shell: bash - env: - JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 - run: | - export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH - # Increase Gradle JVM memory for CI builds - sed -i "s/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g" ./flutter/android/gradle.properties - # temporary use debug sign config - sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle - mv ./flutter/android/app/src/main/jniLibs/arm64-v8a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/ - mv ./flutter/android/app/src/main/jniLibs/armeabi-v7a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/ - mv ./flutter/android/app/src/main/jniLibs/x86_64/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86_64/librustdesk.so - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86_64/ - if [ "${{ env.reltype }}" = "debug" ]; then - mv ./flutter/android/app/src/main/jniLibs/x86/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/x86/librustdesk.so - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/x86/ - fi - # build flutter - pushd flutter - flutter build apk "--${{ env.reltype }}" --target-platform android-arm64,android-arm,android-x64${{ env.x86_target }} - popd - mkdir -p signed-apk - mv ./flutter/build/app/outputs/flutter-apk/app-${{ env.reltype }}.apk signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk - - # https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075 - - name: Setup sign tool version variable - shell: bash - run: | - BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1) - echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV - echo Last build tool version is: $BUILD_TOOL_VERSION - - - uses: r0adkll/sign-android-release@v1 - name: Sign app APK - if: env.ANDROID_SIGNING_KEY != null - id: sign-rustdesk - with: - releaseDirectory: ./signed-apk - signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} - alias: ${{ secrets.ANDROID_ALIAS }} - keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} - keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} - env: - # env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable" - BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }} - - - name: Upload Artifacts - if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - uses: actions/upload-artifact@master - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - path: ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - - - name: Publish signed apk package - if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ${{steps.sign-rustdesk.outputs.signedReleaseFile}} - - - name: Publish unsigned apk package - if: env.ANDROID_SIGNING_KEY == null && env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk - - build-rustdesk-linux: - needs: [generate-bridge] - name: build rustdesk linux ${{ matrix.job.target }} - runs-on: ${{ matrix.job.on }} - strategy: - fail-fast: false - matrix: - # use a high level qemu-user-static - job: - - { - arch: x86_64, - target: x86_64-unknown-linux-gnu, - distro: ubuntu18.04, - on: ubuntu-22.04, - deb_arch: amd64, - vcpkg-triplet: x64-linux, - } - - { - arch: aarch64, - target: aarch64-unknown-linux-gnu, - distro: ubuntu18.04, - on: ubuntu-22.04-arm, - deb_arch: arm64, - vcpkg-triplet: arm64-linux, - } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Maximize build space - run: | - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/lib/android - sudo rm -rf /usr/share/dotnet - sudo apt-get update -y - sudo apt-get install -y nasm - if [[ "${{ matrix.job.arch }}" == "x86_64" ]]; then - sudo apt-get install -y qemu-user-static - fi - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set Swap Space - if: ${{ matrix.job.arch == 'x86_64' }} - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 12 - - - name: Free Space - run: | - df -h - free -m - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - with: - toolchain: ${{ env.RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - name: Save Rust toolchain version - run: | - RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}') - echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV - - - name: Disable rust bridge build - run: | - # only build cdylib - sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml - - - name: Restore bridge files - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - name: Setup vcpkg with Github Actions binary cache - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - uses: lukka/run-vcpkg@v11 - with: - vcpkgDirectory: /opt/artifacts/vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - doNotCache: false - - - name: Install vcpkg dependencies - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - run: | - sudo apt install -y libva-dev && apt show libva-dev - if ! $VCPKG_ROOT/vcpkg \ - install \ - --triplet ${{ matrix.job.vcpkg-triplet }} \ - --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - shell: bash - - - name: Restore bridge files - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - uses: actions/download-artifact@master - with: - name: bridge-artifact - path: ./ - - - uses: rustdesk-org/run-on-arch-action@amd64-support - name: Build rustdesk - id: vcpkg - if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true' - with: - arch: ${{ matrix.job.arch }} - distro: ${{ matrix.job.distro }} - githubToken: ${{ github.token }} - setup: | - ls -l "${PWD}" - ls -l /opt/artifacts/vcpkg/installed - dockerRunArgs: | - --volume "${PWD}:/workspace" - --volume "/opt/artifacts:/opt/artifacts" - shell: /bin/bash - install: | - apt-get update -y - echo -e "installing deps" - apt-get install -y \ - build-essential \ - clang \ - cmake \ - curl \ - gcc \ - git \ - g++ \ - libayatana-appindicator3-dev \ - libasound2-dev \ - libclang-10-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - libpam0g-dev \ - libpulse-dev \ - libva-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - llvm-10-dev \ - nasm \ - ninja-build \ - pkg-config \ - tree \ - python3 \ - rpm \ - unzip \ - wget \ - xz-utils \ - libssl-dev - # we have libopus compiled by us. - apt-get remove -y libopus-dev || true - # output devs - ls -l ./ - tree -L 3 /opt/artifacts/vcpkg/installed - run: | - # disable git safe.directory - git config --global --add safe.directory "*" - # rust - pushd /opt - # do not use rustup, because memory overflow in qemu - wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }}.tar.gz - tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz - cd rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} && ./install.sh - rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} - # edit config - mkdir -p ~/.cargo/ - echo """ - [source.crates-io] - registry = 'https://github.com/rust-lang/crates.io-index' - """ > ~/.cargo/config - cat ~/.cargo/config - # start build - pushd /workspace - export VCPKG_ROOT=/opt/artifacts/vcpkg - if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then - export JOBS="--jobs 3" - else - export JOBS="" - fi - echo $JOBS - cargo build --lib $JOBS --features hwcodec,flutter,unix-file-copy-paste --release - rm -rf target/release/deps target/release/build - rm -rf ~/.cargo - - # Setup Flutter - # disable git safe.directory - git config --global --add safe.directory "*" - pushd /workspace - case ${{ matrix.job.arch }} in - aarch64) - export PATH=/opt/flutter-elinux/bin:$PATH - sed -i "s/flutter build linux --release/flutter-elinux build linux --verbose/g" ./build.py - sed -i "s/x64\/release/arm64\/release/g" ./build.py - ;; - x86_64) - export PATH=/opt/flutter/bin:$PATH - ;; - esac - popd - pushd /opt - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz - tar xf flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz - case ${{ matrix.job.arch }} in - aarch64) - # clone repo and reset to flutter ${{ env.FLUTTER_VERSION }} - git clone https://github.com/sony/flutter-elinux.git || true - pushd flutter-elinux - git fetch - git reset --hard ${{ env.FLUTTER_VERSION }} - bin/flutter-elinux doctor -v - bin/flutter-elinux precache --linux - popd - cp -R flutter/bin/cache/artifacts/engine/linux-x64/shader_lib flutter-elinux/flutter/bin/cache/artifacts/engine/linux-arm64 - rm -rf flutter - ;; - x86_64) - flutter doctor -v - ;; - esac - - if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then - case ${{ matrix.job.arch }} in - aarch64) - pushd /opt/flutter-elinux/flutter - ;; - x86_64) - pushd /opt/flutter - ;; - esac - git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - popd - fi - - # build flutter - pushd /workspace - export CARGO_INCREMENTAL=0 - export DEB_ARCH=${{ matrix.job.deb_arch }} - python3 ./build.py --flutter --skip-cargo - for name in rustdesk*??.deb; do - mv "$name" "${name%%.deb}-${{ matrix.job.arch }}.deb" - done - - # rpm package - echo -e "start packaging fedora package" - pushd /workspace - case ${{ matrix.job.arch }} in - aarch64) - sed -i "s/linux\/x64/linux\/arm64/g" ./res/rpm-flutter.spec - ;; - esac - HBB=`pwd` rpmbuild ./res/rpm-flutter.spec -bb - pushd ~/rpmbuild/RPMS/${{ matrix.job.arch }} - for name in rustdesk*??.rpm; do - mv "$name" /workspace/"${name%%.rpm}.rpm" - done - - # rpm suse package - echo -e "start packaging suse package" - pushd /workspace - case ${{ matrix.job.arch }} in - aarch64) - sed -i "s/linux\/x64/linux\/arm64/g" ./res/rpm-flutter-suse.spec - ;; - esac - HBB=`pwd` rpmbuild ./res/rpm-flutter-suse.spec -bb - pushd ~/rpmbuild/RPMS/${{ matrix.job.arch }} - for name in rustdesk*??.rpm; do - mv "$name" /workspace/"${name%%.rpm}-suse.rpm" - done - - - name: Publish debian/rpm package - if: env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - rustdesk-*.deb - rustdesk-*.rpm - - - name: Upload deb - uses: actions/upload-artifact@master - if: env.UPLOAD_ARTIFACT == 'true' - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb - path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb - - # only x86_64 for arch since we can not find newest arm64 docker image to build - # old arch image does not make sense for arch since it is "arch" which always update to date - # and failed to makepkg arm64 on x86_64 - - name: Patch archlinux PKGBUILD - if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' - run: | - sed -i "s/x86_64/${{ matrix.job.arch }}/g" res/PKGBUILD - if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then - sed -i "s/x86_64/aarch64/g" ./res/PKGBUILD - fi - - - name: Build archlinux package - if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' - uses: rustdesk-org/arch-makepkg-action@master - with: - packages: - scripts: | - cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f - - - name: Publish archlinux package - if: matrix.job.arch == 'x86_64' && env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - res/rustdesk-${{ env.VERSION }}*.zst - - build-rustdesk-linux-sciter: - if: ${{ inputs.upload-artifact }} - runs-on: ${{ matrix.job.on }} - name: build-rustdesk-linux-sciter ${{ matrix.job.target }} - strategy: - fail-fast: false - matrix: - # use a high level qemu-user-static - job: - - { - arch: x86_64, - target: x86_64-unknown-linux-gnu, - on: ubuntu-22.04, - distro: ubuntu18.04, - deb_arch: amd64, - sciter_arch: x64, - vcpkg-triplet: x64-linux, - extra_features: ",hwcodec,unix-file-copy-paste", - } - - { - arch: armv7, - target: armv7-unknown-linux-gnueabihf, - on: ubuntu-22.04-arm, - distro: ubuntu18.04-rustdesk, - deb_arch: armhf, - sciter_arch: arm32, - vcpkg-triplet: arm-linux, - extra_features: ",unix-file-copy-paste", - } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Modify vcpkg.json for armv7 - if: matrix.job.vcpkg-triplet == 'arm-linux' - run: | - # Replace the baseline in vcpkg.json with ARMV7_VCPKG_COMMIT_ID for armv7 builds - sed -i 's/"baseline": ".*"/"baseline": "${{ env.ARMV7_VCPKG_COMMIT_ID }}"/' vcpkg.json - echo "Modified vcpkg.json for armv7 build:" - grep -A 2 -B 2 '"baseline"' vcpkg.json - - - name: Free Space - run: | - df -h - free -m - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.SCITER_RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - name: Save Rust toolchain version - run: | - RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}') - echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV - - - uses: rustdesk-org/run-on-arch-action@amd64-support - name: Build rustdesk sciter binary for ${{ matrix.job.arch }} - id: vcpkg - with: - arch: ${{ matrix.job.arch }} - distro: ${{ matrix.job.distro }} - githubToken: ${{ github.token }} - setup: | - ls -l "${PWD}" - dockerRunArgs: | - --volume "${PWD}:/workspace" - shell: /bin/bash - install: | - apt-get update - apt-get install -y \ - build-essential \ - clang \ - curl \ - gcc \ - git \ - g++ \ - libayatana-appindicator3-dev \ - libasound2-dev \ - libclang-dev \ - libdbus-1-dev \ - libglib2.0-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - liblzma-dev \ - libpam0g-dev \ - libpulse-dev \ - libva-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - ninja-build \ - pkg-config \ - python3 \ - python3.7 \ - rpm \ - unzip \ - wget \ - xz-utils \ - zip \ - libssl-dev - # arm-linux needs CMake and vcokg built from source as there - # are no prebuilts available from Kitware and Microsoft - if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then - # install gcc/g++ 8 for vcpkg and OpenSSL headers for CMake - apt-get install -y gcc-8 g++-8 - # bootstrap CMake amd add it to PATH - git clone --depth 1 https://github.com/kitware/cmake -b "v${{ env.SCITER_ARMV7_CMAKE_VERSION }}" /tmp/cmake - pushd /tmp/cmake - ./bootstrap --generator='Unix Makefiles' "--prefix=/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf" - make -j1 install - popd - rm -rf /tmp/cmake - export PATH="/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf/bin:$PATH" - fi - # bootstrap vcpkg and set VCPKG_ROOT - export VCPKG_ROOT=/opt/artifacts/vcpkg - mkdir -p /opt/artifacts - pushd /opt/artifacts - rm -rf vcpkg - git clone https://github.com/microsoft/vcpkg - pushd vcpkg - # build vcpkg helper executable with gcc-8 for arm-linux but use prebuilt one on x64-linux - if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then - git reset --hard ${{ env.ARMV7_VCPKG_COMMIT_ID }} - CC=/usr/bin/gcc-8 CXX=/usr/bin/g++-8 sh bootstrap-vcpkg.sh -disableMetrics - else - git reset --hard ${{ env.VCPKG_COMMIT_ID }} - sh bootstrap-vcpkg.sh -disableMetrics - fi - popd - popd - # rust - pushd /opt - # do not use rustup, because memory overflow in qemu - wget --output-document rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }}.tar.gz - tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz - pushd rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} - ./install.sh - popd - rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-${{ matrix.job.target }} - popd - # install newer nasm for aom - wget --output-document nasm.deb "http://ftp.us.debian.org/debian/pool/main/n/nasm/nasm_${{ env.SCITER_NASM_DEBVERSION }}_${{ matrix.job.deb_arch }}.deb" - dpkg -i nasm.deb - rm -f nasm.deb - run: | - # disable git safe.directory - git config --global --add safe.directory "*" - # set python3.7 as default python3 - update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1 - # add built CMake to PATH and set VCPKG_FORCE_SYSTEM_BINARIES Afor arm-linux - if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then - export PATH="/opt/cmake-${{ env.SCITER_ARMV7_CMAKE_VERSION }}-linux-armhf/bin:$PATH" - export VCPKG_FORCE_SYSTEM_BINARIES=1 - fi - # edit cargo config - mkdir -p ~/.cargo/ - echo """ - [source.crates-io] - registry = 'https://github.com/rust-lang/crates.io-index' - """ > ~/.cargo/config - cat ~/.cargo/config - # install dependencies from vcpkg - export VCPKG_ROOT=/opt/artifacts/vcpkg - # remove this when support higher version - export USE_AOM_391=1 - if ! $VCPKG_ROOT/vcpkg install --triplet ${{ matrix.job.vcpkg-triplet }} --x-install-root="$VCPKG_ROOT/installed"; then - find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do - echo "$_1:" - echo "======" - cat "$_1" - echo "======" - echo "" - done - exit 1 - fi - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true - # build rustdesk - python3 ./res/inline-sciter.py - export CARGO_INCREMENTAL=0 - cargo build --features inline${{ matrix.job.extra_features }} --release --bins --jobs 1 - # make debian package - mkdir -p ./Release - mv ./target/release/rustdesk ./Release/rustdesk - wget -O ./Release/libsciter-gtk.so https://github.com/c-smile/sciter-sdk/raw/master/bin.lnx/${{ matrix.job.sciter_arch }}/libsciter-gtk.so - export DEB_ARCH=${{ matrix.job.deb_arch }} - ./build.py --package ./Release - - - name: Rename rustdesk - shell: bash - run: | - for name in rustdesk*??.deb; do - # use cp to duplicate deb files to fit other packages. - cp "$name" "${name%%.deb}-${{ matrix.job.arch }}-sciter.deb" - done - - - name: Publish debian package - if: env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb - - - name: Upload deb - uses: actions/upload-artifact@master - if: env.UPLOAD_ARTIFACT == 'true' - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb - path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb - - build-appimage: - name: Build appimage ${{ matrix.job.target }} - needs: [build-rustdesk-linux] - runs-on: ubuntu-22.04 - if: ${{ inputs.upload-artifact }} - strategy: - fail-fast: false - matrix: - job: - - { target: x86_64-unknown-linux-gnu, arch: x86_64 } - - { target: aarch64-unknown-linux-gnu, arch: aarch64 } - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Download Binary - uses: actions/download-artifact@master - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb - path: . - - - name: Rename Binary - run: | - mv rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb appimage/rustdesk.deb - - - name: Build appimage package - shell: bash - run: | - # install libarchive-tools for bsdtar command used in AppImageBuilder.yml - sudo apt-get update -y - # https://github.com/AppImage/AppImageKit/wiki/FUSE - sudo apt-get install -y libarchive-tools libfuse2 - # set-up appimage-builder - # https://github.com/AppImage/AppImageKit/issues/1395 - sudo pip3 install git+https://github.com/rustdesk-org/appimage-builder.git - # run appimage-builder - pushd appimage - sudo appimage-builder --skip-tests --recipe ./AppImageBuilder-${{ matrix.job.arch }}.yml - - - name: Publish appimage package - if: env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ./appimage/rustdesk-${{ env.VERSION }}-*.AppImage - - build-flatpak: - name: Build flatpak ${{ matrix.job.target }}${{ matrix.job.suffix }} - needs: - - build-rustdesk-linux - - build-rustdesk-linux-sciter - runs-on: ${{ matrix.job.on }} - if: ${{ inputs.upload-artifact }} - strategy: - fail-fast: false - matrix: - job: - - { - target: x86_64-unknown-linux-gnu, - # https://github.com/ostreedev/ostree/commit/4bac96a8c817beda37448f9b8c662162bb619981 - distro: ubuntu22.04, - on: ubuntu-22.04, - arch: x86_64, - suffix: "", - } - - { - target: x86_64-unknown-linux-gnu, - distro: ubuntu22.04, - on: ubuntu-22.04, - arch: x86_64, - suffix: "-sciter", - } - - { - target: aarch64-unknown-linux-gnu, - # try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub" - distro: ubuntu22.04, - on: ubuntu-22.04-arm, - arch: aarch64, - suffix: "", - } - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Download Binary - uses: actions/download-artifact@master - with: - name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.deb - path: . - - - name: Rename Binary - run: | - mv rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.deb flatpak/rustdesk.deb - - - uses: rustdesk-org/run-on-arch-action@amd64-support - name: Build rustdesk flatpak package for ${{ matrix.job.arch }} - id: flatpak - with: - arch: ${{ matrix.job.arch }} - distro: ${{ matrix.job.distro }} - githubToken: ${{ github.token }} - setup: | - ls -l "${PWD}" - dockerRunArgs: | - --volume "${PWD}:/workspace" - shell: /bin/bash - install: | - apt-get update -y - apt-get install -y git flatpak flatpak-builder - run: | - # disable git safe.directory - git config --global --add safe.directory "*" - pushd /workspace - # flatpak deps - flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - # package - pushd flatpak - git clone https://github.com/flathub/shared-modules.git --depth=1 - flatpak-builder --user --install-deps-from=flathub -y --force-clean --repo=repo ./build ./rustdesk.json - flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak com.rustdesk.RustDesk - - - name: Publish flatpak package - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - flatpak/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak - - build-rustdesk-web: - if: False - name: build-rustdesk-web - runs-on: ubuntu-22.04 - permissions: - contents: read - strategy: - fail-fast: false - env: - RELEASE_NAME: web-basic - steps: - - name: Checkout source code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Prepare env - run: | - sudo apt-get update -y - sudo apt-get install -y wget npm - - - name: Install flutter - uses: subosito/flutter-action@v2.12.0 #https://github.com/subosito/flutter-action/issues/277 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - - - name: Patch flutter - shell: bash - run: | - cd $(dirname $(dirname $(which flutter))) - [[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff - - # https://rustdesk.com/docs/en/dev/build/web/ - - name: Build web - shell: bash - run: | - pushd flutter/web/js - npm install yarn -g - npm install typescript -g - npm install protoc -g - # Install protoc first, see: https://google.github.io/proto-lens/installing-protoc.html - npm install ts-proto - # Only works with vite <= 2.8, see: https://github.com/vitejs/vite/blob/main/docs/guide/build.md#chunking-strategy - npm install vite@2.8 - yarn install && yarn build - popd - - pushd flutter/web - wget https://github.com/rustdesk/doc.rustdesk.com/releases/download/console/web_deps.tar.gz - tar xzf web_deps.tar.gz - popd - - pushd flutter - flutter build web --release - cd build - cp ../web/README.md web - # TODO: Remove the following line when the web is almost complete. - echo -e "\n\nThis build is for preview and not full functionality." >> web/README.md - dir_name="rustdesk-${{ env.VERSION }}-${{ env.RELEASE_NAME }}" - mv web "${dir_name}" && tar czf "${dir_name}".tar.gz "${dir_name}" - sha256sum "${dir_name}".tar.gz - popd - - - name: Publish web - if: env.UPLOAD_ARTIFACT == 'true' - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - flutter/build/rustdesk-${{ env.VERSION }}-${{ env.RELEASE_NAME }}.tar.gz diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml deleted file mode 100644 index a64dd1197..000000000 --- a/.github/workflows/flutter-ci.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Full Flutter CI - -on: - workflow_dispatch: - pull_request: - paths-ignore: - - "docs/**" - - "README.md" - push: - branches: - - master - paths-ignore: - - ".github/**" - - "docs/**" - - "README.md" - - "res/**" - - "appimage/**" - - "flatpak/**" - -jobs: - run-ci: - uses: ./.github/workflows/flutter-build.yml - with: - upload-artifact: false diff --git a/.github/workflows/flutter-nightly.yml b/.github/workflows/flutter-nightly.yml deleted file mode 100644 index b16db4c4a..000000000 --- a/.github/workflows/flutter-nightly.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Flutter Nightly Build - -on: - schedule: - # schedule build every night - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - run-flutter-nightly-build: - uses: ./.github/workflows/flutter-build.yml - secrets: inherit - with: - upload-artifact: true - upload-tag: "nightly" diff --git a/.github/workflows/flutter-tag.yml b/.github/workflows/flutter-tag.yml deleted file mode 100644 index bf39db5cc..000000000 --- a/.github/workflows/flutter-tag.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Flutter Tag Build - -on: - workflow_dispatch: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+-[0-9]+' - -jobs: - run-flutter-tag-build: - uses: ./.github/workflows/flutter-build.yml - secrets: inherit - with: - upload-artifact: true - upload-tag: ${{ github.ref_name }} \ No newline at end of file diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml deleted file mode 100644 index 110437e0f..000000000 --- a/.github/workflows/playground.yml +++ /dev/null @@ -1,418 +0,0 @@ -name: playground - -on: - #schedule: - # schedule build every night - # - cron: "0/6 * * * *" - workflow_dispatch: - -env: - RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503 - CARGO_NDK_VERSION: "3.1.2" - LLVM_VERSION: "15.0.6" - FLUTTER_VERSION: "3.22.2" - FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" - # for arm64 linux because official Dart SDK does not work - FLUTTER_ELINUX_VERSION: "3.16.9" - TAG_NAME: "nightly" - VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" - VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b" - VERSION: "1.4.6" - NDK_VERSION: "r26d" - #signing keys env variable checks - ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}" - MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}" - # To make a custom build with your own servers set the below secret values - RS_PUB_KEY: "${{ secrets.RS_PUB_KEY }}" - RENDEZVOUS_SERVER: "${{ secrets.RENDEZVOUS_SERVER }}" - API_SERVER: "${{ secrets.API_SERVER }}" - UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}" - SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}" - -jobs: - build-for-macOS: - name: ${{ matrix.job.target }} - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { - target: x86_64-apple-darwin, - os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel - extra-build-args: "", - arch: x86_64, - flutter: "3.13.9", - ref: "f6509e3fd6917aa976bad2fc684182601ebf2434", - bridge: "1.80.1", - date: "20231219" - } - - { - target: x86_64-apple-darwin, - os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel - extra-build-args: "", - arch: x86_64, - flutter: "3.10.6", - ref: "f6509e3fd6917aa976bad2fc684182601ebf2434", - bridge: "1.80.1", - date: "20231219" - } - - { - target: x86_64-apple-darwin, - os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel - extra-build-args: "", - arch: x86_64, - flutter: "3.10.6", - ref: "85ddfc0739f052cab0029c46b899b959ee94eeb8", - bridge: "1.80.1", - date: "20231119" - } - - { - target: x86_64-apple-darwin, - os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel - extra-build-args: "", - arch: x86_64, - flutter: "3.13.9", - ref: "85ddfc0739f052cab0029c46b899b959ee94eeb8", - bridge: "1.80.1", - date: "20231119" - } - steps: - - name: Export GitHub Actions cache environment variables - uses: actions/github-script@v6 - with: - script: | - core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); - core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); - - - name: Checkout source code - uses: actions/checkout@v3 - with: - ref: ${{ matrix.job.ref }} - submodules: recursive - - - name: Import the codesign cert - if: env.MACOS_P12_BASE64 != null - uses: apple-actions/import-codesign-certs@v1 - with: - p12-file-base64: ${{ secrets.MACOS_P12_BASE64 }} - p12-password: ${{ secrets.MACOS_P12_PASSWORD }} - keychain: rustdesk - - - name: Check sign and import sign key - if: env.MACOS_P12_BASE64 != null - run: | - security default-keychain -s rustdesk.keychain - security find-identity -v - - - name: Import notarize key - if: env.MACOS_P12_BASE64 != null - uses: timheuer/base64-to-file@v1.2 - with: - # https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_rcodesign.html#notarizing-and-stapling - fileName: rustdesk.json - fileDir: ${{ github.workspace }} - encodedString: ${{ secrets.MACOS_NOTARIZE_JSON }} - - - name: Install rcodesign tool - if: env.MACOS_P12_BASE64 != null - shell: bash - run: | - pushd /tmp - wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-macos-universal.tar.gz - tar -zxvf apple-codesign-0.22.0-macos-universal.tar.gz - mv apple-codesign-0.22.0-macos-universal/rcodesign /usr/local/bin - popd - - - name: Install build runtime - run: | - brew install llvm create-dmg nasm pkg-config - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ matrix.job.flutter }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_VERSION }} - targets: ${{ matrix.job.target }} - components: "rustfmt" - - - uses: Swatinem/rust-cache@v2 - with: - prefix-key: ${{ matrix.job.os }} - - - name: Install flutter rust bridge deps - shell: bash - run: | - sed -i '' 's/3.1.0/2.17.0/g' flutter/pubspec.yaml; - cargo install flutter_rust_bridge_codegen --version ${{ matrix.job.bridge }} --features "uuid" --locked - # below works for mac to make buildable on 3.13.9 - # pushd flutter/lib; find . -name "*.dart" | xargs -I{} sed -i '' 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' {}; popd; - pushd flutter && flutter pub get && popd - ~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - - - name: Install vcpkg dependencies - run: | - $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed" - - - name: Restore from cache and install vcpkg - uses: lukka/run-vcpkg@v7 - if: false - with: - setupOnly: true - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - - - name: Install vcpkg dependencies - if: false - run: | - $VCPKG_ROOT/vcpkg install libvpx libyuv opus aom - - - name: Show version information (Rust, cargo, Clang) - shell: bash - run: | - clang --version || true - rustup -V - rustup toolchain list - rustup default - cargo -V - rustc -V - - - name: Build rustdesk - run: | - ./build.py --flutter ${{ matrix.job.extra-build-args }} - - - name: create unsigned dmg - run: | - CREATE_DMG="$(command -v create-dmg)" - CREATE_DMG="$(readlink -f "$CREATE_DMG")" - sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" - create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app - - - name: Codesign app and create signed dmg - if: env.MACOS_P12_BASE64 != null - run: | - # Patch create-dmg to give more attempts to unmount image - CREATE_DMG="$(command -v create-dmg)" - CREATE_DMG="$(readlink -f "$CREATE_DMG")" - sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG" - # Unlock keychain - security default-keychain -s rustdesk.keychain - security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain - # start sign the rustdesk.app and dmg - rm -rf *.dmg || true - codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv - create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app - codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict rustdesk-${{ env.VERSION }}.dmg -vvv - # notarize the rustdesk-${{ env.VERSION }}.dmg - rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple rustdesk-${{ env.VERSION }}.dmg - - - name: Rename rustdesk - run: | - for name in rustdesk*??.dmg; do - mv "$name" "${name%%.dmg}-${{ matrix.job.arch }}-flutter${{ matrix.job.flutter }}-flutter${{ matrix.job.date }}.dmg" - done - - - name: Publish DMG package - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - rustdesk*-${{ matrix.job.arch }}*.dmg - - - build-rustdesk-android: - if: false - name: build rustdesk android apk ${{ matrix.job.target }} - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { - arch: aarch64, - target: aarch64-linux-android, - os: ubuntu-22.04, - openssl-arch: android-arm64, - ref: master, # latest - } - steps: - - name: Checkout source code - uses: actions/checkout@v3 - with: - ref: ${{ matrix.job.ref }} - submodules: recursive - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - clang \ - cmake \ - curl \ - gcc-multilib \ - git \ - g++ \ - g++-multilib \ - libayatana-appindicator3-dev\ - libasound2-dev \ - libc6-dev \ - libclang-dev \ - libunwind-dev \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - libgtk-3-dev \ - libpam0g-dev \ - libpulse-dev \ - libva-dev \ - libvdpau-dev \ - libxcb-randr0-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libxdo-dev \ - libxfixes-dev \ - llvm-dev \ - nasm \ - yasm \ - ninja-build \ - openjdk-11-jdk-headless \ - pkg-config \ - tree \ - wget - - - name: Install flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: ${{ env.RUST_VERSION }} - components: "rustfmt" - - - name: Install flutter rust bridge deps - run: | - git config --global core.longpaths true - cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked - sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml - pushd flutter/lib; find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'; popd; - pushd flutter ; flutter pub get ; popd - ~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart - - - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: ${{ env.NDK_VERSION }} - add-to-path: true - - - name: Setup vcpkg with Github Actions binary cache - uses: lukka/run-vcpkg@v11 - with: - vcpkgDirectory: /opt/artifacts/vcpkg - vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }} - - - name: Install vcpkg dependencies - run: | - case ${{ matrix.job.target }} in - aarch64-linux-android) - ./flutter/build_android_deps.sh arm64-v8a - ;; - armv7-linux-androideabi) - ./flutter/build_android_deps.sh armeabi-v7a - ;; - esac - shell: bash - - - name: Clone deps - shell: bash - run: | - pushd /opt - git clone https://github.com/rustdesk-org/rustdesk_thirdparty_lib.git --depth=1 - ls -ls /opt/artifacts/vcpkg/installed/arm64-android/lib/ - # cp -rf /opt/rustdesk_thirdparty_lib/vcpkg/* /opt/artifacts/vcpkg/ - ls -ls /opt/artifacts/vcpkg/installed/arm64-android/lib/ - - - name: Build rustdesk lib - env: - ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }} - run: | - rustup target add ${{ matrix.job.target }} - cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked - case ${{ matrix.job.target }} in - aarch64-linux-android) - ./flutter/ndk_arm64.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so - ;; - armv7-linux-androideabi) - ./flutter/ndk_arm.sh - mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so - ;; - esac - - - name: Build rustdesk - shell: bash - env: - JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64 - run: | - export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH - # temporary use debug sign config - sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle - case ${{ matrix.job.target }} in - aarch64-linux-android) - mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so - # build flutter - pushd flutter - flutter build apk --release --target-platform android-arm64 --split-per-abi - mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - ;; - armv7-linux-androideabi) - mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a - cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/ - cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so - # build flutter - pushd flutter - flutter build apk --release --target-platform android-arm --split-per-abi - mv build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk - ;; - esac - popd - mkdir -p signed-apk; pushd signed-apk - mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk ./rustdesk-test-${{ matrix.job.ref }}-${{ matrix.job.ndk }}.apk - - - uses: r0adkll/sign-android-release@v1 - name: Sign app APK - if: env.ANDROID_SIGNING_KEY != null - id: sign-rustdesk - with: - releaseDirectory: ./signed-apk - signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }} - alias: ${{ secrets.ANDROID_ALIAS }} - keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }} - keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} - env: - # override default build-tools version (29.0.3) -- optional - BUILD_TOOLS_VERSION: "30.0.2" - - - name: Publish signed apk package - uses: softprops/action-gh-release@v1 - with: - prerelease: true - tag_name: ${{ env.TAG_NAME }} - files: | - ${{steps.sign-rustdesk.outputs.signedReleaseFile}} diff --git a/.github/workflows/third-party-RustDeskTempTopMostWindow.yml b/.github/workflows/third-party-RustDeskTempTopMostWindow.yml deleted file mode 100644 index 2f89092b7..000000000 --- a/.github/workflows/third-party-RustDeskTempTopMostWindow.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: build RustDeskTempTopMostWindow - -on: - workflow_call: - inputs: - upload-artifact: - type: boolean - default: true - target: - description: 'Target' - required: true - type: string - default: 'windows-2022' - configuration: - description: 'Configuration' - required: true - type: string - default: 'Release' - platform: - description: 'Platform' - required: true - type: string - default: 'x64' - target_version: - description: 'TargetVersion' - required: true - type: string - default: 'Windows10' - -env: - project_path: WindowInjection/WindowInjection.vcxproj - -jobs: - build-RustDeskTempTopMostWindow: - runs-on: ${{ inputs.target }} - strategy: - fail-fast: false - env: - build_output_dir: RustDeskTempTopMostWindow/WindowInjection/${{ inputs.platform }}/${{ inputs.configuration }} - steps: - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v2 - - - name: Download the source code - run: | - git clone https://github.com/rustdesk-org/RustDeskTempTopMostWindow RustDeskTempTopMostWindow - - # Build. commit 53b548a5398624f7149a382000397993542ad796 is tag v0.3 - - name: Build the project - run: | - cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796 - msbuild ${{ env.project_path }} -p:Configuration=${{ inputs.configuration }} -p:Platform=${{ inputs.platform }} /p:TargetVersion=${{ inputs.target_version }} - - - name: Archive build artifacts - uses: actions/upload-artifact@master - if: ${{ inputs.upload-artifact }} - with: - name: topmostwindow-artifacts - path: | - ./${{ env.build_output_dir }}/WindowInjection.dll diff --git a/.gitignore b/.gitignore index d2e09a906..cce3b90a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,58 +1,10 @@ -/build /target .vscode .idea .DS_Store -.env -libsciter-gtk.so src/ui/inline.rs extractor __pycache__ src/version.rs *dmg -*exe -*tgz -cert.pfx -*.bak -*png -*svg -*jpg sciter.dll -**pdb -src/bridge_generated.rs -src/bridge_generated.io.rs -*deb -rustdesk -*.cache -# appimage -appimage/AppDir -appimage/*.AppImage -appimage/appimage-build -appimage/*.xz -# flutter -flutter/linux/build/** -flutter/linux/cmake-build-debug/** -# flatpak -flatpak/.flatpak-builder/** -flatpak/ccache/** -flatpak/.flatpak-builder/build/** -flatpak/.flatpak-builder/shared-modules/** -flatpak/.flatpak-builder/shared-modules/*.tar.xz -flatpak/.flatpak-builder/debian-binary -flatpak/build/** -flatpak/repo/** -flatpak/*.flatpak -# bridge file -lib/generated_bridge.dart -# vscode devcontainer -.gitconfig -.vscode-server/ -.ssh -.devcontainer/.* -# build cache in examples -examples/**/target/ -# === -vcpkg_installed -flutter/lib/generated_plugin_registrant.dart -libsciter.dylib -flutter/web/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d80e69aa8..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "libs/hbb_common"] - path = libs/hbb_common - url = https://github.com/rustdesk/hbb_common diff --git a/128x128.png b/128x128.png new file mode 100644 index 000000000..045d8f894 Binary files /dev/null and b/128x128.png differ diff --git a/128x128@2x.png b/128x128@2x.png new file mode 100644 index 000000000..39e2b23cf Binary files /dev/null and b/128x128@2x.png differ diff --git a/32x32.png b/32x32.png new file mode 100644 index 000000000..bba85feb6 Binary files /dev/null and b/32x32.png differ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index e36c65fab..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,62 +0,0 @@ -# RustDesk Guide - -## Project Layout - -### Directory Structure -* `src/` Rust app -* `src/server/` audio / clipboard / input / video / network -* `src/platform/` platform-specific code -* `src/ui/` legacy Sciter UI (deprecated) -* `flutter/` current UI -* `libs/hbb_common/` config / proto / shared utils -* `libs/scrap/` screen capture -* `libs/enigo/` input control -* `libs/clipboard/` clipboard -* `libs/hbb_common/src/config.rs` all options - -### Key Components -- **Remote Desktop Protocol**: Custom protocol implemented in `src/rendezvous_mediator.rs` for communicating with rustdesk-server -- **Screen Capture**: Platform-specific screen capture in `libs/scrap/` -- **Input Handling**: Cross-platform input simulation in `libs/enigo/` -- **Audio/Video Services**: Real-time audio/video streaming in `src/server/` -- **File Transfer**: Secure file transfer implementation in `libs/hbb_common/` - -### UI Architecture -- **Legacy UI**: Sciter-based (deprecated) - files in `src/ui/` -- **Modern UI**: Flutter-based - files in `flutter/` - - Desktop: `flutter/lib/desktop/` - - Mobile: `flutter/lib/mobile/` - - Shared: `flutter/lib/common/` and `flutter/lib/models/` - -## Rust Rules - -* Avoid `unwrap()` / `expect()` in production code. -* Exceptions: - - * tests; - * lock acquisition where failure means poisoning, not normal control flow. -* Otherwise prefer `Result` + `?` or explicit handling. -* Do not ignore errors silently. -* Avoid unnecessary `.clone()`. -* Prefer borrowing when practical. -* Do not add dependencies unless needed. -* Keep code simple and idiomatic. - -## Tokio Rules - -* Assume a Tokio runtime already exists. -* Never create nested runtimes. -* Never call `Runtime::block_on()` inside Tokio / async code. -* Do not hide runtime creation inside helpers or libraries. -* Do not hold locks across `.await`. -* Prefer `.await`, `tokio::spawn`, channels. -* Use `spawn_blocking` or dedicated threads for blocking work. -* Do not use `std::thread::sleep()` in async code. - -## Editing Hygiene - -* Change only what is required. -* Prefer the smallest valid diff. -* Do not refactor unrelated code. -* Do not make formatting-only changes. -* Keep naming/style consistent with nearby code. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index c31706425..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md diff --git a/docs/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 89% rename from docs/CONTRIBUTING.md rename to CONTRIBUTING.md index 31fd632e6..b7265a257 100644 --- a/docs/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ efforts from contributors on the same issue. - Commits should be accompanied by a Developer Certificate of Origin (http://developercertificate.org) sign-off, which indicates that you (and your employer if applicable) agree to be bound by the terms of the - [project license](../LICENCE). In git, this is the `-s` option to `git commit` + [project license](LICENSE). In git, this is the `-s` option to `git commit` - If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a @@ -35,11 +35,11 @@ efforts from contributors on the same issue. - Add tests relevant to the fixed bug or new feature. -For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). +For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/Github-workflow). ## Conduct -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md +We follow the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). ## Communication diff --git a/Cargo.lock b/Cargo.lock index fe1f67cc0..d8c112fd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,11 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ab_glyph" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - [[package]] name = "addr2line" -version = "0.22.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" dependencies = [ "gimli", ] @@ -34,115 +18,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler2" -version = "2.0.1" +name = "adler32" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if 1.0.0", - "getrandom 0.3.2", - "once_cell", - "version_check", - "zerocopy 0.8.26", -] +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] -[[package]] -name = "allo-isolate" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b6d794345b06592d0ebeed8e477e41b71e5a0a49df4fc0e4184d5938b99509" -dependencies = [ - "anyhow", - "atomic", - "chrono", - "uuid", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "alsa" -version = "0.9.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce" +checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b" dependencies = [ "alsa-sys", - "bitflags 2.9.1", + "bitflags", "libc", + "nix 0.23.1", ] [[package]] @@ -155,74 +54,22 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "android-activity" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" -dependencies = [ - "android-properties", - "bitflags 2.9.1", - "cc", - "cesu8", - "jni", - "jni-sys", - "libc", - "log", - "ndk 0.9.0", - "ndk-context", - "ndk-sys 0.6.0+11769913", - "num_enum 0.7.2", - "thiserror 1.0.61", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android-wakelock" -version = "0.1.0" -source = "git+https://github.com/rustdesk-org/android-wakelock#d0292e5a367e627c4fa6f1ca6bdfad005dca7d90" -dependencies = [ - "jni", - "log", - "ndk-context", -] - [[package]] name = "android_log-sys" -version = "0.3.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" [[package]] name = "android_logger" -version = "0.13.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f" +checksum = "d9ed09b18365ed295d722d0b5ed59c01b79a826ff2d2a8f73d5ecca8e6fb2f66" dependencies = [ "android_log-sys", - "env_logger 0.10.2", + "env_logger 0.8.4", + "lazy_static", "log", - "once_cell", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", ] [[package]] @@ -234,457 +81,131 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "anstream" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" [[package]] name = "arboard" -version = "3.4.0" -source = "git+https://github.com/rustdesk-org/arboard#85be1218668ff218a7b170c9d424fde73e069914" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d76e1fe0171b6d0857afca5671db12a44e71e80823db13ab39f776fb09ad079" dependencies = [ "clipboard-win", - "core-graphics 0.23.2", - "image 0.25.1", + "core-graphics", + "image", "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc", + "objc-foundation", + "objc_id", + "once_cell", "parking_lot", - "percent-encoding", - "serde 1.0.228", - "serde_derive", - "windows-sys 0.48.0", - "wl-clipboard-rs", - "x11rb 0.13.1", + "scopeguard", + "thiserror", + "winapi 0.3.9", + "x11rb", ] -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "asn1-rs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits 0.2.19", - "rusticata-macros", - "thiserror 1.0.61", - "time 0.3.36", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "associative-cache" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46016233fc1bb55c23b856fe556b7db6ccd05119a0a392e04f0b3b7c79058f16" - -[[package]] -name = "async-broadcast" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" -dependencies = [ - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-compression" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" -dependencies = [ - "compression-codecs", - "compression-core", - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "async-executor" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg 1.3.0", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg 1.3.0", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" -dependencies = [ - "async-lock 3.4.0", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.2", - "rustix 0.38.34", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.3.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" -dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", - "blocking", - "cfg-if 1.0.0", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.34", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "async-signal" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" -dependencies = [ - "async-io 2.3.3", - "async-lock 3.4.0", - "atomic-waker", - "cfg-if 1.0.0", - "futures-core", - "futures-io", - "rustix 0.38.34", - "signal-hook-registry", - "slab", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "atk" -version = "0.18.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" +checksum = "812b4911e210bd51b24596244523c856ca749e6223c50a7fbbba3f89ee37c426" dependencies = [ "atk-sys", - "glib 0.18.5", + "bitflags", + "glib", + "glib-sys", + "gobject-sys", "libc", ] [[package]] name = "atk-sys" -version = "0.18.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" +checksum = "f530e4af131d94cc4fa15c5c9d0348f0ef28bac64ba660b6b2a1cf2605dedfce" dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[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", + "hermit-abi", "libc", "winapi 0.3.9", ] [[package]] -name = "auto_impl" -version = "1.3.0" +name = "autocfg" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" -version = "0.1.8" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide 0.4.4", "object", "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" -version = "0.21.7" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] -name = "base64" -version = "0.22.1" +name = "bindgen" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239" dependencies = [ - "serde 1.0.228", + "bitflags", + "cexpr 0.4.0", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 0.1.1", ] [[package]] @@ -693,127 +214,29 @@ version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ - "bitflags 1.3.2", - "cexpr", + "bitflags", + "cexpr 0.6.0", "clang-sys", - "clap 2.34.0", - "env_logger 0.9.3", + "clap", + "env_logger 0.9.0", "lazy_static", "lazycell", "log", "peeking_take_while", - "proc-macro2 1.0.93", - "quote 1.0.36", + "proc-macro2", + "quote", "regex", - "rustc-hash 1.1.0", - "shlex", - "which", + "rustc-hash", + "shlex 1.1.0", + "which 4.2.2", ] -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease", - "proc-macro2 1.0.93", - "quote 1.0.36", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.98", - "which", -] - -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2 1.0.93", - "quote 1.0.36", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.98", -] - -[[package]] -name = "bindgen" -version = "0.71.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" -dependencies = [ - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "proc-macro2 1.0.93", - "quote 1.0.36", - "regex", - "rustc-hash 2.1.1", - "shlex", - "syn 2.0.98", -] - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -dependencies = [ - "serde 1.0.228", -] - -[[package]] -name = "bitmask-enum" -version = "2.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb15541e888071f64592c0b4364fdff21b7cb0a247f984296699351963a8721" -dependencies = [ - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block" version = "0.1.6" @@ -822,295 +245,70 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - -[[package]] -name = "block2" -version = "0.2.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" -dependencies = [ - "block-sys", - "objc2-encode 2.0.0-pre.2", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.4", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "build-target" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" - [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytecodec" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adf4c9d0bbf32eea58d7c0f812058138ee8edaf0f2802b6d03561b504729a325" -dependencies = [ - "byteorder", - "trackable 0.2.24", -] +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.10.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -dependencies = [ - "serde 1.0.228", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cacao" -version = "0.4.0-beta2" -source = "git+https://github.com/clslaid/cacao?branch=feat/set-file-urls#05e1536b0b43aaae308ec72c0eed703e875b7b95" -dependencies = [ - "bitmask-enum", - "block2 0.2.0-alpha.6", - "core-foundation 0.9.3", - "core-graphics 0.23.1", - "dispatch", - "lazy_static", - "libc", - "objc2 0.3.0-beta.2", - "os_info", - "percent-encoding", - "url", -] +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cairo-rs" -version = "0.18.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +checksum = "c5c0f2e047e8ca53d0ff249c54ae047931d7a6ebe05d00af73e0ffeb6e34bdb8" dependencies = [ - "bitflags 2.9.1", + "bitflags", "cairo-sys-rs", - "glib 0.18.5", + "glib", + "glib-sys", + "gobject-sys", "libc", - "once_cell", - "thiserror 1.0.61", + "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.18.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +checksum = "2ed2639b9ad5f1d6efa76de95558e11339e7318426d84ac4890b86c03e828ca7" dependencies = [ - "glib-sys 0.18.1", + "glib-sys", "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.9.1", - "log", - "polling 3.7.2", - "rustix 0.38.34", - "slab", - "thiserror 1.0.61", -] - -[[package]] -name = "calloop" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" -dependencies = [ - "bitflags 2.9.1", - "polling 3.7.2", - "rustix 1.1.2", - "slab", - "tracing", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.34", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" -dependencies = [ - "calloop 0.14.3", - "rustix 1.1.2", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", + "system-deps", ] [[package]] name = "cc" -version = "1.2.13" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" dependencies = [ "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "ccm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" -dependencies = [ - "aead", - "cipher", - "ctr", - "subtle", ] [[package]] @@ -1119,23 +317,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom 5.1.2", +] + [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", + "nom 7.1.0", ] [[package]] @@ -1150,103 +347,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits 0.2.19", - "wasm-bindgen", - "windows-link 0.1.1", -] - -[[package]] -name = "cidr-utils" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2315f7119b7146d6a883de6acd63ddf96071b5f79d9d98d2adaa84d749f6abf1" -dependencies = [ - "debug-helper", - "num-bigint", - "num-traits 0.2.19", - "once_cell", - "regex", -] - -[[package]] -name = "cidre" -version = "0.4.0" -source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf" -dependencies = [ - "cidre-macros", - "parking_lot", -] - -[[package]] -name = "cidre-macros" -version = "0.1.0" -source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf" - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", - "zeroize", -] - [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" dependencies = [ "glob", "libc", - "libloading 0.8.4", + "libloading", ] [[package]] @@ -1257,94 +366,36 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags 1.3.2", + "bitflags", "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] -[[package]] -name = "clap" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim 0.11.1", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "clipboard" -version = "0.1.0" -dependencies = [ - "cacao", - "cc", - "dashmap 5.5.3", - "dirs 5.0.1", - "fsevent", - "fuser", - "hbb_common", - "lazy_static", - "libc", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "once_cell", - "parking_lot", - "percent-encoding", - "rand 0.8.5", - "serde 1.0.228", - "serde_derive", - "thiserror 1.0.61", - "utf16string", - "uuid", - "x11-clipboard 0.8.1", - "x11rb 0.12.0", - "xattr", -] - [[package]] name = "clipboard-master" -version = "4.0.0-beta.6" -source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459887701008d8ee21f8de7f45f0f0707417c7eea3311973f6e67222bd686b7a" dependencies = [ "objc", "objc-foundation", "objc_id", - "wayland-client", - "wayland-protocols", - "wayland-protocols-wlr", + "winapi 0.3.9", "windows-win", - "wl-clipboard-rs", - "x11-clipboard 0.9.2", - "x11rb 0.13.1", + "x11-clipboard", ] [[package]] name = "clipboard-win" -version = "5.4.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +checksum = "1951fb8aa063a2ee18b4b4d217e4aa2ec9cc4f2430482983f607fa10cd36d7aa" dependencies = [ "error-code", + "str-buf", + "winapi 0.3.9", ] [[package]] @@ -1353,75 +404,45 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "cmake" -version = "0.1.50" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" dependencies = [ "cc", ] [[package]] name = "cocoa" -version = "0.20.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.7.0", - "core-graphics 0.19.2", - "foreign-types 0.3.2", - "libc", - "objc", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", + "bitflags", "block", "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics 0.22.3", - "foreign-types 0.3.2", - "libc", - "objc", -] - -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "foreign-types 0.5.0", + "core-foundation", + "core-graphics", + "foreign-types", "libc", "objc", ] [[package]] name = "cocoa-foundation" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" dependencies = [ - "bitflags 1.3.2", + "bitflags", "block", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-foundation", + "core-graphics-types", + "foreign-types", "libc", "objc", ] @@ -1432,183 +453,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "colorchoice" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" - [[package]] name = "combine" -version = "4.6.7" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5" dependencies = [ "bytes", "memchr", ] -[[package]] -name = "compression-codecs" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" -dependencies = [ - "compression-core", - "flate2", - "memchr", -] - -[[package]] -name = "compression-core" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "confy" -version = "0.4.0-2" -source = "git+https://github.com/rustdesk-org/confy#83db9ec19a2f97e9718aef69e4fc5611bb382479" +version = "0.4.1" +source = "git+https://github.com/open-trade/confy#27fa12941291b44ccd856aef4a5452c1eb646047" dependencies = [ - "directories-next", - "serde 1.0.228", - "thiserror 1.0.61", - "toml 0.5.11", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "const_fn" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" - -[[package]] -name = "const_format" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "unicode-xid 0.2.4", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - -[[package]] -name = "core-foundation" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -dependencies = [ - "core-foundation-sys 0.7.0", - "libc", + "directories", + "serde 1.0.133", + "toml", ] [[package]] name = "core-foundation" -version = "0.9.3" -source = "git+https://github.com/madsmtm/core-foundation-rs.git?rev=7d593d016175755e492a92ef89edca68ac3bd5cd#7d593d016175755e492a92ef89edca68ac3bd5cd" -dependencies = [ - "core-foundation-sys 0.8.6", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" dependencies = [ - "core-foundation-sys 0.8.7", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys 0.8.7", + "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "git+https://github.com/madsmtm/core-foundation-rs.git?rev=7d593d016175755e492a92ef89edca68ac3bd5cd#7d593d016175755e492a92ef89edca68ac3bd5cd" -dependencies = [ - "objc2-encode 2.0.0-pre.2", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.7.0", - "foreign-types 0.3.2", - "libc", -] +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "core-graphics" @@ -1616,346 +495,174 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.3.2", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.23.1" -source = "git+https://github.com/madsmtm/core-foundation-rs.git?rev=7d593d016175755e492a92ef89edca68ac3bd5cd#7d593d016175755e492a92ef89edca68ac3bd5cd" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.3", - "core-graphics-types 0.1.2", - "foreign-types 0.5.0", - "libc", - "objc2-encode 2.0.0-pre.2", -] - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", "libc", ] [[package]] name = "core-graphics-types" -version = "0.1.2" -source = "git+https://github.com/madsmtm/core-foundation-rs.git?rev=7d593d016175755e492a92ef89edca68ac3bd5cd#7d593d016175755e492a92ef89edca68ac3bd5cd" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.3", - "libc", - "objc2-encode 2.0.0-pre.2", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core-media-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273bf3fc5bf51fd06a7766a84788c1540b6527130a0bce39e00567d6ab9f31f1" -dependencies = [ - "cfg-if 0.1.10", - "core-foundation-sys 0.7.0", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation 0.9.4", - "core-graphics 0.22.3", - "foreign-types 0.3.2", - "libc", -] - -[[package]] -name = "core-video-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" -dependencies = [ - "cfg-if 0.1.10", - "core-foundation-sys 0.7.0", - "core-graphics 0.19.2", - "libc", - "metal", - "objc", -] - -[[package]] -name = "core_maths" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ - "libm", + "bitflags", + "core-foundation", + "foreign-types", + "libc", ] [[package]] name = "coreaudio-rs" -version = "0.11.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88" dependencies = [ - "bitflags 1.3.2", - "core-foundation-sys 0.8.7", + "bitflags", "coreaudio-sys", ] [[package]] name = "coreaudio-sys" -version = "0.2.15" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9" +checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa" dependencies = [ - "bindgen 0.69.4", + "bindgen 0.56.0", ] [[package]] name = "cpal" -version = "0.15.3" -source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#6b374bcaed076750ca8fce6da518ab39b882e14a" +version = "0.13.4" +source = "git+https://github.com/open-trade/cpal#3a30569687271c2d5d67279c4883d63f4003d34c" dependencies = [ "alsa", - "cidre", - "core-foundation-sys 0.8.7", + "core-foundation-sys", "coreaudio-rs", - "dasp_sample", "jni", "js-sys", + "lazy_static", "libc", - "mach2", - "ndk 0.8.0", - "ndk-context", + "mach", + "ndk", + "ndk-glue", + "nix 0.23.1", "oboe", - "wasm-bindgen", - "wasm-bindgen-futures", + "parking_lot", + "stdweb", + "thiserror", "web-sys", - "windows 0.54.0", + "winapi 0.3.9", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" dependencies = [ "generic-array", - "rand_core 0.6.4", - "typenum", ] [[package]] -name = "ctor-lite" -version = "0.1.0" +name = "cstr_core" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "644828c273c063ab0d39486ba42a5d1f3a499d35529c759e763a9c6cb8a0fb08" dependencies = [ - "cipher", + "cty", + "memchr", ] [[package]] name = "ctrlc" -version = "3.4.4" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ - "nix 0.28.0", - "windows-sys 0.52.0", + "nix 0.23.1", + "winapi 0.3.9", ] [[package]] -name = "cursor-icon" -version = "1.2.0" +name = "cty" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "darling" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", + "darling_core", + "darling_macro", ] [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "darling_core" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn", ] [[package]] -name = "dart-sys" -version = "4.1.5" +name = "darling_macro" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "cc", + "darling_core", + "quote", + "syn", ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "darwin-libproc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "cc629b7cf42586fee31dae31f9ab73fa5ff5f0170016aa61be5fcbc12a90c516" dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "darwin-libproc-sys", + "libc", + "memchr", ] [[package]] -name = "dashmap" -version = "6.1.0" +name = "darwin-libproc-sys" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "ef0aa083b94c54aa4cfd9bbfd37856714c139d1dc511af80270558c7ba3b4816" dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "libc", ] [[package]] @@ -2077,17 +784,11 @@ dependencies = [ "dasp_sample", ] -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - [[package]] name = "dbus" -version = "0.9.7" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +checksum = "de0a745c25b32caa56b82a3950f5fec7893a960f4c10ca3b02060b0c38d8c2ce" dependencies = [ "libc", "libdbus-sys", @@ -2095,102 +796,45 @@ dependencies = [ ] [[package]] -name = "dbus-crossroads" -version = "0.5.2" +name = "deflate" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" dependencies = [ - "dbus", + "adler32", + "byteorder", ] [[package]] -name = "debug-helper" -version = "0.3.13" +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" - -[[package]] -name = "default-net" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4898b43aed56499fad6b294d15b3e76a51df68079bf492e5daae38ca084e003" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "dlopen2", - "libc", - "memalloc", - "netlink-packet-core", - "netlink-packet-route", - "netlink-sys", - "once_cell", - "system-configuration", - "windows 0.32.0", -] - -[[package]] -name = "default_net" -version = "0.1.0" -source = "git+https://github.com/rustdesk-org/default_net#78f8f70cd85151a3a2c4a3230d80d5272703c02e" -dependencies = [ - "anyhow", - "regex", - "winapi 0.3.9", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits 0.2.19", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", + "generic-array", +] + +[[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", ] [[package]] @@ -2203,43 +847,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" -dependencies = [ - "cfg-if 0.1.10", - "dirs-sys 0.3.7", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys 0.5.0", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -2252,39 +859,15 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", - "redox_users 0.4.5", + "redox_users", "winapi 0.3.9", ] -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.5", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2292,7 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.5", + "redox_users", "winapi 0.3.9", ] @@ -2302,88 +885,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.4", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.4", -] - -[[package]] -name = "dlopen" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" -dependencies = [ - "dlopen_derive", - "lazy_static", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "dlopen2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b121caccfc363e4d9a4589528f3bef7c71b83c6ed01c8dc68cbeeb7fd29ec698" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi 0.3.9", -] - -[[package]] -name = "dlopen2_derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a09ac8bb8c16a282264c379dffba707b9c998afc7506009137f3c6136888078" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "dlopen_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" -dependencies = [ - "libc", - "quote 0.6.13", - "syn 0.15.44", -] - -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - [[package]] name = "docopt" version = "1.1.1" @@ -2392,244 +893,51 @@ checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" dependencies = [ "lazy_static", "regex", - "serde 1.0.228", + "serde 1.0.133", "strsim 0.10.0", ] -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" - -[[package]] -name = "drm" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" -dependencies = [ - "bitflags 2.9.1", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "rustix 0.38.34", -] - -[[package]] -name = "drm-ffi" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" -dependencies = [ - "drm-sys", - "rustix 0.38.34", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" -dependencies = [ - "libc", - "linux-raw-sys 0.6.5", -] - -[[package]] -name = "dtls" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f531dd7c181beaf3cebab3716afa4d0d41ab888be85232583f56bbaf07ca208a" -dependencies = [ - "aes", - "aes-gcm", - "async-trait", - "bincode", - "byteorder", - "cbc", - "ccm", - "chacha20poly1305", - "der-parser", - "hmac", - "log", - "p256", - "p384", - "portable-atomic", - "rand 0.9.2", - "rand_core 0.6.4", - "rcgen", - "ring", - "rustls", - "sec1", - "serde 1.0.228", - "sha1", - "sha2", - "thiserror 1.0.61", - "tokio", - "webrtc-util", - "x25519-dalek", - "x509-parser", -] - [[package]] name = "dtoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dylib_virtual_display" -version = "0.1.0" -dependencies = [ - "cc", - "hbb_common", - "lazy_static", - "serde 1.0.228", - "serde_derive", - "thiserror 1.0.61", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature 2.2.0", - "spki", -] - [[package]] name = "ed25519" -version = "1.5.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" dependencies = [ - "signature 1.6.4", + "signature", ] [[package]] name = "either" -version = "1.13.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "enigo" version = "0.0.14" dependencies = [ - "core-graphics 0.22.3", - "hbb_common", - "libxdo-sys", + "core-graphics", + "libc", "log", "objc", "pkg-config", - "rdev", - "serde 1.0.228", + "serde 1.0.133", "serde_derive", - "tfc", "unicode-segmentation", "winapi 0.3.9", ] [[package]] -name = "enquote" -version = "1.1.0" +name = "env_logger" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c36cb11dbde389f4096111698d8b567c0720e3452fd5ac3e6b4e47e1939932" -dependencies = [ - "thiserror 1.0.61", -] - -[[package]] -name = "enum-map" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" -dependencies = [ - "enum-map-derive", -] - -[[package]] -name = "enum-map-derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "enumflags2" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" -dependencies = [ - "enumflags2_derive", - "serde 1.0.228", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", @@ -2637,9 +945,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.9.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", @@ -2649,340 +957,92 @@ dependencies = [ ] [[package]] -name = "env_logger" -version = "0.10.2" +name = "err-derive" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "dcc7f65832b62ed38939f98966824eb6294911c3629b0e9a262bfb80836d9686" dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - -[[package]] -name = "epoll" -version = "4.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74351c3392ea1ff6cd2628e0042d268ac2371cb613252ff383b6dfa50d22fa79" -dependencies = [ - "bitflags 2.9.1", - "libc", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.52.0", + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn", + "synstructure", ] [[package]] name = "error-code" -version = "3.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" - -[[package]] -name = "evdev" -version = "0.11.5" -source = "git+https://github.com/rustdesk-org/evdev#cec616e37790293d2cd2aa54a96601ed6b1b35a9" -dependencies = [ - "bitvec", - "libc", - "nix 0.23.2", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.1", - "pin-project-lite", -] - -[[package]] -name = "exr" -version = "1.72.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide 0.7.4", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "fdeflate" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset 0.9.1", - "rustc_version", -] - -[[package]] -name = "filedescriptor" -version = "0.8.2" -source = "git+https://github.com/rustdesk-org/wezterm?branch=rustdesk/pty_based_0.8.1#80174f8009f41565f0fa8c66dab90d4f9211ae16" +checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" dependencies = [ "libc", - "thiserror 1.0.61", - "winapi 0.3.9", + "str-buf", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", ] [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "redox_syscall", + "winapi 0.3.9", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "flate2" -version = "1.1.9" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ + "cfg-if 1.0.0", "crc32fast", - "miniz_oxide 0.8.9", + "libc", + "miniz_oxide 0.4.4", ] [[package]] name = "flexi_logger" -version = "0.27.4" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469e584c031833564840fb0cdbce99bdfe946fd45480a188545e73a76f45461c" +checksum = "11be38a063886b7be57de89636d65c07d318c1f9bd985cd8ab2c343786a910bc" dependencies = [ - "chrono", - "crossbeam-channel", - "crossbeam-queue", + "ansi_term", + "atty", "glob", - "is-terminal", "lazy_static", "log", - "nu-ansi-term 0.49.0", "regex", - "thiserror 1.0.61", + "rustversion", + "thiserror", + "time", ] -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "spin", -] - -[[package]] -name = "flutter_rust_bridge" -version = "1.80.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0305ebc9f097d9826530a55fc2acd63222e912c663f7adce3ab641ecc0f346" -dependencies = [ - "allo-isolate", - "anyhow", - "build-target", - "bytemuck", - "cc", - "chrono", - "console_error_panic_hook", - "dart-sys", - "flutter_rust_bridge_macros", - "js-sys", - "lazy_static", - "libc", - "log", - "parking_lot", - "threadpool", - "uuid", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "flutter_rust_bridge_macros" -version = "1.82.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7fe743d921bedf4578b9472346d03a9643a01cd565ca7df7961baebad534ba5" - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "fon" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad46a0e6c9bc688823a742aa969b5c08fdc56c2a436ee00d5c6fbcb5982c55c4" -dependencies = [ - "libm", -] - -[[package]] -name = "fontconfig-parser" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" -dependencies = [ - "roxmltree", -] - -[[package]] -name = "fontdb" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2", - "slotmap", - "tinyvec", - "ttf-parser", -] - [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "foreign-types-shared", ] [[package]] @@ -2991,53 +1051,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fruitbasket" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898289b8e0528c84fb9b88f15ac9d5109bcaf23e0e49bb6f64deee0d86b6a351" -dependencies = [ - "dirs 2.0.2", - "objc", - "objc-foundation", - "objc_id", - "time 0.1.45", -] - -[[package]] -name = "fsevent" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8836d1f147a0a195bf517a5fd211ea7023d19ced903135faf6c4504f2cf8775f" -dependencies = [ - "bitflags 1.3.2", - "fsevent-sys", -] - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -3045,31 +1058,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] -name = "funty" -version = "2.0.0" +name = "fuchsia-zircon" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "fuser" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53274f494609e77794b627b1a3cddfe45d675a6b2e9ba9c0fdc8d8eee2184369" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "libc", - "log", - "memchr", - "nix 0.29.0", - "page_size", - "smallvec", - "zerocopy 0.8.26", + "bitflags", + "fuchsia-zircon-sys", ] [[package]] -name = "futures" -version = "0.3.30" +name = "fuchsia-zircon-sys" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" dependencies = [ "futures-channel", "futures-core", @@ -3082,9 +1090,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", "futures-sink", @@ -3092,15 +1100,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" dependencies = [ "futures-core", "futures-task", @@ -3109,66 +1117,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ "futures-channel", "futures-core", @@ -3183,261 +1163,169 @@ dependencies = [ ] [[package]] -name = "gdk" -version = "0.18.0" +name = "fxhash" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db00839b2a68a7a10af3fa28dfb3febaba3a20c3a9ac2425a33b7df1f84a6b7d" +dependencies = [ + "bitflags", "cairo-rs", + "cairo-sys-rs", "gdk-pixbuf", "gdk-sys", "gio", - "glib 0.18.5", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", "libc", "pango", ] [[package]] name = "gdk-pixbuf" -version = "0.18.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +checksum = "8f6dae3cb99dd49b758b88f0132f8d401108e63ae8edd45f432d42cdff99998a" dependencies = [ "gdk-pixbuf-sys", "gio", - "glib 0.18.5", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", "libc", - "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.18.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +checksum = "3bfe468a7f43e97b8d193a762b6c5cf67a7d36cacbc0b9291dbcae24bfea1e8f" dependencies = [ "gio-sys", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "gdk-sys" -version = "0.18.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +checksum = "0a9653cfc500fd268015b1ac055ddbc3df7a5c9ea3f4ccef147b3957bd140d69" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", "pango-sys", "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a" -dependencies = [ - "gdk-sys", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043" -dependencies = [ - "gdk-sys", - "glib-sys 0.18.1", - "libc", - "system-deps 6.2.2", - "x11 2.21.0", + "system-deps", ] [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "gethostname" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" dependencies = [ "libc", "winapi 0.3.9", ] -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", + "wasi", ] [[package]] name = "gimli" -version = "0.29.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] name = "gio" -version = "0.18.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +checksum = "1fb60242bfff700772dae5d9e3a1f7aa2e4ebccf18b89662a16acb2822568561" dependencies = [ + "bitflags", + "futures", "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", - "glib 0.18.5", + "glib", + "glib-sys", + "gobject-sys", "libc", "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.61", + "thiserror", ] [[package]] name = "gio-sys" -version = "0.18.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +checksum = "5e24fb752f8f5d2cf6bbc2c606fd2bc989c81c5e2fe321ab974d54f8b6344eac" dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", "winapi 0.3.9", ] -[[package]] -name = "git2" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "glib" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5" dependencies = [ - "bitflags 1.3.2", + "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", - "glib-macros 0.10.1", - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib-macros", + "glib-sys", + "gobject-sys", "libc", "once_cell", ] -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.9.1", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros 0.18.5", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.61", -] - [[package]] name = "glib-macros" version = "0.10.1" @@ -3445,27 +1333,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41486a26d1366a8032b160b59065a59fb528530a46a49f627e7048fb8c064039" dependencies = [ "anyhow", - "heck 0.3.3", - "itertools 0.9.0", + "heck", + "itertools", "proc-macro-crate 0.1.5", "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3475,24 +1349,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e9b997a66e9a23d073f2b1abb4dbfc3925e0b8952f67efd8d9b6e168e4cdc1" dependencies = [ "libc", - "system-deps 1.3.2", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "glob" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "gobject-sys" @@ -3500,31 +1364,9 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "952133b60c318a62bf82ee75b93acc7e84028a093e06b9e27981c2b6fe68218c" dependencies = [ - "glib-sys 0.10.1", + "glib-sys", "libc", - "system-deps 1.3.2", -] - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys 0.18.1", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", + "system-deps", ] [[package]] @@ -3533,14 +1375,14 @@ version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff5d0f7ff308ae37e6eb47b6ded17785bdea06e438a708cd09e0288c1862f33" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if 1.0.0", "futures-channel", "futures-core", "futures-util", - "glib 0.10.3", - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib", + "glib-sys", + "gobject-sys", "gstreamer-sys", "libc", "muldiv", @@ -3548,7 +1390,7 @@ dependencies = [ "once_cell", "paste", "pretty-hex", - "thiserror 1.0.61", + "thiserror", ] [[package]] @@ -3557,12 +1399,12 @@ version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc80888271338c3ede875d8cafc452eb207476ff5539dcbe0018a8f5b827af0e" dependencies = [ - "bitflags 1.3.2", + "bitflags", "futures-core", "futures-sink", - "glib 0.10.3", - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib", + "glib-sys", + "gobject-sys", "gstreamer", "gstreamer-app-sys", "gstreamer-base", @@ -3577,11 +1419,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "813f64275c9e7b33b828b9efcf9dfa64b95996766d4de996e84363ac65b87e3d" dependencies = [ - "glib-sys 0.10.1", + "glib-sys", "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps 1.3.2", + "system-deps", ] [[package]] @@ -3590,10 +1432,10 @@ version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafd01c56f59cb10f4b5a10f97bb4bdf8c2b2784ae5b04da7e2d400cf6e6afcf" dependencies = [ - "bitflags 1.3.2", - "glib 0.10.3", - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "bitflags", + "glib", + "glib-sys", + "gobject-sys", "gstreamer", "gstreamer-base-sys", "gstreamer-sys", @@ -3606,11 +1448,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b7b6dc2d6e160a1ae28612f602bd500b3fa474ce90bf6bb2f08072682beef5" dependencies = [ - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib-sys", + "gobject-sys", "gstreamer-sys", "libc", - "system-deps 1.3.2", + "system-deps", ] [[package]] @@ -3619,10 +1461,10 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1f154082d01af5718c5f8a8eb4f565a4ea5586ad8833a8fc2c2aa6844b601d" dependencies = [ - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib-sys", + "gobject-sys", "libc", - "system-deps 1.3.2", + "system-deps", ] [[package]] @@ -3631,12 +1473,12 @@ version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7bbb1485d87469849ec45c08e03c2f280d3ea20ff3c439d03185be54e3ce98e" dependencies = [ - "bitflags 1.3.2", + "bitflags", "futures-channel", "futures-util", - "glib 0.10.3", - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib", + "glib-sys", + "gobject-sys", "gstreamer", "gstreamer-base", "gstreamer-base-sys", @@ -3652,159 +1494,92 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92347e46438007d6a2386302125f62cb9df6769cdacb931af5c0f12c1ee21de4" dependencies = [ - "glib-sys 0.10.1", - "gobject-sys 0.10.0", + "glib-sys", + "gobject-sys", "gstreamer-base-sys", "gstreamer-sys", "libc", - "system-deps 1.3.2", + "system-deps", ] [[package]] name = "gtk" -version = "0.18.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c" +checksum = "2f022f2054072b3af07666341984562c8e626a79daa8be27b955d12d06a5ad6a" dependencies = [ "atk", + "bitflags", "cairo-rs", - "field-offset", - "futures-channel", + "cairo-sys-rs", + "cc", "gdk", "gdk-pixbuf", + "gdk-pixbuf-sys", + "gdk-sys", "gio", - "glib 0.18.5", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", "gtk-sys", - "gtk3-macros", "libc", + "once_cell", "pango", + "pango-sys", "pkg-config", ] [[package]] name = "gtk-sys" -version = "0.18.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" +checksum = "89acda6f084863307d948ba64a4b1ef674e8527dddab147ee4cdcc194c880457" dependencies = [ "atk-sys", "cairo-sys-rs", "gdk-pixbuf-sys", "gdk-sys", "gio-sys", - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", "pango-sys", - "system-deps 6.2.2", + "system-deps", ] -[[package]] -name = "gtk3-macros" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if 1.0.0", - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" - [[package]] name = "hbb_common" version = "0.1.0" dependencies = [ "anyhow", - "async-recursion", - "backtrace", - "base64 0.22.1", "bytes", - "chrono", - "clap 4.5.53", "confy", - "default_net", "directories-next", "dirs-next", - "dlopen", - "env_logger 0.11.6", + "env_logger 0.9.0", "filetime", - "flexi_logger", "futures", "futures-util", - "httparse", "lazy_static", - "libc", - "libloading 0.8.4", "log", "mac_address", - "machine-uid", - "osascript", "protobuf", - "protobuf-codegen", - "rand 0.8.5", + "protobuf-codegen-pure", + "quinn", + "rand 0.8.4", "regex", - "rustls-native-certs", - "rustls-pki-types", - "rustls-platform-verifier", - "serde 1.0.228", + "serde 1.0.133", "serde_derive", - "serde_json 1.0.118", - "sha2", - "smithay-client-toolkit 0.20.0", + "serde_json 1.0.74", "socket2 0.3.19", "sodiumoxide", - "sysinfo", - "thiserror 1.0.61", "tokio", - "tokio-native-tls", - "tokio-rustls", "tokio-socks", - "tokio-tungstenite", "tokio-util", - "toml 0.7.8", - "tungstenite", - "url", - "users 0.11.0", - "uuid", - "webpki-roots 1.0.4", - "webrtc", - "whoami", + "toml", "winapi 0.3.9", - "x11 2.21.0", - "zstd 0.13.1", + "zstd", ] [[package]] @@ -3816,18 +1591,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -3837,111 +1600,11 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "hound" -version = "3.5.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - -[[package]] -name = "html-escape" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" -dependencies = [ - "utf8-width", -] - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.11", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" [[package]] name = "humantime" @@ -3950,317 +1613,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "hwcodec" -version = "0.7.1" -source = "git+https://github.com/rustdesk-org/hwcodec#398e5a8938dd8768ade0fcdc27ea80e8b4b38738" -dependencies = [ - "bindgen 0.59.2", - "cc", - "log", - "serde 1.0.228", - "serde_derive", - "serde_json 1.0.118", -] - -[[package]] -name = "hyper" -version = "1.7.0" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa 1.0.11", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots 1.0.4", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys 0.8.7", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "image" -version = "0.24.9" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" dependencies = [ "bytemuck", "byteorder", "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-traits 0.2.19", - "png 0.17.13", - "qoi", + "num-iter", + "num-rational", + "num-traits 0.2.14", + "png", "tiff", ] -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "num-traits 0.2.19", - "png 0.17.13", - "tiff", -] - -[[package]] -name = "impersonate_system" -version = "0.1.0" -source = "git+https://github.com/rustdesk-org/impersonate-system#2f429010a5a10b1fe5eceb553c6672fd53d20167" -dependencies = [ - "cc", -] - -[[package]] -name = "include_dir" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - -[[package]] -name = "inotify" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "block-padding", - "generic-array", -] - [[package]] name = "instant" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] [[package]] -name = "interceptor" -version = "0.15.0" +name = "iovec" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea51375727680dc15f06e8ad90fa31df75d79dd030100e8ad60eef1c27fe2c98" -dependencies = [ - "async-trait", - "bytes", - "futures", - "log", - "portable-atomic", - "rand 0.9.2", - "rtcp", - "rtp", - "thiserror 1.0.61", - "tokio", - "waitgroup", - "webrtc-srtp", - "webrtc-util", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "ioctl-rs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" dependencies = [ "libc", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde 1.0.228", -] - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi 0.5.0", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "is_debug" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" - [[package]] name = "itertools" version = "0.9.0" @@ -4270,15 +1661,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.3.4" @@ -4287,24 +1669,28 @@ checksum = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" [[package]] name = "itoa" -version = "1.0.11" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "jni" -version = "0.21.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ "cesu8", - "cfg-if 1.0.0", "combine", "jni-sys", "log", - "thiserror 1.0.61", + "thiserror", "walkdir", - "windows-sys 0.45.0", ] [[package]] @@ -4315,69 +1701,28 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ - "once_cell", "wasm-bindgen", ] -[[package]] -name = "kcp-sys" -version = "0.1.0" -source = "git+https://github.com/rustdesk-org/kcp-sys#32a6c09fc6223f54aea83981a6aa8995931d29be" -dependencies = [ - "anyhow", - "auto_impl", - "bindgen 0.71.1", - "bitflags 2.9.1", - "bytes", - "cc", - "dashmap 6.1.0", - "log", - "parking_lot", - "rand 0.8.5", - "thiserror 2.0.17", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "zerocopy 0.7.34", -] - -[[package]] -name = "keepawake" -version = "0.4.3" -source = "git+https://github.com/rustdesk-org/keepawake-rs#64d568586dd16551d02120e19668d2b0fec8e3c9" -dependencies = [ - "anyhow", - "cfg-if 1.0.0", - "core-foundation 0.9.4", - "shadow-rs", - "windows 0.48.0", - "winres", - "zbus", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -4388,31 +1733,11 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.9.1", - "serde 1.0.228", - "unicode-segmentation", -] - -[[package]] -name = "kurbo" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" -dependencies = [ - "arrayvec", -] - [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" @@ -4420,19 +1745,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +version = "0.6.1" +source = "git+https://github.com/liyue201/libappindicator-rs#3763cfd629dd90050af1feafa643cbfca0bf487e" dependencies = [ - "glib 0.18.5", + "glib", "gtk", "gtk-sys", "libappindicator-sys", @@ -4441,87 +1759,57 @@ dependencies = [ [[package]] name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +version = "0.6.1" +source = "git+https://github.com/liyue201/libappindicator-rs#3763cfd629dd90050af1feafa643cbfca0bf487e" dependencies = [ "gtk-sys", - "libloading 0.7.4", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "libdbus-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" -dependencies = [ "pkg-config", ] [[package]] -name = "libgit2-sys" -version = "0.14.2+1.5.1" +name = "libc" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libdbus-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b" dependencies = [ - "cc", - "libc", - "libz-sys", "pkg-config", ] [[package]] name = "libloading" -version = "0.7.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", ] -[[package]] -name = "libloading" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" -dependencies = [ - "cfg-if 1.0.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "libpulse-binding" -version = "2.28.1" +version = "2.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3557a2dfc380c8f061189a01c6ae7348354e0c9886038dc6c171219c08eaff" +checksum = "86835d7763ded6bc16b6c0061ec60214da7550dfcd4ef93745f6f0096129676a" dependencies = [ - "bitflags 1.3.2", + "bitflags", "libc", "libpulse-sys", - "num-derive 0.3.3", - "num-traits 0.2.19", + "num-derive", + "num-traits 0.2.14", "winapi 0.3.9", ] [[package]] name = "libpulse-simple-binding" -version = "2.28.1" +version = "2.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05fd6b68f33f6a251265e6ed1212dc3107caad7c5c6fdcd847b2e65ef58c308d" +checksum = "d6a22538257c4d522bea6089d6478507f5d2589ea32150e20740aaaaaba44590" dependencies = [ "libpulse-binding", "libpulse-simple-sys", @@ -4530,9 +1818,9 @@ dependencies = [ [[package]] name = "libpulse-simple-sys" -version = "1.21.1" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6613b4199d8b9f0edcfb623e020cb17bbd0bee8dd21f3c7cc938de561c4152" +checksum = "7c73f96f9ca34809692c4760cfe421225860aa000de50edab68a16221fd27cc1" dependencies = [ "libpulse-sys", "pkg-config", @@ -4540,33 +1828,22 @@ dependencies = [ [[package]] name = "libpulse-sys" -version = "1.21.0" +version = "1.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc19e110fbf42c17260d30f6d3dc545f58491c7830d38ecb9aaca96e26067a9b" +checksum = "991e6bd0efe2a36e6534e136e7996925e4c1a8e35b7807fe533f2beffff27c30" dependencies = [ "libc", - "num-derive 0.3.3", - "num-traits 0.2.19", + "num-derive", + "num-traits 0.2.14", "pkg-config", "winapi 0.3.9", ] -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.1", - "libc", - "redox_syscall 0.5.2", -] - [[package]] name = "libsamplerate-sys" -version = "0.1.12" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28853b399f78f8281cd88d333b54a63170c4275f6faea66726a2bea5cca72e0d" +checksum = "403163258e75b5780cd6245c04cddd7f3166c5f8dd2bf5462e596c9ca4eb9653" dependencies = [ "cmake", ] @@ -4583,122 +1860,58 @@ dependencies = [ "walkdir", ] -[[package]] -name = "libxdo" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db" -dependencies = [ - "libxdo-sys", -] - -[[package]] -name = "libxdo-sys" -version = "0.11.0" -dependencies = [ - "hbb_common", -] - -[[package]] -name = "libz-sys" -version = "1.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "linux-raw-sys" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ - "autocfg 1.3.0", "scopeguard", ] [[package]] name = "log" -version = "0.4.22" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] [[package]] name = "mac_address" -version = "1.1.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8836fae9d0d4be2c8b4efcdd79e828a2faa058a90d005abf42f91cac5493a08e" +checksum = "89544d9544366f6cda81244514a80809b137b5a179947b73bfa9f2797480de69" dependencies = [ - "nix 0.28.0", + "nix 0.22.0", "winapi 0.3.9", ] [[package]] -name = "mach2" -version = "0.4.2" +name = "mach" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" dependencies = [ "libc", ] [[package]] name = "machine-uid" -version = "0.3.0" -source = "git+https://github.com/rustdesk-org/machine-uid#381ff579c1dc3a6c54db9dfec47c44bcb0246542" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1595709b0a7386bcd56ba34d250d626e5503917d05d32cdccddcd68603e212" dependencies = [ - "bindgen 0.59.2", - "cc", - "winreg 0.11.0", + "winreg 0.6.2", ] [[package]] name = "magnum-opus" version = "0.4.0" -source = "git+https://github.com/rustdesk-org/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256" +source = "git+https://github.com/open-trade/magnum-opus#3c3d0b86ae95c84930bebffe4bcb03b3bd83342b" dependencies = [ "bindgen 0.59.2", - "pkg-config", "target_build_utils", ] @@ -4711,48 +1924,11 @@ dependencies = [ "libc", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if 1.0.0", - "digest", -] - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "memalloc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" - [[package]] name = "memchr" -version = "2.7.4" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" -dependencies = [ - "libc", -] +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" @@ -4760,40 +1936,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "metal" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa 0.20.2", - "core-graphics 0.19.2", - "foreign-types 0.3.2", - "log", - "objc", + "autocfg 1.0.1", ] [[package]] @@ -4804,91 +1947,95 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" dependencies = [ - "adler", - "simd-adler32", + "adler32", ] [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ - "adler2", - "simd-adler32", + "adler", + "autocfg 1.0.1", ] [[package]] name = "mio" -version = "0.8.11" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", ] [[package]] -name = "mio" -version = "1.0.3" +name = "mio-named-pipes" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "log", + "mio 0.6.23", + "miow 0.3.7", + "winapi 0.3.9", ] [[package]] -name = "mozjpeg" -version = "0.10.11" +name = "miow" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55571bce4f12d80ceb4296526e7614f796df72daaaac85f265ab732fa47b7bc9" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" dependencies = [ - "arrayvec", - "bytemuck", - "libc", - "mozjpeg-sys", - "rgb", + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", ] [[package]] -name = "mozjpeg-sys" -version = "2.2.2" +name = "miow" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3626d7942d5b56cc6d47b1c59724c0a976b786fca059c5aaa904aef6324d55" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "cc", - "dunce", - "libc", - "nasm-rs", + "winapi 0.3.9", ] [[package]] -name = "muda" -version = "0.17.1" +name = "miow" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +checksum = "a7377f7792b3afb6a3cba68daa54ca23c032137010460d667fda53a8d66be00e" dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "libxdo", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "png 0.17.13", - "thiserror 2.0.17", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -4898,397 +2045,169 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" [[package]] -name = "nanorand" -version = "0.7.0" +name = "ndk" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c" dependencies = [ - "getrandom 0.2.15", + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", ] [[package]] -name = "nasm-rs" -version = "0.3.0" +name = "ndk-glue" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fcfa1bd49e0342ec1d07ed2be83b59963e7acbeb9310e1bb2c07b69dadd959" +checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420" dependencies = [ - "jobserver", -] - -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 2.10.0", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "native-windows-gui" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7003a669f68deb6b7c57d74fff4f8e533c44a3f0b297492440ef4ff5a28454" -dependencies = [ - "bitflags 1.3.2", "lazy_static", + "libc", + "log", + "ndk", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" +dependencies = [ + "darling", + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", "winapi 0.3.9", - "winapi-build", -] - -[[package]] -name = "ndk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum 0.5.11", - "raw-window-handle 0.5.2", - "thiserror 1.0.61", -] - -[[package]] -name = "ndk" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" -dependencies = [ - "bitflags 2.9.1", - "jni-sys", - "log", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.2", - "thiserror 1.0.61", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.9.1", - "jni-sys", - "log", - "ndk-sys 0.6.0+11769913", - "num_enum 0.7.2", - "raw-window-handle 0.6.2", - "thiserror 1.0.61", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "netlink-packet-core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5cf0b54effda4b91615c40ff0fd12d0d4c9a6e0f5116874f03941792ff535a" -dependencies = [ - "anyhow", - "byteorder", - "libc", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea993e32c77d87f01236c38f572ecb6c311d592e56a06262a007fd2a6e31253c" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror 1.0.61", -] - -[[package]] -name = "netlink-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" -dependencies = [ - "bytes", - "libc", - "log", ] [[package]] name = "nix" -version = "0.23.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.25.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" dependencies = [ - "autocfg 1.3.0", - "bitflags 1.3.2", + "bitflags", + "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", - "pin-utils", + "memoffset", ] [[package]] name = "nix" -version = "0.26.4" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags 1.3.2", + "bitflags", + "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.7.1", - "pin-utils", -] - -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.9.1", - "cfg-if 1.0.0", - "cfg_aliases 0.1.1", - "libc", - "memoffset 0.9.1", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.9.1", - "cfg-if 1.0.0", - "cfg_aliases 0.2.1", - "libc", -] - -[[package]] -name = "nokhwa" -version = "0.10.7" -source = "git+https://github.com/rustdesk-org/nokhwa.git?branch=fix_from_raw_parts#c2f74662b6ce117f7f94301693fdfadc0b1ec91a" -dependencies = [ - "flume", - "image 0.25.1", - "nokhwa-bindings-linux", - "nokhwa-bindings-macos", - "nokhwa-bindings-windows", - "nokhwa-core", - "paste", - "thiserror 2.0.17", -] - -[[package]] -name = "nokhwa-bindings-linux" -version = "0.1.1" -source = "git+https://github.com/rustdesk-org/nokhwa.git?branch=fix_from_raw_parts#c2f74662b6ce117f7f94301693fdfadc0b1ec91a" -dependencies = [ - "nokhwa-core", - "v4l", -] - -[[package]] -name = "nokhwa-bindings-macos" -version = "0.2.2" -source = "git+https://github.com/rustdesk-org/nokhwa.git?branch=fix_from_raw_parts#c2f74662b6ce117f7f94301693fdfadc0b1ec91a" -dependencies = [ - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-media-sys", - "core-video-sys", - "flume", - "nokhwa-core", - "objc", - "once_cell", -] - -[[package]] -name = "nokhwa-bindings-windows" -version = "0.4.2" -source = "git+https://github.com/rustdesk-org/nokhwa.git?branch=fix_from_raw_parts#c2f74662b6ce117f7f94301693fdfadc0b1ec91a" -dependencies = [ - "dlopen", - "lazy_static", - "nokhwa-core", - "once_cell", - "windows 0.43.0", -] - -[[package]] -name = "nokhwa-core" -version = "0.1.5" -source = "git+https://github.com/rustdesk-org/nokhwa.git?branch=fix_from_raw_parts#c2f74662b6ce117f7f94301693fdfadc0b1ec91a" -dependencies = [ - "bytes", - "image 0.25.1", - "mozjpeg", - "thiserror 2.0.17", + "memoffset", ] [[package]] name = "nom" -version = "7.1.3" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr", "minimal-lexical", + "version_check", ] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 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 = "nu-ansi-term" -version = "0.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits 0.2.19", -] - [[package]] name = "num-complex" -version = "0.4.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ - "num-traits 0.2.19", + "num-traits 0.2.14", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "num-traits 0.2.19", + "autocfg 1.0.1", + "num-traits 0.2.14", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits 0.2.14", ] [[package]] @@ -5297,9 +2216,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.0.1", "num-integer", - "num-traits 0.2.19", + "num-traits 0.2.14", ] [[package]] @@ -5308,77 +2227,47 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.19", + "num-traits 0.2.14", ] [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.3.0", + "autocfg 1.0.1", ] [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.5.11" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive 0.7.2", + "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate 2.0.2", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -5388,7 +2277,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -5402,298 +2290,6 @@ dependencies = [ "objc_id", ] -[[package]] -name = "objc-sys" -version = "0.2.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.3.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a49f420f16c8814efdcd6b4258664de9d9920cbc26b6f95d034a1ca9850ccc2c" -dependencies = [ - "block2 0.2.0-alpha.6", - "objc-sys 0.2.0-beta.2", - "objc2-encode 2.0.0-pre.2", -] - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys 0.3.5", - "objc2-encode 4.1.0", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode 4.1.0", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation 0.2.2", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.9.1", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.9.1", - "dispatch2", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.9.1", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.9.1", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.9.1", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -5705,278 +2301,55 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" -dependencies = [ - "memchr", -] +checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "oboe" -version = "0.6.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1" dependencies = [ "jni", - "ndk 0.8.0", - "ndk-context", - "num-derive 0.4.2", - "num-traits 0.2.19", + "ndk", + "ndk-glue", + "num-derive", + "num-traits 0.2.14", "oboe-sys", ] [[package]] name = "oboe-sys" -version = "0.6.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc" dependencies = [ "cc", ] -[[package]] -name = "oid-registry" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" -dependencies = [ - "asn1-rs", -] - [[package]] name = "once_cell" -version = "1.19.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags 2.9.1", - "cfg-if 1.0.0", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.5.3+3.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6bad8cd0233b63971e232cc9c5e83039375b8586d2312f31fda85db8f888c2" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "orbclient" -version = "0.3.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" -dependencies = [ - "libredox", -] - -[[package]] -name = "ordered-multimap" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" -dependencies = [ - "dlv-list", - "hashbrown 0.12.3", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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_info" -version = "3.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" -dependencies = [ - "log", - "serde 1.0.228", - "windows-sys 0.52.0", -] - -[[package]] -name = "os_pipe" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "osascript" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc" -dependencies = [ - "serde 1.0.228", - "serde_derive", - "serde_json 1.0.118", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "pam" -version = "0.7.0" -source = "git+https://github.com/rustdesk-org/pam#7bfd25510202cd269292cbdd7c71f3977a6fd762" -dependencies = [ - "libc", - "pam-macros", - "pam-sys", - "users 0.10.0", -] - -[[package]] -name = "pam-macros" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94f3b9b97df3c6d4e51a14916639b24e02c7d15d1dba686ce9b1118277cb811" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "pam-sys" -version = "1.0.0-alpha4" -source = "git+https://github.com/rustdesk-org/pam-sys?branch=fix/v1.0.0-alpha4_gnuc_va_list#3337c9bb9a9c68d7497ec8c93cad2368c26091b7" -dependencies = [ - "bindgen 0.59.2", - "libc", -] +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" [[package]] name = "pango" -version = "0.18.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +checksum = "9937068580bebd8ced19975938573803273ccbcbd598c58d4906efd4ac87c438" dependencies = [ - "gio", - "glib 0.18.5", + "bitflags", + "glib", + "glib-sys", + "gobject-sys", "libc", "once_cell", "pango-sys", @@ -5984,86 +2357,61 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.18.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +checksum = "24d2650c8b62d116c020abd0cea26a4ed96526afda89b1c4ea567131fdefc890" dependencies = [ - "glib-sys 0.18.1", - "gobject-sys 0.18.0", + "glib-sys", + "gobject-sys", "libc", - "system-deps 6.2.2", + "system-deps", ] [[package]] name = "parity-tokio-ipc" -version = "0.7.3-6" -source = "git+https://github.com/rustdesk-org/parity-tokio-ipc#d0ae39bffe5d5a3e8d82a1b6bcb1ca5a9b2f1c01" +version = "0.7.3" +source = "git+https://github.com/open-trade/parity-tokio-ipc#52515618bd30ea8101bf46f6c7835e88cec9187f" dependencies = [ "futures", "libc", "log", - "rand 0.8.5", + "mio-named-pipes", + "miow 0.4.0", + "rand 0.8.4", "tokio", "winapi 0.3.9", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ + "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", + "instant", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "winapi 0.3.9", ] [[package]] name = "paste" -version = "1.0.15" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "peeking_take_while" @@ -6071,57 +2419,13 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "phf" version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" dependencies = [ - "phf_shared 0.7.24", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared 0.11.3", + "phf_shared", ] [[package]] @@ -6130,18 +2434,8 @@ version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" dependencies = [ - "phf_generator 0.7.24", - "phf_shared 0.7.24", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", + "phf_generator", + "phf_shared", ] [[package]] @@ -6150,88 +2444,44 @@ version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" dependencies = [ - "phf_shared 0.7.24", + "phf_shared", "rand 0.6.5", ] -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - [[package]] name = "phf_shared" version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" dependencies = [ - "siphasher 0.2.3", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.1", -] - -[[package]] -name = "piet" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e381186490a3e2017a506d62b759ea8eaf4be14666b13ed53973e8ae193451b1" -dependencies = [ - "kurbo", - "unic-bidi", -] - -[[package]] -name = "piet-coregraphics" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a819b41d2ddb1d8abf3e45e49422f866cba281b4abb5e2fb948bba06e2c3d3f7" -dependencies = [ - "associative-cache", - "core-foundation 0.9.4", - "core-foundation-sys 0.8.7", - "core-graphics 0.22.3", - "core-text", - "foreign-types 0.3.2", - "piet", + "siphasher", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -6239,164 +2489,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" -dependencies = [ - "atomic-waker", - "fastrand 2.1.0", - "futures-io", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] -name = "plist" -version = "1.6.1" +name = "platforms" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" -dependencies = [ - "base64 0.21.7", - "indexmap", - "line-wrap", - "quick-xml 0.31.0", - "serde 1.0.228", - "time 0.3.36", -] +checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "png" -version = "0.17.13" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.7.4", + "deflate", + "miniz_oxide 0.3.7", ] -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.9.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg 1.3.0", - "bitflags 1.3.2", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" -dependencies = [ - "cfg-if 1.0.0", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.34", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "portable-pty" -version = "0.8.1" -source = "git+https://github.com/rustdesk-org/wezterm?branch=rustdesk/pty_based_0.8.1#80174f8009f41565f0fa8c66dab90d4f9211ae16" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "downcast-rs", - "filedescriptor", - "lazy_static", - "libc", - "log", - "nix 0.25.1", - "serial", - "shared_library", - "shell-words", - "winapi 0.3.9", - "winreg 0.10.1", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "pretty-hex" @@ -6404,61 +2525,32 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" -[[package]] -name = "prettyplease" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" -dependencies = [ - "proc-macro2 1.0.93", - "syn 2.0.98", -] - [[package]] name = "primal-check" -version = "0.3.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +checksum = "01419cee72c1a1ca944554e23d83e483e1bccf378753344e881de28b5487511d" dependencies = [ "num-integer", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", + "thiserror", + "toml", ] [[package]] @@ -6468,9 +2560,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", + "proc-macro2", + "quote", + "syn", "version_check", ] @@ -6480,107 +2572,94 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", + "proc-macro2", + "quote", "version_check", ] [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ - "unicode-xid 0.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", + "unicode-xid", ] [[package]] name = "protobuf" -version = "3.7.2" +version = "3.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" -dependencies = [ - "bytes", - "once_cell", - "protobuf-support", - "thiserror 1.0.61", -] +checksum = "9d5ef59c35c7472ce5e1b6c5924b87585143d1fc2cf39eae0009bba6c4df62f1" [[package]] name = "protobuf-codegen" -version = "3.7.2" +version = "3.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3976825c0014bbd2f3b34f0001876604fe87e0c86cd8fa54251530f1544ace" +checksum = "89100ee819f69b77a4cab389fec9dd155a305af4c615e6413ec1ef9341f333ef" dependencies = [ "anyhow", - "once_cell", "protobuf", "protobuf-parse", - "regex", - "tempfile", - "thiserror 1.0.61", + "thiserror", +] + +[[package]] +name = "protobuf-codegen-pure" +version = "3.0.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79453e74d08190551e821533ee42c447f9e21ca26f83520e120e6e8af27f6879" +dependencies = [ + "anyhow", + "protobuf", + "protobuf-codegen", + "protobuf-parse", + "thiserror", ] [[package]] name = "protobuf-parse" -version = "3.7.2" +version = "3.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" +checksum = "c265ffc69976efc3056955b881641add3186ad0be893ef10622482d80d1d2b68" dependencies = [ "anyhow", - "indexmap", - "log", "protobuf", - "protobuf-support", + "protoc", "tempfile", - "thiserror 1.0.61", - "which", + "thiserror", ] [[package]] -name = "protobuf-support" -version = "3.7.2" +name = "protoc" +version = "3.0.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +checksum = "1f1f8b318a54d18fbe542513331e058f4f8ce6502e542e057c50c7e5e803fdab" dependencies = [ - "thiserror 1.0.61", + "anyhow", + "log", + "thiserror", + "which 4.2.2", ] [[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +name = "psutil" +version = "3.2.1" +source = "git+https://github.com/open-trade/rust-psutil#22b2e1bb4e29433a6ddb0c1bb259fe01e894c94f" dependencies = [ - "bytemuck", + "cfg-if 1.0.0", + "darwin-libproc", + "derive_more", + "glob", + "mach", + "nix 0.23.1", + "num_cpus", + "once_cell", + "platforms", + "thiserror", + "unescape", ] -[[package]] -name = "qrcode-generator" -version = "4.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d06cb9646c7a14096231a2474d7f21e5e8c13de090c68d13bde6157cfe7f159" -dependencies = [ - "html-escape", - "image 0.24.9", - "qrcodegen", -] - -[[package]] -name = "qrcodegen" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" - [[package]] name = "quest" version = "0.3.0" @@ -6590,133 +2669,93 @@ dependencies = [ "cfg-if 0.1.10", "rpassword 2.1.0", "tempfile", - "termios 0.3.3", + "termios", "winapi 0.3.9", ] [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.9" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "61a84d97630b137463c8e6802adc1dfe9de81457b41bb1ac59189e6761ab9255" dependencies = [ "bytes", - "cfg_aliases 0.2.1", - "pin-project-lite", + "futures-channel", + "futures-util", + "fxhash", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", "rustls", - "socket2 0.5.10", - "thiserror 2.0.17", + "thiserror", "tokio", "tracing", - "web-time", + "webpki", ] [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "063dedf7983c8d57db474218f258daa85b627de6f2dbc458b690a93b1de790e8" dependencies = [ "bytes", - "getrandom 0.3.2", - "lru-slab", - "rand 0.9.2", + "fxhash", + "rand 0.8.4", "ring", - "rustc-hash 2.1.1", "rustls", - "rustls-pki-types", + "rustls-native-certs", + "rustls-pemfile", "slab", - "thiserror 2.0.17", + "thiserror", "tinyvec", "tracing", - "web-time", + "webpki", ] [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "5f7996776e9ee3fc0e5c14476c1a640a17e993c847ae9c81191c2c102fbef903" dependencies = [ - "cfg_aliases 0.2.1", + "futures-util", "libc", - "once_cell", - "socket2 0.5.10", + "mio 0.7.14", + "quinn-proto", + "socket2 0.4.2", + "tokio", "tracing", - "windows-sys 0.52.0", ] [[package]] name = "quote" -version = "0.6.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ - "proc-macro2 0.4.30", + "proc-macro2", ] -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2 1.0.93", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg 0.1.8", + "autocfg 0.1.7", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", - "rand_hc", + "rand_hc 0.1.0", "rand_isaac", "rand_jitter", "rand_os", @@ -6727,23 +2766,14 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] @@ -6752,7 +2782,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg 0.1.8", + "autocfg 0.1.7", "rand_core 0.3.1", ] @@ -6763,17 +2793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.6.3", ] [[package]] @@ -6793,20 +2813,11 @@ checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.2", + "getrandom", ] [[package]] @@ -6818,6 +2829,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -6858,7 +2878,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg 0.1.8", + "autocfg 0.1.7", "rand_core 0.4.2", ] @@ -6871,76 +2891,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time 0.3.36", - "x509-parser", - "yasna", -] - -[[package]] -name = "rdev" -version = "0.5.0-2" -source = "git+https://github.com/rustdesk-org/rdev#f9b60b1dd0f3300a1b797d7a74c116683cd232c8" -dependencies = [ - "cocoa 0.24.1", - "core-foundation 0.9.4", - "core-foundation-sys 0.8.7", - "core-graphics 0.22.3", - "dispatch", - "enum-map", - "epoll", - "inotify", - "lazy_static", - "libc", - "log", - "mio 0.8.11", - "strum 0.24.1", - "strum_macros 0.24.3", - "widestring", - "winapi 0.3.9", - "x11 2.21.0", -] - [[package]] name = "rdrand" version = "0.4.0" @@ -6952,70 +2902,37 @@ dependencies = [ [[package]] name = "realfft" -version = "3.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571" +checksum = "d7695c87f31dc3644760f23fb59a3fed47659703abf76cf2d111f03b9e712342" dependencies = [ "rustfft", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" -dependencies = [ - "bitflags 2.9.1", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.61", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 2.0.17", + "getrandom", + "redox_syscall", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick", "memchr", @@ -7024,17 +2941,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] -name = "remote_printer" -version = "0.1.0" +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "hbb_common", "winapi 0.3.9", - "windows-strings 0.3.1", ] [[package]] @@ -7047,100 +2964,21 @@ dependencies = [ "flate2", ] -[[package]] -name = "reqwest" -version = "0.12.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" -dependencies = [ - "async-compression", - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde 1.0.228", - "serde_json 1.0.118", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 1.0.4", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "rgb" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - [[package]] name = "ring" -version = "0.17.14" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", - "cfg-if 1.0.0", - "getrandom 0.2.15", "libc", + "once_cell", + "spin", "untrusted", - "windows-sys 0.52.0", + "web-sys", + "winapi 0.3.9", ] -[[package]] -name = "ringbuf" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "roxmltree" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" - [[package]] name = "rpassword" version = "2.1.0" @@ -7154,98 +2992,50 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.3.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" dependencies = [ "libc", - "rtoolbox", - "windows-sys 0.48.0", -] - -[[package]] -name = "rtcp" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d30d1c4091644431c22acf9f8be6191b56805e0e977f15ca7104b4a6d6eaec" -dependencies = [ - "bytes", - "thiserror 1.0.61", - "webrtc-util", -] - -[[package]] -name = "rtoolbox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "rtp" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f126f38ea84c02480e32e547c1459a939052f74fb92117ac3eef23fdac6b023" -dependencies = [ - "bytes", - "memchr", - "portable-atomic", - "rand 0.9.2", - "serde 1.0.228", - "thiserror 1.0.61", - "webrtc-util", + "winapi 0.3.9", ] [[package]] name = "rubato" -version = "0.12.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70209c27d5b08f5528bdc779ea3ffb418954e28987f9f9775c6eac41003f9c" +checksum = "d29cf25e25288b595458df0e00c3065db08c31afe4b4e5a74cbfc5a9b8e763cd" dependencies = [ + "log", "num-complex", "num-integer", - "num-traits 0.2.19", + "num-traits 0.2.14", "realfft", ] [[package]] name = "runas" -version = "1.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96d6b6c505282b007a9b009f2aa38b2fd0359b81a0430ceacc60f69ade4c6a0" +checksum = "a620b0994a180cdfa25c0439e6d58c0628272571501880d626ffff58e96a0799" dependencies = [ - "libc", - "security-framework-sys", - "which", - "windows-sys 0.48.0", -] - -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if 1.0.0", - "ordered-multimap", + "cc", + "which 3.1.1", ] [[package]] name = "rust-pulsectl" -version = "0.2.12" -source = "git+https://github.com/rustdesk-org/pulsectl#aa34dde499aa912a3abc5289cc0b547bd07dd6e2" +version = "0.2.11" +source = "git+https://github.com/open-trade/pulsectl#f8ed9c538d48e486ee2cc5678aa7aee26b377646" dependencies = [ "libpulse-binding", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc-hash" @@ -7253,298 +3043,119 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustdesk" -version = "1.4.6" +version = "1.1.8" dependencies = [ - "android-wakelock", "android_logger", "arboard", - "async-process", "async-trait", - "bytemuck", - "bytes", + "base64", "cc", "cfg-if 1.0.0", - "chrono", - "cidr-utils", - "clap 4.5.53", - "clipboard", + "clap", "clipboard-master", - "cocoa 0.24.1", - "core-foundation 0.9.4", - "core-graphics 0.22.3", + "cocoa", + "core-foundation", + "core-graphics", "cpal", - "crossbeam-queue", + "crc32fast", "ctrlc", "dasp", - "dbus", - "dbus-crossroads", - "default-net", "dispatch", - "docopt", "enigo", - "errno", - "evdev", - "flutter_rust_bridge", - "fon", - "fontdb", - "foreign-types 0.3.2", - "fruitbasket", - "gtk", + "flexi_logger", "hbb_common", - "hex", "hound", - "image 0.24.9", - "impersonate_system", - "include_dir", - "jni", - "kcp-sys", - "keepawake", "lazy_static", + "libc", "libpulse-binding", "libpulse-simple-binding", - "libxdo-sys", "mac_address", + "machine-uid", "magnum-opus", - "nix 0.29.0", - "num_cpus", "objc", - "objc_id", - "once_cell", - "openssl", - "os-version", - "pam", "parity-tokio-ipc", - "percent-encoding", - "piet", - "piet-coregraphics", - "portable-pty", - "qrcode-generator", - "rdev", - "remote_printer", + "psutil", "repng", - "reqwest", - "ringbuf", - "rpassword 7.3.1", + "rpassword 5.0.1", "rubato", "runas", "rust-pulsectl", "samplerate", "sciter-rs", "scrap", - "serde 1.0.228", + "serde 1.0.133", "serde_derive", - "serde_json 1.0.118", - "serde_repr", + "serde_json 1.0.74", "sha2", - "shared_memory", - "shutdown_hooks", - "softbuffer", - "stunclient", "sys-locale", - "system_shutdown", - "tao", - "tauri-winrt-notification", - "terminfo", - "termios 0.3.3", - "tiny-skia", - "totp-rs", - "tray-icon", - "ttf-parser", - "url", + "systray", "uuid", - "virtual_display", - "wallpaper", + "whoami", "winapi 0.3.9", - "windows 0.61.1", "windows-service", - "winit", - "winreg 0.11.0", - "winres", - "wol-rs", - "x11-clipboard 0.8.1", - "x11rb 0.12.0", - "zip", -] - -[[package]] -name = "rustdesk-portable-packer" -version = "1.4.6" -dependencies = [ - "brotli", - "dirs 5.0.1", - "md5", - "native-windows-gui", - "winapi 0.3.9", - "windows 0.61.1", + "winreg 0.10.1", "winres", ] [[package]] name = "rustfft" -version = "6.2.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" +checksum = "b1d089e5c57521629a59f5f39bca7434849ff89bd6873b521afe389c1c602543" dependencies = [ "num-complex", "num-integer", - "num-traits 0.2.19", + "num-traits 0.2.14", "primal-check", "strength_reduce", "transpose", - "version_check", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ - "log", - "once_cell", "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", + "sct", + "webpki", ] [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" dependencies = [ "openssl-probe", - "rustls-pki-types", + "rustls-pemfile", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] -name = "rustls-pki-types" -version = "1.11.0" +name = "rustls-pemfile" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ - "web-time", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys 0.8.7", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.5.1", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "base64", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -7566,17 +3177,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ - "windows-sys 0.52.0", + "lazy_static", + "winapi 0.3.9", ] [[package]] name = "sciter-rs" version = "0.5.57" -source = "git+https://github.com/rustdesk-org/rust-sciter?branch=dyn#5322f3a755a0e6bf999fbc60d1efc35246c0f821" +source = "git+https://github.com/open-trade/rust-sciter?branch=dyn#4cd10f985e76d64fbf3438ffe7532489936f489a" dependencies = [ "lazy_static", "libc", @@ -7584,24 +3196,17 @@ dependencies = [ "objc-foundation", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scrap" version = "0.5.0" dependencies = [ - "android_logger", - "bindgen 0.65.1", + "bindgen 0.59.2", "block", "cfg-if 1.0.0", "dbus", @@ -7609,108 +3214,50 @@ dependencies = [ "gstreamer", "gstreamer-app", "gstreamer-video", - "hbb_common", - "hwcodec", - "jni", - "lazy_static", - "log", - "ndk 0.7.0", - "ndk-context", - "nokhwa", + "libc", "num_cpus", - "pkg-config", "quest", "repng", - "serde 1.0.228", - "serde_json 1.0.118", + "serde 1.0.133", "target_build_utils", "tracing", "webm", "winapi 0.3.9", - "zbus", ] [[package]] -name = "sctk-adwaita" -version = "0.10.1" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit 0.19.2", - "tiny-skia", -] - -[[package]] -name = "sdp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c374dceda16965d541c8800ce9cc4e1c14acfd661ddf7952feeedc3411e5c6" -dependencies = [ - "rand 0.9.2", - "substring", - "thiserror 1.0.61", - "url", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", + "ring", + "untrusted", ] [[package]] name = "security-framework" -version = "2.10.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-foundation-sys 0.8.7", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", - "core-foundation-sys 0.8.7", + "bitflags", + "core-foundation", + "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" dependencies = [ - "core-foundation-sys 0.8.7", + "core-foundation-sys", "libc", ] -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" version = "0.9.15" @@ -7719,32 +3266,22 @@ checksum = "34b623917345a631dc9608d5194cc206b3fe6c3554cd1c75b937e55e285254af" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -7761,105 +3298,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" dependencies = [ - "itoa 1.0.11", + "itoa 1.0.1", "ryu", - "serde 1.0.228", -] - -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "serde_spanned" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" -dependencies = [ - "serde 1.0.228", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.11", - "ryu", - "serde 1.0.228", -] - -[[package]] -name = "serial" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" -dependencies = [ - "serial-core", - "serial-unix", - "serial-windows", -] - -[[package]] -name = "serial-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" -dependencies = [ - "libc", -] - -[[package]] -name = "serial-unix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" -dependencies = [ - "ioctl-rs", - "libc", - "serial-core", - "termios 0.2.2", -] - -[[package]] -name = "serial-windows" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" -dependencies = [ - "libc", - "serial-core", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest", + "serde 1.0.133", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -7867,98 +3319,31 @@ dependencies = [ ] [[package]] -name = "shadow-rs" -version = "0.21.0" +name = "shlex" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427f07ab5f873000cf55324882e12a88c0a7ea7025df4fc1e7e35e688877a583" -dependencies = [ - "const_format", - "git2", - "is_debug", - "time 0.3.36", - "tzdb 0.5.10", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - -[[package]] -name = "shared_memory" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8593196da75d9dc4f69349682bd4c2099f8cde114257d1ef7ef1b33d1aba54" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "nix 0.23.2", - "rand 0.8.5", - "win-sys", -] - -[[package]] -name = "shell-words" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "shlex" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shutdown_hooks" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6057adedbec913419c92996f395ba69931acbd50b7d56955394cd3f7bedbfa45" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] [[package]] name = "signature" -version = "1.6.4" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" [[package]] name = "siphasher" @@ -7966,96 +3351,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" -version = "0.4.9" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.9.1", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.34", - "thiserror 1.0.61", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" -dependencies = [ - "bitflags 2.9.1", - "calloop 0.14.3", - "calloop-wayland-source 0.4.1", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 1.1.2", - "thiserror 2.0.17", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-experimental", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde 1.0.228", -] +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "socket2" @@ -8070,24 +3376,14 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.10" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ "libc", "winapi 0.3.9", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "sodiumoxide" version = "0.2.7" @@ -8097,78 +3393,32 @@ dependencies = [ "ed25519", "libc", "libsodium-sys", - "serde 1.0.228", -] - -[[package]] -name = "softbuffer" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "cfg_aliases 0.2.1", - "core-graphics 0.23.2", - "drm", - "fastrand 2.1.0", - "foreign-types 0.5.0", - "js-sys", - "log", - "memmap2", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core", - "raw-window-handle 0.6.2", - "redox_syscall 0.5.2", - "rustix 0.38.34", - "tiny-xlib", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", - "windows-sys 0.52.0", - "x11rb 0.13.1", + "serde 1.0.133", ] [[package]] name = "spin" -version = "0.9.8" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "spki" -version = "0.7.3" +name = "stdweb" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] +checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "str-buf" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" [[package]] name = "strength_reduce" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254" [[package]] name = "strsim" @@ -8178,15 +3428,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "strsim" -version = "0.10.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "strsim" -version = "0.11.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" @@ -8194,193 +3444,52 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - [[package]] name = "strum_macros" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ - "heck 0.3.3", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.93", - "quote 1.0.36", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "stun" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a512c5d501e3e3b5a4bb3e8e31462d56d54a66b95a28b8596e14422bf21c32b" -dependencies = [ - "base64 0.22.1", - "crc", - "lazy_static", - "md-5", - "rand 0.9.2", - "ring", - "subtle", - "thiserror 1.0.61", - "tokio", - "url", - "webrtc-util", -] - -[[package]] -name = "stun_codec" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feed9dafe0bda84f2b6ca3ce726b0a1f1ac2e8b63c6ecfb89b08b32313247b5b" -dependencies = [ - "bytecodec", - "byteorder", - "crc", - "hmac", - "md5", - "sha1", - "trackable 1.3.0", -] - -[[package]] -name = "stunclient" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c969a14b4a4c09c320416ebf880b3d5a81ad1612065741eb10521951c06c8991" -dependencies = [ - "bytecodec", - "rand 0.8.5", - "stun_codec", - "tokio", -] - -[[package]] -name = "substring" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" -dependencies = [ - "autocfg 1.3.0", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "syn" -version = "1.0.109" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "synstructure" -version = "0.13.2" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", + "unicode-xid", ] [[package]] name = "sys-locale" -version = "0.3.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df" dependencies = [ + "cc", + "cstr_core", "libc", -] - -[[package]] -name = "sysinfo" -version = "0.29.10" -source = "git+https://github.com/rustdesk-org/sysinfo?branch=rlim_max#90b1705d909a4902dbbbdea37ee64db17841077d" -dependencies = [ - "cfg-if 1.0.0", - "core-foundation-sys 0.8.7", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.51.1", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys 0.8.7", - "libc", + "web-sys", + "winapi 0.3.9", ] [[package]] @@ -8389,164 +3498,62 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" dependencies = [ - "heck 0.3.3", + "heck", "pkg-config", - "strum 0.18.0", - "strum_macros 0.18.0", - "thiserror 1.0.61", - "toml 0.5.11", - "version-compare 0.0.10", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", ] [[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +name = "systray" +version = "0.4.1" +source = "git+https://github.com/liyue201/systray-rs#84cca4b4171661bc6c4d1ba5aaa2320ff8e085aa" dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare 0.2.0", -] - -[[package]] -name = "system_shutdown" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7567f71160af5e9abfb4f5a21532cf2174cefe91ac5c336419295685a695cc66" -dependencies = [ - "windows 0.44.0", - "zbus", -] - -[[package]] -name = "tao" -version = "0.25.0" -source = "git+https://github.com/rustdesk-org/tao?branch=dev#288c219cb0527e509590c2b2d8e7072aa9feb2d3" -dependencies = [ - "bitflags 1.3.2", - "cc", - "cocoa 0.25.0", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "crossbeam-channel", - "dispatch", - "gdkwayland-sys", - "gdkx11-sys", + "glib", "gtk", - "image 0.24.9", - "instant", - "jni", - "lazy_static", + "libappindicator", "libc", "log", - "ndk 0.7.0", - "ndk-context", - "ndk-sys 0.4.1+23.1.7779620", - "objc", - "once_cell", - "parking_lot", - "png 0.17.13", - "raw-window-handle 0.6.2", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows 0.52.0", - "windows-implement 0.52.0", - "windows-version", - "x11-dl", - "zbus", + "winapi 0.3.9", ] -[[package]] -name = "tao-macros" -version = "0.1.2" -source = "git+https://github.com/rustdesk-org/tao?branch=dev#288c219cb0527e509590c2b2d8e7072aa9feb2d3" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - [[package]] name = "target_build_utils" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "013d134ae4a25ee744ad6129db589018558f620ddfa44043887cdd45fa08e75c" dependencies = [ - "phf 0.7.24", - "phf_codegen 0.7.24", + "phf", + "phf_codegen", "serde_json 0.9.10", ] -[[package]] -name = "tauri-winrt-notification" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2" -dependencies = [ - "quick-xml 0.30.0", - "windows 0.51.1", -] - [[package]] name = "tempfile" -version = "3.10.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", - "fastrand 2.1.0", - "rustix 0.38.34", - "windows-sys 0.52.0", + "libc", + "rand 0.8.4", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", ] [[package]] name = "termcolor" -version = "1.4.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] -[[package]] -name = "terminfo" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" -dependencies = [ - "dirs 4.0.0", - "fnv", - "nom", - "phf 0.11.3", - "phf_codegen 0.11.3", -] - -[[package]] -name = "termios" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" -dependencies = [ - "libc", -] - [[package]] name = "termios" version = "0.3.3" @@ -8565,240 +3572,103 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "tfc" -version = "0.7.0" -source = "git+https://github.com/rustdesk-org/The-Fat-Controller?branch=history/rebase_upstream_20240722#78bb80a8e596e4c14ae57c8448f5fca75f91f2b0" -dependencies = [ - "anyhow", - "core-graphics 0.23.2", - "unicode-segmentation", - "winapi 0.3.9", - "x11 2.19.0", -] - [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ - "thiserror-impl 1.0.61", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if 1.0.0", - "once_cell", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tiff" -version = "0.9.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" dependencies = [ - "flate2", "jpeg-decoder", + "miniz_oxide 0.4.4", "weezl", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" dependencies = [ + "itoa 0.4.8", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa 1.0.11", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde 1.0.228", - "time-core", "time-macros", ] -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if 1.0.0", - "log", - "png 0.17.13", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-xlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" -dependencies = [ - "as-raw-xcb-connection", - "ctor-lite", - "libloading 0.8.4", - "pkg-config", - "tracing", -] +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.44.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" dependencies = [ - "backtrace", "bytes", "libc", - "mio 1.0.3", + "memchr", + "mio 0.7.14", + "num_cpus", + "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.10", "tokio-macros", - "windows-sys 0.52.0", + "winapi 0.3.9", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tokio-socks" -version = "0.5.2-3" -source = "git+https://github.com/rustdesk-org/tokio-socks#bdb9aa3de5bac41602d0742b8ef6bbc6bfebd127" +version = "0.5.1" +source = "git+https://github.com/fufesou/tokio-socks#63e27388e4d569316945c1c24353010d86f342a6" dependencies = [ "bytes", "either", @@ -8806,42 +3676,22 @@ dependencies = [ "futures-sink", "futures-util", "pin-project", - "thiserror 2.0.17", + "thiserror", "tokio", "tokio-util", ] -[[package]] -name = "tokio-tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" -dependencies = [ - "futures-util", - "log", - "native-tls", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tungstenite", - "webpki-roots 0.26.9", -] - [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", - "futures-util", - "hashbrown 0.15.4", + "log", "pin-project-lite", "slab", "tokio", @@ -8849,140 +3699,20 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde 1.0.228", + "serde 1.0.133", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde 1.0.228", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde 1.0.228", - "serde_spanned", - "toml_datetime", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde 1.0.228", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "serde 1.0.228", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap", - "serde 1.0.228", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "totp-rs" -version = "5.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f" -dependencies = [ - "base32", - "constant_time_eq 0.2.6", - "hmac", - "rand 0.8.5", - "sha1", - "sha2", - "url", - "urlencoding", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.1", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ - "log", + "cfg-if 1.0.0", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -8990,471 +3720,79 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "nu-ansi-term 0.46.0", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "trackable" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" -dependencies = [ - "trackable 1.3.0", - "trackable_derive", -] - -[[package]] -name = "trackable" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" -dependencies = [ - "trackable_derive", -] - -[[package]] -name = "trackable_derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" -dependencies = [ - "quote 1.0.36", - "syn 1.0.109", + "lazy_static", ] [[package]] name = "transpose" -version = "0.2.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +checksum = "95f9c900aa98b6ea43aee227fd680550cdec726526aab8ac801549eadb25e39f" dependencies = [ "num-integer", "strength_reduce", ] -[[package]] -name = "tray-icon" -version = "0.21.3" -source = "git+https://github.com/tauri-apps/tray-icon#0a5835b0e6828e37a1f781de9c2d671ae7a939e6" -dependencies = [ - "crossbeam-channel", - "dirs 6.0.0", - "libappindicator", - "muda", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "once_cell", - "png 0.18.1", - "thiserror 2.0.17", - "windows-sys 0.60.2", -] - -[[package]] -name = "tree_magic_mini" -version = "3.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a727cac55b41448315cc10427c069c618ac59bb6a4480283fcd811749bdc2" -dependencies = [ - "fnv", - "home", - "memchr", - "nom", - "once_cell", - "petgraph", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - -[[package]] -name = "tungstenite" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" -dependencies = [ - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.9.2", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "sha1", - "thiserror 2.0.17", - "utf-8", - "webpki-roots 0.26.9", -] - -[[package]] -name = "turn" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed995882f66ab94238de77c62e5e778389698ab700afa4696f4754da8f457cb" -dependencies = [ - "async-trait", - "base64 0.22.1", - "futures", - "log", - "md-5", - "portable-atomic", - "rand 0.9.2", - "ring", - "stun", - "thiserror 1.0.61", - "tokio", - "tokio-util", - "webrtc-util", -] - [[package]] name = "typenum" -version = "1.17.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] -name = "tz-rs" -version = "0.6.14" +name = "unescape" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" -dependencies = [ - "const_fn", -] - -[[package]] -name = "tzdb" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a18ee5bde3433d683d41859650804a5ad89cad17f153a53f1e6a96e0da2d969" -dependencies = [ - "iana-time-zone", - "tz-rs", - "tzdb 0.6.1", -] - -[[package]] -name = "tzdb" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b580f6b365fa89f5767cdb619a55d534d04a4e14c2d7e5b9a31e94598687fb1" -dependencies = [ - "iana-time-zone", - "tz-rs", - "tzdb_data", -] - -[[package]] -name = "tzdb_data" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1889fdffac09d65c1d95c42d5202e9b21ad8c758f426e9fe09088817ea998d6" -dependencies = [ - "tz-rs", -] - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset 0.9.1", - "tempfile", - "winapi 0.3.9", -] - -[[package]] -name = "uname" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" -dependencies = [ - "libc", -] - -[[package]] -name = "unic-bidi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09" -dependencies = [ - "matches", - "unic-ucd-bidi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-bidi" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "untrusted" -version = "0.9.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde 1.0.228", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "users" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" -dependencies = [ - "libc", - "log", -] - -[[package]] -name = "users" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", - "log", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16string" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b62a1e85e12d5d712bf47a85f426b73d303e2d00a90de5f3004df3596e9d216" -dependencies = [ - "byteorder", -] - -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "uuid" -version = "1.16.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.3.2", + "getrandom", ] -[[package]] -name = "v4l" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fbfea44a46799d62c55323f3c55d06df722fbe577851d848d328a1041c3403" -dependencies = [ - "bitflags 1.3.2", - "libc", - "v4l2-sys-mit", -] - -[[package]] -name = "v4l2-sys-mit" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6779878362b9bacadc7893eac76abe69612e8837ef746573c4a5239daf11990b" -dependencies = [ - "bindgen 0.65.1", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vec_map" version = "0.8.2" @@ -9467,321 +3805,88 @@ version = "0.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "virtual_display" -version = "0.1.0" -dependencies = [ - "hbb_common", - "lazy_static", -] - -[[package]] -name = "waitgroup" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" -dependencies = [ - "atomic-waker", -] - -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" -version = "2.5.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", + "winapi 0.3.9", "winapi-util", ] -[[package]] -name = "wallpaper" -version = "3.2.0" -source = "git+https://github.com/rustdesk-org/wallpaper.rs#ce4a0cd3f58327c7cc44d15a63706fb0c022bacf" -dependencies = [ - "dirs 5.0.1", - "enquote", - "rust-ini", - "thiserror 1.0.61", - "winapi 0.3.9", - "winreg 0.11.0", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" +version = "0.10.2+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 = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ "cfg-if 1.0.0", - "once_cell", - "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", + "lazy_static", "log", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" dependencies = [ - "quote 1.0.36", + "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wayland-backend" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" -dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.2", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" -dependencies = [ - "bitflags 2.9.1", - "rustix 1.1.2", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.9.1", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" -dependencies = [ - "rustix 0.38.34", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-experimental" -version = "20250721.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" -dependencies = [ - "bitflags 2.9.1", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" -dependencies = [ - "proc-macro2 1.0.93", - "quick-xml 0.37.5", - "quote 1.0.36", -] - -[[package]] -name = "wayland-sys" -version = "0.31.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", @@ -9789,259 +3894,74 @@ dependencies = [ [[package]] name = "webm" -version = "1.1.0" -source = "git+https://github.com/rustdesk-org/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb047148a12ef1fd8ab26302bca7e82036f005c3073b48e17cc1b44ec577136" dependencies = [ "webm-sys", ] [[package]] name = "webm-sys" -version = "1.0.4" -source = "git+https://github.com/rustdesk-org/rust-webm#d2c4d3ac133c7b0e4c0f656da710b48391981e64" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ef7a997b20442888e2310242e3962935e90fb8aeb8a840bad81ac88d1ac64a" dependencies = [ "cc", ] [[package]] -name = "webpki-root-certs" -version = "1.0.3" +name = "webpki" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "0.26.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webrtc" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08fd686c0920ac08f3a57eacc48e31f0e4ca1ffefba4478784606f78c14e83ad" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "dtls", - "hex", - "interceptor", - "lazy_static", - "log", - "portable-atomic", - "rand 0.9.2", - "rcgen", - "regex", "ring", - "rtcp", - "rtp", - "sdp", - "serde 1.0.228", - "serde_json 1.0.118", - "sha2", - "smol_str", - "stun", - "thiserror 1.0.61", - "tokio", - "turn", - "unicase", - "url", - "waitgroup", - "webrtc-data", - "webrtc-ice", - "webrtc-mdns", - "webrtc-media", - "webrtc-sctp", - "webrtc-srtp", - "webrtc-util", -] - -[[package]] -name = "webrtc-data" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062a5438d63bb0756a221693d76cc0dd6119affee1dfdfe57abe3a2a8c8b3eea" -dependencies = [ - "bytes", - "log", - "portable-atomic", - "thiserror 1.0.61", - "tokio", - "webrtc-sctp", - "webrtc-util", -] - -[[package]] -name = "webrtc-ice" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cb13fd1a373e68addc4bba0c8ca058627518e54342583d024bdcbb8ae5d97d" -dependencies = [ - "arc-swap", - "async-trait", - "crc", - "log", - "portable-atomic", - "rand 0.9.2", - "serde 1.0.228", - "serde_json 1.0.118", - "stun", - "thiserror 1.0.61", - "tokio", - "turn", - "url", - "uuid", - "waitgroup", - "webrtc-mdns", - "webrtc-util", -] - -[[package]] -name = "webrtc-mdns" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17279a067e75df72ce923fdeb7f04cd808f6f5aa4910dc6bcb4fbe66b396ace" -dependencies = [ - "log", - "socket2 0.5.10", - "thiserror 1.0.61", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-media" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a84c910fec0848fd5a0d8a5651e0ddbdedaf25a7d3ae3f0b15f71ac73a1773" -dependencies = [ - "byteorder", - "bytes", - "rand 0.9.2", - "rtp", - "thiserror 1.0.61", -] - -[[package]] -name = "webrtc-sctp" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f985465467d8910c1f8ac4382cd64f83b1f6a1a75021a82b221546f6fb3b856f" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "crc", - "log", - "portable-atomic", - "rand 0.9.2", - "thiserror 1.0.61", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-srtp" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d8cdc33413f1d0192670a80ce93d17cb78d57fe3a2414be30d6f6dff121123" -dependencies = [ - "aead", - "aes", - "aes-gcm", - "byteorder", - "bytes", - "ctr", - "hmac", - "log", - "rtcp", - "rtp", - "sha1", - "subtle", - "thiserror 1.0.61", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-util" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c0c7e0c8f280f2bbfae442701465777ac07adaf46ce0c5863cd58e13fe472a" -dependencies = [ - "async-trait", - "bitflags 1.3.2", - "bytes", - "ipnet", - "lazy_static", - "log", - "nix 0.26.4", - "portable-atomic", - "rand 0.9.2", - "thiserror 1.0.61", - "tokio", - "winapi 0.3.9", + "untrusted", ] [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" [[package]] name = "which" -version = "4.4.2" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "failure", + "libc", +] + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" dependencies = [ "either", - "home", - "once_cell", - "rustix 0.38.34", + "lazy_static", + "libc", ] [[package]] name = "whoami" -version = "1.6.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" dependencies = [ - "redox_syscall 0.5.2", - "wasite", + "wasm-bindgen", "web-sys", ] [[package]] name = "widestring" -version = "1.1.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - -[[package]] -name = "win-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5" -dependencies = [ - "windows 0.34.0", -] +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" [[package]] name = "winapi" @@ -10073,18 +3993,18 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "windows-sys 0.52.0", + "winapi 0.3.9", ] [[package]] name = "winapi-wsapoll" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" dependencies = [ "winapi 0.3.9", ] @@ -10095,708 +4015,77 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" -dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - -[[package]] -name = "windows" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" -dependencies = [ - "windows_aarch64_msvc 0.34.0", - "windows_i686_gnu 0.34.0", - "windows_i686_msvc 0.34.0", - "windows_x86_64_gnu 0.34.0", - "windows_x86_64_msvc 0.34.0", -] - -[[package]] -name = "windows" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" -dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-implement 0.52.0", - "windows-interface 0.52.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" -dependencies = [ - "windows-collections", - "windows-core 0.61.0", - "windows-future", - "windows-link 0.1.1", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.0", -] - -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" -dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link 0.1.1", - "windows-result 0.3.2", - "windows-strings 0.4.0", -] - -[[package]] -name = "windows-future" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" -dependencies = [ - "windows-core 0.61.0", - "windows-link 0.1.1", -] - -[[package]] -name = "windows-implement" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.0", - "windows-link 0.1.1", -] - -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link 0.1.1", -] - [[package]] name = "windows-service" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9db37ecb5b13762d95468a2fc6009d4b2c62801243223aabd44fca13ad13c8" -dependencies = [ - "bitflags 1.3.2", - "widestring", - "windows-sys 0.45.0", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link 0.1.1", -] - -[[package]] -name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "0c643e10139d127d30d6d753398c8a6f0a43532e8370f6c9d29ebbff29b984ab" dependencies = [ - "windows-link 0.1.1", + "bitflags", + "err-derive", + "widestring", + "winapi 0.3.9", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-version" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" -dependencies = [ - "windows-targets 0.52.6", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", ] [[package]] name = "windows-win" -version = "3.0.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e23e33622b3b52f948049acbec9bcc34bf6e26d74176b88941f213c75cf2dc" +checksum = "8d4243ec23afe4e9b4e668b3c0a0e973f1b8265f6a46223cfcbc16fd267480c0" dependencies = [ - "error-code", + "winapi 0.3.9", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" - -[[package]] -name = "windows_i686_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_i686_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" [[package]] -name = "windows_x86_64_msvc" -version = "0.34.0" +name = "winreg" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winit" -version = "0.30.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ - "ahash 0.8.12", - "android-activity", - "atomic-waker", - "bitflags 2.9.1", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases 0.2.1", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics 0.23.2", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk 0.9.0", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle 0.6.2", - "redox_syscall 0.4.1", - "rustix 0.38.34", - "sctk-adwaita", - "smithay-client-toolkit 0.19.2", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb 0.13.1", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", + "winapi 0.3.9", ] [[package]] @@ -10808,481 +4097,82 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "winreg" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" -dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", -] - [[package]] name = "winres" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] - -[[package]] -name = "wl-clipboard-rs" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de22eebb1d1e2bad2d970086e96da0e12cde0b411321e5b0f7b2a1f876aa26f" -dependencies = [ - "libc", - "log", - "os_pipe", - "rustix 0.38.34", - "tempfile", - "thiserror 1.0.61", - "tree_magic_mini", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-wlr", -] - -[[package]] -name = "wol-rs" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5a8a033ef9b208ec8b5946761958ed2b2693ac49b04f647fdc013000870b8f" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x11" -version = "2.19.0" -source = "git+https://github.com/bjornsnoen/x11-rs#c2e9bfaa7b196938f8700245564d8ac5d447786a" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-clipboard" -version = "0.8.1" -source = "git+https://github.com/clslaid/x11-clipboard?branch=feat/store-batch#5fc2e73bc01ada3681159b34cf3ea8f0d14cd904" -dependencies = [ - "x11rb 0.12.0", -] - -[[package]] -name = "x11-clipboard" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" -dependencies = [ - "libc", - "x11rb 0.13.1", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" -dependencies = [ - "gethostname 0.3.0", - "nix 0.26.4", - "winapi 0.3.9", - "winapi-wsapoll", - "x11rb-protocol 0.12.0", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "as-raw-xcb-connection", - "gethostname 0.4.3", - "libc", - "libloading 0.8.4", - "once_cell", - "rustix 0.38.34", - "x11rb-protocol 0.13.1", -] - -[[package]] -name = "x11rb-protocol" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" -dependencies = [ - "nix 0.26.4", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core 0.6.4", - "serde 1.0.228", - "zeroize", -] - -[[package]] -name = "x509-parser" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 1.0.61", - "time 0.3.36", -] - -[[package]] -name = "xattr" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" -dependencies = [ - "libc", - "linux-raw-sys 0.4.14", - "rustix 0.38.34", -] - -[[package]] -name = "xcursor" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" - -[[package]] -name = "xdg-home" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.9.1", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" +name = "ws2_32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" dependencies = [ - "time 0.3.36", + "winapi 0.2.8", + "winapi-build", ] [[package]] -name = "zbus" -version = "3.15.2" +name = "x11-clipboard" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "473068b7b80ac86a18328824f1054e5e007898c47b5bbc281bd7abe32bc3653c" dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "byteorder", - "derivative", - "enumflags2", - "event-listener 2.5.3", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.26.4", - "once_cell", - "ordered-stream", - "rand 0.8.5", - "serde 1.0.228", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", + "xcb", +] + +[[package]] +name = "x11rb" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ffb080b3f2f616242a4eb8e7d325035312127901025b0052bc3154a282d0f19" +dependencies = [ + "gethostname", + "nix 0.20.0", "winapi 0.3.9", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "winapi-wsapoll", ] [[package]] -name = "zbus_macros" -version = "3.15.2" +name = "xcb" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.93", - "quote 1.0.36", - "regex", - "syn 1.0.109", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" -dependencies = [ - "serde 1.0.228", - "static_assertions", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" -dependencies = [ - "byteorder", - "zerocopy-derive 0.7.34", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive 0.8.26", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 2.0.98", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq 0.1.5", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2", - "sha1", - "time 0.3.36", - "zstd 0.11.2+zstd.1.5.2", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe 5.0.2+zstd.1.5.2", -] - -[[package]] -name = "zstd" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" -dependencies = [ - "zstd-safe 7.1.0", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "771e2b996df720cd1c6dd9ff90f62d91698fd3610cc078388d0564bdd6622a9c" dependencies = [ "libc", - "zstd-sys", + "log", + "quick-xml", +] + +[[package]] +name = "zstd" +version = "0.9.1+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538b8347df9257b7fbce37677ef7535c00a3c7bf1f81023cc328ed7fe4b41de8" +dependencies = [ + "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "4.1.2+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "9fb4cfe2f6e6d35c5d27ecd9d256c4b6f7933c4895654917460ec56c29336cc1" dependencies = [ + "libc", "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "1.6.2+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" dependencies = [ "cc", - "pkg-config", -] - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zvariant" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" -dependencies = [ - "byteorder", - "enumflags2", "libc", - "serde 1.0.228", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.36", - "syn 1.0.109", ] diff --git a/Cargo.toml b/Cargo.toml index fa22dcd7b..d5029532f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,10 @@ [package] name = "rustdesk" -version = "1.4.6" +version = "1.1.8" authors = ["rustdesk "] -edition = "2021" +edition = "2018" build= "build.rs" -description = "RustDesk Remote Desktop" -default-run = "rustdesk" -rust-version = "1.75" - -[lib] -name = "librustdesk" -crate-type = ["cdylib", "staticlib", "rlib"] - -[[bin]] -name = "naming" -path = "src/naming.rs" - -[[bin]] -name = "service" -path = "src/service.rs" +description = "A remote control software." [features] inline = [] @@ -26,124 +12,54 @@ cli = [] use_samplerate = ["samplerate"] use_rubato = ["rubato"] use_dasp = ["dasp"] -flutter = ["flutter_rust_bridge"] default = ["use_dasp"] -hwcodec = ["scrap/hwcodec"] -vram = ["scrap/vram"] -mediacodec = ["scrap/mediacodec"] -plugin_framework = [] -linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"] -unix-file-copy-paste = [ - "dep:x11-clipboard", - "dep:x11rb", - "dep:percent-encoding", - "dep:once_cell", - "clipboard/unix-file-copy-paste", -] -screencapturekit = ["cpal/screencapturekit"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = "0.1" -scrap = { path = "libs/scrap", features = ["wayland"] } +whoami = "1.2" +scrap = { path = "libs/scrap" } hbb_common = { path = "libs/hbb_common" } +enigo = { path = "libs/enigo" } +sys-locale = "0.1" serde_derive = "1.0" serde = "1.0" serde_json = "1.0" -serde_repr = "0.1" cfg-if = "1.0" lazy_static = "1.4" sha2 = "0.10" repng = "0.2" -parity-tokio-ipc = { git = "https://github.com/rustdesk-org/parity-tokio-ipc" } -magnum-opus = { git = "https://github.com/rustdesk-org/magnum-opus" } +libc = "0.2" +parity-tokio-ipc = { git = "https://github.com/open-trade/parity-tokio-ipc" } +flexi_logger = "0.22" +runas = "0.2" +magnum-opus = { git = "https://github.com/open-trade/magnum-opus" } dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpolate"], optional = true } -rubato = { version = "0.12", optional = true } +rubato = { version = "0.10", optional = true } samplerate = { version = "0.2", optional = true } -uuid = { version = "1.3", features = ["v4"] } -clap = "4.2" -rpassword = "7.2" -num_cpus = "1.15" -bytes = { version = "1.4", features = ["serde"] } -default-net = "0.14" -wol-rs = "1.0" -flutter_rust_bridge = { version = "=1.80", features = ["uuid"], optional = true} -errno = "0.3" -rdev = { git = "https://github.com/rustdesk-org/rdev" } -url = { version = "2.3", features = ["serde"] } -crossbeam-queue = "0.3" -hex = "0.4" -chrono = "0.4" -cidr-utils = "0.5" -fon = "0.6" -zip = "0.6" -shutdown_hooks = "0.1" -totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] } -stunclient = "0.4" -kcp-sys= { git = "https://github.com/rustdesk-org/kcp-sys"} -reqwest = { version = "0.12", features = ["blocking", "socks", "json", "native-tls", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false } +async-trait = "0.1" +crc32fast = "1.3" +uuid = { version = "0.8", features = ["v4"] } +clap = "2.34" +rpassword = "5.0" +base64 = "0.13" -[target.'cfg(not(target_os = "linux"))'.dependencies] -# https://github.com/rustdesk/rustdesk/discussions/10197, not use cpal on linux -cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" } -ringbuf = "0.3" +[target.'cfg(not(any(target_os = "android")))'.dependencies] +cpal = { git = "https://github.com/open-trade/cpal" } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] +machine-uid = "0.2" mac_address = "1.1" -sciter-rs = { git = "https://github.com/rustdesk-org/rust-sciter", branch = "dyn" } -sys-locale = "0.3" -enigo = { path = "libs/enigo", features = [ "with_serde" ] } -clipboard = { path = "libs/clipboard" } +sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" } ctrlc = "3.2" -# arboard = { version = "3.4", features = ["wayland-data-control"] } -arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] } -clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" } -portable-pty = { git = "https://github.com/rustdesk-org/wezterm", branch = "rustdesk/pty_based_0.8.1", package = "portable-pty" } - -system_shutdown = "4.0" -qrcode-generator = "4.1" +arboard = "2.0" +clipboard-master = "3.1" [target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3", features = [ - "winuser", - "wincrypt", - "shellscalingapi", - "pdh", - "synchapi", - "memoryapi", - "shellapi", - "devguid", - "setupapi", - "cguid", - "cfgmgr32", - "ioapiset", - "winspool", -] } -windows = { version = "0.61", features = [ - "Win32", - "Win32_Foundation", - "Win32_Security", - "Win32_Security_Authorization", - "Win32_Storage_FileSystem", - "Win32_System", - "Win32_System_Diagnostics", - "Win32_System_Diagnostics_ToolHelp", - "Win32_System_Environment", - "Win32_System_IO", - "Win32_System_Memory", - "Win32_System_Pipes", - "Win32_System_Threading", - "Win32_UI_Shell", -] } -winreg = "0.11" -windows-service = "0.6" -virtual_display = { path = "libs/virtual_display" } -remote_printer = { path = "libs/remote_printer" } -impersonate_system = { git = "https://github.com/rustdesk-org/impersonate-system" } -shared_memory = "0.12" -tauri-winrt-notification = "0.1" -runas = "1.2" +systray = { git = "https://github.com/liyue201/systray-rs" } +winapi = { version = "0.3", features = ["winuser"] } +winreg = "0.10" +windows-service = "0.4" [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2" @@ -151,100 +67,49 @@ cocoa = "0.24" dispatch = "0.2" core-foundation = "0.9" core-graphics = "0.22" -include_dir = "0.7" -fruitbasket = "0.10" -objc_id = "0.1" -# If we use piet "0.7" here, we must also update core-graphics to "0.24". -piet = "0.6" -piet-coregraphics = "0.6" -foreign-types = "0.3" - -[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies] -tray-icon = { git = "https://github.com/tauri-apps/tray-icon", version = "0.21.3" } -tao = { git = "https://github.com/rustdesk-org/tao", branch = "dev" } -image = "0.24" - -[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies] -keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" } - -[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies] -wallpaper = { git = "https://github.com/rustdesk-org/wallpaper.rs" } -tiny-skia = "0.11" -softbuffer = "0.4" -fontdb = "0.23" -bytemuck = "1.23" -ttf-parser = "0.25" [target.'cfg(target_os = "linux")'.dependencies] -libxdo-sys = "0.11" -psimple = { package = "libpulse-simple-binding", version = "2.27" } -pulse = { package = "libpulse-binding", version = "2.27" } -rust-pulsectl = { git = "https://github.com/rustdesk-org/pulsectl" } -async-process = "1.7" -evdev = { git="https://github.com/rustdesk-org/evdev" } -dbus = "0.9" -dbus-crossroads = "0.5" -pam = { git="https://github.com/rustdesk-org/pam" } -x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} -x11rb = {version = "0.12", features = ["all-extensions"], optional = true} -percent-encoding = {version = "2.3", optional = true} -once_cell = {version = "1.18", optional = true} -nix = { version = "0.29", features = ["term", "process"]} -gtk = "0.18" -termios = "0.3" -terminfo = "0.8" -winit = "0.30" +libpulse-simple-binding = "2.24" +libpulse-binding = "2.25" +rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" } -[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] -openssl = { version = "0.10", features = ["vendored"] } +[target.'cfg(not(any(target_os = "windows", target_os = "android", target_os = "ios")))'.dependencies] +psutil = { version = "3.2", features = [ "process" ], git = "https://github.com/open-trade/rust-psutil" } [target.'cfg(target_os = "android")'.dependencies] -android_logger = "0.13" -jni = "0.21" -android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" } +android_logger = "0.10" [workspace] -members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"] -exclude = ["vdi/host", "examples/custom_plugin"] - -# Patch libxdo-sys to use a stub implementation that doesn't require libxdo -# This allows building and running on systems without libxdo installed (e.g., Wayland-only) -[patch.crates-io] -libxdo-sys = { path = "libs/libxdo-sys-stub" } +members = ["libs/scrap", "libs/hbb_common", "libs/enigo"] [package.metadata.winres] -LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved." -ProductName = "RustDesk" -FileDescription = "RustDesk Remote Desktop" -OriginalFilename = "rustdesk.exe" +LegalCopyright = "Copyright © 2020" +# this FileDescription overrides package.description +FileDescription = "RustDesk" [target.'cfg(target_os="windows")'.build-dependencies] winres = "0.1" -winapi = { version = "0.3", features = [ "winnt", "pdh", "synchapi" ] } +winapi = { version = "0.3", features = [ "winnt" ] } [build-dependencies] cc = "1.0" hbb_common = { path = "libs/hbb_common" } -os-version = "0.2" [dev-dependencies] -hound = "3.5" -docopt = "1.1" +hound = "3.4" [package.metadata.bundle] name = "RustDesk" identifier = "com.carriez.rustdesk" -icon = ["res/32x32.png", "res/128x128.png", "res/128x128@2x.png"] +icon = ["32x32.png", "128x128.png", "128x128@2x.png"] +deb_depends = ["libgtk-3-0", "libxcb-randr0", "libxdo3", "libxfixes3", "libxcb-shape0", "libxcb-xfixes0", "libasound2", "libsystemd0", "pulseaudio"] osx_minimum_system_version = "10.14" #https://github.com/johnthagen/min-sized-rust +#!!! rembember call "strip target/release/rustdesk" +# which reduce binary size a lot [profile.release] -lto = true -codegen-units = 1 -panic = 'abort' -strip = true +#lto = true +#codegen-units = 1 +#panic = 'abort' #opt-level = 'z' # only have smaller size after strip -rpath = true - -[profile.dev] -debug = 1 diff --git a/Dockerfile b/Dockerfile index f0e4e4a4a..94d713f6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,64 +1,20 @@ -FROM debian:bullseye-slim +FROM debian WORKDIR / -ARG DEBIAN_FRONTEND=noninteractive -ENV VCPKG_FORCE_SYSTEM_BINARIES=1 -RUN apt update -y && \ - apt install --yes --no-install-recommends \ - g++ \ - gcc \ - git \ - curl \ - nasm \ - yasm \ - libgtk-3-dev \ - clang \ - libxcb-randr0-dev \ - libxdo-dev \ - libxfixes-dev \ - libxcb-shape0-dev \ - libxcb-xfixes0-dev \ - libasound2-dev \ - libpam0g-dev \ - libpulse-dev \ - make \ - wget \ - libssl-dev \ - unzip \ - zip \ - sudo \ - libgstreamer1.0-dev \ - libgstreamer-plugins-base1.0-dev \ - ca-certificates \ - ninja-build && \ - rm -rf /var/lib/apt/lists/* +RUN apt update -y && 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 unzip zip sudo -RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.6/cmake-3.30.6.tar.gz --no-check-certificate && \ - tar xzf cmake-3.30.6.tar.gz && \ - cd cmake-3.30.6 && \ - ./configure --prefix=/usr/local && \ - make && \ - make install - -RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \ - /vcpkg/bootstrap-vcpkg.sh -disableMetrics && \ - /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom - -RUN groupadd -r user && \ - useradd -r -g user user --home /home/user && \ - mkdir -p /home/user/rustdesk && \ - chown -R user: /home/user && \ - echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user +RUN git clone https://github.com/microsoft/vcpkg && cd vcpkg && git checkout 134505003bb46e20fbace51ccfb69243fbbc5f82 +RUN /vcpkg/bootstrap-vcpkg.sh -disableMetrics +RUN /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus +RUN groupadd -r user && useradd -r -g user user --home /home/user && mkdir -p /home/user && chown user /home/user && echo "user ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/user WORKDIR /home/user -RUN curl -LO https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so - +RUN wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so USER user -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \ - chmod +x rustup.sh && \ - ./rustup.sh -y +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh +RUN chmod +x rustup.sh +RUN ./rustup.sh -y USER root -ENV HOME=/home/user -COPY ./entrypoint.sh / -ENTRYPOINT ["/entrypoint.sh"] +COPY ./entrypoint / +ENTRYPOINT ["/entrypoint"] diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index c31706425..000000000 --- a/GEMINI.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md diff --git a/LICENCE b/LICENSE similarity index 86% rename from LICENCE rename to LICENSE index 0ad25db4b..f288702d2 100644 --- a/LICENCE +++ b/LICENSE @@ -1,5 +1,5 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,15 +7,17 @@ Preamble - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to +the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. @@ -60,7 +72,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU Affero General Public License. + "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. + 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single +under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General +Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published +GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's +versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License + You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see +For more information on this, and how to apply and follow the GNU GPL, see . + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README-DE.md b/README-DE.md new file mode 100644 index 000000000..2ac9a6bdd --- /dev/null +++ b/README-DE.md @@ -0,0 +1,162 @@ +

+ RustDesk - Your remote desktop
+ Server • + Kompilieren • + Docker • + Dateistruktur • + Screenshots
+ [English] | [中文] | [Español] | [Français] | [Nederlands] | [Polski] | [日本語] | [Русский] | [Português]
+ Wir brauchen deine Hilfe um diese README Datei zu verbessern und aktualisieren +

+ +Rede mit uns: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +Das hier ist ein Programm was man nutzen kann, um einen Computer fernzusteuern, es wurde in Rust geschrieben. Es funktioniert ohne Konfiguration oder ähnliches, man kann es einfach direkt nutzen. Du hast volle Kontrolle über deine Daten und brauchst dir daher auch keine Sorgen um die Sicherheit dieser Daten zu machen. Du kannst unseren rendezvous/relay Server nutzen, [einen eigenen Server eröffnen](https://rustdesk.com/blog/id-relay-set/) oder [einen neuen eigenen Server programmieren](https://github.com/rustdesk/rustdesk-server-demo). + +RustDesk heißt jegliche Mitarbeit willkommen. Schau dir [`CONTRIBUTING.md`](CONTRIBUTING.md) an, wenn du Hilfe brauchst für den Start. + +[**PROGRAMM DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) + +## Kostenlose öffentliche Server + +Hier sind die Server die du kostenlos nutzen kannst, es kann sein das 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. + +| Standort | Serverart | Spezifikationen | Kommentare | +| --------- | ------------- | ------------------ | ---------- | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | | +| Singapore | Vultr | 1 VCPU / 1GB RAM | | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## Abhängigkeiten + +Die Desktop Versionen nutzen [Sciter](https://sciter.com/) für die Oberfläche, bitte lade die dynamische Sciter Bibliothek 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) | +[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) + +## Die groben Schritte zum Kompilieren + +- Bereite deine Rust Entwicklungsumgebung und C++ Entwicklungsumgebung vor + +- Installiere [vcpkg](https://github.com/microsoft/vcpkg) und füge die `VCPKG_ROOT` Systemumgebungsvariable 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` + +## Kompilieren auf Linux + +### 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 +``` + +### 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 pulseaudio +``` + +### vcpkg installieren + +```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 reparieren (Für 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 +``` + +### Kompilieren + +```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 +cargo run +``` + +### Ändere Wayland zu X11 (Xorg) + +RustDesk unterstützt "Wayland" nicht. Siehe [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) um Xorg als Standard GNOME Session zu nutzen. + +## Auf Docker Kompilieren + +Beginne damit das Repository zu klonen und den Docker Container zu bauen: + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +Jedes Mal, wenn du das Programm Kompilieren musst, nutze diesen Befehl: + +```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 Mal Kompilieren länger dauern kann, da die Abhängigkeiten erst kompiliert werden müssen bevor sie zwischengespeichert werden können. Darauf folgende Kompiliervorgänge werden schneller sein. Falls du zusätzliche oder andere Argumente für den Kompilierbefehl angeben musst, kannst du diese am Ende des Befehls an der `` Position machen. Wenn du zum Beispiel eine optimierte Releaseversion kompilieren willst, kannst du das tun indem du `--release` am Ende des Befehls anhängst. Das daraus entstehende Programm kannst du im “target” Ordner auf deinem System finden. Du kannst es mit folgenden Befehlen ausführen: + +```sh +target/debug/rustdesk +``` + +Oder, wenn du eine Releaseversion benutzt: + +```sh +target/release/rustdesk +``` + +Bitte gehe sicher, dass du diese Befehle vom Stammverzeichnis vom RustDesk Repository nutzt, sonst kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenke auch, dass Unterbefehle von Cargo, wie z.B. `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. + +## Dateistruktur + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video Codec, Konfiguration, TCP/UDP Wrapper, Protokoll Puffer, fs Funktionen für Dateitransfer, und ein paar andere nützliche Funktionen +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: Bildschirmaufnahme +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: Plattformspezifische Maus und Tastatur Steuerung +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI +- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: Audio/Zwischenablage/Eingabe/Videodienste und Netzwerk Verbindungen +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Starten einer Peer-Verbindung +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Mit [rustdesk-server](https://github.com/rustdesk/rustdesk-server) kommunizieren, für Verbindung von außen warten, direkt (TCP hole punching) oder weitergeleitet +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: Plattformspezifischer Code + +## Screenshots + +![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/README-ES.md b/README-ES.md new file mode 100644 index 000000000..a28864901 --- /dev/null +++ b/README-ES.md @@ -0,0 +1,160 @@ +

+ RustDesk - Your remote desktop
+ Servidores • + Compilar • + Docker • + Estructura • + Captura de pantalla
+ [English] | [中文] | [Deutsch] | [Français] | [Nederlands] | [Polski] | [日本語] | [Русский] | [Português]
+ Necesitamos tu ayuda para traducir este README a tu idioma +

+ +Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +Otro software de escritorio remoto, escrito en Rust. Funciona de forma inmediata, sin necesidad de configuración. Tienes el control total de sus datos, sin preocupaciones sobre la seguridad. Puedes utilizar nuestro servidor de rendezvous/relay, [set up your own](https://rustdesk.com/blog/id-relay-set/), o [escribir tu propio servidor rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo). + +RustDesk agradece la contribución de todo el mundo. Ve [`CONTRIBUTING.md`](CONTRIBUTING.md) para ayuda inicial. + +[**DESCARGA DE BINARIOS**](https://github.com/rustdesk/rustdesk/releases) + +## Servidores gratis de uso público + +A continuación se muestran los servidores que está utilizando de forma gratuita, puede cambiar en algún momento. Si no estás cerca de uno de ellos, tu red puede ser lenta. + +- Seoul, AWS lightsail, 1 VCPU/0.5G RAM +- Singapore, Vultr, 1 VCPU/1G RAM +- Dallas, Vultr, 1 VCPU/1G RAM + +## Dependencies + +La versión Desktop usa [sciter](https://sciter.com/) para GUI, por favor bajate la librería sciter tu mismo.. + +[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) + +## Pasos para compilar desde el inicio + +- Prepara el entono de desarrollode Rust y el entorno de compilación de C++ y Rust. + +- Instala [vcpkg](https://github.com/microsoft/vcpkg), y configura la variable de entono `VCPKG_ROOT` correctamente. + + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/Osx: vcpkg install libvpx libyuv opus + +- run `cargo run` + +## Como compilar en linux + +### 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 +``` + +### 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 pulseaudio +``` + +### Install 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 +``` + +### Soluciona libvpx (For Fedora) + +```sh +cd vcpkg/buildtrees/libvpx/src +cd * +./configure +sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile +sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile +make +cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ +cd +``` + +### Compila + +```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 +cargo run +``` + +### Cambia Wayland a X11 (Xorg) + +RustDesk no soporta Wayland. Comprueba [aquí](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) para configurar Xorg en la sesión por defecto de GNOME. + +## Como compilar con Docker + +Empieza clonando el repositorio y compilando el contenedor de docker: + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +Entonces, cada vez que necesites compilar una modificación, ejecuta el siguiente comando: + +```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 +``` + +Ten en cuenta que la primera compilación puede tardar más tiempo antes de que las dependencias se almacenen en la caché, las siguientes compilaciones serán más rápidas. Además, si necesitas especificar diferentes argumentos a la orden de compilación, puede hacerlo al final de la linea de comandos en el apartado``. Por ejemplo, si desea compilar una versión optimizada para publicación, deberá ejecutar el comando anterior seguido de `---release`. El ejecutable resultante estará disponible en la carpeta de destino en su sistema, y puede ser ejecutado con: + +```sh +target/debug/rustdesk +``` + +O si estas ejecutando una versión para su publicación: + +```sh +target/release/rustdesk +``` + +Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del repositorio de RustDesk, de lo contrario la aplicación puede ser incapaz de encontrar los recursos necesarios. También hay que tener en cuenta que otros subcomandos de carga como `install` o `run` no estan actualmente soportados via este metodo y podrían requerir ser instalados dentro del contenedor y no en el host. + +## Estructura de archivos + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, configuración, tcp/udp wrapper, protobuf, fs funciones para transferencia de ficheros, y alguna función de utilidad. +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: captura de pantalla +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control específico por cada plataforma para el teclado/ratón +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI +- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: sonido/portapapeles/entrada/servicios de video, y conexiones de red +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: iniciar una conexión "peer to peer" +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicación con [rustdesk-server](https://github.com/rustdesk/rustdesk-server), esperar la conexión remota directa ("TCP hole punching") o conexión indirecta ("relayed") +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico de cada plataforma + +## Captura de pantalla + +![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-FI.md b/README-FI.md similarity index 70% rename from docs/README-FI.md rename to README-FI.md index 4c167978c..86e87befa 100644 --- a/docs/README-FI.md +++ b/README-FI.md @@ -1,24 +1,33 @@

- RustDesk - Etätyöpöytäsi
+ RustDesk - Etätyöpöytäsi
PalvelimetRakennaDockerRakenneTilannevedos
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
+ [中文] | [Español] | [Français] | [Deutsch] | [Nederlands] | [Polski] | [日本語] | [Русский] | [Português] | [Suomi]
Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi

-Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Edistyneet%20Ominaisuudet-blue)](https://rustdesk.com/pricing.html) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Vielä yksi etätyöpöytäohjelmisto, ohjelmoitu Rust-kielellä. Toimii suoraan pakkauksesta, ei tarvitse asetusta. Hallitset täysin tietojasi, ei tarvitse murehtia turvallisuutta. Voit käyttää meidän rendezvous/relay-palvelinta, [aseta omasi](https://rustdesk.com/server), tai [kirjoittaa oma rendezvous/relay-palvelin](https://github.com/rustdesk/rustdesk-server-demo). +Vielä yksi etätyöpöytäohjelmisto, ohjelmoitu Rust-kielellä. Toimii suoraan pakkauksesta, ei tarvitse asetuksia. Hallitset täysin tietojasi, ei tarvitse murehtia turvallisuutta. Voit käyttää meidän rendezvous/relay-palvelinta, [aseta omasi](https://rustdesk.com/blog/id-relay-set/), tai [kirjoita oma rendezvous/relay-palvelin](https://github.com/rustdesk/rustdesk-server-demo). -RustDesk toivottaa avustukset tervetulleiksi kaikilta. Katso lisätietoja [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) avun saamiseksi. +RustDesk toivottaa avustukset tervetulleiksi kaikilta. Katso lisätietoja [`CONTRIBUTING.md`](CONTRIBUTING.md) avun saamiseksi. [**BINAARILATAUS**](https://github.com/rustdesk/rustdesk/releases) +## Vapaita julkisia palvelimia + +Alla on palvelimia, joita voit käyttää ilmaiseksi, ne saattavat muuttua ajan mittaan. Jos et ole lähellä yhtä näistä, verkkosi voi olla hidas. +| Sijainti | Myyjä | Määrittely | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + ## Riippuvuudet Desktop-versiot käyttävät [sciter](https://sciter.com/) graafisena käyttöliittymänä, lataa sciter-dynaaminen kirjasto itsellesi. @@ -33,12 +42,12 @@ Desktop-versiot käyttävät [sciter](https://sciter.com/) graafisena käyttöli - Asenna [vcpkg](https://github.com/microsoft/vcpkg), ja aseta `VCPKG_ROOT`-ympäristömuuttuja oikein - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus -- suorita `cargo run` +- aja `cargo run` -## Kuinka rakentaa Linux:issa +## Kuinka rakentaa Linuxissa ### Ubuntu 18 (Debian 10) @@ -55,7 +64,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- ### 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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### Asenna vcpkg @@ -63,14 +72,14 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` -### Korjaa libvpx (Fedora) +### Korjaa libvpx (Fedora-linux-versiota varten) ```sh cd vcpkg/buildtrees/libvpx/src @@ -96,6 +105,10 @@ mv libsciter-gtk.so target/debug VCPKG_ROOT=$HOME/vcpkg cargo run ``` +### Vaihda Wayland-ympäristö X11 (Xorg)-ympäristöön + +RustDesk ei tue Waylandia. Tarkista [tämä](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) asettamaan Xorg oletus GNOME-istuntona. + ## Kuinka rakennetaan Dockerin kanssa Aloita kloonaamalla tietovarasto ja rakentamalla docker-säiliö: @@ -106,13 +119,13 @@ cd rustdesk docker build -t "rustdesk-builder" . ``` -Sitten, joka kerta kun sinun on rakennettava sovellus, suorita seuraava komento: +Sitten, joka kerta kun sinun on rakennettava sovellus, aja seuraava komento: ```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 ``` -Huomaa, että ensimmäinen rakentaminen saattaa kestää pitempään ennen kuin riippuvuudet on siirretty välimuistiin, seuraavat rakentamiset ovat nopeampia. Lisäksi, jos sinun on määritettävä eri väittämiä rakentamiskomennolle, saatat tehdä sen niin, että komennon lopussa `-kohdassa. Esimerkiksi, jos haluat rakentaa optimoidun julkaisuversion, sinun on ajettava komento yllä siten, että sitä seuraa väittämä`--release`. Suoritettava tiedosto on saatavilla järjestelmäsi kohdehakemistossa, ja se voidaan suorittaa seuraavan kera: +Huomaa, että ensimmäinen rakentaminen saattaa kestää pitempään ennen kuin riippuvuudet on siirretty välimuistiin, seuraavat rakentamiset ovat nopeampia. Lisäksi, jos sinun on määritettävä eri argumentteja rakentamiskomennolle, saatat tehdä sen niin, että komennon lopussa `-kohdassa. Esimerkiksi, jos haluat rakentaa optimoidun julkaisuversion, sinun on ajettava komento yllä siten, että sitä seuraa argumentti `---release`. Suoritettava tiedosto on saatavilla järjestelmäsi kohdehakemistossa, ja se voidaan suorittaa seuraavan kera: ```sh target/debug/rustdesk diff --git a/docs/README-FR.md b/README-FR.md similarity index 60% rename from docs/README-FR.md rename to README-FR.md index 345e53b58..2cf4e81c0 100644 --- a/docs/README-FR.md +++ b/README-FR.md @@ -1,24 +1,32 @@

- RustDesk - Your remote desktop
+ RustDesk - Your remote desktop
Serveurs - Build - Docker - Structure - Images
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
+ [English] | [中文] | [Deutsch] | [Española] | [Nederlands] | [Polski] | [日本語] | [Русский] | [Português]
Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle.

-Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Fonctionnalit%C3%A9s%20Avanc%C3%A9es-blue)](https://rustdesk.com/pricing.html) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Encore un autre logiciel de bureau à distance, écrit en Rust. Fonctionne directement, aucune configuration n'est nécessaire. Vous avez le contrôle total de vos données, sans aucun souci de sécurité. Vous pouvez utiliser notre serveur de rendez-vous/relais, [configurer le vôtre](https://rustdesk.com/server), ou [écrire votre propre serveur de rendez-vous/relais](https://github.com/rustdesk/rustdesk-server-demo). +Encore un autre logiciel de bureau à distance, écrit en Rust. Fonctionne directement, aucune configuration n'est nécessaire. Vous avez le contrôle total de vos données, sans aucun souci de sécurité. Vous pouvez utiliser notre serveur de rendez-vous/relais, [configurer le vôtre](https://rustdesk.com/blog/id-relay-set/), ou [écrire votre propre serveur de rendez-vous/relais](https://github.com/rustdesk/rustdesk-server-demo). -RustDesk accueille les contributions de tout le monde. Voir [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) pour plus d'informations. +RustDesk accueille les contributions de tout le monde. Voir [`CONTRIBUTING.md`](CONTRIBUTING.md) pour plus d'informations. [**TÉLÉCHARGEMENT BINAIRE**](https://github.com/rustdesk/rustdesk/releases) +## Serveurs publics libres + +Ci-dessous se trouvent les serveurs que vous utilisez gratuitement, cela peut changer au fil du temps. Si vous n'êtes pas proche de l'un d'entre eux, votre réseau peut être lent. + +- Séoul, AWS lightsail, 1 VCPU/0.5G RAM +- Singapour, Vultr, 1 VCPU/1G RAM +- Dallas, Vultr, 1 VCPU/1G RAM + ## Dépendances Les versions de bureau utilisent [sciter](https://sciter.com/) pour l'interface graphique, veuillez télécharger la bibliothèque dynamique sciter vous-même. @@ -33,10 +41,10 @@ Les versions de bureau utilisent [sciter](https://sciter.com/) pour l'interface - Installez [vcpkg](https://github.com/microsoft/vcpkg), et définissez correctement la variable d'environnement `VCPKG_ROOT`. - - Windows : vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/macOS : vcpkg install libvpx libyuv opus aom + - Windows : vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/Osx : vcpkg install libvpx libyuv opus -- Exécutez `cargo run` +- Exécuter `cargo run` ## Comment compiler/build sous Linux @@ -55,7 +63,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- ### 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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### Installer vcpkg @@ -63,11 +71,11 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` ### Corriger libvpx (Pour Fedora) @@ -90,12 +98,16 @@ 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 +mkdir -p cible/debug wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so mv libsciter-gtk.so target/debug -cargo run +Exécution du cargo ``` +### Changer Wayland en X11 (Xorg) + +RustDesk ne supporte pas Wayland. Lisez [cela](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) pour configurer Xorg comme la session GNOME par défaut. + ## Comment construire avec Docker Commencez par cloner le dépôt et construire le conteneur Docker : @@ -106,13 +118,13 @@ cd rustdesk docker build -t "rustdesk-builder" . ``` -Ensuite, chaque fois que vous devez compiler le logiciel, exécutez la commande suivante : +Ensuite, chaque fois que vous devez build le logiciel, exécutez la commande suivante : ```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 ``` -Notez que la première compilation peut prendre plus de temps avant que les dépendances ne soient mises en cache, les compilations suivantes seront plus rapides. De plus, si vous devez spécifier différents arguments à la commande de compilation, vous pouvez le faire à la fin de la commande à la position ``. Par exemple, si vous voulez compiler une version de release optimisée, vous devez exécuter la commande ci-dessus suivie de `--release`. L'exécutable résultant sera disponible dans le dossier cible sur votre système, et peut être lancé avec : +Notez que le premier build peut prendre plus de temps avant que les dépendances ne soient mises en cache, les constructions suivantes seront plus rapides. De plus, si vous devez spécifier différents arguments à la commande de compilation, vous pouvez le faire à la fin de la commande dans la position ``. Par exemple, si vous voulez construire une version optimisée de la version release, vous devez exécuter la commande ci-dessus suivie de `---release`. L'exécutable résultant sera disponible dans le dossier cible sur votre système, et peut être lancé avec : ```sh target/debug/rustdesk @@ -124,23 +136,19 @@ Ou, si vous exécutez un exécutable provenant d'une release : target/release/rustdesk ``` -Veuillez vous assurer que vous exécutez ces commandes à partir de la racine du dépôt RustDesk, sinon l'application ne pourra pas trouver les ressources requises. Notez également que les autres sous-commandes de cargo telles que `install` ou `run` ne sont pas actuellement supportées par cette méthode car elles installeraient ou exécuteraient le programme à l'intérieur du conteneur au lieu de l'hôte. +Veuillez vous assurer que vous exécutez ces commandes à partir de la racine du référentiel RustDesk, sinon l'application ne pourra pas trouver les ressources requises. Notez également que les autres sous-commandes de cargo telles que `install` ou `run` ne sont pas actuellement supportées par cette méthode car elles installeraient ou exécuteraient le programme à l'intérieur du conteneur au lieu de l'hôte. ## Structure du projet - **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)** : codec vidéo, config, wrapper tcp/udp, protobuf, fonctions fs pour le transfert de fichiers, et quelques autres fonctions utilitaires. - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)** : capture d'écran - **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)** : contrôle clavier/souris spécifique à la plate-forme -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)** : interface graphique +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)** : INTERFACE GRAPHIQUE - **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)** : services audio/clipboard/input/vidéo, et connexions réseau - **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)** : démarrer une connexion entre pairs - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée. - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme -> [!Attention] -> **Avertissement contre l'utilisation abusive:**
-> Les développeurs de RustDesk ne cautionnent ni ne soutiennent aucune utilisation non éthique ou illégale de ce logiciel. Toute utilisation abusive, telle que l'accès non autorisé, le contrôle ou l'invasion de la vie privée, est strictement contraire à nos directives. Les auteurs ne sont pas responsables de toute utilisation abusive de l'application. - ## Images ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) diff --git a/README-JP.md b/README-JP.md new file mode 100644 index 000000000..e4d0ed71d --- /dev/null +++ b/README-JP.md @@ -0,0 +1,164 @@ +

+ RustDesk - Your remote desktop
+ Servers • + Build • + Docker • + Structure • + Snapshot
+ [中文] | [Español] | [Français] | [Deutsch] | [Nederlands] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Русский] | [Português]
+ このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。 +

+ +Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分で設定する](https://rustdesk.com/blog/id-relay-set/) ことも、 [自分でランデブー/リレーサーバを書くこともできます。](https://github.com/rustdesk/rustdesk-server-demo). + +RustDeskは誰からの貢献も歓迎します。 貢献するには [`CONTRIBUTING.md`](CONTRIBUTING.md) を参照してください。 + +[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) + +## 無料のパブリックサーバー + +下記のサーバーは、無料で使用できますが、後々変更されることがあります。これらのサーバーから遠い場合、接続が遅い可能性があります。 +| Location | Vendor | Specification | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## 依存関係 + +デスクトップ版ではGUIに [sciter](https://sciter.com/) が使われています。 sciter dynamic library をダウンロードしてください。 + +[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) + +## ビルド手順 + +- 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 + +- run `cargo run` + +## Linuxでのビルド手順 + +### 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 +``` + +### 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 pulseaudio +``` + +### Install 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 +``` + +### Fix libvpx (For Fedora) + +```sh +cd vcpkg/buildtrees/libvpx/src +cd * +./configure +sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile +sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile +make +cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ +cd +``` + +### 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セッションとして構成します。 + +## Dockerでビルドする方法 + +リポジトリのクローンを作成し、Dockerコンテナを構築することから始めます。 + + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +その後、アプリケーションをビルドする必要があるたびに、以下のコマンドを実行します。 + +```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 +``` + +なお、最初のビルドでは、依存関係がキャッシュされるまで時間がかかることがありますが、その後のビルドではより速くなります。さらに、ビルドコマンドに別の引数を指定する必要がある場合は、コマンドの最後にある `` の位置で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に +`---release` を実行します。できあがった実行ファイルは、システムのターゲット・フォルダに格納され、次のコマンドで実行できます。 + +```sh +target/debug/rustdesk +``` + +あるいは、リリース用の実行ファイルを実行している場合: + +```sh +target/release/rustdesk +``` + +これらのコマンドをRustDeskリポジトリのルートから実行していることを確認してください。そうしないと、アプリケーションが必要なリソースを見つけられない可能性があります。また、 `install` や `run` などの他の cargo サブコマンドは、ホストではなくコンテナ内にプログラムをインストールまたは実行するため、現在この方法ではサポートされていないことに注意してください。 + +## File Structure + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: ビデオコーデック、コンフィグ、tcp/udpラッパー、protobuf、ファイル転送用のfs関数、その他のユーティリティ関数 +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: スクリーンキャプチャ +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: プラットフォーム固有のキーボード/マウスコントロール +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI +- **[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 hole punching) または中継接続を待つ。 +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: プラットフォーム固有のコード + +## Snapshot + +![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-ML.md b/README-ML.md similarity index 76% rename from docs/README-ML.md rename to README-ML.md index 225d7b952..4348bd1dd 100644 --- a/docs/README-ML.md +++ b/README-ML.md @@ -1,25 +1,34 @@

- RustDesk - Your remote desktop
+ RustDesk - Your remote desktop
ServersBuildDockerStructureSnapshot
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
+ [中文] | [Español] | [Français] | [Deutsch] | [Nederlands] | [Polski] | [Suomi] | [日本語] | [Русский] | [Português]
ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്

-ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%E0%B4%B5%E0%B4%BF%E0%B4%95%E0%B4%B8%E0%B4%BF%E0%B4%A4%20%E0%B4%B8%E0%B4%B5%E0%B4%BF%E0%B4%B6%E0%B5%87%E0%B4%B7%E0%B4%A4%E0%B4%95%E0%B5%BE-blue)](https://rustdesk.com/pricing.html) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -റസ്റ്റിൽ എഴുതിയ മറ്റൊരു റിമോട്ട് ഡെസ്ക്ടോപ്പ് സോഫ്റ്റ്‌വെയർ. ബോക്‌സിന് പുറത്ത് പ്രവർത്തിക്കുന്നു, കോൺഫിഗറേഷൻ ആവശ്യമില്ല. സുരക്ഷയെക്കുറിച്ച് ആശങ്കകളൊന്നുമില്ലാതെ, നിങ്ങളുടെ ഡാറ്റയുടെ പൂർണ്ണ നിയന്ത്രണം നിങ്ങൾക്കുണ്ട്. നിങ്ങൾക്ക് ഞങ്ങളുടെ rendezvous/relay സെർവർ ഉപയോഗിക്കാം, [സ്വന്തമായി സജ്ജീകരിക്കുക](https://rustdesk.com/server), അല്ലെങ്കിൽ [നിങ്ങളുടെ സ്വന്തം rendezvous/relay സെർവർ എഴുതുക](https://github.com/rustdesk/rustdesk-server-demo). +റസ്റ്റിൽ എഴുതിയ മറ്റൊരു റിമോട്ട് ഡെസ്ക്ടോപ്പ് സോഫ്റ്റ്‌വെയർ. ബോക്‌സിന് പുറത്ത് പ്രവർത്തിക്കുന്നു, കോൺഫിഗറേഷൻ ആവശ്യമില്ല. സുരക്ഷയെക്കുറിച്ച് ആശങ്കകളൊന്നുമില്ലാതെ, നിങ്ങളുടെ ഡാറ്റയുടെ പൂർണ്ണ നിയന്ത്രണം നിങ്ങൾക്കുണ്ട്. നിങ്ങൾക്ക് ഞങ്ങളുടെ rendezvous/relay സെർവർ ഉപയോഗിക്കാം, [സ്വന്തമായി സജ്ജീകരിക്കുക](https://rustdesk.com/blog/id-relay-set/), അല്ലെങ്കിൽ [നിങ്ങളുടെ സ്വന്തം rendezvous/relay സെർവർ എഴുതുക](https://github.com/rustdesk/rustdesk-server-demo). -എല്ലാവരുടെയും സംഭാവനയെ RustDesk സ്വാഗതം ചെയ്യുന്നു. ആരംഭിക്കുന്നതിനുള്ള സഹായത്തിന് [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) കാണുക. +എല്ലാവരുടെയും സംഭാവനയെ RustDesk സ്വാഗതം ചെയ്യുന്നു. ആരംഭിക്കുന്നതിനുള്ള സഹായത്തിന് [`CONTRIBUTING.md`](CONTRIBUTING.md) കാണുക. [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) -## ഡിപെൻഡൻസികൾ +## സൗജന്യ പൊതു സെർവറുകൾ + +നിങ്ങൾ സൗജന്യമായി ഉപയോഗിക്കുന്ന സെർവറുകൾ ചുവടെയുണ്ട്, അത് സമയത്തിനനുസരിച്ച് മാറിയേക്കാം. നിങ്ങൾ ഇവയിലൊന്നിനോട് അടുത്തല്ലെങ്കിൽ, നിങ്ങളുടെ നെറ്റ്‌വർക്ക് സ്ലോ ആയേക്കാം. +| സ്ഥാനം | കച്ചവടക്കാരൻ | വിവരണം | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## ഡിപെൻഡൻസികൾ ഡെസ്‌ക്‌ടോപ്പ് പതിപ്പുകൾ GUI-യ്‌ക്കായി [sciter](https://sciter.com/) ഉപയോഗിക്കുന്നു, ദയവായി സ്‌സൈറ്റർ ഡൈനാമിക് ലൈബ്രറി സ്വയം ഡൗൺലോഡ് ചെയ്യുക. @@ -33,8 +42,8 @@ - [vcpkg](https://github.com/microsoft/vcpkg) ഇൻസ്റ്റാൾ ചെയ്ത് `VCPKG_ROOT` env വേരിയബിൾ ശരിയായി സജ്ജമാക്കുക - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus - run `cargo run` @@ -55,7 +64,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- ### ആർച് (മഞ്ചാരോ) ```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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### vcpkg ഇൻസ്റ്റാൾ ചെയ്യുക @@ -63,11 +72,11 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` ### libvpx പരിഹരിക്കുക (ഫെഡോറയ്ക്ക്) @@ -83,7 +92,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ cd ``` -### നിർമാണം +### നിർമാണം ```sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -96,6 +105,10 @@ mv libsciter-gtk.so target/debug VCPKG_ROOT=$HOME/vcpkg cargo run ``` +### വേലാൻഡ് X11 (Xorg) ആയി മാറ്റുക + +RustDesk Wayland-നെ പിന്തുണയ്ക്കുന്നില്ല. സ്ഥിരസ്ഥിതി ഗ്നോം സെഷനായി Xorg കോൺഫിഗർ ചെയ്യുന്നതിന് [ഇത്](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) പരിശോധിക്കുക. + ## ഡോക്കർ ഉപയോഗിച്ച് എങ്ങനെ നിർമ്മിക്കാം റെപ്പോസിറ്റോറി ക്ലോണുചെയ്‌ത് ഡോക്കർ കണ്ടെയ്‌നർ നിർമ്മിക്കുന്നതിലൂടെ ആരംഭിക്കുക: @@ -112,7 +125,7 @@ docker build -t "rustdesk-builder" . docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder ``` -ഡിപൻഡൻസികൾ കാഷെ ചെയ്യുന്നതിനുമുമ്പ് ആദ്യ ബിൽഡ് കൂടുതൽ സമയമെടുത്തേക്കാം, തുടർന്നുള്ള ബിൽഡുകൾ വേഗത്തിലാകും. കൂടാതെ, നിങ്ങൾക്ക് ബിൽഡ് കമാൻഡിലേക്ക് വ്യത്യസ്ത ആർഗ്യുമെന്റുകൾ വ്യക്തമാക്കണമെങ്കിൽ, കമാൻഡിന്റെ അവസാനം `` സ്ഥാനത്ത് നിങ്ങൾക്ക് അങ്ങനെ ചെയ്യാം. ഉദാഹരണത്തിന്, നിങ്ങൾ ഒരു ഒപ്റ്റിമൈസ് ചെയ്ത റിലീസ് പതിപ്പ് നിർമ്മിക്കാൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ, മുകളിലുള്ള കമാൻഡ് തുടർന്ന് `--release` നിങ്ങൾ പ്രവർത്തിപ്പിക്കും. തത്ഫലമായുണ്ടാകുന്ന എക്സിക്യൂട്ടബിൾ നിങ്ങളുടെ സിസ്റ്റത്തിലെ ടാർഗെറ്റ് ഫോൾഡറിൽ ലഭ്യമാകും, കൂടാതെ ഇത് ഉപയോഗിച്ച് പ്രവർത്തിപ്പിക്കാം: +ഡിപൻഡൻസികൾ കാഷെ ചെയ്യുന്നതിനുമുമ്പ് ആദ്യ ബിൽഡ് കൂടുതൽ സമയമെടുത്തേക്കാം, തുടർന്നുള്ള ബിൽഡുകൾ വേഗത്തിലാകും. കൂടാതെ, നിങ്ങൾക്ക് ബിൽഡ് കമാൻഡിലേക്ക് വ്യത്യസ്ത ആർഗ്യുമെന്റുകൾ വ്യക്തമാക്കണമെങ്കിൽ, കമാൻഡിന്റെ അവസാനം `` സ്ഥാനത്ത് നിങ്ങൾക്ക് അങ്ങനെ ചെയ്യാം. ഉദാഹരണത്തിന്, നിങ്ങൾ ഒരു ഒപ്റ്റിമൈസ് ചെയ്ത റിലീസ് പതിപ്പ് നിർമ്മിക്കാൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ, മുകളിലുള്ള കമാൻഡ് തുടർന്ന് `---release` നിങ്ങൾ പ്രവർത്തിപ്പിക്കും. തത്ഫലമായുണ്ടാകുന്ന എക്സിക്യൂട്ടബിൾ നിങ്ങളുടെ സിസ്റ്റത്തിലെ ടാർഗെറ്റ് ഫോൾഡറിൽ ലഭ്യമാകും, കൂടാതെ ഇത് ഉപയോഗിച്ച് പ്രവർത്തിപ്പിക്കാം: ```sh target/debug/rustdesk diff --git a/docs/README-NL.md b/README-NL.md similarity index 59% rename from docs/README-NL.md rename to README-NL.md index 45d68b20e..6d8b51e0b 100644 --- a/docs/README-NL.md +++ b/README-NL.md @@ -1,55 +1,52 @@

- RustDesk - Uw bureaublad op afstand
+ RustDesk - Jouw verbinding op afstand
ServersBouwenDockerStructuurSnapshot
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
- Wij hebben uw hulp nodig om dit README bestand te vertalen, RustDesk UI en Doc naar uw moedertaal + [中文] | [Español] | [Français] | [Deutsch] | [Nederlands] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Русский] | [Português]
+ We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal

-Chat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +Praat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Geavanceerde%20Functies-blue)](https://rustdesk.com/pricing.html) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Alweer een andere programma voor -bureaublad op afstand-, geschreven in Rust. Werkt -out of the box-, geen configuratie nodig. U heeft volledige controle over uw gegevens, en hoeft zich geen zorgen te maken over de beveiliging. U kunt onze rendez-vous/relay server gebruiken, [je eigen server opzetten](https://rustdesk.com/blog/id-relay-set), of [je eigen rendez-vous/relay-server schrijven](https://github.com/rustdesk/rustdesk-server-demo). +Nog weer een applicatie voor toegang op afstand, geschreven in Rust. Werkt meteen, geen configuratie nodig. Je hebt volledig beheer over je data, zonder na te hoeven denken over veiligheid. Je kunt onze rendez-vous/relay-server gebruiken, [je eigen server opzetten](https://rustdesk.com/blog/id-relay-set), of [je eigen rendez-vous/relay-server schrijven](https://github.com/rustdesk/rustdesk-server-demo). -RustDesk verwelkomt bijdragen van iedereen. Zie [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) voor hulp om aan de slag te gaan. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) +RustDesk verwelkomt bijdragen van iedereen. Zie [`CONTRIBUTING.md`](CONTRIBUTING.md) om te lezen hoe je van start kunt gaan. [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) -[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) (meest recente build) +## Gratis openbare servers -[Download het op F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) +Onderstaande servers zijn de servers die je gratis kunt gebruiken, ze kunnen op termijn veranderen. Als je niet fysiek dichtbij een van deze servers bent, kan je verbinding traag werken. +| Locatie | Aanbieder | Specificaties | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | -## Afhankelijkheden +## Afhankelijkheden -Desktop versies gebruiken [sciter](https://sciter.com/) of Flutter voor GUI, deze handleiding is alleen voor Sciter. - -Download zelf de dynamic library van Sciter. +Desktopversies gebruiken [sciter](https://sciter.com/) voor de grafische schil. Gelieve zelf de sciter-library te downloaden. [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) -## Ruwe stappen om te bouwen +## Handmatige bouwinstructies - Bereid je Rust-ontwikkelomgeving en C++-bouwomgeving voor. - Installeer [vcpkg](https://github.com/microsoft/vcpkg) en configureer de `VCPKG_ROOT` omgevingsvariabele op de juiste manier: - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus - Voer uit: `cargo run` -## [Bouwen](https://rustdesk.com/docs/en/dev/build/) - ## Bouwen op Linux ### Ubuntu 18 (Debian 10) @@ -58,12 +55,6 @@ Download zelf de dynamic library van Sciter. 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 ``` -### 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 @@ -73,7 +64,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- ### 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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### Installatie van vcpkg @@ -81,11 +72,11 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` ### Fix voor libvpx (voor Fedora) @@ -114,9 +105,13 @@ mv libsciter-gtk.so target/debug VCPKG_ROOT=$HOME/vcpkg cargo run ``` +### Wissel van Wayland naar X11 (Xorg) + +RustDesk ondersteunt Wayland niet. Lees [hier](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) hoe je Xorg als standaardsessie kunt instellen voor GNOME. + ## Bouwen met Docker -Begin met het klonen van de repository en het bouwen van de docker container: +Kloon eerst deze repository en bouw de Docker-container: ```sh git clone https://github.com/rustdesk/rustdesk @@ -124,15 +119,15 @@ cd rustdesk docker build -t "rustdesk-builder" . ``` -Elke keer dat u de toepassing moet bouwen, voert u het volgende commando uit: +Voer vervolgens de volgende commando's uit iedere keer dat je de applicatie opnieuw moet bouwen: ```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 ``` -Let op dat de eerste build langer kan duren omdat de dependencies nog niet zijn gecached; latere builds zullen sneller zijn. Als je extra command line arguments wilt toevoegen aan het build-commando, dan kun je dat doen aan het einde van de opdrachtregel in plaats van ``. Bijvoorbeeld: als je een geoptimaliseerde releaseversie wilt bouwen, draai dan het bovenstaande commando gevolgd door `--release`. +Let op dat de eerste build langer kan duren omdat de dependencies nog niet zijn gecached; latere builds zullen sneller zijn. Als je extra command line arguments wilt toevoegen aan het build-commando, dan kun je dat doen aan het einde van de opdrachtregel in plaats van ``. Bijvoorbeeld: als je een geoptimaliseerde releaseversie wilt bouwen, draai dan het bovenstaande commando gevolgd door `---release`. - Het uitvoerbare bestand, in debug-modus, zal verschijnen in de target-map, en kan als volgt worden uitgevoerd: +Het uitvoerbare bestand, in debug-modus, zal verschijnen in de target-map, en kan als volgt worden uitgevoerd: ```sh target/debug/rustdesk @@ -146,7 +141,7 @@ target/release/rustdesk Zorg ervoor dat je deze commando's van de root van de RustDesk-repository uitvoert, anders kan het programma de nodige afhankelijkheden mogelijk niet vinden. Let ook op dat andere cargo-subcommando's zoals `install` en `run` zijn momenteel niet ondersteund, aangezien deze zouden worden uitgevoerd in een container in plaats van op de host. -## Bestandsstructuur +## Bestandsstructuur - **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: videocodec, configuratie, TCP/UDP-wrapper, protobuf, bestandssysteemfuncties voor bestandsoverdracht en nog wat andere nuttige functies - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: schermopname diff --git a/docs/README-PL.md b/README-PL.md similarity index 55% rename from docs/README-PL.md rename to README-PL.md index 437682a9c..ed937da7b 100644 --- a/docs/README-PL.md +++ b/README-PL.md @@ -1,52 +1,49 @@

- RustDesk - Twój zdalny pulpit
+ RustDesk - Your remote desktop
SerweryKompilacjaDockerStrukturaSnapshot
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
+ [English] | [中文] | [Deutsch] | [Española] | [Français] | [Nederlands] | [日本語] | [Русский] | [Português]
Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język

-Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Zaawansowane%20Funkcje-blue)](https://rustdesk.com/pricing.html) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -## O projekcie +Kolejny program do zdalnego pulpitu, napisany w Rust. Działa od samego początku, nie wymaga konfiguracji. Masz pełną kontrolę nad swoimi danymi, bez obaw o bezpieczeństwo. Możesz skorzystać z naszego darmowego serwera publicznego , [skonfigurować własny](https://rustdesk.com/blog/id-relay-set/), lub [napisać własny serwer rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo). -RustDesk to wieloplatformowe oprogramowanie do zdalnego pulpitu, napisane w języku Rust, zaprojektowane z myślą o prostocie wdrożenia, bezpieczeństwie i pełnej kontroli użytkownika nad danymi. Aplikacja działa od razu po uruchomieniu i nie wymaga skomplikowanej konfiguracji. Możesz skorzystać z naszego darmowego serwera publicznego, [skonfigurować własny](https://rustdesk.com/server), lub [napisać własny serwer](https://github.com/rustdesk/rustdesk-server-demo). +RustDesk zaprasza do współpracy każdego. Zobacz [`CONTRIBUTING.md`](CONTRIBUTING.md) pomoc w uruchomieniu programu. -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) +[**POBIERZ KOMPILACJE**](https://github.com/rustdesk/rustdesk/releases) -RustDesk zaprasza do współpracy każdego. Zobacz [`docs/CONTRIBUTING-PL.md`](CONTRIBUTING-PL.md) pomoc w uruchomieniu programu. +## Darmowe Serwery Publiczne -[**PYTANIA I ODPOWIEDZI (FAQ)**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**POBIERANIE BINARIÓW**](https://github.com/rustdesk/rustdesk/releases) - -[**WERSJE TESTOWE (NIGHTLY)**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) +Poniżej znajdują się serwery, z których można korzystać za darmo, może się to zmienić z upływem czasu. Jeśli nie znajdujesz się w pobliżu jednego z nich, Twoja prędkość połączenia może być niska. +| Lokalizacja | Dostawca | Specyfikacja | +| --------- | ------------- | ------------------ | +| Seul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapur | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | ## Zależności -Wersje desktopowe korzystają z biblioteki [sciter](https://sciter.com/) jako silnika GUI. Bibliotekę Sciter należy pobrać i zainstalować samodzielnie. +Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobrać bibliotekę dynamiczną sciter samodzielnie. [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) -## Podstawowe kroki do kompilacji +## Podstawowe kroki do kompilacji. - Przygotuj środowisko programistyczne Rust i środowisko programowania C++ -- Zainstaluj [vcpkg](https://github.com/microsoft/vcpkg), i ustaw prawidłowo zmienną `VCPKG_ROOT` +- Zainstaluj [vcpkg](https://github.com/microsoft/vcpkg), i ustaw `VCPKG_ROOT` env zmienną prawidłowo - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus - uruchom `cargo run` @@ -58,12 +55,6 @@ Wersje desktopowe korzystają z biblioteki [sciter](https://sciter.com/) jako si 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 ``` -### 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 @@ -73,7 +64,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- ### 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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### Zainstaluj vcpkg @@ -81,14 +72,14 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` -### Popraw libvpx (Dla Fedora) +### Fix libvpx (For Fedora) ```sh cd vcpkg/buildtrees/libvpx/src @@ -114,6 +105,10 @@ mv libsciter-gtk.so target/debug cargo run ``` +### Zmień Wayland na X11 (Xorg) + +RustDesk nie obsługuje Waylanda. Sprawdź [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) by skonfigurować Xorg jako domyślną sesję GNOME. + ## Jak kompilować za pomocą Dockera Rozpocznij od sklonowania repozytorium i stworzenia kontenera docker: @@ -130,13 +125,13 @@ Następnie, za każdym razem, gdy potrzebujesz skompilować aplikację, uruchom 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 ``` -Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zostaną zbuforowane, kolejne będą szybsze. Dodatkowo, jeśli potrzebujesz określić inne argumenty dla polecenia budowania, możesz to zrobić na końcu komendy w miejscu ``. Na przykład, jeśli chciałbyś zbudować zoptymalizowaną wersję wydania, uruchomiłbyś powyższą komendę a następnie `--release`. Powstały plik wykonywalny będzie dostępny w folderze docelowym w twoim systemie i może być uruchomiony z: +Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zostaną zbuforowane, kolejne będą szybsze. Dodatkowo, jeśli potrzebujesz określić inne argumenty dla polecenia budowania, możesz to zrobić na końcu komendy w miejscu ``. Na przykład, jeśli chciałbyś zbudować zoptymalizowaną wersję wydania, uruchomiłbyś powyższą komendę a następnie `---release`. Powstały plik wykonywalny będzie dostępny w folderze docelowym w twoim systemie, i może być uruchomiony z: ```sh target/debug/rustdesk ``` -Lub jeśli uruchamiasz plik wykonywalny wersji: +Lub, jeśli uruchamiasz plik wykonywalny wersji: ```sh target/release/rustdesk @@ -146,18 +141,16 @@ Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium Ru ## Struktura plików -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: kodek wideo, konfiguracja, obsługa tcp/udp, protobuf, funkcje systemu plików do transferu plików i kilka innych funkcji użytkowych +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: kodek wideo, config, wrapper tcp/udp, protobuf, funkcje fs do transferu plików i kilka innych funkcji użytkowych - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: przechwytywanie ekranu - **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: specyficzne dla danej platformy sterowanie klawiaturą/myszą - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI - **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/schowek/wejście(input)/wideo oraz połączenia sieciowe -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: uruchamia połączenie bezpośrednie -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Komunikacja z [rustdesk-server](https://github.com/rustdesk/rustdesk-server), czekanie na bezpośrednie (odpytywanie TCP) lub przekazywane połączenie -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: kod specyficzny dla danej platformy -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: kod Flutter dla urządzeń mobilnych -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript dla Flutter - klient web +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: uruchamia połączenie peer +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Komunikacja z [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)**: specyficzny dla danej platformy kod -## Zrzuty ekranu +## Migawki(Snapshoty) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) @@ -166,4 +159,3 @@ Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium Ru ![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/README-ZH.md b/README-ZH.md new file mode 100644 index 000000000..8b51c6357 --- /dev/null +++ b/README-ZH.md @@ -0,0 +1,213 @@ +

+ RustDesk - Your remote desktop
+ 服务器 • + 编译 • + Docker • + 结构 • + 截图
+ [English] | [Español] | [Français] | [Deutsch] | [Nederlands] | [Polski] | [日本語] | [Русский] | [Português]
+

+ +Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +远程桌面软件,开箱即用,无需任何配置。您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器, +或者[自己设置](https://rustdesk.com/blog/id-relay-set/), +亦或者[开发您的版本](https://github.com/rustdesk/rustdesk-server-demo)。 + +欢迎大家贡献代码, 请看 [`CONTRIBUTING.md`](CONTRIBUTING.md). + +[**可执行程序下载**](https://github.com/rustdesk/rustdesk/releases) + +## 免费公共服务器 + +以下是您免费使用的服务器,它可能会随着时间的推移而变化。如果您不靠近其中之一,您的网络可能会很慢。 + +- 首尔, AWS lightsail, 1 VCPU/0.5G RAM +- 新加坡, Vultr, 1 VCPU/1G RAM +- 达拉斯, Vultr, 1 VCPU/1G RAM + +## 依赖 + +桌面版本界面使用[sciter](https://sciter.com/), 请自行下载。 + +[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) + +## 基本构建步骤 + +- 请准备好 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/Osx: vcpkg install libvpx libyuv opus + +- 运行 `cargo run` + +## 在 Linux 上编译 + +### 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 +``` + +### 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 pulseaudio +``` + +### 安装 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 +``` + +### 构建 + +```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 +cargo run +``` + +### 把 Wayland 修改成 X11 (Xorg) + +RustDesk 暂时不支持 Wayland,不过正在积极开发中. +请查看[this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/)配置 X11. + +## 使用 Docker 编译 + +首先克隆存储库并构建 docker 容器: + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +针对国内网络访问问题,可以做以下几点优化: + +1. Dockerfile 中修改系统的源到国内镜像 + + ``` + 在Dockerfile的RUN apt update之前插入两行: + + RUN sed -i "s/deb.debian.org/mirrors.163.com/g" /etc/apt/sources.list + RUN sed -i "s/security.debian.org/mirrors.163.com/g" /etc/apt/sources.list + ``` + +2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码: + + ``` + RUN echo '[source.crates-io]' > ~/.cargo/config \ + && echo 'registry = "https://github.com/rust-lang/crates.io-index"' >> ~/.cargo/config \ + && echo '# 替换成你偏好的镜像源' >> ~/.cargo/config \ + && echo "replace-with = 'sjtu'" >> ~/.cargo/config \ + && echo '# 上海交通大学' >> ~/.cargo/config \ + && echo '[source.sjtu]' >> ~/.cargo/config \ + && echo 'registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"' >> ~/.cargo/config \ + && echo '' >> ~/.cargo/config + ``` + +3. Dockerfile 中加入代理的 env + + ``` + 在User root后插入两行 + + ENV http_proxy=http://host:port + ENV https_proxy=http://host:port + ``` + +4. docker build 命令后面加上 proxy 参数 + ``` + docker build -t "rustdesk-builder" . --build-arg http_proxy=http://host:port --build-arg https_proxy=http://host:port + ``` + +然后,每次需要构建应用程序时,运行以下命令: + +```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 +``` + +运行若遇到无权限问题,出现以下提示: + +``` +usermod: user user is currently used by process 1 +groupmod: Permission denied. +groupmod: cannot lock /etc/group; try again later. +``` + +可以尝试把`-e PUID="$(id -u)" -e PGID="$(id -g)"`参数去掉。(出现这一问题的原因是容器中的 entrypoint 脚本中判定 uid 和 gid 与给定的环境变量不一致时会修改 user 的 uid 和 gid 重新运行,但是重新运行时取不到环境变量中的 uid 和 gid 了,会再次进入 uid 与 gid 与给定值不一致的逻辑分支) + +请注意,第一次构建可能需要比较长的时间,因为需要缓存依赖项(国内网络经常出现拉取失败,可多尝试几次),后续构建会更快。此外,如果您需要为构建命令指定不同的参数, +您可以在命令末尾的 `` 位置执行此操作。例如,如果你想构建一个优化的发布版本,你可以在命令后跟 `---release`。 +将在 target 下产生可执行程序,请通过以下方式运行调试版本: + +```sh +target/debug/rustdesk +``` + +或者运行发布版本: + +```sh +target/release/rustdesk +``` + +请确保您从 RustDesk 存储库的根目录运行这些命令,否则应用程序可能无法找到所需的资源。另请注意,此方法当前不支持其他`Cargo`子命令, +例如 `install` 或 `run`,因为运行在容器里,而不是宿主机上。 + +## 文件结构 + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数 +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 截屏 +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入 +- **[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 服务, 已经连接实现 +- **[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)保持 UDP 通讯, 等待远程连接(通过打洞直连或者中继) +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码 + +## 截图 + +![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/README.md b/README.md index ae5c8d37c..7d6713ddc 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,70 @@

- RustDesk - Your remote desktop
+ RustDesk - Your remote desktop
+ ServersBuildDockerStructureSnapshot
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe] | [Norsk] | [Română]
- We need your help to translate this README, RustDesk UI and RustDesk Doc to your native language + [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Русский] | [Português]
+ We need your help to translate this README and RustDesk UI to your native language

-> [!Caution] -> **Misuse Disclaimer:**
-> The developers of RustDesk do not condone or support any unethical or illegal use of this software. Misuse, such as unauthorized access, control or invasion of privacy, is strictly against our guidelines. The authors are not responsible for any misuse of the application. +Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) +Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/blog/id-relay-set/), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo). -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Advanced%20Features-blue)](https://rustdesk.com/pricing.html) - -Yet another remote desktop solution, written in Rust. Works out of the box with no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/server), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk welcomes contribution from everyone. See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for help getting started. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) +RustDesk welcomes contribution from everyone. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for help getting started. [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) -[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) +## Free Public Servers -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) +Below are the servers you are using for free, it may change along the time. If you are not close to one of these, your network may be slow. +| Location | Vendor | Specification | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | ## Dependencies -Desktop versions use Flutter or Sciter (deprecated) for GUI, this tutorial is for Sciter only, since it is easier and more friendly to start. Check out our [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for building Flutter version. - -Please download Sciter dynamic library yourself. +Desktop versions use [sciter](https://sciter.com/) for GUI, please download sciter dynamic library yourself. [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) +[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) -## Raw Steps to build +## Raw steps to build - Prepare your Rust development env and C++ build env - Install [vcpkg](https://github.com/microsoft/vcpkg), and set `VCPKG_ROOT` env variable correctly - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus - run `cargo run` -## [Build](https://rustdesk.com/docs/en/dev/build/) - -## How to Build on Linux +## How to build on Linux ### Ubuntu 18 (Debian 10) ```sh -sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ - libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel +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 ``` ### Fedora 28 (CentOS 8) ```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel +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 +sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` ### Install vcpkg @@ -92,11 +72,11 @@ sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-c ```sh git clone https://github.com/microsoft/vcpkg cd vcpkg -git checkout 2023.04.15 +git checkout 2021.12.01 cd .. vcpkg/bootstrap-vcpkg.sh export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom +vcpkg/vcpkg install libvpx libyuv opus ``` ### Fix libvpx (For Fedora) @@ -117,7 +97,7 @@ cd ```sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env -git clone --recurse-submodules https://github.com/rustdesk/rustdesk +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 @@ -125,14 +105,17 @@ mv libsciter-gtk.so target/debug VCPKG_ROOT=$HOME/vcpkg cargo run ``` +### Change Wayland to X11 (Xorg) + +RustDesk does not support Wayland. Check [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) to configuring Xorg as the default GNOME session. + ## How to build with Docker -Begin by cloning the repository and building the Docker container: +Begin by cloning the repository and building the docker container: ```sh git clone https://github.com/rustdesk/rustdesk cd rustdesk -git submodule update --init --recursive docker build -t "rustdesk-builder" . ``` @@ -142,7 +125,7 @@ Then, each time you need to build the application, run the following command: 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 ``` -Note that the first build may take longer before dependencies are cached, subsequent builds will be faster. Additionally, if you need to specify different arguments to the build command, you may do so at the end of the command in the `` position. For instance, if you wanted to build an optimized release version, you would run the command above followed by `--release`. The resulting executable will be available in the target folder on your system, and can be run with: +Note that the first build may take longer before dependencies are cached, subsequent builds will be faster. Additionally, if you need to specify different arguments to the build command, you may do so at the end of the command in the `` position. For instance, if you wanted to build an optimized release version, you would run the command above followed by `---release`. The resulting executable will be available in the target folder on your system, and can be run with: ```sh target/debug/rustdesk @@ -154,29 +137,25 @@ Or, if you're running a release executable: target/release/rustdesk ``` -Please ensure that you run these commands from the root of the RustDesk repository, or the application may not find the required resources. Also note that other cargo subcommands such as `install` or `run` are not currently supported via this method as they would install or run the program inside the container instead of the host. +Please ensure that you are running these commands from the root of the RustDesk repository, otherwise the application may be unable to find the required resources. Also note that other cargo subcommands such as `install` or `run` are not currently supported via this method as they would install or run the program inside the container instead of the host. ## File Structure - **[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 -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: file copy and paste implementation for Windows, Linux, macOS. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: obsolete Sciter UI (deprecated) +- **[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 desktop and mobile -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: JavaScript for Flutter web client -## Screenshots +## Snapshot -![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) +![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) -![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) +![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) -![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) +![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/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f1114f913 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| --------- | ------------------ | +| 1.1.x | :white_check_mark: | +| 1.x | :white_check_mark: | +| Below 1.0 | :x: | + +## Reporting a Vulnerability + +Here we should write what to do in case of a security vulnerability diff --git a/appimage/AppImageBuilder-aarch64.yml b/appimage/AppImageBuilder-aarch64.yml deleted file mode 100644 index 64d6c2cfa..000000000 --- a/appimage/AppImageBuilder-aarch64.yml +++ /dev/null @@ -1,102 +0,0 @@ -# appimage-builder recipe see https://appimage-builder.readthedocs.io for details -version: 1 -script: - - rm -rf ./AppDir || true - - bsdtar -zxvf rustdesk.deb - - tar -xvf ./data.tar.xz - - mkdir ./AppDir - - mv ./usr ./AppDir/usr - # 32x32 icon - - for i in {32,64,128}; do mkdir -p ./AppDir/usr/share/icons/hicolor/$i\x$i/apps/; cp ../res/$i\x$i.png ./AppDir/usr/share/icons/hicolor/$i\x$i/apps/rustdesk.png; done - - mkdir -p ./AppDir/usr/share/icons/hicolor/scalable/apps/; cp ../res/scalable.svg ./AppDir/usr/share/icons/hicolor/scalable/apps/rustdesk.svg - # desktop file - # - sed -i "s/Icon=\/usr\/share\/rustdesk\/files\/rustdesk.png/Icon=rustdesk/g" ./AppDir/usr/share/applications/rustdesk.desktop - - rm -rf ./AppDir/usr/share/applications -AppDir: - path: ./AppDir - app_info: - id: rustdesk - name: rustdesk - icon: rustdesk - version: 1.4.6 - exec: usr/share/rustdesk/rustdesk - exec_args: $@ - apt: - arch: - - arm64 - allow_unauthenticated: true - sources: - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main restricted universe multiverse - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-updates main restricted universe multiverse - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-backports main restricted - universe multiverse - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal-security main restricted - universe multiverse - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - include: - - libc6:arm64 - - libgtk-3-0 - - libxcb-randr0 - - libxdo3 - - libxfixes3 - - libxcb-shape0 - - libxcb-xfixes0 - - libasound2 - - libsystemd0 - - curl - - libva2 - - libva-drm2 - - libva-x11-2 - - libgstreamer-plugins-base1.0-0 - - gstreamer1.0-pipewire - - libwayland-client0 - - libwayland-cursor0 - - libwayland-egl1 - - libpulse0 - - packagekit-gtk3-module - - libcanberra-gtk3-module - - libpam0g - - libdrm2 - exclude: - - humanity-icon-theme - - hicolor-icon-theme - - adwaita-icon-theme - - ubuntu-mono - files: - include: [] - exclude: - - usr/share/man - - usr/share/doc/*/README.* - - usr/share/doc/*/changelog.* - - usr/share/doc/*/NEWS.* - - usr/share/doc/*/TODO.* - runtime: - env: - GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules - GDK_BACKEND: x11 - APPDIR_LIBRARY_PATH: /lib64:/usr/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/aarch64-linux-gnu:$APPDIR/usr/lib/aarch64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/aarch64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/aarch64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/aarch64-linux-gnu/pulseaudio:$APPDIR/usr/lib/aarch64-linux-gnu/sasl2:$APPDIR/usr/lib/aarch64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/aarch64 - GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0 - GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0 - test: - fedora-30: - image: appimagecrafters/tests-env:fedora-30 - command: ./AppRun - debian-stable: - image: appimagecrafters/tests-env:debian-stable - command: ./AppRun - archlinux-latest: - image: appimagecrafters/tests-env:archlinux-latest - command: ./AppRun - centos-7: - image: appimagecrafters/tests-env:centos-7 - command: ./AppRun - ubuntu-xenial: - image: appimagecrafters/tests-env:ubuntu-xenial - command: ./AppRun -AppImage: - arch: aarch64 - update-information: guess - comp: gzip diff --git a/appimage/AppImageBuilder-x86_64.yml b/appimage/AppImageBuilder-x86_64.yml deleted file mode 100644 index 933673cef..000000000 --- a/appimage/AppImageBuilder-x86_64.yml +++ /dev/null @@ -1,105 +0,0 @@ -# appimage-builder recipe see https://appimage-builder.readthedocs.io for details -version: 1 -script: - - rm -rf ./AppDir || true - - bsdtar -zxvf rustdesk.deb - - tar -xvf ./data.tar.xz - - mkdir ./AppDir - - mv ./usr ./AppDir/usr - # 32x32 icon - - for i in {32,64,128}; do mkdir -p ./AppDir/usr/share/icons/hicolor/$i\x$i/apps/; cp ../res/$i\x$i.png ./AppDir/usr/share/icons/hicolor/$i\x$i/apps/rustdesk.png; done - - mkdir -p ./AppDir/usr/share/icons/hicolor/scalable/apps/; cp ../res/scalable.svg ./AppDir/usr/share/icons/hicolor/scalable/apps/rustdesk.svg - # desktop file - # - sed -i "s/Icon=\/usr\/share\/rustdesk\/files\/rustdesk.png/Icon=rustdesk/g" ./AppDir/usr/share/applications/rustdesk.desktop - - rm -rf ./AppDir/usr/share/applications -AppDir: - path: ./AppDir - app_info: - id: rustdesk - name: rustdesk - icon: rustdesk - version: 1.4.6 - exec: usr/share/rustdesk/rustdesk - exec_args: $@ - apt: - arch: - - amd64 - allow_unauthenticated: true - sources: - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal main restricted - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal universe - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates universe - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted - universe multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted - universe multiverse - include: - # https://github.com/rustdesk/rustdesk/issues/9103 - # Because of APPDIR_LIBRARY_PATH, this libc6 is not used, use LD_PRELOAD: $APPDIR/usr/lib/x86_64-linux-gnu/libc.so.6 may help, If you have time, please have a try. - # We modify APPDIR_LIBRARY_PATH to use system lib first because gst crashed if not doing so, but you can try to change it. - - libc6:amd64 - - libgtk-3-0 - - libxcb-randr0 - - libxdo3 - - libxfixes3 - - libxcb-shape0 - - libxcb-xfixes0 - - libasound2 - - libsystemd0 - - curl - - libva2 - - libva-drm2 - - libva-x11-2 - - libgstreamer-plugins-base1.0-0 - - gstreamer1.0-pipewire - - libwayland-client0 - - libwayland-cursor0 - - libwayland-egl1 - - libpulse0 - - packagekit-gtk3-module - - libcanberra-gtk3-module - - libpam0g - - libdrm2 - exclude: - - humanity-icon-theme - - hicolor-icon-theme - - adwaita-icon-theme - - ubuntu-mono - files: - include: [] - exclude: - - usr/share/man - - usr/share/doc/*/README.* - - usr/share/doc/*/changelog.* - - usr/share/doc/*/NEWS.* - - usr/share/doc/*/TODO.* - runtime: - env: - GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/x86_64-linux-gnu/gio/modules:$APPDIR/usr/lib/x86_64-linux-gnu/gio/modules - GDK_BACKEND: x11 - APPDIR_LIBRARY_PATH: /lib64:/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/x86_64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/x86_64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/x86_64-linux-gnu/pulseaudio:$APPDIR/usr/lib/x86_64-linux-gnu/sasl2:$APPDIR/usr/lib/x86_64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/x86_64 - GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0 - GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0 - test: - fedora-30: - image: appimagecrafters/tests-env:fedora-30 - command: ./AppRun - debian-stable: - image: appimagecrafters/tests-env:debian-stable - command: ./AppRun - archlinux-latest: - image: appimagecrafters/tests-env:archlinux-latest - command: ./AppRun - centos-7: - image: appimagecrafters/tests-env:centos-7 - command: ./AppRun - ubuntu-xenial: - image: appimagecrafters/tests-env:ubuntu-xenial - command: ./AppRun -AppImage: - arch: x86_64 - update-information: guess - comp: gzip diff --git a/build.py b/build.py deleted file mode 100755 index 5c53e4fc8..000000000 --- a/build.py +++ /dev/null @@ -1,647 +0,0 @@ -#!/usr/bin/env python3 - -import os -import pathlib -import platform -import zipfile -import urllib.request -import shutil -import hashlib -import argparse -import sys -from pathlib import Path - -windows = platform.platform().startswith('Windows') -osx = platform.platform().startswith( - 'Darwin') or platform.platform().startswith("macOS") -hbb_name = 'rustdesk' + ('.exe' if windows else '') -exe_path = 'target/release/' + hbb_name -if windows: - flutter_build_dir = 'build/windows/x64/runner/Release/' -elif osx: - flutter_build_dir = 'build/macos/Build/Products/Release/' -else: - flutter_build_dir = 'build/linux/x64/release/bundle/' -flutter_build_dir_2 = f'flutter/{flutter_build_dir}' -skip_cargo = False - - -def get_deb_arch() -> str: - custom_arch = os.environ.get("DEB_ARCH") - if custom_arch is None: - return "amd64" - return custom_arch - -def get_deb_extra_depends() -> str: - custom_arch = os.environ.get("DEB_ARCH") - if custom_arch == "armhf": # for arm32v7 libsciter-gtk.so - return ", libatomic1" - return "" - -def system2(cmd): - exit_code = os.system(cmd) - if exit_code != 0: - sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n") - sys.exit(-1) - - -def get_version(): - with open("Cargo.toml", encoding="utf-8") as fh: - for line in fh: - if line.startswith("version"): - return line.replace("version", "").replace("=", "").replace('"', '').strip() - return '' - - -def parse_rc_features(feature): - available_features = {} - apply_features = {} - if not feature: - feature = [] - - def platform_check(platforms): - if windows: - return 'windows' in platforms - elif osx: - return 'osx' in platforms - else: - return 'linux' in platforms - - def get_all_features(): - features = [] - for (feat, feat_info) in available_features.items(): - if platform_check(feat_info['platform']): - features.append(feat) - return features - - if isinstance(feature, str) and feature.upper() == 'ALL': - return get_all_features() - elif isinstance(feature, list): - if windows: - # download third party is deprecated, we use github ci instead. - # feature.append('PrivacyMode') - pass - for feat in feature: - if isinstance(feat, str) and feat.upper() == 'ALL': - return get_all_features() - if feat in available_features: - if platform_check(available_features[feat]['platform']): - apply_features[feat] = available_features[feat] - else: - print(f'Unrecognized feature {feat}') - return apply_features - else: - raise Exception(f'Unsupported features param {feature}') - - -def make_parser(): - parser = argparse.ArgumentParser(description='Build script.') - parser.add_argument( - '-f', - '--feature', - dest='feature', - metavar='N', - type=str, - nargs='+', - default='', - help='Integrate features, windows only.' - 'Available: [Not used for now]. Special value is "ALL" and empty "". Default is empty.') - parser.add_argument('--flutter', action='store_true', - help='Build flutter package', default=False) - parser.add_argument( - '--hwcodec', - action='store_true', - help='Enable feature hwcodec' + ( - '' if windows or osx else ', need libva-dev.') - ) - parser.add_argument( - '--vram', - action='store_true', - help='Enable feature vram, only available on windows now.' - ) - parser.add_argument( - '--portable', - action='store_true', - help='Build windows portable' - ) - parser.add_argument( - '--unix-file-copy-paste', - action='store_true', - help='Build with unix file copy paste feature' - ) - parser.add_argument( - '--skip-cargo', - action='store_true', - help='Skip cargo build process, only flutter version + Linux supported currently' - ) - if windows: - parser.add_argument( - '--skip-portable-pack', - action='store_true', - help='Skip packing, only flutter version + Windows supported' - ) - parser.add_argument( - "--package", - type=str - ) - if osx: - parser.add_argument( - '--screencapturekit', - action='store_true', - help='Enable feature screencapturekit' - ) - return parser - - -# Generate build script for docker -# -# it assumes all build dependencies are installed in environments -# Note: do not use it in bare metal, or may break build environments -def generate_build_script_for_docker(): - with open("/tmp/build.sh", "w") as f: - f.write(''' - #!/bin/bash - # environment - export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation: " | cut -d' ' -f4-)/include" - # flutter - pushd /opt - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.0.5-stable.tar.xz - tar -xvf flutter_linux_3.0.5-stable.tar.xz - export PATH=`pwd`/flutter/bin:$PATH - popd - # flutter_rust_bridge - dart pub global activate ffigen --version 5.0.1 - pushd /tmp && git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1 && popd - pushd /tmp/flutter_rust_bridge/frb_codegen && cargo install --path . && popd - pushd flutter && flutter pub get && popd - ~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart - # install vcpkg - pushd /opt - export VCPKG_ROOT=`pwd`/vcpkg - git clone https://github.com/microsoft/vcpkg - vcpkg/bootstrap-vcpkg.sh - popd - $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed" - # build rustdesk - ./build.py --flutter --hwcodec - ''') - system2("chmod +x /tmp/build.sh") - system2("bash /tmp/build.sh") - - -# Downloading third party resources is deprecated. -# We can use this function in an offline build environment. -# Even in an online environment, we recommend building third-party resources yourself. -def download_extract_features(features, res_dir): - import re - - proxy = '' - - def req(url): - if not proxy: - return url - else: - r = urllib.request.Request(url) - r.set_proxy(proxy, 'http') - r.set_proxy(proxy, 'https') - return r - - for (feat, feat_info) in features.items(): - includes = feat_info['include'] if 'include' in feat_info and feat_info['include'] else [] - includes = [re.compile(p) for p in includes] - excludes = feat_info['exclude'] if 'exclude' in feat_info and feat_info['exclude'] else [] - excludes = [re.compile(p) for p in excludes] - - print(f'{feat} download begin') - download_filename = feat_info['zip_url'].split('/')[-1] - checksum_md5_response = urllib.request.urlopen( - req(feat_info['checksum_url'])) - for line in checksum_md5_response.read().decode('utf-8').splitlines(): - if line.split()[1] == download_filename: - checksum_md5 = line.split()[0] - filename, _headers = urllib.request.urlretrieve(feat_info['zip_url'], - download_filename) - md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest() - if checksum_md5 != md5: - raise Exception(f'{feat} download failed') - print(f'{feat} download end. extract bein') - zip_file = zipfile.ZipFile(filename) - zip_list = zip_file.namelist() - for f in zip_list: - file_exclude = False - for p in excludes: - if p.match(f) is not None: - file_exclude = True - break - if file_exclude: - continue - - file_include = False if includes else True - for p in includes: - if p.match(f) is not None: - file_include = True - break - if file_include: - print(f'extract file {f}') - zip_file.extract(f, res_dir) - zip_file.close() - os.remove(download_filename) - print(f'{feat} extract end') - - -def external_resources(flutter, args, res_dir): - features = parse_rc_features(args.feature) - if not features: - return - - print(f'Build with features {list(features.keys())}') - if os.path.isdir(res_dir) and not os.path.islink(res_dir): - shutil.rmtree(res_dir) - elif os.path.exists(res_dir): - raise Exception(f'Find file {res_dir}, not a directory') - os.makedirs(res_dir, exist_ok=True) - download_extract_features(features, res_dir) - if flutter: - os.makedirs(flutter_build_dir_2, exist_ok=True) - for f in pathlib.Path(res_dir).iterdir(): - print(f'{f}') - if f.is_file(): - shutil.copy2(f, flutter_build_dir_2) - else: - shutil.copytree(f, f'{flutter_build_dir_2}{f.stem}') - - -def get_features(args): - features = ['inline'] if not args.flutter else [] - if args.hwcodec: - features.append('hwcodec') - if args.vram: - features.append('vram') - if args.flutter: - features.append('flutter') - if args.unix_file_copy_paste: - features.append('unix-file-copy-paste') - if osx: - if args.screencapturekit: - features.append('screencapturekit') - print("features:", features) - return features - - -def generate_control_file(version): - control_file_path = "../res/DEBIAN/control" - system2('/bin/rm -rf %s' % control_file_path) - - content = """Package: rustdesk -Section: net -Priority: optional -Version: %s -Architecture: %s -Maintainer: rustdesk -Homepage: https://rustdesk.com -Depends: libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s -Recommends: libayatana-appindicator3-1 -Description: A remote control software. - -""" % (version, get_deb_arch(), get_deb_extra_depends()) - file = open(control_file_path, "w") - file.write(content) - file.close() - - -def ffi_bindgen_function_refactor(): - # workaround ffigen - system2( - 'sed -i "s/ffi.NativeFunction> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") - - system2('mkdir -p tmpdeb/DEBIAN') - generate_control_file(version) - system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') - md5_file_folder("tmpdeb/") - system2('dpkg-deb -b tmpdeb rustdesk.deb;') - - system2('/bin/rm -rf tmpdeb/') - system2('/bin/rm -rf ../res/DEBIAN/control') - os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) - os.chdir("..") - - -def build_deb_from_folder(version, binary_folder): - os.chdir('flutter') - system2('mkdir -p tmpdeb/usr/bin/') - system2('mkdir -p tmpdeb/usr/share/rustdesk') - system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') - system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/') - system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/') - system2('mkdir -p tmpdeb/usr/share/applications/') - system2('mkdir -p tmpdeb/usr/share/polkit-1/actions') - system2('rm tmpdeb/usr/bin/rustdesk || true') - system2( - f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/') - system2( - 'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') - system2( - 'cp ../res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png') - system2( - 'cp ../res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg') - system2( - 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') - system2( - 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - system2( - "echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") - - system2('mkdir -p tmpdeb/DEBIAN') - generate_control_file(version) - system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') - md5_file_folder("tmpdeb/") - system2('dpkg-deb -b tmpdeb rustdesk.deb;') - - system2('/bin/rm -rf tmpdeb/') - system2('/bin/rm -rf ../res/DEBIAN/control') - os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) - os.chdir("..") - - -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 - system2( - f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --release') - # copy dylib - system2( - "cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib") - os.chdir('flutter') - system2('flutter build macos --release') - system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/') - ''' - 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: - system2(f'cargo build --features {features} --lib --release') - ffi_bindgen_function_refactor() - os.chdir('flutter') - system2('flutter build linux --release') - system2(f'strip {flutter_build_dir}/lib/librustdesk.so') - os.chdir('../res') - system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f') - - -def build_flutter_windows(version, features, skip_portable_pack): - if not skip_cargo: - 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') - system2('flutter build windows --release') - os.chdir('..') - shutil.copy2('target/release/deps/dylib_virtual_display.dll', - flutter_build_dir_2) - if skip_portable_pack: - return - os.chdir('libs/portable') - system2('pip3 install -r requirements.txt') - system2( - f'python3 ./generate.py -f ../../{flutter_build_dir_2} -o . -e ../../{flutter_build_dir_2}/rustdesk.exe') - os.chdir('../..') - if os.path.exists('./rustdesk_portable.exe'): - os.replace('./target/release/rustdesk-portable-packer.exe', - './rustdesk_portable.exe') - else: - os.rename('./target/release/rustdesk-portable-packer.exe', - './rustdesk_portable.exe') - print( - f'output location: {os.path.abspath(os.curdir)}/rustdesk_portable.exe') - os.rename('./rustdesk_portable.exe', f'./rustdesk-{version}-install.exe') - print( - f'output location: {os.path.abspath(os.curdir)}/rustdesk-{version}-install.exe') - - -def main(): - global skip_cargo - parser = make_parser() - args = parser.parse_args() - - if os.path.exists(exe_path): - os.unlink(exe_path) - if os.path.isfile('/usr/bin/pacman'): - system2('git checkout src/ui/common.tis') - version = get_version() - features = ','.join(get_features(args)) - flutter = args.flutter - if not flutter: - system2('python3 res/inline-sciter.py') - print(args.skip_cargo) - if args.skip_cargo: - skip_cargo = True - portable = args.portable - package = args.package - if package: - build_deb_from_folder(version, package) - return - res_dir = 'resources' - external_resources(flutter, args, res_dir) - if windows: - # build virtual display dynamic library - os.chdir('libs/virtual_display/dylib') - system2('cargo build --release') - os.chdir('../../..') - - if flutter: - build_flutter_windows(version, features, args.skip_portable_pack) - return - 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: - # https://certera.com/kb/tutorial-guide-for-safenet-authentication-client-for-code-signing/ - 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') - system2( - f'cp -rf target/release/RustDesk.exe {res_dir}') - os.chdir('libs/portable') - system2('pip3 install -r requirements.txt') - system2( - f'python3 ./generate.py -f ../../{res_dir} -o . -e ../../{res_dir}/rustdesk-{version}-win7-install.exe') - system2(f'mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..') - elif os.path.isfile('/usr/bin/pacman'): - # pacman -S -needed base-devel - system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version) - if flutter: - build_flutter_arch_manjaro(version, features) - else: - 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'): - system2('cargo build --release --features ' + features) - system2('strip target/release/rustdesk') - system2( - "sed -i 's/Version: .*/Version: %s/g' res/rpm.spec" % version) - 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'): - system2('cargo build --release --features ' + features) - system2('strip target/release/rustdesk') - system2( - "sed -i 's/Version: .*/Version: %s/g' res/rpm-suse.spec" % version) - 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 - else: - if flutter: - if osx: - build_flutter_dmg(version, features) - pass - else: - # system2( - # 'mv target/release/bundle/deb/rustdesk*.deb ./flutter/rustdesk.deb') - build_flutter_deb(version, features) - else: - system2('cargo bundle --release --features ' + features) - if osx: - system2( - 'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk') - system2( - 'cp libsciter.dylib target/release/bundle/osx/RustDesk.app/Contents/MacOS/') - # https://github.com/sindresorhus/create-dmg - system2('/bin/rm -rf *.dmg') - pa = os.environ.get('P') - if pa: - 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 - #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/libsciter.dylib - #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 - # goto "Keychain Access" -> "My Certificates" for below id which starts with "Developer ID Application:" - 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)) - system2( - 'create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version) - os.rename('RustDesk %s.dmg' % - version, 'rustdesk-%s.dmg' % version) - if pa: - 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/ - # goto xcode and login with apple id, manager certificates (Developer ID Application and/or Developer ID Installer) online there (only download and double click (install) cer file can not export p12 because no private key) - #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./rustdesk-{1}.dmg - codesign -s "Developer ID Application: {0}" --force --options runtime ./rustdesk-{1}.dmg - # https://appstoreconnect.apple.com/access/api - # https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_getting_started.html#apple-codesign-app-store-connect-api-key - # p8 file is generated when you generate api key (can download only once) - rcodesign notary-submit --api-key-path ../.p12/api-key.json --staple rustdesk-{1}.dmg - # verify: spctl -a -t exec -v /Applications/RustDesk.app - '''.format(pa, version)) - else: - print('Not signed') - else: - # build deb package - system2( - 'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb') - system2('dpkg-deb -R rustdesk.deb tmpdeb') - system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') - system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/') - system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/') - system2( - 'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') - system2( - 'cp res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png') - system2( - 'cp res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg') - system2( - 'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') - system2( - 'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - os.system('mkdir -p tmpdeb/etc/rustdesk/') - os.system('cp -a res/startwm.sh tmpdeb/etc/rustdesk/') - os.system('mkdir -p tmpdeb/etc/X11/rustdesk/') - os.system('cp res/xorg.conf tmpdeb/etc/X11/rustdesk/') - os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/') - os.system('mkdir -p tmpdeb/etc/pam.d/') - os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk') - system2('strip tmpdeb/usr/bin/rustdesk') - system2('mkdir -p tmpdeb/usr/share/rustdesk') - system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/') - system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/') - md5_file_folder("tmpdeb/") - system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/') - os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version) - - -def md5_file(fn): - md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest() - system2('echo "%s /%s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn)) - -def md5_file_folder(base_dir): - base_path = Path(base_dir) - for file in base_path.rglob('*'): - if file.is_file() and 'DEBIAN' not in file.parts: - relative_path = file.relative_to(base_path) - md5_file(str(relative_path)) - - -if __name__ == "__main__": - main() diff --git a/build.rs b/build.rs index 92fb1f4b4..d3cb36ac5 100644 --- a/build.rs +++ b/build.rs @@ -1,25 +1,9 @@ #[cfg(windows)] fn build_windows() { - let file = "src/platform/windows.cc"; - let file2 = "src/platform/windows_delete_test_cert.cc"; - cc::Build::new().file(file).file(file2).compile("windows"); + cc::Build::new().file("src/windows.cc").compile("windows"); println!("cargo:rustc-link-lib=WtsApi32"); - println!("cargo:rerun-if-changed={}", file); - println!("cargo:rerun-if-changed={}", file2); -} - -#[cfg(target_os = "macos")] -fn build_mac() { - let file = "src/platform/macos.mm"; - 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.flag("-std=c++17").file(file).compile("macos"); - println!("cargo:rerun-if-changed={}", file); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=windows.cc"); } #[cfg(all(windows, feature = "inline"))] @@ -27,12 +11,12 @@ fn build_manifest() { use std::io::Write; if std::env::var("PROFILE").unwrap() == "release" { let mut res = winres::WindowsResource::new(); - res.set_icon("res/icon.ico") + res.set_icon("icon.ico") .set_language(winapi::um::winnt::MAKELANGID( winapi::um::winnt::LANG_ENGLISH, winapi::um::winnt::SUBLANG_ENGLISH_US, )) - .set_manifest_file("res/manifest.xml"); + .set_manifest_file("manifest.xml"); match res.compile() { Err(e) => { write!(std::io::stderr(), "{}", e).unwrap(); @@ -43,7 +27,7 @@ fn build_manifest() { } } -fn install_android_deps() { +fn install_oboe() { let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); if target_os != "android" { return; @@ -51,44 +35,40 @@ fn install_android_deps() { let mut target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); if target_arch == "x86_64" { target_arch = "x64".to_owned(); - } else if target_arch == "x86" { - target_arch = "x86".to_owned(); } else if target_arch == "aarch64" { target_arch = "arm64".to_owned(); } else { target_arch = "arm".to_owned(); } - let target = format!("{}-android", target_arch); + let target = format!("{}-android-static", target_arch); let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap(); let mut path: std::path::PathBuf = vcpkg_root.into(); - if let Ok(vcpkg_root) = std::env::var("VCPKG_INSTALLED_ROOT") { - path = vcpkg_root.into(); - } else { - path.push("installed"); - } + path.push("installed"); path.push(target); println!( - "cargo:rustc-link-search={}", - path.join("lib").to_str().unwrap() + "{}", + format!( + "cargo:rustc-link-search={}", + path.join("lib").to_str().unwrap() + ) ); - println!("cargo:rustc-link-lib=ndk_compat"); println!("cargo:rustc-link-lib=oboe"); println!("cargo:rustc-link-lib=c++"); println!("cargo:rustc-link-lib=OpenSLES"); + // I always got some strange link error with oboe, so as workaround, put oboe.cc into oboe src: src/common/AudioStreamBuilder.cpp + // also to avoid libc++_shared not found issue, cp ndk's libc++_shared.so to jniLibs, e.g. + // ./flutter_hbb/android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so + // let include = path.join("include"); + //cc::Build::new().file("oboe.cc").include(include).compile("oboe_wrapper"); } fn main() { - hbb_common::gen_version(); - install_android_deps(); #[cfg(all(windows, feature = "inline"))] build_manifest(); #[cfg(windows)] build_windows(); - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); - if target_os == "macos" { - #[cfg(target_os = "macos")] - build_mac(); - println!("cargo:rustc-link-lib=framework=ApplicationServices"); - } - println!("cargo:rerun-if-changed=build.rs"); + #[cfg(target_os = "macos")] + println!("cargo:rustc-link-lib=framework=ApplicationServices"); + hbb_common::gen_version(); + install_oboe(); } diff --git a/docs/CODE_OF_CONDUCT-DE.md b/docs/CODE_OF_CONDUCT-DE.md deleted file mode 100644 index ea4254552..000000000 --- a/docs/CODE_OF_CONDUCT-DE.md +++ /dev/null @@ -1,137 +0,0 @@ - -# Verhaltenskodex (Code of Conduct) für Mitwirkende - -## Unsere Verpflichtung - -Wir als Mitglieder, Mitwirkende und Führungskräfte verpflichten uns, -die Teilnahme unserer Community zu einer Erfahrung zu machen, -die für alle frei von Belästigungen ist, unabhängig von Alter, Körpergröße, -sichtbarer oder unsichtbarer Behinderung, ethnischer Zugehörigkeit, -Geschlechtsmerkmalen, Geschlechtsidentität und -ausdruck, Erfahrungsniveau, -Bildung, sozioökonomischem Status, Nationalität, persönlichem Erscheinungsbild, -Rasse, Religion oder sexueller Identität und Orientierung. - -Wir verpflichten uns, so zu handeln und zu interagieren, dass wir zu einer offenen, -einladenden, vielfältigen, integrativen und lebendigen Gemeinschaft beitragen. - -## Unsere Standards - -Beispiele für Verhaltensweisen, die zu einem positiven Umfeld für unsere -Gemeinschaft beitragen, sind: - -* Empathie und Freundlichkeit gegenüber anderen Menschen zu zeigen -* Respektvoll gegenüber anderen Meinungen, Sichtweisen und Erfahrungen zu sein -* Das Vergeben von sowie das großzügige Empfangen von konstruktivem Feedback -* Verantwortung übernehmen, sich bei den Betroffenen entschuldigen - und aus den Erfahrungen lernen -* Nicht darauf zu achten, was das Beste für sich selbst, - sondern zu Achten, was das Beste für die gesamte Community ist - -Beispiele für nicht akzeptables Verhalten sind: - -* Die Verwendung sexualisierter bzw. anstößiger Sprache oder Bilder - sowie sexuelle Aufmerksamkeit oder Annäherungsversuche jeglicher Art -* Trolling, beleidigende oder herabwürdigende Kommentare - sowie persönliche oder politische Angriffe -* Öffentliche sowie private Belästigung -* Das Teilen privater Informationen anderer Leute ohne deren explizite Zustimmung, - wie bspw. die physische oder die E-Mail-Adresse -* Anderes Verhalten, das in einem professionellen Umfeld begründeter Weise als - unangemessen angesehen werden könnte - -## Durchsetzungsbefugnisse - -Die Leiter der Community sind dafür verantwortlich, unsere Standards für -akzeptables Verhalten zu klären und durchzusetzen und werden angemessene -und faire Korrekturmaßnahmen ergreifen, wenn sie ein Verhalten als unangemessen, -bedrohlich, beleidigend oder schädlich erachten. - -Die Leiter der Community haben das Recht und die Pflicht, Kommentare, Commits, -Code, Wiki-Bearbeitungen, Issues und andere Beiträge, die nicht mit dem -Verhaltenskodex vereinbar sind, zu entfernen, zu bearbeiten oder abzulehnen. -Sie werden, falls angebracht, die Gründe für Moderationsentscheidungen mitteilen. - -## Geltungsbereich - -Dieser Verhaltenskodex gilt in allen Community-Bereichen und auch dann, wenn -eine Person die Community offiziell in öffentlichen Bereichen vertritt. -Beispiele für die Vertretung unserer Community sind die Verwendung einer -offiziellen E-Mail-Adresse, das Posten über einen offiziellen -Social-Media-Account oder die Tätigkeit als ernannter -Vertreter bei einer Online- oder Präsenzveranstaltung. - -## Geltendmachung - -Fälle von missbräuchlichem, belästigendem oder anderweitig inakzeptablem Verhalten können -den für die Durchsetzung zuständigen Community-Leitern -unter [info@rustdesk.com](mailto:info@rustdesk.com) gemeldet werden. -Jeder Fall wird umgehend und fair geprüft und untersucht. - -## Richtlinien zur Geltendmachung - -Die Community-Leiter werden die folgenden Community-Auswirkungsrichtlinien befolgen, -um die Konsequenzen für jede Handlung zu bestimmen, die sie als Verstoß gegen diesen -Verhaltenskodex ansehen: - -### 1. Korrektur - -**Auswirkungen auf die Community**: Verwendung unangemessener Sprache oder anderes -Verhalten, welches als unprofessionell oder in der Community unerwünscht angesehen wird. - -**Konsequenz**: Eine private, schriftliche Verwarnung durch die Leiter der Community, -in der die Art des Verstoßes klar dargelegt und erklärt wird, warum das -Verhalten unangemessen war. Eine öffentliche Entschuldigung kann verlangt werden. - -### 2. Warnung - -**Auswirkungen auf die Community**: Ein Verstoß durch einen einzelnen Vorfall -oder eine Reihe von Handlungen. - -**Konsequenz**: Eine Verwarnung mit Konsequenzen für das weitere Verhalten. Keine -Interaktion mit den beteiligten Personen, einschließlich unaufgeforderter Interaktion mit -denjenigen, die den Verhaltenskodex durchsetzen, für einen bestimmten Zeitraum. Dies -schließt die Vermeidung von Interaktionen in Gemeinschaftsräumen sowie externen Kanälen -wie sozialen Medien ein. Ein Verstoß gegen diese Bedingungen kann zu einer vorübergehenden oder -dauerhaften Sperrung führen. - -### 3. Temporärer Sperrung - - -**Auswirkungen auf die Community**: Ein schwerwiegender Verstoß gegen die Community-Standards, -einschließlich anhaltend unangemessenem Verhalten. - -**Konsequenz**: Eine vorübergehende Sperrung jeglicher Art von Interaktion oder öffentlicher -Kommunikation mit der Community für einen bestimmten Zeitraum. Während dieses Zeitraums sind -keine öffentlichen oder privaten Interaktionen mit den betroffenen Personen, -einschließlich unaufgeforderter Interaktionen mit denjenigen, -die den Verhaltenskodex durchsetzen, erlaubt. -Ein Verstoß gegen diese Bedingungen kann zu einer dauerhaften Sperrung führen. - -### 4. Dauerhafte Sperrung - -**Auswirkungen auf die Community**: Wiederholte Verstöße gegen die Community-Standards, -einschließlich anhaltend unangemessenem Verhalten, Belästigung einer -Person oder Aggression gegenüber oder Herabwürdigung von Personengruppen. - -**Konsequenz**: Ein dauerhafter Ausschluss von jeglicher öffentlicher -Interaktion innerhalb der Community. - -## Quellenangabe - -Dieser Verhaltenskodex ist eine Adaption des [Contributor Covenant][homepage], -Version 2.0, verfügbar unter -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Die Richtlinien zu den Auswirkungen auf die Gemeinschaft wurden inspiriert von -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -Für Antworten auf häufig gestellte Fragen zu diesem Verhaltenskodex siehe die -häufig gestellten Fragen (FAQ) unter -[https://www.contributor-covenant.org/faq][FAQ]. Übersetzungen sind verfügbar -unter [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-FR.md b/docs/CODE_OF_CONDUCT-FR.md deleted file mode 100644 index dca61e0aa..000000000 --- a/docs/CODE_OF_CONDUCT-FR.md +++ /dev/null @@ -1,143 +0,0 @@ - -# Code de conduite des contributeurs - -## Notre engagement - -En tant que membres, contributeurs et responsables, nous nous engageons à faire -de la participation à notre communauté une expérience exempte de harcèlement pour -tous, indépendamment de l'âge, de la taille corporelle, du handicap visible ou -invisible, de l'origine ethnique, des caractéristiques sexuelles, de l'identité -et de l'expression de genre, du niveau d'expérience, de l'éducation, du statut -socio-économique, de la nationalité, de l'apparence personnelle, de la race, de -la religion ou de l'identité et de l'orientation sexuelle. - -Nous nous engageons à agir et à interagir de manière à contribuer à une -communauté ouverte, accueillante, diversifiée, inclusive et saine. - -## Nos standards - -Exemples de comportements qui contribuent à un environnement positif pour notre -communauté : - -* Faire preuve d'empathie et de bienveillance envers les autres -* Respecter les opinions, les points de vue et les expériences différents -* Donner et accepter gracieusement les retours constructifs -* Assumer ses responsabilités, s'excuser auprès des personnes affectées par nos - erreurs et apprendre de l'expérience -* Se concentrer sur ce qui est le mieux non seulement pour nous en tant - qu'individus, mais pour l'ensemble de la communauté - -Exemples de comportements inacceptables : - -* L'utilisation de langage ou d'images à caractère sexuel, et les attentions ou - avances sexuelles de quelque nature que ce soit -* Le trolling, les commentaires insultants ou désobligeants, et les attaques - personnelles ou politiques -* Le harcèlement public ou privé -* La publication d'informations privées d'autrui, telles qu'une adresse physique - ou électronique, sans autorisation explicite -* Tout autre comportement qui pourrait raisonnablement être considéré comme - inapproprié dans un cadre professionnel - -## Responsabilités en matière d'application - -Les responsables de la communauté sont chargés de clarifier et d'appliquer nos -standards de comportement acceptable et prendront des mesures correctives -appropriées et équitables en réponse à tout comportement qu'ils jugent -inapproprié, menaçant, offensant ou nuisible. - -Les responsables de la communauté ont le droit et la responsabilité de -supprimer, modifier ou rejeter les commentaires, commits, code, modifications -du wiki, issues et autres contributions qui ne sont pas conformes à ce Code de -conduite, et communiqueront les raisons de leurs décisions de modération le cas -échéant. - -## Portée - -Ce Code de conduite s'applique dans tous les espaces communautaires, et -s'applique également lorsqu'une personne représente officiellement la communauté -dans les espaces publics. Les exemples de représentation de notre communauté -incluent l'utilisation d'une adresse e-mail officielle, la publication via un -compte de réseau social officiel, ou le fait d'agir en tant que représentant -désigné lors d'un événement en ligne ou hors ligne. - -## Application - -Les cas de comportements abusifs, harcelants ou autrement inacceptables peuvent -être signalés aux responsables de la communauté chargés de l'application à -[info@rustdesk.com](mailto:info@rustdesk.com). -Toutes les plaintes seront examinées et feront l'objet d'une enquête rapide et -équitable. - -Tous les responsables de la communauté sont tenus de respecter la vie privée et -la sécurité de la personne ayant signalé un incident. - -## Directives d'application - -Les responsables de la communauté suivront ces Directives d'impact communautaire -pour déterminer les conséquences de toute action qu'ils jugent en violation de ce -Code de conduite : - -### 1. Correction - -**Impact communautaire** : Utilisation d'un langage inapproprié ou autre -comportement jugé non professionnel ou indésirable dans la communauté. - -**Conséquence** : Un avertissement écrit et privé de la part des responsables de -la communauté, expliquant la nature de la violation et pourquoi le comportement -était inapproprié. Des excuses publiques peuvent être demandées. - -### 2. Avertissement - -**Impact communautaire** : Une violation par un incident isolé ou une série -d'actions. - -**Conséquence** : Un avertissement avec des conséquences en cas de comportement -répété. Aucune interaction avec les personnes impliquées, y compris les -interactions non sollicitées avec les personnes chargées d'appliquer le Code de -conduite, pendant une période déterminée. Cela inclut d'éviter les interactions -dans les espaces communautaires ainsi que dans les canaux externes comme les -réseaux sociaux. Le non-respect de ces conditions peut entraîner une exclusion -temporaire ou permanente. - -### 3. Exclusion temporaire - -**Impact communautaire** : Une violation grave des standards communautaires, y -compris un comportement inapproprié persistant. - -**Conséquence** : Une exclusion temporaire de toute interaction ou communication -publique avec la communauté pendant une période déterminée. Aucune interaction -publique ou privée avec les personnes impliquées, y compris les interactions non -sollicitées avec les personnes chargées d'appliquer le Code de conduite, n'est -autorisée pendant cette période. Le non-respect de ces conditions peut entraîner -une exclusion permanente. - -### 4. Exclusion permanente - -**Impact communautaire** : Démontrer un schéma de violation des standards -communautaires, y compris un comportement inapproprié persistant, le harcèlement -d'une personne, ou une agression envers des catégories de personnes ou leur -dénigrement. - -**Conséquence** : Une exclusion permanente de toute interaction publique au sein -de la communauté. - -## Attribution - -Ce Code de conduite est adapté du [Contributor Covenant][homepage], version 2.0, -disponible à l'adresse -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Les Directives d'impact communautaire ont été inspirées par -[l'échelle d'application du code de conduite de Mozilla][Mozilla CoC]. - -Pour des réponses aux questions fréquentes sur ce code de conduite, consultez la -FAQ à l'adresse [https://www.contributor-covenant.org/faq][FAQ]. Des traductions -sont disponibles à l'adresse -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-JP.md b/docs/CODE_OF_CONDUCT-JP.md deleted file mode 100644 index 5b216244e..000000000 --- a/docs/CODE_OF_CONDUCT-JP.md +++ /dev/null @@ -1,101 +0,0 @@ - -# コントリビューター規約 行動規範 - -## 私たちの誓い - -私たちは、メンバー、貢献者、リーダーとして、年齢、体格、目に見える・見えない障害、 -民族性、性の特徴、性自認と表現、経験のレベル、教育、社会経済的地位、国籍、個人の外見、 -人種、宗教、性的自認と指向に関係なく、誰もがハラスメントのないコミュニティに参加できるようにすることを誓います。 - -私たちは、開かれた、歓迎された、多様で、包容力のある、健全な地域社会に貢献するように行動し、交流することを誓います。 - -## 私たちの基準 - -地域社会にとって好ましい環境にコントリビュートする行動の例には、以下のようなものがある: - -* 他者への共感と優しさ -* 異なる意見、視点、経験を尊重すること -* 建設的なフィードバックを与え、潔く受け入れること -* 私たちの過ちによって影響を受けた人々に責任を受け入れ、謝罪し、経験から学ぶこと -* 私たち個人にとってだけでなく、地域社会全体にとって何が最善であるかに焦点を合わせること - -許されない行為の例: - -* 性的な言葉やイメージの使用、性的な注目や誘いかけ -* 荒らし、侮辱的または軽蔑的なコメント、個人的または政治的な攻撃 -* 公的または私的な嫌がらせ -* 明示的な許可なく、他人の住所や電子メールアドレスなどの個人情報を公開すること -* 職業上不適切と見なされるその他の行為 - -## 執行責任 - -コミュニティリーダーは、許容される行動の基準を明確にし、実施する責任があり、 -不適切、脅迫的、攻撃的、または有害と判断される行動に対しては、適切かつ公正な是正措置をとります - -コミュニティリーダーは、本行動規範に沿わないコメント、コミット、コード、ウィキ編集、 -課題、その他の貢献を削除、編集、拒否する権利と責任を有し、適切な場合にはモデレーション決定の理由を伝えます。 - -## スコープ - -この行動規範は、すべてのコミュニティスペースで適用され、また個人が公的なスペースでコミュニティを公式に代表している場合にも適用されます。 -当コミュニティを代表する例としては、公式 E メールアドレスの使用、公式ソーシャルメディアアカウントによる投稿、 -オンラインまたはオフラインのイベントでの任命された代表としての行動などが挙げられます。 - -## 施行 - -虐待、ハラスメント、その他容認できない行為があった場合は、[info@rustdesk.com](mailto:info@rustdesk.com) の -執行担当コミュニティリーダーに報告することができる。 -すべての苦情は、迅速かつ公正に検討・調査されます。 - -すべての地域社会の指導者は、いかなる事件の報告者のプライバシーと安全を尊重する義務がある。 - -## 執行ガイドライン - -コミュニティリーダーは、本行動規範に違反すると判断した行為に対する結果を決定する際、 -以下の「コミュニティへの影響に関するガイドライン」に従います: - -### 1. 修正 - -**コミュニティへの影響**: 不適切な言葉の使用、またはプロフェッショナルでない、あるいは地域社会で歓迎されないとみなされるその他の行動。 - -**結果**: コミュニティリーダーからの私的な書面による警告。違反の性質と、 -なぜその行為が不適切であったのかについての説明を明確にする。公的な謝罪が要求される場合もある。 - -### 2. 警告 - -**コミュニティへの影響**: 単一の出来事または一連の行動による違反。 - -**結果**: 行動を続けた場合の結果を伴う警告。一定期間、行動規範の実施者との勝手な交流を含め、 -関係者と交流しないこと。これには、ソーシャルメディアなどの外部チャンネルだけでなく、 -コミュニティスペースでの交流を避けることも含まれます。これらの条件に違反した場合、一時的または恒久的に追放される可能性があります。 - -### 3. 一時的な禁止 - -**コミュニティへの影響**: 継続的な不適切な行動を含む、コミュニティ基準に対する重大な違反。 - -**結果**: 一定期間、地域社会とのあらゆる交流や公的なコミュニケーションを一時的に禁止すること。 -この期間中は、行動規範を執行する人々との未承諾の交流を含め、関係者との公私にわたる交流は許されない。 -これらの条件に違反した場合、永久禁止となる可能性があります。 - -### 4. 永久禁止 - -**コミュニティへの影響**: 継続的な不適切な行動、個人に対する嫌がらせ、 -または個人クラスに対する攻撃や中傷など、地域社会の基準に対する違反のパターンを示すこと。 - -**結果**: コミュニティ内でのあらゆる公的交流の永久禁止。 - -## 帰属 - -この行動規範は、[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0] に掲載されている -[コントリビューター規約][ホームページ]、バージョン 2.0 から引用したものです。 - -コミュニティインパクトガイドラインは、[Mozilla's code of conduct enforcement ladder][Mozilla CoC] に触発されました。 - -この行動規範に関するよくある質問については、[https://www.contributor-covenant.org/faq][FAQ] の FAQ をご覧ください。 -翻訳は [https://www.contributor-covenant.org/translations][翻訳] にあります。 - -[ホームページ]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[翻訳]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-KR.md b/docs/CODE_OF_CONDUCT-KR.md deleted file mode 100644 index 40fea02eb..000000000 --- a/docs/CODE_OF_CONDUCT-KR.md +++ /dev/null @@ -1,133 +0,0 @@ - -# 기여자 계약 행동 강령 - -## 우리의 서약 - -회원, 기여자, 리더로서 우리는 나이, 신체 크기, 눈에 -보이거나 보이지 않는 장애, 민족, 성 특성, 성 정체성 및 -표현, 경험 수준, 교육, 사회 경제적 지위, 국적, 외모, -인종, 종교, 성적 정체성 및 지향에 관계없이 모든 사람이 -괴롭힘 없이 커뮤니티에 참여할 수 있도록 할 것을 -서약합니다. - -우리는 개방적이고 환영하며 다양하고 포용적이며 건강한 커뮤니티에 -기여하는 방식으로 행동하고 교류할 것을 약속합니다. - -## 우리의 표준 - -커뮤니티의 긍정적인 환경에 기여하는 행동의 예는 -다음과 같습니다: - -* 다른 사람들에게 공감과 친절을 보여주기 -* 다양한 의견, 관점, 경험을 존중하기 -* 건설적인 피드백을 제공하고 우아하게 받아들이기 -* 우리의 실수로 인해 영향을 받은 사람들에게 책임을 받아들이고 사과하며 - 그 경험을 통해 배우기 -* 우리 개인뿐만 아니라 전체 커뮤니티에 가장 좋은 것이 무엇인지 - 집중하기 - -용납할 수 없는 행동의 예는 다음과 같습니다: - -* 성적인 언어 또는 이미지의 사용, 모든 종류의 성적 관심 또는 - 접근 행위 -* 트롤링, 모욕적이거나 경멸적인 댓글, 개인적 또는 정치적 공격 -* 공개적 또는 사적인 괴롭힘 -* 명시적인 허가 없이 타인의 실제 주소 또는 이메일 주소와 같은 - 개인정보를 게시하는 행위 -* 직업적 환경에서 합리적으로 부적절하다고 간주될 수 있는 - 기타 행위 - -## 시행 책임 - -커뮤니티 리더는 허용되는 행동의 기준을 명확히 하고 시행할 -책임이 있으며 부적절하거나 위협적이거나 모욕적이거나 -유해하다고 판단되는 행동에 대해 적절하고 공정한 시정 조치를 -취합니다. - -커뮤니티 리더는 본 행동 강령에 부합하지 않는 댓글, 커밋, -코드, 위키 편집, 이슈 및 기타 기여를 삭제, 편집 또는 거부할 -권한과 책임이 있으며, 적절한 경우 중재 결정의 이유를 -전달합니다. - -## 범위 - -본 행동 강령은 모든 커뮤니티 공간에서 적용되며, 개인이 공개 -공간에서 커뮤니티를 공식적으로 대표하는 경우에도 적용됩니다. -커뮤니티를 대표하는 예로는 공식 이메일 주소 사용, 공식 소셜 미디어 -계정을 통한 게시, 온라인 또는 오프라인 이벤트에서 지정된 대표자로 -활동하는 것 등이 있습니다. - -## 시행 - -모욕적, 괴롭힘 또는 기타 용납할 수 없는 행동은 - [info@rustdesk.com](mailto:info@rustdesk.com)으로 법 집행을 담당하는 커뮤니티 리더에게 -신고하실 수 있습니다. -모든 불만 사항은 신속하고 공정하게 검토 및 조사됩니다. - -모든 커뮤니티 리더는 모든 사건 신고자의 사생활과 보안을 존중할 의무가 -있습니다. - -## 시행 지침 - -커뮤니티 리더는 이 행동 강령을 위반한 것으로 간주되는 모든 행동에 대한 -결과를 결정할 때 다음 커뮤니티 영향 지침을 따릅니다: - -### 1. 수정 - -**커뮤니티 영향**: 커뮤니티에서 비전문적이거나 환영받지 못하는 -것으로 간주되는 부적절한 언어 사용이나 기타 행위입니다. - -**결과**: 커뮤니티 리더의 비공개 서면 경고. 위반 사항의 성격과 -해당 행동이 부적절했던 이유를 명확히 설명해야 합니다. -공개 사과를 요청할 수도 있습니다. - -### 2. 경고 - -**커뮤니티 영향**: 단일 사건 또는 일련의 행위를 통한 -위반입니다. - -**결과**: 지속적인 행동에 대한 경고 및 결과. 행동 강령 시행 담당자와의 -원치 않는 상호작용을 포함하여 관련자와의 상호작용은 일정 -기간 동안 금지됩니다. 여기에는 공동 공간 및 소셜 미디어와 -같은 외부 채널에서의 상호작용 금지가 포함됩니다. 이러한 -조건을 위반할 경우 일시적 또는 영구적으로 이용이 금지될 수 -있습니다. - -### 3. 일시 금지 - -**커뮤니티 영향**: 지속적인 부적절한 행동을 포함하여 -커뮤니티 기준을 심각하게 위반한 경우입니다. - -**결과**: 일정 기간 동안 커뮤니티와의 모든 상호작용이나 공개적인 소통이 -일시적으로 금지됩니다. 이 기간 동안에는 행동 강령을 시행하는 -사람들과의 원치 않는 상호작용을 포함하여 관련자들과의 공개적 또는 -사적인 상호작용이 허용되지 않습니다. -이러한 조건을 위반할 경우 영구적으로 이용이 금지될 수 있습니다. - -### 4. 영구 금지 - -**커뮤니티 영향**: 지속적인 부적절한 행동, 특정 개인에 대한 괴롭힘, -특정 계층에 대한 공격성 또는 비하 등 공동체 기준을 위반하는 -행동을 보이는 경우입니다. - -**결과**: 공동체 내 모든 종류의 공개적인 상호작용이 영구적으로 -금지됩니다. - -## 귀속 - -본 행동 강령은 [Contributor Covenant][homepage] 버전 2.0을 바탕으로 작성되었으며 -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]에서 - 확인하실 수 있습니다. - -커뮤니티 영향 지침은 -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]에서 영감을 받았습니다. - -본 행동 강령에 대한 일반적인 질문은 [https://www.contributor-covenant.org/faq][FAQ]에서 FAQ를 -참조하세요. 번역은 [https://www.contributor-covenant.org/translations][translations]에서 -확인하실 수 있습니다. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-NL.md b/docs/CODE_OF_CONDUCT-NL.md deleted file mode 100644 index 49923a2d6..000000000 --- a/docs/CODE_OF_CONDUCT-NL.md +++ /dev/null @@ -1,136 +0,0 @@ - -# Gedragscode Overeenkomst Medewerkers - -## Onze Belofte - -Wij als leden, medewerkers en leiders beloven deelname aan onze -gemeenschap een pesterij-vrije ervaring te maken voor iedereen, ongeacht leeftijd, lichaamsgrootte, -zichtbare of onzichtbare handicap, etniciteit, geslachtskenmerken, gender -identiteit en expressie, ervaringsniveau, opleiding, sociaal-economische status, -nationaliteit, persoonlijk voorkomen, ras, religie of seksuele identiteit -en geaardheid. - -Wij beloven te handelen en met elkaar om te gaan op manieren die bijdragen aan een open, gastvrije, -diverse, inclusieve en gezonde gemeenschap. - -## Onze Normen - -Voorbeelden van gedrag dat bijdraagt tot een positieve omgeving voor onze -gemeenschap omvatten: - -* Medeleven en vriendelijkheid tonen tegenover andere mensen -* Respect hebben voor verschillende meningen, standpunten en ervaringen -* Constructieve feedback geven en met dank aanvaarden -* Verantwoordelijkheid accepteren en excuses aanbieden aan degenen die door onze fouten zijn getroffen, - en leren van de ervaring -* Focussen op wat het beste is, niet alleen voor ons als individu, maar voor de - totale gemeenschap - -Voorbeelden van onaanvaardbaar gedrag zijn: - -* Het gebruik van seksueel getinte taal of beelden, en seksuele aandacht of - alle soorten avances -* Treiteren, beledigende of denigrerende opmerkingen en persoonlijke of politieke aanvallen. -* Openbare of persoonlijke intimidatie -* Publiceren van andermans persoonlijke informatie, zoals een fysiek adres of e-mail, - zonder hun uitdrukkelijke toestemming -* Ander gedrag dat normaal als ongepast kan worden beschouwd in een - professionele omgeving - -## Verantwoordelijkheden inzake Handhaving - -De leiders van de Gemeenschap zijn verantwoordelijk voor het verduidelijken -en handhaven van onze normen voor aanvaardbaar gedrag en zullen passende -en billijke corrigerende maatregelen nemen als reactie op gedrag dat zij ongepast, -bedreigend, beledigend of schadelijk achten. - -Leiders van de Gemeenschap hebben het recht en de verantwoordelijkheid om -commentaar, bijdragen, code, wikibewerkingen, issues en andere bijdragen die -niet in overeenstemming zijn met deze Gedragscode te verwijderen, te bewerken of -af te wijzen, en zullen de redenen voor moderatiebeslissingen zo nodig meedelen. - -## Toepassingsgebied - -Deze Gedragscode geldt binnen alle gemeenschapsruimtes en is ook van toepassing -wanneer iemand de gemeenschap officieel vertegenwoordigt in openbare ruimtes. -Voorbeelden van het vertegenwoordigen van onze gemeenschap zijn het gebruik van -een officieel e-mailadres, het posten via een officieel sociaal media-account of het -optreden als aangewezen vertegenwoordiger bij een online of offline evenement. - -## Handhaving - -Gevallen van beledigend, intimiderend of anderszins onaanvaardbaar gedrag kunnen -worden gemeld aan de gemeenschapsleiders die verantwoordelijk zijn voor de -handhaving op [info@rustdesk.com](mailto:info@rustdesk.com). -Alle klachten zullen snel en eerlijk worden onderzocht. - -Alle leiders van de gemeenschap zijn verplicht de privacy en de veiligheid van -de melder van een incident te respecteren. - -## Handhaving Richtlijnen - -De leiders van de Gemeenschap volgen deze Communautaire Impact Richtlijnen bij -het bepalen van de consequenties voor elke actie die zij in strijd achten -met deze Gedragscode: - -### 1. Rechtzetting - -**Gevolgen Gemeenschap**: Gebruik van ongepast taalgebruik of ander gedrag -dat onprofessioneel of ongewenst wordt geacht in de gemeenschap. - -**Gevolgen**: Een persoonlijke, schriftelijke waarschuwing van de leiders van -de gemeenschap, met duidelijkheid over de aard van de overtreding en een -uitleg waarom het gedrag ongepast was. -Een publieke verontschuldiging kan worden gevraagd. - -### 2. Waarschuwing - -**Gevolgen Gemeenschap**: Een overtreding door een enkel incident of -een reeks handelingen. - -**Gevolgen**: Geen interactie met de betrokken personen, inclusief -ongevraagde interactie met degenen die de Gedragscode handhaven, -gedurende een bepaalde periode. Dit omvat het vermijden van interacties -in gemeenschapsruimtes en externe kanalen zoals sociale media. -Overtreding van deze voorwaarden kan leiden tot een tijdelijke -of permanente uitsluiting. - -### 3. Tijdelijke Uitsluiting - -**Gevolgen Gemeenschap**: Een ernstige schending van de -gemeenschapsnormen, waaronder aanhoudend ongepast gedrag. - -**Gevolgen**: Een tijdelijk verbod op elke vorm van interactie -of openbare communicatie met de gemeenschap voor een bepaalde - periode. Geen openbare of private interactie met de betrokkenen, - inclusief ongevraagde interactie met degenen die de gedragscode - handhaven, is gedurende deze periode toegestaan. - Overtreding van deze voorwaarden kan leiden tot een permanente uitsluiting. - -### 4. Permanente Uitsluiting - -**Gevolgen Gemeenschap**: Aantonen van een patroon van schending van -de gemeenschapsnormen, waaronder aanhoudend ongepast gedrag, intimidatie -van een individu, of agressie tegen of vernedering van klassen van individuen. - -**Gevolgen**: Een permanente uitsluiting van elke vorm van publieke interactie -binnen de gemeenschap. - -## Naamsvermelding - -Deze gedragscode is overgenomen uit de [Bijdrager Overeenkomst][homepagina], -versie 2.0, beschikbaar op -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -De Invloed op Richtlijnen voor Gemeenschap zijn gebaseerd op -[Mozilla's gedragscode handhavingslijst][Mozilla CoC]. - -Voor antwoorden op veelgestelde vragen over deze gedragscode, zie de FAQ op -[https://www.contributor-covenant.org/faq][FAQ]. Vertalingen zijn beschikbaar -op [https://www.contributor-covenant.org/translations][translations]. - -[homepagina]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[vertalingen]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-NO.md b/docs/CODE_OF_CONDUCT-NO.md deleted file mode 100644 index baefda051..000000000 --- a/docs/CODE_OF_CONDUCT-NO.md +++ /dev/null @@ -1,125 +0,0 @@ - -# Atferdskodeks for bidragsyterpaktern - -## Hva Vi Står For - -Vi som medlemer, bidragere, og ledere står for å skape ett hat-fritt felleskap, -uansett alder, kroppstørrelse, synlig eller usynlige funksjonsnedsettninger, -etnesitet, kjønns karaktertrekk, kjønnsidentitet, kunnskapsnivå, utdanning, -sosial-økonomisk status, nasjonalitet, utsende, rase, religion, eller seksual -identitet og orientasjon. - -Vi står for åpen, velkommende, mangfold, inklusiv og sunn oppførsel i vårt felleskap. - -## Våre Standarer - -Eksempler på oppførsel som hjelper ett positivt felleskap inkluderer: - -* Vise empati og vennlighet mot andre mennesker -* Være respektfull ovenfor ulike meninger, synspunkter og erfaringer -* Gi og ta konstruktiv kritikk i beste mening -* Akseptere ansvar og unskylde seg for de som er utsatt av våre feil, - og lære av disse -* Fokusere på det som er best ikke bare for individer, men for felleskapet - -Eksempler på uakseptabel oppførsel inkluderer: - -* Bruk av seksualisert språk eller bilder, og seksual oppmerksomhet. -* Troll-ene, fornermende og nedsettende kommentarer, og personlig eller politiske angrep -* Offentlig eller privat trakassering -* Publisering av andres private informasjon, sånn som bosteds- og epost-addresser, - uten deres godskjenning. -* Andre rettningslinjer som kan bli sett på som upassende i en profesjonell setting. - -## Håndhevingsansvar - -Felleskapets ledere har ansvar for å klarifisere og håndheve våre standarer av -akseptert oppførsel og vill ta rimelige og rettferdige handliger som respons på -oppførsel de anser som upassende, truende, fornermende eller skadelig. - -Felleskapets ledere har retten og ansvaret til å fjerne, redigere, eller avslå -kommentarer, commits, kode, wiki endringer, issues, og andre birag som ikke -samsvarer med disse etiske rettningslinjene, og vill kommunisere grunner for -moderatorenes valg når passende. - -## Omfang - -Disse etiske rettningslinjene gjelder innenfor alle platformene til felleskapet, og -de gjelder også når ett individ representerer felleskapet på offentlige medier. -Eksempler på representasjon av vårt felleskap inkluderer bruke av offisielle e-mail -addresser, publisering gjennom en offisiell sosial media bruker, eller oppførsel som en -utpekt representant på digitale og fysiske arrangsjemanger. - -## Håndheving - -Hendelser av misbruk, trakasserende eller på noen måte uakseptert oppførsel kann -bli raportert til felleskapets ledere med ansvar for håndheving på -[info@rustdesk.com](mailto:info@rustdesk.com). -All tilbakemelding vill bli sett gjennom og investigert rettferdig så fort som mulig. - -Alle felleskapets ledere er obligert til å respektere privatlivet og sikkerhetet ovenfor -den som raporterer en hendelse. - -## Håndhevings Guide - -Felleskapets ledere vill følge disse Rettningslinjene for sammfunspåvirkning med -tanke på konsekvenser for en handling de anser i brudd med disse etiske rettningslinjene: - -### 1. Korreksjon - -**Sammfunspåvirkning**: Bruk av upassende språk eller annen oppførsel ansett som -uprofesjonelt eller uvelkommen i dette felleskapet. - -**Konsekvens**: En privat, skrevet advarsel fra en leder av felleskapet, som -klarifiserer grunnlaget til hvorfor denne oppførselen var upassende. En offentlig -unskyldning kan bli forespurt. - -### 2. Advarsel - -**Sammfunspåvirkning**: Ett brudd på en singulær hendelse eller en serie handlinger. - -**Konsekvens**: En advarsel med konsekvenser for kontinuerende oppførsel. Ingen -interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med -de som håndhever disse etiske rettningslinjene, er tillat for en spesifisert tidsperiode. -Dette inkluderer å unngå interaksjoner i felleskapets platformer, samt eksterne -kanaler, som f.eks sosial media. Brudd av disse vilkårene kan føre til midlertidig -eller permanent bannlysning. - -### 3. Midlertidig Bannlysning - -**Sammfunspåvirkning**: Ett særiøst brudd på felleskapets standarer, inkludert -vedvarende upassende oppførsel. - -**Konsekvens**: En midlertidig bannlysning fra noen som helst interaksjon eller -offentlig kommunikasjon med felleskapet for en spesifisert tidsperiode. Ingen -interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med -de som håndhever disse etiske rettningslinjene, er tillat for denne perioden. -Brudd på disse vilkårene kan føre til permanent bannlysning. - -### 4. Permanent Bannlysning - -**Sammfunspåvirkning**: Demonstasjon av mønster i brudd på felleskapets standarer, -inklusivt vedvarende upassende oppførsel, trakassering av ett individ, eller -aggresjon mot eller nedsettelse av grupper individer. - -**Konsekvens**: En permanent bannlysning fra alle offentlige interaksjoner i -felleskapet - -## Attribusjon - -Disse etiske rettningslinjene er adaptert fra [Contributor Covenant][homepage], -versjon 2.0, tilgjengelig ved -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Sammfunspåvirknings guid inspirert av -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For svar til vanlige spørsmål angående disse etiske rettningslinjene, se FAQ på -[https://www.contributor-covenant.org/faq][FAQ]. Oversettelse tilgjengelig -ved [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-PL.md b/docs/CODE_OF_CONDUCT-PL.md deleted file mode 100644 index 8aedf837d..000000000 --- a/docs/CODE_OF_CONDUCT-PL.md +++ /dev/null @@ -1,133 +0,0 @@ - -# Kod postępowania Contributor Covenant Code of Conduct - -## Nasza przysięga - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Nasze standardy - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[info@rustdesk.com](mailto:info@rustdesk.com). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-RO.md b/docs/CODE_OF_CONDUCT-RO.md deleted file mode 100644 index 6fe8564a3..000000000 --- a/docs/CODE_OF_CONDUCT-RO.md +++ /dev/null @@ -1,85 +0,0 @@ -# Codul de Conduită al Contributorilor - -## Angajamentul Nostru - -Noi, ca membri, contribuitori și lideri, ne angajăm să facem ca participarea în comunitatea noastră să fie o experiență fără hărțuire pentru toată lumea, indiferent de vârstă, dimensiunea corpului, dizabilități vizibile sau invizibile, etnie, caracteristici sexuale, identitate și exprimare de gen, nivel de experiență, educație, statut socio-economic, naționalitate, aspect personal, rasă, religie sau identitate și orientare sexuală. - -Ne angajăm să acționăm și să interacționăm în moduri care contribuie la o comunitate deschisă, primitoare, diversă, incluzivă și sănătoasă. - -## Standardele Noastre - -Exemple de comportamente care contribuie la un mediu pozitiv pentru comunitatea noastră includ: - -* Demonstrarea empatiei și a bunătății față de ceilalți -* Respectarea opiniilor, punctelor de vedere și experiențelor diferite -* Oferirea și acceptarea cu grație a feedback-ului constructiv -* Asumarea responsabilității și cererea de scuze celor afectați de greșelile noastre și învățarea din experiență -* Concentrarea pe ceea ce este cel mai bun nu doar pentru noi ca indivizi, ci pentru întreaga comunitate - -Exemple de comportamente inacceptabile includ: - -* Utilizarea limbajului sau imaginilor sexualizate, precum și atenția sau avansurile sexuale de orice fel -* Trollare, insulte sau comentarii denigratoare și atacuri personale sau politice -* Hărțuire publică sau privată -* Publicarea informațiilor private ale altora, cum ar fi adresa fizică sau de e-mail, fără permisiunea explicită -* Alte comportamente care ar putea fi considerate inadecvate într-un cadru profesional - -## Responsabilități de Aplicare - -Liderii comunității sunt responsabili pentru clarificarea și aplicarea standardelor noastre de comportament acceptabil și vor lua măsuri corective adecvate și echitabile ca răspuns la orice comportament pe care îl consideră inadecvat, amenințător, ofensator sau dăunător. - -Liderii comunității au dreptul și responsabilitatea de a elimina, edita sau respinge comentarii, commit-uri, cod, editări wiki, probleme și alte contribuții care nu se aliniază acestui Cod de Conduită și vor comunica motivele pentru deciziile de moderare atunci când este cazul. - -## Domeniu de Aplicare - -Acest Cod de Conduită se aplică în toate spațiile comunității și se aplică și atunci când un individ reprezintă oficial comunitatea în spații publice. -Exemple de reprezentare a comunității includ utilizarea unei adrese de e-mail oficiale, postarea printr-un cont oficial de social media sau acționarea ca reprezentant desemnat la un eveniment online sau offline. - -## Aplicare - -Cazurile de comportament abuziv, hărțuitor sau altfel inacceptabil pot fi raportate liderilor comunității responsabili pentru aplicare la [info@rustdesk.com](mailto:info@rustdesk.com). -Toate plângerile vor fi revizuite și investigate prompt și corect. - -Toți liderii comunității sunt obligați să respecte confidențialitatea și securitatea persoanei care raportează orice incident. - -## Ghiduri de Aplicare - -Liderii comunității vor urma aceste Ghiduri privind Impactul Comunității pentru a stabili consecințele pentru orice acțiune pe care o consideră o încălcare a acestui Cod de Conduită: - -### 1. Corectare - -**Impact asupra comunității**: Utilizarea limbajului neadecvat sau alte comportamente considerate neprofesionale sau nedorite în comunitate. - -**Consecință**: O avertizare scrisă și privată din partea liderilor comunității, oferind claritate asupra naturii încălcării și o explicație despre motivul pentru care comportamentul a fost inadecvat. Poate fi cerută o scuză publică. - -### 2. Avertisment - -**Impact asupra comunității**: Încălcare printr-un incident singular sau o serie de acțiuni. - -**Consecință**: Un avertisment cu consecințe pentru continuarea comportamentului. Nicio interacțiune cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, pentru o perioadă specificată. Aceasta include evitarea interacțiunilor în spațiile comunității, precum și pe canale externe, cum ar fi rețelele sociale. Încălcarea acestor termeni poate duce la o suspendare temporară sau permanentă. - -### 3. Suspendare Temporară - -**Impact asupra comunității**: O încălcare serioasă a standardelor comunității, inclusiv comportament neadecvat susținut. - -**Consecință**: Suspendare temporară de la orice tip de interacțiune sau comunicare publică cu comunitatea pentru o perioadă specificată. Nicio interacțiune publică sau privată cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, nu este permisă în această perioadă. Încălcarea acestor termeni poate duce la o interdicție permanentă. - -### 4. Interdicție Permanentă - -**Impact asupra comunității**: Demonstrând un tipar de încălcare a standardelor comunității, inclusiv comportament neadecvat susținut, hărțuire a unei persoane sau agresiune față de sau denigrare a unor grupuri de persoane. - -**Consecință**: Interdicție permanentă de la orice tip de interacțiune publică în cadrul comunității. - -## Atribuire - -Acest Cod de Conduită este adaptat din [Contributor Covenant][homepage], versiunea 2.0, disponibil la [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Ghidurile privind Impactul Comunității au fost inspirate de [scara de aplicare a codului de conduită Mozilla][Mozilla CoC]. - -Pentru răspunsuri la întrebări frecvente despre acest cod de conduită, vezi FAQ la [https://www.contributor-covenant.org/faq][FAQ]. Traduceri sunt disponibile la [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/docs/CODE_OF_CONDUCT-RU.md b/docs/CODE_OF_CONDUCT-RU.md deleted file mode 100644 index 53f4ab8c8..000000000 --- a/docs/CODE_OF_CONDUCT-RU.md +++ /dev/null @@ -1,134 +0,0 @@ - -# Кодекс поведения участников и вкладчиков - -## Наше обещание - -Мы, как члены, вкладчики и лидеры, обязуемся сделать участие в нашем -сообществе свободным от притеснений для всех, независимо от возраста, -размера тела, видимой или невидимой инвалидности, этнической принадлежности, половых характеристик, гендерной -идентичности и самовыражения, уровня опыта, образования, социально-экономического статуса, -национальности, внешнего вида, расы, религии или сексуальной идентичности -и ориентации. - -Мы обязуемся действовать и взаимодействовать таким образом, чтобы способствовать созданию открытого, гостеприимного, -разнообразного, инклюзивного и здорового сообщества. - -## Наши Стандарты - -Примеры поведения, способствующего созданию благоприятной среды для нашего -сообщества, включают: - -* Демонстрация сочувствия и доброты по отношению к другим людям -* Уважительное отношение к различным мнениям, точкам зрения и опыту -* Предоставление и вежливое принятие конструктивной обратной связи -* Принятие ответственности и извинения перед теми, кто пострадал от наших ошибок, -а также извлечение уроков из накопленного опыта -* Сосредоточение внимания на том, что лучше не только для нас как отдельных людей, но и для -всего сообщества в целом. - -Примеры неприемлемого поведения включают: - -* Использование сексуализированных выражений или образов, а также сексуальное внимание или -заигрывания любого рода -* Троллинг, оскорбительные или уничижительные комментарии, а также личные или политические нападки -* Публичные или частные домогательства -* Публикация личной информации других лиц, такой как физический адрес или адрес электронной -почты, без их явного разрешения -* Другое поведение, которое можно обоснованно считать неуместным в -профессиональной среде - -## Правоприменительные обязанности - -Лидеры сообщества несут ответственность за разъяснение и обеспечение соблюдения наших стандартов -приемлемого поведения и предпримут надлежащие и справедливые корректирующие действия в -ответ на любое поведение, которое они сочтут неуместным, угрожающим, оскорбительным -или вредным. - -Лидеры сообщества имеют право и ответственность удалять, редактировать или отклонять -комментарии, коммиты, код, вики-правки, проблемы и другие материалы, которые -не соответствуют настоящему Кодексу поведения, и -при необходимости сообщат причины принятия решений о модерации. - -## Сфера действия - -Этот Кодекс поведения применяется во всех общественных местах, а также применяется, когда -физическое лицо официально представляет сообщество в общественных местах. -Примеры представления нашего сообщества включают использование официального адреса электронной почты, -размещение сообщений через официальную учетную запись в социальных сетях или выступление в качестве назначенного -представителя на онлайн- или оффлайн-мероприятии. - -## Правоприменение - -О случаях оскорбительного, домогательского или иного неприемлемого поведения можно -сообщать лидерам сообщества, ответственным за правоприменение в -[info@rustdesk.com ](mailto:info@rustdesk.com). -Все жалобы будут рассмотрены и расследованы быстро и справедливо. - -Все лидеры сообщества обязаны уважать частную жизнь и безопасность -репортера о любом инциденте. - -## Руководящие принципы воздействия - -Лидеры сообщества будут следовать этим руководящим принципам воздействия на сообщество при определении -последствий любого действия, которое они сочтут нарушением настоящего Кодекса поведения: - -### 1. Правки - -**Воздействие на сообщество**: Использование неподобающих выражений или другого поведения, которое считается -непрофессиональным или нежелательным в сообществе. - -**Последствие**: частное письменное предупреждение от лидеров сообщества, дающее -ясность в отношении характера нарушения и объяснение того, почему -поведение было неуместным. Могут быть запрошены публичные извинения. - - -### 2. Предупреждение - -**Воздействие на сообщество**: нарушение в результате одного инцидента или серии -действий. - -**Последствие**: Предупреждение с последствиями для дальнейшего поведения. Никакого -взаимодействия с вовлеченными лицами, включая нежелательное взаимодействие с -теми, кто обеспечивает соблюдение Кодекса поведения, в течение определенного периода времени. Это -включает в себя избегание взаимодействия в общественных пространствах, а также внешних каналов -, таких как социальные сети. Нарушение этих условий может привести к временному или -постоянному запрету. - -### 3. Временная блокировка - -**Воздействие на сообщество**: Серьезное нарушение стандартов сообщества, включая -длительное неподобающее поведение. - -**Последствие**: Временный запрет на любое взаимодействие или публичное -общение с сообществом в течение определенного периода времени. -В течение этого периода не допускается никакое публичное или частное взаимодействие с вовлеченными лицами, включая незапрашиваемое взаимодействие -с теми, кто обеспечивает соблюдение Кодекса поведения. -Нарушение этих условий может привести к постоянному запрету. - -### 4. Блокировка навсегда - -**Воздействие на сообщество**: Демонстрация модели нарушения -стандартов сообщества, включая постоянное неподобающее поведение, преследование отдельного -лица или агрессию по отношению к классам людей или пренебрежительное отношение к ним. - -**Последствие**: Постоянный запрет на любое публичное взаимодействие внутри -сообщества. - -## Определение - -Настоящий Кодекс поведения адаптирован из [Соглашения о вкладчиках][homepage], -версии 2.0, доступной по ссылке -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Руководящие принципы воздействия на сообщество были вдохновлены -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -Ответы на распространенные вопросы об этом кодексе поведения см. в разделе Часто задаваемые вопросы по адресу -[https://www.contributor-covenant.org/faq][FAQ]. Переводы доступны -по адресу [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-TR.md b/docs/CODE_OF_CONDUCT-TR.md deleted file mode 100644 index 76088bd95..000000000 --- a/docs/CODE_OF_CONDUCT-TR.md +++ /dev/null @@ -1,89 +0,0 @@ -# Katkıda Bulunanların Davranış Kuralları - -## Taahhüdümüz - -Biz üyeler, katkıda bulunanlar ve liderler olarak, yaş, beden büyüklüğü, görünür veya görünmez engellilik, etnik köken, cinsiyet özellikleri, cinsiyet kimliği ve ifadesi, deneyim seviyesi, eğitim, sosyo-ekonomik durum, milliyet, kişisel görünüm, ırk, din veya cinsel kimlik ve yönelim ayrımı gözetmeksizin herkes için topluluğumuzdaki katılımı taciz içermeyen bir deneyim haline getirmeyi taahhüt ederiz. - -Açık, hoşgörülü, çeşitli, kapsayıcı ve sağlıklı bir topluluğa katkıda bulunacak şekillerde hareket etmeyi ve etkileşimde bulunmayı taahhüt ederiz. - -## Standartlarımız - -Topluluğumuz için olumlu bir ortam yaratmaya katkıda bulunan davranış örnekleri şunlardır: - -* Diğer insanlara empati ve nezaket göstermek -* Farklı görüşlere, bakış açılarına ve deneyimlere saygılı olmak -* Yapıcı eleştiriyi vermek ve zarifçe kabul etmek -* Hatalarımızdan etkilenenlere sorumluluk kabul etmek, özür dilemek ve deneyimden öğrenmek -* Sadece bireyler olarak değil, aynı zamanda genel topluluk için en iyisi üzerine odaklanmak - -Kabul edilemez davranış örnekleri şunları içerir: - -* Cinselleştirilmiş dil veya imgelerin kullanımı ve cinsel ilgi veya herhangi bir türdeki yaklaşımlar -* Trollük, aşağılayıcı veya hakaret içeren yorumlar ve kişisel veya siyasi saldırılar -* Kamuoyu veya özel taciz -* Başkalarının fiziksel veya e-posta adresi gibi özel bilgilerini, açık izinleri olmadan yayınlamak -* Profesyonel bir ortamda makul bir şekilde uygunsuz kabul edilebilecek diğer davranışlar - -## Uygulama Sorumlulukları - -Topluluk liderleri, kabul edilebilir davranış standartlarımızı açıklığa kavuşturmak ve uygulamakla sorumludur ve uygunsuz, tehditkar, saldırgan veya zarar verici herhangi bir davranışa yanıt olarak uygun ve adil düzeltici önlemler alacaklardır. - -Topluluk liderleri, bu Davranış Kurallarına uyumlu olmayan yorumları, taahhütlerini veya kodu, wiki düzenlemelerini, sorunları ve diğer katkıları kaldırma, düzenleme veya reddetme hakkına sahiptir. Denetim kararlarının nedenlerini uygun olduğunda ileteceklerdir. - -## Kapsam - -Bu Davranış Kuralları, tüm topluluk alanlarında geçerlidir ve aynı zamanda birey resmi olarak topluluğu halka açık alanlarda temsil ettiğinde de geçerlidir. Topluluğumuzu temsil etme örnekleri, resmi bir e-posta adresi kullanmak, resmi bir sosyal medya hesabı üzerinden gönderi yapmak veya çevrimiçi veya çevrimdışı bir etkinlikte atanmış bir temsilci olarak hareket etmeyi içerir. - -## Uygulama - -Taciz edici, rahatsız edici veya başka türlü kabul edilemez davranış örnekleri, [info@rustdesk.com](mailto:info@rustdesk.com) adresindeki uygulama sorumlularına bildirilebilir. Tüm şikayetler hızlı ve adil bir şekilde incelenecek ve araştırılacaktır. - -Tüm topluluk liderleri, olayın raporlayıcısının gizliliğine ve güvenliğine saygı gösterme yükümlülüğündedir. - -## Uygulama Kılavuzları - -Topluluk liderleri, bu Davranış Kurallarını ihlal olarak değerlendirdikleri herhangi bir eylem için bu Topluluk Etkisi Kılavuzlarını izleyeceklerdir: - -### 1. Düzeltme - -**Topluluk Etkisi**: Topluluk içinde profesyonel veya hoşgörülü olmayan uygun olmayan dil veya diğer davranışların kullanımı. - -**Sonuç**: Topluluk liderlerinden özel ve yazılı bir uyarı almak, ihlalin niteliği ve davranışın nedeninin açıklığa kavuşturulması. Bir kamu özrü istenebilir. - -### 2. Uyarı - -**Topluluk Etkisi**: Tek bir olay veya dizi aracılığıyla bir ihlal. - -**Sonuç**: Devam eden davranış için sonuçları olan bir uyarı. Topluluk liderleri de dahil olmak üzere ihlalle ilgili kişilerle etkileşim, belirli bir süre boyunca önerilmez. Bu, topluluk alanlarında ve sosyal medya gibi harici kanallarda etkileşimleri içerir. Bu koşulları ihlal etmek geçici veya kalıcı bir yasağa yol açabilir. - -### 3. Geçici Yasak - -**Topluluk Etkisi**: Sürekli uygunsuz davranış da dahil olmak üzere topluluk standartlarının ciddi bir ihlali. - -**Sonuç**: Belirli bir süre için toplulukla herhangi bir türdeki etkileşim veya halka açık iletişimden geçici bir yasak. Bu dönem boyunca, toplul - -ukla veya uygulama kurallarını uygulayanlarla her türlü kamuoyu veya özel etkileşim izin verilmez. Bu koşulları ihlal etmek geçici veya kalıcı bir yasağa yol açabilir. - -### 4. Kalıcı Yasak - -**Topluluk Etkisi**: Topluluk standartlarının ihlalinde sürekli bir desen sergilemek, bireye sürekli olarak uygun olmayan davranışlarda bulunmak, bir bireye tacizde bulunmak veya birey sınıflarına karşı saldırganlık veya aşağılama yapmak. - -**Sonuç**: Topluluk içinde her türlü halka açık etkileşimden kalıcı bir yasak. - -## Atıf - -Bu Davranış Kuralları, [Contributor Covenant][anasayfa], 2.0 sürümünden uyarlanmıştır ve -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0] adresinde bulunmaktadır. - -Topluluk Etkisi Kılavuzları, -[Mozilla'nın davranış kuralları uygulama merdiveni][Mozilla DK] tarafından ilham alınarak oluşturulmuştur. - -Bu davranış kuralları hakkında yaygın soruların cevapları için, SSS'ye göz atın: -[https://www.contributor-covenant.org/faq][SSS]. Çeviriler, -[https://www.contributor-covenant.org/translations][çeviriler] adresinde bulunabilir. - -[anasayfa]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla DK]: https://github.com/mozilla/diversity -[SSS]: https://www.contributor-covenant.org/faq -[çeviriler]: https://www.contributor-covenant.org/translations diff --git a/docs/CODE_OF_CONDUCT-ZH.md b/docs/CODE_OF_CONDUCT-ZH.md deleted file mode 100644 index 0877ab20f..000000000 --- a/docs/CODE_OF_CONDUCT-ZH.md +++ /dev/null @@ -1,87 +0,0 @@ - -# 贡献者公约行为准则 - -## 我们的承诺 - -身为社区成员、贡献者和领袖,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。 - -我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。 - -## 我们的标准 - -有助于为我们的社区创造积极环境的行为例子包括但不限于: - -* 表现出对他人的同情和善意 -* 尊重不同的主张、观点和感受 -* 提出和大方接受建设性意见 -* 承担责任并向受我们错误影响的人道歉 -* 注重社区共同诉求,而非个人得失 - -不当行为例子包括: - -* 使用情色化的语言或图像,及性引诱或挑逗 -* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击 -* 公开或私下的骚扰行为 -* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址 -* 其他有理由认定为违反职业操守的不当行为 - -## 责任和权力 - -社区领袖有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。 - -社区领导有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论(comment)、提交(commits)、代码、维基(wiki)编辑、议题(issues)或其他贡献,并在适当时机知采取措施的理由。 - -## 适用范围 - -本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。 - -代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。 - -## 监督 - -辱骂、骚扰或其他不可接受的行为可通过[info@rustdesk.com](mailto:info@rustdesk.com)向负责监督的社区领袖报告。 所有投诉都将得到及时和公平的审查和调查。 - -所有社区领袖都有义务尊重任何事件报告者的隐私和安全。 - -## 处理方针 - -社区领袖将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式: - -### 1. 纠正 - -**社区影响**: 使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。 - -**处理意见**: 由社区领袖发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。 - -### 2. 警告 - -**社区影响**: 单个或一系列违规行为。 - -**处理意见**: 警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。 - -### 3. 临时封禁 - -**社区影响**: 严重违反社区准则,包括持续的不当行为。 - -**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。 - -### 4. 永久封禁 - -**社区影响**: 行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。 - -**处理意见**: 永久禁止在社区内进行任何形式的公开互动。 - -## 参见 - -本行为准则改编自[参与者公约][homepage]2.0 版, 参见 -[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0]. - -指导方针借鉴自[Mozilla纪检分级][Mozilla CoC]. - -有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。 其他语言翻译参见[https://www.contributor-covenant.org/translations][translations]。 - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md deleted file mode 100644 index e5db8edef..000000000 --- a/docs/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,133 +0,0 @@ - -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[info@rustdesk.com](mailto:info@rustdesk.com). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/docs/CONTRIBUTING-DE.md b/docs/CONTRIBUTING-DE.md deleted file mode 100644 index b45c23d50..000000000 --- a/docs/CONTRIBUTING-DE.md +++ /dev/null @@ -1,50 +0,0 @@ -# Beiträge zu RustDesk - -RustDesk begrüßt Beiträge von jedem. Hier sind die Richtlinien, wenn Sie uns -helfen möchten: - -## Beiträge - -Beiträge zu RustDesk oder seinen Abhängigkeiten sollten in Form von Pull -Requests auf GitHub erfolgen. Jeder Pull Request wird von einem Hauptakteur -(jemand mit der Erlaubnis, Korrekturen einzubringen) geprüft und entweder in den -Hauptbaum eingefügt oder Feedback für notwendige Änderungen gegeben. Alle -Beiträge sollten diesem Format folgen, auch die von Hauptakteuren. - -Wenn Sie an einem Problem arbeiten möchten, melden Sie es bitte zuerst an, indem -Sie auf GitHub erklären, dass Sie daran arbeiten möchten. Damit soll verhindert -werden, dass Beiträge zum gleichen Thema doppelt bearbeitet werden. - -## Checkliste für Pull Requests - -- Verzweigen Sie sich vom Master-Branch und, falls nötig, wechseln Sie zum - aktuellen Master-Branch, bevor Sie Ihren Pull Request einreichen. Wenn das - Zusammenführen mit dem Master nicht reibungslos funktioniert, werden Sie - möglicherweise aufgefordert, Ihre Änderungen zu überarbeiten. - -- Commits sollten so klein wie möglich sein und gleichzeitig sicherstellen, dass - jeder Commit unabhängig voneinander korrekt ist (d. h., jeder Commit sollte - sich übersetzen lassen und Tests bestehen). - -- Commits sollten von einem "Herkunftszertifikat für 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` für `git commit`. - -- Wenn Ihr Patch nicht begutachtet wird oder Sie eine bestimmte Person zur - Begutachtung benötigen, können Sie einem Gutachter mit @ antworten und um eine - Begutachtung des Pull Requests oder einen Kommentar bitten. Sie können auch - per [E-Mail](mailto:info@rustdesk.com) um eine Begutachtung bitten. - -- Fügen 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 häufig im [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-FR.md b/docs/CONTRIBUTING-FR.md deleted file mode 100644 index 6f800de7d..000000000 --- a/docs/CONTRIBUTING-FR.md +++ /dev/null @@ -1,55 +0,0 @@ - -# Contribuer à RustDesk - -RustDesk accueille les contributions de tous. Voici les directives si vous -envisagez de nous aider : - -## Contributions - -Les contributions à RustDesk ou à ses dépendances doivent être soumises sous -forme de pull requests GitHub. Chaque pull request sera examinée par un -contributeur principal (une personne ayant la permission d'intégrer des -correctifs) et sera soit intégrée dans la branche principale, soit accompagnée -de retours sur les modifications requises. Toutes les contributions doivent -suivre ce format, même celles des contributeurs principaux. - -Si vous souhaitez travailler sur une issue, veuillez d'abord la revendiquer en -commentant sur l'issue GitHub indiquant que vous souhaitez la traiter. Cela -permet d'éviter les efforts en double de la part des contributeurs sur la même -issue. - -## Liste de vérification pour les pull requests - -- Partez de la branche master et, si nécessaire, effectuez un rebase sur la - branche master actuelle avant de soumettre votre pull request. Si elle ne - fusionne pas proprement avec master, il vous sera peut-être demandé de - rebaser vos modifications. - -- Les commits doivent être aussi petits que possible, tout en s'assurant que - chaque commit est correct de manière indépendante (c.-à-d. que chaque commit - doit compiler et passer les tests). - -- Les commits doivent être accompagnés d'une signature Developer Certificate of - Origin (http://developercertificate.org), indiquant que vous (et votre - employeur le cas échéant) acceptez d'être liés par les termes de la - [licence du projet](../LICENCE). Dans git, il s'agit de l'option `-s` de - `git commit`. - -- Si votre correctif n'est pas examiné ou si vous avez besoin qu'une personne - spécifique l'examine, vous pouvez @-mentionner un relecteur pour demander une - revue dans la pull request ou un commentaire, ou vous pouvez demander une - revue par [e-mail](mailto:info@rustdesk.com). - -- Ajoutez des tests relatifs au bug corrigé ou à la nouvelle fonctionnalité. - -Pour des instructions git spécifiques, consultez le -[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Conduite - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md - -## Communication - -Les contributeurs de RustDesk se retrouvent fréquemment sur -[Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-ID.md b/docs/CONTRIBUTING-ID.md deleted file mode 100644 index cdff6c01f..000000000 --- a/docs/CONTRIBUTING-ID.md +++ /dev/null @@ -1,31 +0,0 @@ -# Berkontribusi dalam pengembangan RustDesk - -RustDesk mengajak semua orang untuk ikut berkontribusi. Berikut ini adalah panduan jika kamu sedang mempertimbangkan untuk memberikan bantuan kepada kami: - -## Kontirbusi - -Untuk melakukan kontribusi pada RustDesk atau dependensinya, sebaiknya dilakukan dalam bentuk pull request di GitHub. Setiap permintaan pull request akan ditinjau oleh kontributor utama atau seseorang yang memiliki wewenang untuk menggabungkan perubahan kode, baik yang sudah dimasukkan ke dalam struktur utama ataupun memberikan umpan balik untuk perubahan yang akan diperlukan. Setiap kontribusi harus sesuai dengan format ini, juga termasuk yang berasal dari kontributor utama. - -Apabila kamu ingin mengatasi sebuah masalah yang sudah ada di daftar issue, harap klaim terlebih dahulu dengan memberikan komentar pada GitHub issue yang ingin kamu kerjakan. Hal ini dilakukan untuk mencegah terjadinya duplikasi dari kontributor pada daftar issue yang sama. - -## Pemeriksaan Pull Request - -- Branch yang menjadi acuan adalah branch master dari repositori utama dan, jika diperlukan, lakukan rebase ke branch master yang terbaru sebelum kamu mengirim pull request. Apabila terdapat masalah kita melakukan proses merge ke branch master kemungkinan kamu akan diminta untuk melakukan rebase pada perubahan yang sudah dibuat. - -- Sebaiknya buatlah commit seminimal mungkin, sambil memastikan bahwa setiap commit yang dibuat sudah benar (contohnya, setiap commit harus bisa di kompilasi dan berhasil melewati tahap test). - -- Setiap commit harus disertai dengan tanda tangan Sertifikat Asal Pengembang (Developer Certificate of Origin) (), yang mengindikasikan bahwa kamu (and your employer if applicable) bersedia untuk patuh terhadap persyaratan dari [lisensi projek](../LICENCE). Di git bash, ini adalah opsi parameter `-s` pada `git commit` - -- Jika perubahan yang kamu buat tidak mendapat tinjauan atau kamu membutuhkan orang tertentu untuk meninjaunya, kamu bisa @-reply seorang reviewer meminta peninjauan dalam permintaan pull request atau komentar, atau kamu bisa meminta tinjauan melalui [email](mailto:info@rustdesk.com). - -- Sertakan test yang relevan terhadap bug atau fitur baru yang sudah dikerjakan. - -Untuk instruksi Git yang lebih lanjut, cek disini [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Tindakan - - - -## Komunikasi - -Kontributor RustDesk sering berkunjung ke [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-IT.md b/docs/CONTRIBUTING-IT.md deleted file mode 100644 index a3a5fd2b6..000000000 --- a/docs/CONTRIBUTING-IT.md +++ /dev/null @@ -1,37 +0,0 @@ -# Contribuzione a RustDesk - -RustDesk accoglie con favore il contributo di tutti. -Ecco le linee guida se stai pensando di aiutarci. - -## Contribuzione - -I contributi a RustDesk o alle sue dipendenze dovrebbero essere forniti sotto forma di richieste pull GitHub. -Ogni richiesta pull verr esaminata da un collaboratore principale (qualcuno con il permesso di applicare) ed abilitato all'uso dell'albero principale o dare un feedback per le modifiche che sarebbero necessarie. -Tutti i contributi dovrebbero seguire questo formato, anche quelli dei contributori principali. - -Se desideri lavorare su un problema, rivendicalo prima commentando -il problema di GitHub su cui vuoi lavorare. -Questo per evitare duplicati sforzi dei contributori sullo stesso problema. - -## Elenco di controllo delle richieste pull - -- Branch del master branch e, se necessario, rebase al master attuale branch prima di inviare la richiesta pull. - Se l'unione non in mod pulito con il master ti potrebbe essere chiesto di effettuare il rebase delle modifiche. - -- Le modifiche dovrebbero essere le pi piccole possibile, assicurando al tempo stesso che ogni modifica sia corretta in modo indipendente (ovvero, ogni modifica dovrebbe essere compilabile e superare i test). - -- Le modifiche devono essere accompagnati da un certificato di origine per sviluppatori firmato (http://developercertificate.org), che indica che tu (e il tuo datore di lavoro se applicabile) accetti di essere vincolato dai termini della [licenza progetto](../LICENCE). In git, questa l'opzione `-s` di `git commit` - -- Se la tua patch non viene esaminata o hai bisogno che una persona specifica la esamini, puoi @-rispondere ad un revisore chiedendo una revisione nella richiesta pull o un commento, oppure puoi chiedere una revisione tramite [email](mailto:info@rustdesk.com). - -- Aggiungi test relativi al bug corretto o alla nuova funzionalit. - -Per istruzioni specifiche su git, vedi [Workflow GitHub - 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Condotta - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT-IT.md - -## Comunicazioni - -I contributori di RustDesk frequentano [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-JP.md b/docs/CONTRIBUTING-JP.md deleted file mode 100644 index 03a1853bf..000000000 --- a/docs/CONTRIBUTING-JP.md +++ /dev/null @@ -1,41 +0,0 @@ -# RustDesk へのコントリビュート - -RustDesk は皆さんからのコントリビュートを歓迎します。ご協力いただける方のガイドラインは -以下の通りです: - -## コントリビューション - -RustDesk またはその依存関係へのコントリビュートは、GitHub のプルリクエストの形で行ってください。 -それぞれのプルリクエストは、コアコントリビューター(パッチの適用を許可されている人)によってレビューされ、 -メインツリーに適用されるか、必要な変更についてのフィードバックが与えられます。 -コアコントリビューターからのものであっても、すべてのコントリビューターはこのフォーマットに従うべきです。 - -ある issue に取り組みたい場合は、GitHub の issue にコメントすることで、まずその対応を主張してください。 -これは、同じ issue に対するコントリビューターの重複作業を防ぐためです。 - -## プルリクエストのチェックリスト - -- master ブランチからブランチし、必要であればプルリクエストを提出する前に現在の master ブランチにリベースしてください。 - master と正しくマージできない場合、変更をリベースするよう求められる可能性があります。 - -- コミットは、各コミットが独立して正しい(すなわち、各コミットがコンパイルされ、テストに合格する)ことを保証しながら、 - 可能な限り小さくすべきです。 - -- コミットには、Developer Certificate of Origin (http://developercertificate.org) の sign-off を添えてください。 - これは、あなた(および該当する場合はあなたの雇用主)が [プロジェクトのライセンス](../LICENCE) の条項に拘束されることに - 同意していることを示すものです。git では、これは `git commit` の `-s` オプションを使います。 - -- もしあなたのパッチがレビューされなかったり、特定の人にレビューしてもらう必要がある場合、 - プルリクエストやコメントでレビューを依頼するレビュアーに@返信したり、[email](mailto:info@rustdesk.com) でレビューを依頼することができます。 - -- 修正したバグや新機能に関連するテストを追加する。 - -具体的なgitの手順については、[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow)を参照してください。 - -## 行動規範 - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md - -## コミュニケーション - -RustDesk のコントリビューターは、[Discord](https://discord.gg/nDceKgxnkV) を良く使っています。 diff --git a/docs/CONTRIBUTING-KR.md b/docs/CONTRIBUTING-KR.md deleted file mode 100644 index 5e432648e..000000000 --- a/docs/CONTRIBUTING-KR.md +++ /dev/null @@ -1,46 +0,0 @@ -# RustDesk 기여하기 - -RustDesk는 모든 분들의 참여를 환영합니다. 저희를 도와주실 생각이 있으시다면 - 다음 지침을 따르세요: - -## 기여 - -RustDesk 또는 그 종속성에 대한 기여는 GitHub 풀 리퀘스트 형태로 -이루어져야 합니다. 각 풀 리퀘스트는 핵심 기여자 (패치 적용 권한이 -있는 사람)가 검토하여 메인 트리에 추가하거나 필요한 변경 사항에 -대한 피드백을 제공합니다. 핵심 기여자의 기여를 포함하여 모든 기여는 -이 형식을 따라야 합니다. - -이슈에 대해 작업하고 싶으시면 먼저 해당 이슈에 대해 작업하고 싶다는 -댓글을 달아 해당 이슈를 요청하세요. 이는 동일한 이슈에 대한 기여자의 -중복된 노력을 방지하기 위한 것입니다. - -## 풀 리퀘스트 체크리스트 - -- Master 브랜치에서 브랜치를 만들고, 필요한 경우 풀 리퀘스트를 제출하기 - 전에 현재 마스터 브랜치로 리베이스하세요. 마스터 브랜치와 깔끔하게 - 병합되지 않으면 변경 사항을 리베이스하라는 요청을 받을 수 있습니다. - -- 커밋은 가능한 한 작아야 하지만, 각 커밋이 독립적으로 올바른지 확인 - 해야 합니다 (즉, 각 커밋은 컴파일되어 테스트를 통과해야 함). - -- 커밋에는 개발자 출처 증명서 (http://developercertificate.org) - 서명이 첨부되어야 하며, 이는 귀하 (및 해당되는 경우 고용주)가 - [프로젝트 라이선스](../LICENCE). 조건에 구속되는 데 동의한다는 것을 나타냅니다. - git에서는 `git commit`에 `-s` 옵션입니다 - -- 패치가 검토되지 않거나 특정인이 검토해야 하는 경우, 풀 리퀘스트나 - 댓글에서 검토자에게 @-답글을 보내 검토를 요청하거나 - [이메일](mailto:info@rustdesk.com)을 통해 검토를 요청할 수 있습니다. - -- 수정된 버그 또는 새 기능과 관련된 테스트를 추가합니다. - -구체적인 git 지침은, [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow)을 참조하세요. - -## 행동 강령 - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md - -## 커뮤니케이션 - -RustDesk 기여자들은 [Discord](https://discord.gg/nDceKgxnkV)에서 활동하고 있습니다. diff --git a/docs/CONTRIBUTING-NL.md b/docs/CONTRIBUTING-NL.md deleted file mode 100644 index a39e8cef1..000000000 --- a/docs/CONTRIBUTING-NL.md +++ /dev/null @@ -1,50 +0,0 @@ -# Bijdragen aan RustDesk - -RustDesk verwelkomt bijdragen van iedereen. Hier zijn de richtlijnen als u denkt -ons te willen helpen: - -## Bijdragen - -Bijdragen aan RustDesk of haar afhankelijkheden moeten worden gedaan in de -vorm van GitHub pull verzoeken. Elk pull verzoek zal worden beoordeeld door -een core bijdrager (iemand met toestemming om patches te plaatsen) en ofwel -worden geplaatst in de hoofd structuur of feedback krijgen voor veranderingen -die nodig zouden zijn. Alle bijdragen zouden dit formaat moeten volgen, -zelfs die van kernmedewerkers. - -Als je aan een onderwerp wilt werken, eis het dan eerst op door commentaar -te geven op het GitHub onderwerp dat je eraan wilt werken. Dit is om dubbele -inspanningen van medewerkers aan hetzelfde issue te voorkomen. - -## Checklist Pull Aanvragen - -- Maak een vertakking vanaf de master tak en, indien nodig, veranker naar de - huidige master tak voordat je je pull verzoek indient. Als je het niet netjes - samenvoegt met master kan je gevraagd worden om je wijzigingen - opnieuw op te bouwen. - -- Toezeggingen moeten zo klein mogelijk zijn, terwijl er voor gezorgd moet - worden dat elke toezegging onafhankelijk correct is (dat wil zeggen, elke - toezegging moet compileren en testen doorstaan). - -- Toezeggingen moeten vergezeld gaan van een Certificaat van Oorsprong - van de Ontwikkelaar (http://developercertificate.org) ondertekening, die aangeeft - dat u (en uw werkgever indien van toepassing) akkoord gaat met de - voorwaarden van het [project licentie](../LICENCE). - In git is dit de `-s` optie van `git commit` - -- Als je patch niet beoordeeld wordt of je hebt een specifiek persoon nodig om hem - te beoordelen kunt u @-reply een reviewer vragen in het pull verzoek of een - commentaar, of je kunt om een review vragen via [email](mailto:info@rustdesk.com). - -- Tests toevoegen die relevant zijn voor de gerepareerde bug of de nieuwe functie. - -Voor specifieke git instructies, zie [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Gedrag - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md - -## Communicatie - -RustDesk medewerkers bezoeken frequent [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-NO.md b/docs/CONTRIBUTING-NO.md deleted file mode 100644 index 89a574563..000000000 --- a/docs/CONTRIBUTING-NO.md +++ /dev/null @@ -1,46 +0,0 @@ -# Bidrag til RustDesk - -RustDesk er åpene for bidrag fra alle. Her er reglene for de som har lyst til å -hjelpe oss: - -## Bidrag - -Bidrag til RustDesk eller deres avhengigheter burde være i form av GitHub pull requests. -Hver pull request vill bli sett igjennom av en kjerne bidrager (noen med autoritet til -å godkjenne endringene) og enten bli sendt til main treet eller respondert med -tilbakemelding på endringer som er nødvendig. Alle bidrag burde følge dette formate -også de fra kjerne bidragere. - -Om du ønsker å jobbe på en issue må du huske å gjøre krav på den først. Dette -kann gjøres ved å kommentere på den GitHub issue-en du ønsker å jobbe på. -Dette er for å hindre duplikat innsats på samme problem. - -## Pull Request Sjekkliste - -- Lag en gren fra master grenen og, hvis det er nødvendig, rebase den til den nåværende - master grenen før du sender inn din pull request. Hvis ikke dette gjøres på rent - vis vill du bli spurt om å rebase dine endringer. - -- Commits burde være så små som mulig, samtidig som de må være korrekt uavhenging av hverandre - (hver commit burde kompilere og bestå tester). - -- Commits burde være akkopaniert med en Developer Certificate of Origin - (http://developercertificate.org), som indikerer att du (og din arbeidsgiver - i det tilfellet) godkjenner å bli knyttet til vilkårene av [prosjekt lisensen](../LICENCE). - Ved bruk av git er dette `-s` opsjonen til `git commit`. - -- Hvis dine endringer ikke blir sett eller hvis du trenger en spesefik person til - å se på dem kan du @-svare en med autoritet til å godkjenne dine endringer. - Dette kann gjøres i en pull request, en kommentar eller via epost på [email](mailto:info@rustdesk.com). - -- Legg til tester relevant til en fikset bug eller en ny tilgjengelighet. - -For spesefike git instruksjoner, se [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Oppførsel - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md - -## Kommunikasjon - -RustDesk bidragere burker [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-PL.md b/docs/CONTRIBUTING-PL.md deleted file mode 100644 index 8341692ba..000000000 --- a/docs/CONTRIBUTING-PL.md +++ /dev/null @@ -1,45 +0,0 @@ -# Współtworzenie RustDesk - -RustDesk z zadowoleniem przyjmuje wkład od każdego. Oto wytyczne, jeśli chcesz nam pomóc: - -## Współtwórcy - -Contributions to RustDesk or its dependencies should be made in the form of GitHub -pull requests. Each pull request will be reviewed by a core contributor -(someone with permission to land patches) and either landed in the main tree or -given feedback for changes that would be required. All contributions should -follow this format, even those from core contributors. - -Should you wish to work on an issue, please claim it first by commenting on -the GitHub issue that you want to work on it. This is to prevent duplicated -efforts from contributors on the same issue. - -## Pull Request Checklist - -- Branch from the master branch and, if needed, rebase to the current master - branch before submitting your pull request. If it doesn't merge cleanly with - master you may be asked to rebase your changes. - -- Commits should be as small as possible, while ensuring that each commit is - correct independently (i.e., each commit should compile and pass tests). - -- Commits should be accompanied by a Developer Certificate of Origin - (http://developercertificate.org) sign-off, which indicates that you (and - your employer if applicable) agree to be bound by the terms of the - [project license](../LICENCE). In git, this is the `-s` option to `git commit` - -- If your patch is not getting reviewed or you need a specific person to review - it, you can @-reply a reviewer asking for a review in the pull request or a - comment, or you can ask for a review via [email](mailto:info@rustdesk.com). - -- Add tests relevant to the fixed bug or new feature. - -For specific git instructions, see [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Kodeks postępowania - -[Kodeks postępowania](CODE_OF_CONDUCT-PL.md) - -## Komunikacja - -RustDesk contributors frequent the [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-RO.md b/docs/CONTRIBUTING-RO.md deleted file mode 100644 index 8249fb80f..000000000 --- a/docs/CONTRIBUTING-RO.md +++ /dev/null @@ -1,31 +0,0 @@ -# Contribuții la RustDesk - -RustDesk primește cu plăcere contribuții din partea tuturor. Iată ghidurile dacă te gândești să ne ajuți: - -## Contribuții - -Contribuțiile la RustDesk sau la dependențele sale ar trebui făcute sub forma de pull request-uri pe GitHub. Fiecare pull request va fi revizuit de un contributor principal (cineva cu permisiunea de a aplica patch-uri) și fie va fi integrat în arborele principal, fie vor fi oferite sugestii pentru modificările necesare. Toate contribuțiile trebuie să urmeze acest format, chiar și cele ale contributorilor principali. - -Dacă dorești să lucrezi la o problemă, te rugăm să o revendici mai întâi comentând pe GitHub issue-ul pe care vrei să lucrezi. Aceasta previne eforturi duplicate din partea contributorilor asupra aceleiași probleme. - -## Lista de verificare pentru Pull Request - -- Creează un branch din branch-ul `master` și, dacă este necesar, fă rebase la branch-ul `master` curent înainte de a trimite pull request-ul. Dacă nu se poate integra curat cu `master`, ți se poate cere să faci rebase la modificările tale. - -- Commit-urile ar trebui să fie cât mai mici posibil, asigurând totodată că fiecare commit este corect independent (adică fiecare commit ar trebui să compileze și să treacă testele). - -- Commit-urile trebuie să fie însoțite de un semnătura Developer Certificate of Origin (http://developercertificate.org), care indică faptul că tu (și angajatorul tău, dacă este cazul) ești de acord să respecți termenii [licenței proiectului](../LICENCE). În git, aceasta este opțiunea `-s` la `git commit`. - -- Dacă patch-ul tău nu este revizuit sau ai nevoie ca o anumită persoană să-l revizuiască, poți @-reply unui reviewer cerând o revizuire în pull request sau într-un comentariu, sau poți solicita o revizuire prin [email](mailto:info@rustdesk.com). - -- Adaugă teste relevante pentru bug-ul corectat sau pentru funcționalitatea nouă. - -Pentru instrucțiuni specifice git, vezi [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## Conduită - -[Codul de Conduită RustDesk](https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md) - -## Comunicare - -Contributorii RustDesk frecventează [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-RU.md b/docs/CONTRIBUTING-RU.md deleted file mode 100644 index 1cf9a472d..000000000 --- a/docs/CONTRIBUTING-RU.md +++ /dev/null @@ -1,45 +0,0 @@ -# Вклад в RustDesk - -RustDesk приветствует вклад каждого. -Ниже приведены рекомендации, если вы собираетесь помочь нам: - -## Вклад в развитие - -Вклады в развитие RustDesk или его зависимости должны быть сделаны в виде `pull request` на GitHub. -Каждый такой `pull request` будет рассмотрен основным участником (кем-то, у кого есть разрешение -на влив исправлений) и либо помещен в основное дерево, либо Вам будет дан отзыв о необходимых правках. -Все материалы должны соответствовать этому формату, даже те, которые поступают от основных авторов. - -Если вы хотите поработать над какой-либо проблемой, то пожалуйста, сначала напишите об этом, -создав `issue` на GitHub, и описав, над чем вы хотите поработать. Это делается для того, -чтобы предотвратить дублирование усилий участников по одному и тому же вопросу. - -## Контрольный список для Ваших `pull request` - -- Ответвляйтесь от главной ветки и, при необходимости, делайте `rebase` в текущую `master` - ветку перед отправкой `pull request`. При наличии конфликтов слияния вам будет - предложено их устранить, возможно при помощи того же `rebase`. - -- Коммиты должны быть, по возможности, небольшими, при этом гарантируя, что каждый - коммит является независимо правильным (т.е., каждый коммит должен компилироваться и проходить тесты). - -- Коммиты должны сопровождаться подписью `Developer Certificate of Origin` - (http://developercertificate.org), которая укажет на то, что вы (и ваш работодатель, - если это применимо) согласны соблюдать условия [лицензии проекта](../LICENCE). - В `git` это флаг `-s` при использовании `git commit` - -- Если ваш патч не проходит рецензирование или вам нужно, - чтобы его проверил конкретный человек, Вы можете ответить рецензенту через `@`, - в обсуждениях вашего `pull request` или Вы можете запросить рецензию через[email](mailto:info@rustdesk.com). - -- Добавьте тесты, относящиеся к исправленной ошибке или новой функции. - -Для получения конкретных инструкций `git` см. [GitHub workflow 101](https://github.com/servo/servo/wiki/Github-workflow). - -## Правила поведения участников и вкладчиков - -Нормы поведения внутри сообщества подробно описаны [здесь](CODE_OF_CONDUCT-RU.md). - -## Общение - -RustDesk контрибьюторы могут посетить [Discord](https://discord.gg/nDceKgxnkV). diff --git a/docs/CONTRIBUTING-TR.md b/docs/CONTRIBUTING-TR.md deleted file mode 100644 index 6e9e3f3ed..000000000 --- a/docs/CONTRIBUTING-TR.md +++ /dev/null @@ -1,31 +0,0 @@ -# RustDesk'a Katkı Sağlamak - -RustDesk, herkesten katkıyı memnuniyetle karşılar. Eğer bize yardımcı olmayı düşünüyorsanız, işte rehberlik eden kurallar: - -## Katkılar - -RustDesk veya bağımlılıklarına yapılan katkılar, GitHub pull istekleri şeklinde yapılmalıdır. Her bir pull isteği, çekirdek katkıcı tarafından gözden geçirilecek (yamaları kabul etme izni olan biri) ve ana ağaca kabul edilecek veya gerekli değişiklikler için geri bildirim verilecektir. Tüm katkılar bu formata uymalıdır, çekirdek katkıcılardan gelenler bile. - -Eğer bir konu üzerinde çalışmak isterseniz, önce üzerinde çalışmak istediğinizi belirten bir yorum yaparak konuyu talep ediniz. Bu, katkı sağlayanların aynı konuda çift çalışmasını engellemek içindir. - -## Pull İstek Kontrol Listesi - -- Master dalından dallandırın ve gerekiyorsa pull isteğinizi göndermeden önce mevcut master dalına rebase yapın. Eğer master ile temiz bir şekilde birleşmezse, değişikliklerinizi rebase yapmanız istenebilir. - -- Her bir commit mümkün olduğunca küçük olmalıdır, ancak her commit'in bağımsız olarak doğru olduğundan emin olun (örneğin, her commit derlenebilir ve testleri geçmelidir). - -- Commit'ler, bir Geliştirici Sertifikası ile desteklenmelidir (http://developercertificate.org). Bu, [proje lisansının](../LICENCE) koşullarına uymayı kabul ettiğinizi gösteren bir onaydır. Git'te bunu `git commit` seçeneği olarak `-s` seçeneği ile yapabilirsiniz. - -- Yamalarınız gözden geçirilmiyorsa veya belirli bir kişinin gözden geçirmesine ihtiyacınız varsa, çekme isteği veya yorum içinde bir gözden geçirmeyi istemek için bir inceleyiciyi @etiketleyebilir veya inceleme için [e-posta](mailto:info@rustdesk.com) ile talep edebilirsiniz. - -- Düzelttiğiniz hatanın veya eklediğiniz yeni özelliğin ilgili testlerini ekleyin. - -Daha spesifik git talimatları için, [GitHub iş akışı 101](https://github.com/servo/servo/wiki/GitHub-workflow)'e bakınız. - -## Davranış - -https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT-TR.md - -## İletişim - -RustDesk katkı sağlayıcıları, [Discord](https://discord.gg/nDceKgxnkV) kanalını sık sık ziyaret ederler. diff --git a/docs/CONTRIBUTING-ZH.md b/docs/CONTRIBUTING-ZH.md deleted file mode 100644 index 718cdac69..000000000 --- a/docs/CONTRIBUTING-ZH.md +++ /dev/null @@ -1,32 +0,0 @@ -# 为RustDesk做贡献 - -Rust欢迎每一位贡献者,如果您有意向为我们做出贡献,请遵循以下指南: - -## 贡献方式 - -对 RustDesk 或其依赖项的贡献需要通过 GitHub 的 Pull Request (PR) 的形式提交。每个 PR 都会由核心贡献者(即有权限合并代码的人)进行审核,审核通过后代码会合并到主分支,或者您会收到需要修改的反馈。所有贡献者,包括核心贡献者,提交的代码都应遵循此流程。 - -如果您希望处理某个问题,请先在对应的 GitHub issue 下发表评论,声明您将处理该问题,以避免该问题被多位贡献者重复处理。 - -## PR 注意事项 - -- 从 master 分支创建一个新的分支,并在提交PR之前,如果需要,将您的分支 变基(rebase) 到最新的 master 分支。如果您的分支无法顺利合并到 master 分支,您可能会被要求更新您的代码。 - -- 每次提交的改动应该尽可能少,并且要保证每次提交的代码都是正确的(即每个 commit 都应能成功编译并通过测试)。 - -- 每个提交都应附有开发者证书签名(http://developercertificate.org), 表明您(以及您的雇主,若适用)同意遵守项目[许可证条款](../LICENCE)。在使用 git 提交代码时,可以通过在 `git commit` 时使用 `-s` 选项加入签名 - -- 如果您的 PR 未被及时审核,或需要指定的人员进行审核,您可以通过在 PR 或评论中 @ 提到相关审核者,以及发送[电子邮件](mailto:info@rustdesk.com)的方式请求审核。 - -- 请为修复的 bug 或新增的功能添加相应的测试用例。 - -有关具体的 git 使用说明,请参考[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow). - -## 行为准则 - -请遵守项目的[贡献者公约行为准则](./CODE_OF_CONDUCT-ZH.md)。 - - -## 沟通渠道 - -RustDesk 的贡献者主要通过 [Discord](https://discord.gg/nDceKgxnkV) 进行交流。 diff --git a/docs/README-AR.md b/docs/README-AR.md deleted file mode 100644 index 5aa09da88..000000000 --- a/docs/README-AR.md +++ /dev/null @@ -1,173 +0,0 @@ -

- RustDesk - Your remote desktop
- 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] | [Ελληνικά]
- لغتك الأم, Doc و RustDesk UI, README نحن بحاجة إلى مساعدتك لترجمة هذا -

- -[Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) :تواصل معنا عبر - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%D8%A7%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA%20%D8%A7%D9%84%D9%85%D8%AA%D9%82%D8%AF%D9%85%D8%A9-blue)](https://rustdesk.com/pricing.html) - -.Rustبرنامج آخر لسطح المكتب عن بعد، مكتوب بـ -يعمل خارج الصندوق، لا حاجة إلى إعدادات. لديك سيطرة كاملة على بياناتك، دون مخاوف بشأن الأمن. يمكنك استخدام خادم - الخاص بنا rendezvous/relay -[جهز لنفسك واحدا](https://rustdesk.com/server), أو -[خاص بك rendezvous/relay أكتب خادم](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -لمساعدتك على ذلك [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) يرحب بمساهمة الجميع. اطلع على RustDesk. - -[**؟ RustDesk كيفية يعمل**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) - -[**BINARY تنزيل**](https://github.com/rustdesk/rustdesk/releases) - - -## التبعيات - - لواجهة المستخدم الرسومية [sciter](https://sciter.com/) نسخة سطح المكتب تستخدم - بنفسك sciter dynamic library عليك تحميل - -[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) - - Sciter إلى Flutter سنقوم بترحيل نسخة سطح المكتب من .Flutter تستخدم إصدارات الهاتف المحمول. - -## خطوات البناء - -- C++ build env و Rust development env قم بإعداد - -- بطريقة صحيحة `VCPKG_ROOT` env variable وأعد [vcpkg](https://github.com/microsoft/vcpkg) ثبت - - - Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static` - - Linux/MacOS: `vcpkg install libvpx libyuv opus aom` - -- run `cargo run` - -## [البناء](https://rustdesk.com/docs/en/dev/build/) - -## Linux - -### 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 -``` - -### 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 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Fix libvpx (For Fedora) - -```sh -cd vcpkg/buildtrees/libvpx/src -cd * -./configure -sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile -sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile -make -cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ -cd -``` - -### البناء - -```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 -``` - -## Docker طريقة البناء باستخدام - -ابدأ باستنساخ المستودع وبناء الكونتاينر: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -ثم، في كل مرة تحتاج إلى بناء التطبيق، قم بتشغيل الأمر التالي: - -```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 -``` - -لاحظ أن البناء الأول قد يستغرق وقتًا أطول قبل تخزين التبعيات، وسيكون البناء اللاحق أسرع. بالإضافة إلى ذلك، إذا كنت بحاجة إلى تحديد وسائط مختلفة لأمر البناء، فيمكنك القيام بذلك في نهاية الأمر بوضع -`` -على سبيل المثال، إذا كنت ترغب في بناء إصدار محسن، فستقوم بتشغيل الأمر أعلاه متبوعًا بـ -`--release` -:سيكون الملف القابل للتنفيذ الناتج متاحًا في مجلد تارغت، ويمكن تشغيله باستخدام - -```sh -target/debug/rustdesk -``` - -:أو في حال قمت ببناء إصدار محسن - -```sh -target/release/rustdesk -``` - -RustDesk يرجى التأكد من أنك تنفذ هذه الأوامر من جذر مستودع -وإلا فقد لا يتمكن التطبيق من العثور على الموارد المطلوبة. لاحظ أيضًا أن الأوامر الفرعية الأخرى مثل -`install` أو `run` -لا يتم دعمها حاليًا عبر هذه الطريقة لأنها ستقوم بتثبيت أو تشغيل البرنامج داخل الكونتاينر بدلاً من الهوست. - -## هيكل الملف - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: وظائف لنقل الملفات، وبعض وظائف المرافق الأخرى tcp/udp، protobuf ترميز الفيديو، إعدادات - -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: التقاط الشاشة -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: التحكم في لوحة المفاتيح/الماوس الخاصة بكل منصة -- **[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)**: أو المنقول عن بُعد (TCP hole punching) انتظر الاتصال المباشر [rustdesk-server](https://github.com/rustdesk/rustdesk-server) الإتصال ب -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: رمز خاص بكل منصة -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: رمز الهاتف المحمول -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**:Flutter لعميل الويب الخاص ب Javascript - -## لقطات - -![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-CS.md b/docs/README-CS.md deleted file mode 100644 index b208414fe..000000000 --- a/docs/README-CS.md +++ /dev/null @@ -1,157 +0,0 @@ -

- RustDesk – vaše vzdálená plocha
- Servery • - Sestavení ze zdrojových kódů • - Docker • - Struktura • - Ukázky
- [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řekladem tohoto README, uživatelského rozhraní aplikace RustDesk a dokumentace k ní do vašeho jazyka -

- -Popovídejte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Pokro%C4%8Dil%C3%A9%20Funkce-blue)](https://rustdesk.com/pricing.html) - -Zase další software pro přístup k ploše na dálku, naprogramovaný v jazyce Rust. Funguje hned tak, jak je – není třeba žádného nastavování. Svá data máte ve svých rukách, bez obav o zabezpečení. Je možné používat námi poskytovaný propojovací/předávací (relay) server, [vytvořit si svůj vlastní](https://rustdesk.com/server), nebo [si dokonce svůj vlastní naprogramovat](https://github.com/rustdesk/rustdesk-server-demo), budete-li chtít. - -Projekt RustDesk vítá přiložení ruky k dílu od každého. Jak začít se dozvíte z [`docs/CONTRIBUTING.md`](CONTRIBUTING.md). - -[**Jak RustDesk funguje?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) - -[**STAHOVÁNÍ ZKOMPILOVANÝCH APLIKACÍ**](https://github.com/rustdesk/rustdesk/releases) - -## Softwarové součásti, na kterých závisí - -Varianta pro počítač používá pro grafické uživatelské rozhraní [sciter](https://sciter.com/) – stáhněte si potřebnou knihovnu. - -[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) - -Varianta pro mobilní platformy používá aplikační rámec (framework) Flutter. Na tu také v budoucnu předěláme i variantu pro počítač. - -## Stručně kroky pro sestavení ze zdrojových kódů - -- Připravte si vývojové prostředí pro jazyky Rust a C++ - -- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a správně nastavte proměnnou prostředí `VCPKG_ROOT` - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- spusťte `cargo run` - -## [Sestavení ze zdrojových kódů](https://rustdesk.com/docs/en/dev/build/) - -## Jak zkompilovat na Linuxu - -### 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 -``` - -### 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 -``` - -### Instalace vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Oprava libvpx (pro Fedoru) - -```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 -``` - -### Sestavení - -```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 -``` - -## Jak sestavit prostřednictvím Docker kontejnerizace - -Začněte tím, že si naklonujete tento repozitář a sestavíte docker kontejner: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Poté pokaždé, když bude třeba aplikaci sestavit, spusťte následující příkaz: - -```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 -``` - -Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Pokud navíc potřebujete zadat různé argumenty příkazu pro sestavení, můžete tak učinit na konci příkazu v pozici ``. Například, pokud byste chtěli sestavit optimalizovanou verzi pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí: - -```sh -target/debug/rustdesk -``` - -Nebo, pokud spouštíte variantu pro vydání: - -```sh -target/release/rustdesk -``` - -Ujistěte se, že tyto příkazy spouštíte z kořenového adresáře RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému. - -## Struktura souborů - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: kodek videa, nastavení, obalovaní tcp/udp, vyrovnávací paměť protokolu, funkce souborového systému pro přenos souborů a pár dalších podpůrných funkcí -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: zachytávání obsahu obrazovky -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: ovládání klávesnice/myši pro jednotlivé platformy -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: grafické uživatelské rozhraní -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: služby pro zvuk/schránku/zadávání/video a síťová spojení -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: spouští připojení k protějšku -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: komunikace s [rustdesk-server](https://github.com/rustdesk/rustdesk-server), očekávání vzdálených příméhých („proděrováváním“ TCP) nebo předávaných (relay) spojení -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: zdrojové kódy, specifické pro jednotlivé platformy -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: zdrojové kódy pro použití s aplikačním rámcem (framework) Flutter pro mobilní platformy -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript pro Flutter webový klient - -## Ukázky - -![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-DA.md b/docs/README-DA.md deleted file mode 100644 index 9ad109dde..000000000 --- a/docs/README-DA.md +++ /dev/null @@ -1,149 +0,0 @@ -

- RustDesk - Your remote desktop
- Servere • - Byg • - Docker • - Filstruktur • - Skærmbilleder
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
- Vi har brug for din hjælp til at oversætte denne README, RustDesk UI og Dokument til dit modersmål -

- -Chat med os: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Avancerede%20Funktioner-blue)](https://rustdesk.com/pricing.html) - -Endnu en fjernskrivebordssoftware, skrevet i Rust. Fungerer ud af æsken, ingen konfiguration påkrævet. Du har fuld kontrol over dine data uden bekymringer om sikkerhed. Du kan bruge vores rendezvous/relay-server, [opsætte din egen](https://rustdesk.com/server), eller [skrive din egen rendezvous/relay-server](https://github.com/rustdesk/rustdesk- server-demo). - -RustDesk hilser bidrag fra alle velkommen. Se [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) for at få hjælp til at komme i gang. - -[**PROGRAM DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) - -## Afhængigheder - -Desktopversioner bruger [sciter](https://sciter.com/) eller Flutter til GUI, denne vejledning er kun for Sciter. - -Hent venligst sciter dynamic library selv. - -[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | -[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | -[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) - -## Rå trin til at bygge - -- Forbered din Rust-udviklings-env og C++ build-env - -- Installer [vcpkg](https://github.com/microsoft/vcpkg), og indstil env-variabelen "VCPKG_ROOT" korrekt - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- kør `cargo run` - -## [Byg](https://rustdesk.com/docs/en/dev/build/) - -## Sådan bygger du på Linux - -### 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 -``` - -### 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 installation - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### libvpx rettelse (For Fedora) - -```sh -cd vcpkg/buildtrees/libvpx/src -cd * -./configure -sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile -sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile -make -cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ -cd -``` - -### Byg - -```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 -cargo run -``` - -## Sådan bygger du med Docker - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Kør derefter følgende kommando, hver gang du skal bygge applikationen: -```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 -``` - -Bemærk, at den første bygning kan tage længere tid, før afhængigheder cachelagres, efterfølgende bygninger vil være hurtigere. Derudover, hvis du har brug for at angive forskellige argumenter til bygge-kommandoen, kan du gøre det i slutningen af kommandoen i ``-positionen. For eksempel, hvis du ville bygge en optimeret udgivelsesversion, ville du køre kommandoen ovenfor efterfulgt af `--release`. Den resulterende eksekverbare vil være tilgængelig i målmappen på dit system og kan køres med: - -```sh -target/debug/rustdesk -``` - -Eller, hvis du kører en udgivelses eksekverbar: - -```sh -target/release/rustdesk -``` - -Sørg for, at du kører disse kommandoer fra roden af RustDesk-lageret, ellers kan applikationen muligvis ikke finde de nødvendige ressourcer. Bemærk også, at andre cargo underkommandoer såsom 'install' eller 'run' i øjeblikket ikke understøttes via denne metode, da de ville installere eller køre programmet inde i containeren i stedet for værten. - -## Filstruktur - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs funktioner til filoverførsel og nogle andre hjælpefunktioner -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: Skærmbillede -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specifik tastatur/mus kontrol -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: lyd/udklipsholder/input/videotjenester og netværksforbindelser -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: starte en peer-forbindelse -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Kommuniker med [rustdesk-server](https://github.com/rustdesk/rustdesk-server), vent på direkte fjernforbindelse (TCP-hulning) eller relæforbindelse -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: Javascript til Flutter webklient - -## Skærmbilleder - -![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-DE.md b/docs/README-DE.md deleted file mode 100644 index ba8894411..000000000 --- a/docs/README-DE.md +++ /dev/null @@ -1,182 +0,0 @@ -

- RustDesk - Dein Remote-Desktop
- Kompilieren • - Docker • - Dateistruktur • - Screenshots
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe] | [Norsk] | [Română]
- Wir brauchen Ihre Hilfe, um dieses README, die RustDesk-Benutzeroberfläche und die Dokumentation in Ihre Muttersprache zu übersetzen. -

- -> [!Caution] -> **Haftungsausschluss bei Missbrauch::**
-> Die Entwickler von RustDesk billigen oder unterstützen keine unethische oder illegale Nutzung dieser Software. Missbrauch, wie unbefugter Zugriff, unbefugte Kontrolle oder Verletzung der Privatsphäre, verstößt strikt gegen unsere Richtlinien. Die Autoren sind nicht verantwortlich für jeglichen Missbrauch der Anwendung. - - -Reden Sie mit uns auf: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Erweiterte%20Funktionen-blue)](https://rustdesk.com/pricing.html) - -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. Schauen Sie sich [CONTRIBUTING-DE.md](CONTRIBUTING-DE.md) an, wenn Sie Unterstützung beim Start brauchen. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**Programm herunterladen**](https://github.com/rustdesk/rustdesk/releases) - -[**Nightly Builds**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## Abhängigkeiten - -Desktop-Versionen verwenden [Sciter](https://sciter.com/) oder Flutter für die GUI, dieses Tutorial ist nur für Sciter. - -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) | -[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) - -## Grobe Schritte zum Kompilieren - -- Bereiten Sie Ihre Rust-Entwicklungsumgebung und C++-Build-Umgebung vor - -- 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 aom:x64-windows-static` - - Linux/macOS: `vcpkg install libvpx libyuv opus aom` - -- Nutzen Sie `cargo run` - -## [Erstellen](https://rustdesk.com/docs/de/dev/build/) - -## Kompilieren auf Linux - -### Ubuntu 18 (Debian 10) - -```sh -sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ - libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### vcpkg installieren - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### libvpx reparieren (für 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 -``` - -### Kompilieren - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -git clone --recurse-submodules 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 -``` - -## Auf Docker kompilieren - -Beginnen Sie damit, das Repository zu klonen und den Docker-Container zu bauen: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -git submodule update --init --recursive -docker build -t "rustdesk-builder" . -``` - -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 -``` - -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 Sie eine Releaseversion benutzen: - -```sh -target/release/rustdesk -``` - -Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System. - -## Dateistruktur - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: Bildschirmaufnahme -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: Plattformspezifische Maus- und Tastatursteuerung -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Datei kopieren und einfügen Implementierung für Windows, Linux, macOS. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: Audio/Zwischenablage/Eingabe/Videodienste und Netzwerkverbindungen -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Starten einer Peer-Verbindung -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Mit [rustdesk-server](https://github.com/rustdesk/rustdesk-server) kommunizieren, warten auf direkte (TCP hole punching) oder weitergeleitete Verbindung -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: Plattformspezifischer Code -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter-Code für Handys -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript für Flutter-Webclient - -## Screenshots - -![Verbindungsmanager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Verbunden zu einem Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![Dateiübertragung](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP-Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) - diff --git a/docs/README-EO.md b/docs/README-EO.md deleted file mode 100644 index d2a9315ec..000000000 --- a/docs/README-EO.md +++ /dev/null @@ -1,148 +0,0 @@ -

- RustDesk - Your remote desktop
- Serviloj • - Kompili • - Docker • - Strukturo • - Ekrankopio
- [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 -

- -Babili kun ni: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Altnivela%20Funkcioj-blue)](https://rustdesk.com/pricing.html) - -Denove alia fora labortabla programo, skribita en Rust. Ĝi funkcias elskatole, ne bezonas konfiguraĵon. Vi havas la tutan kontrolon sur viaj datumoj, sen zorgo pri sekureco. Vi povas uzi nian servilon rendezvous/relajsan, [agordi vian propran](https://rustdesk.com/server), aŭ [skribi vian propran servilon rendezvous/relajsan](https://github.com/rustdesk/rustdesk-server-demo). - -RustDesk bonvenigas kontribuon de ĉiuj. Vidu [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) por helpo komenci. - -[**BINARA ELŜUTO**](https://github.com/rustdesk/rustdesk/releases) - -## Dependantaĵoj - -La labortabla versio uzas [sciter](https://sciter.com/) por la interfaco, bonvolu elŝuti la bibliotekon dinamikan 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) - -## Paŝoj por kompili - -- Preparu vian medion de programado Rust kaj vian medion de kompilado C++ - -- Instalu [vcpkg](https://github.com/microsoft/vcpkg), kaj agordu la medivariablon `VCPKG_ROOT` korekte - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- Plenumu `cargo run` - -## Kiel kompili sur Linukso - -### 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 -``` - -### 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 -``` - -### Instali vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Ripari libvpx (Por 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 -``` - -### Kompili - -```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 -``` - -## Kiel kompili kun Docker - -Komencu klonante la deponejon kaj kompilu la konteneron Docker: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Tiam, ĉiuj fojoj, kiuj vi bezonas kompili la programon, plenumu tiun komandon: - -```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 -``` - -Notu, ke la unua kompilado povas daŭri longe, antaŭ ke la dependantaĵoj estu kaŝitaj, sekvaj kompiladoj estos pli rapidaj. Aldone, se vi bezonas specifi diferentajn argumentojn por la kompilkomando, vi povas fari ĝin en la fine de la komando, en la posicio ``. Ekzemple, se vi volas kompili version de eldono optimigita, vi plenumus la komandon supre, kun `--release`. La plenumebla dosiero disponeblos en la cela dosierujo sur via sistemo, kaj povos esti plenumita kun: - -```sh -target/debug/rustdesk -``` - -Aŭ, se vi plenumas eldonan plenumeblan dosieron: - -```sh -target/release/rustdesk -``` - -Bonvolu certigi, ke vi plenumas tiujn komandojn el la radiko de la deponejo RustDesk, alie la programo povus esti nekapabla de trovi la devigajn resursojn. Ankaŭ notu, ke la aliaj subkomandoj de cargo kiel `install` aŭ `run` momente ne estas subtenitaj per tiu metodo, ĉar instalus aŭ plenumus la programon en la kontenero anstataŭ de la gastiganto. - -## Dosierstrukturo - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: videa kodeko, agordado, kovrilo tcp/udp, protobuf, funkcioj fs por dosiertransigo, kaj aliaj utilaĵaj funkcioj -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ekrankaptado -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: operaciumspecifa kontrolo de klavaro/muso -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: interfaco -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: aŭdio/poŝo/enigo/videa servoj, kaj retkonektoj -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: starti konekto kun samtavolo -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: komuniki kun [rustdesk-server](https://github.com/rustdesk/rustdesk-server), atendi foran direktan (TCP hole punching) aŭ relajsatan konekton -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: operaciumspecifa kodo - -## Ekrankopio - -![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-ES.md b/docs/README-ES.md deleted file mode 100644 index da939bd7b..000000000 --- a/docs/README-ES.md +++ /dev/null @@ -1,180 +0,0 @@ -

- RustDesk - Your remote desktop
- Servidores • - Compilar • - Docker • - Estructura • - Capturas de pantalla
- [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 -

- -> [!Caution] -> **Descargo de responsabilidad por mal uso:**
-> Los desarrolladores de RustDesk no aprueban ni apoyan ningún uso no ético o ilegal de este software. El mal uso, como el acceso no autorizado, el control o la invasión de la privacidad, va estrictamente en contra de nuestras directrices. Los autores no se hacen responsables de ningún uso indebido de la aplicación. - -Chatea con nosotros: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Caracter%C3%ADsticas%20Avanzadas-blue)](https://rustdesk.com/pricing.html) - -Otro software de escritorio remoto, escrito en Rust. Funciona de forma inmediata, sin necesidad de configuración. Tienes el control total de tus datos, sin preocupaciones sobre la seguridad. Puedes utilizar nuestro servidor de rendezvous/relay, [instalar el tuyo](https://rustdesk.com/server), o [escribir tu propio servidor rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk agradece la contribución de todo el mundo. Lee [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) para ayuda para empezar. - -[**¿Cómo funciona rustdesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) - -[**DESCARGA DE BINARIOS**](https://github.com/rustdesk/rustdesk/releases) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## Dependencias - -Las versiones de escritorio utilizan Flutter o Sciter (obsoleto) para GUI, este tutorial es sólo para Sciter, ya que es más fácil y más amigable para empezar. Echa un vistazo a nuestro [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) para la construcción de la versión Flutter. - -Por favor descarga la librería dinámica de Sciter tú mismo. - -[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) - -## Pasos para compilar desde el inicio - -- Prepara el entorno de desarrollo de Rust y el entorno de compilación de C++ y Rust. - -- Instala [vcpkg](https://github.com/microsoft/vcpkg), y configura la variable de entono `VCPKG_ROOT` correctamente. - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/Osx: vcpkg install libvpx libyuv opus aom - -- Corre `cargo run` - -## Como compilar en linux - -### Ubuntu 18 (Debian 10) - -```sh -sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ - libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### Instala vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Arregla libvpx (Para 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 -``` - -### Compila - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -git clone --recurse-submodules 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 -``` - -## Como compilar con Docker - -Empieza clonando el repositorio y compilando el contenedor de docker: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -git submodule update --init --recursive -docker build -t "rustdesk-builder" . -``` - -Entonces, cada vez que necesites compilar la aplicación, ejecuta el siguiente comando: - -```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 -``` - -Ten en cuenta que la primera compilación puede tardar más tiempo antes de que las dependencias se almacenen en la caché, las siguientes compilaciones serán más rápidas. Además, si necesitas especificar diferentes argumentos al comando de compilación, puedes hacerlo al final del comando en la posición ``. Por ejemplo, si deseas compilar una versión optimizada para publicación, deberas ejecutar el comando anterior seguido de `--release`. El ejecutable resultante estará disponible en la carpeta de destino en tu sistema, y puede ser ejecutado con: - -```sh -target/debug/rustdesk -``` - -O si estas ejecutando una versión para su publicación: - -```sh -target/release/rustdesk -``` - -Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del repositorio de RustDesk, de lo contrario la aplicación puede ser incapaz de encontrar los recursos necesarios. También ten en cuenta que otros subcomandos de cargo como `install` o `run` no estan actualmente soportados usando este metodo, ya que instalarían o ejecutarían el programa dentro del contenedor en lugar del host. - -## Estructura de archivos - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec de video, configuración, tcp/udp wrapper, protobuf, funciones para transferencia de archivos, y otras funciones de utilidad. -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: captura de pantalla -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control del teclado/mouse especificos de cada plataforma -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: sonido/portapapeles/input/servicios de video, y conexiones de red -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: iniciar una conexión "peer to peer" -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicación con [rustdesk-server](https://github.com/rustdesk/rustdesk-server), esperar la conexión remota directa ("TCP hole punching") o conexión indirecta ("relayed") -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico de cada plataforma -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para el cliente web Flutter - -> [!Precaución] -> **Descargo de responsabilidad por uso indebido:**
-> Los desarrolladores de RustDesk no aprueban ni apoyan ningún uso no ético o ilegal de este software. El uso indebido, como el acceso no autorizado, el control o la invasión de la privacidad, está estrictamente en contra de nuestras directrices. Los autores no son responsables de ningún uso indebido de la aplicación. - -## Capturas de pantalla - -![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) diff --git a/docs/README-FA.md b/docs/README-FA.md deleted file mode 100644 index a0645e02b..000000000 --- a/docs/README-FA.md +++ /dev/null @@ -1,159 +0,0 @@ -

- RustDesk - Your remote desktop
- تصاویر محیط نرم‌افزار • - ساختار • - داکر • - ساخت • - سرور -

-

[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) | [YouTube](https://www.youtube.com/@rustdesk) - - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%D9%88%DB%8C%DA%98%DA%AF%DB%8C%E2%80%8C%D9%87%D8%A7%DB%8C%20%D9%BE%DB%8C%D8%B4%D8%B1%D9%81%D8%AA%D9%87-blue)](https://rustdesk.com/pricing.html) - -راست‌دسک (RustDesk) نرم‌افزاری برای کارکردن با رایانه‌ی رومیزی از راه دور است و با زبان برنامه‌نویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آن‌ها کنترل کامل داشته باشید. - -می‌توانید از سرور rendezvous/relay ما استفاده کنید، [سرور خودتان را راه‌اندازی کنید](https://rustdesk.com/server) یا -[ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk). - -ما از مشارکت همه استقبال می کنیم. برای راهنمایی جهت مشارکت به[`docs/CONTRIBUTING.md`](CONTRIBUTING.md) مراجعه کنید. - -[راست‌دسک چطور کار می کند؟](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) - -[دریافت نرم‌افزار](https://github.com/rustdesk/rustdesk/releases) - -## وابستگی ها - -نسخه‌های رومیزی از [sciter](https://sciter.com/) برای رابط کاربری گرافیکی استفاده می‌کنند. خواهشمندیم کتابخانه‌ی پویای sciter را خودتان دانلود کنید از این منابع دریافت کنید. - -- [ویندوز](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) -- [لینوکس](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) -- [مک](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) - -نسخه های همراه از Flutter استفاده می کنند. نسخه‌ی رومیزی را هم از Sciter به Flutter منتقل خواهیم کرد. - -## نیازمندی‌های ساخت - -- محیط توسعه نرم افزار Rust و محیط ساخت ++C خود را آماده کنید - -- نرم افزار [vcpkg](https://github.com/microsoft/vcpkg) را نصب کنید و متغیر `VCPKG_ROOT` را به درستی تنظیم کنید. -- بسته‌های vcpkg مورد نیاز را نصب کنید: - - ویندوز: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static` - - مک و لینوکس: `vcpkg install libvpx libyuv opus aom` -- این دستور را اجرا کنید: `cargo run` - -## [ساخت](https://rustdesk.com/docs/en/dev/build/) - -## نحوه ساخت بر روی لینوکس - -### ساخت بر روی (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 -``` - -### ساخت بر روی (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 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### رفع ایراد libvpx (برای فدورا) - -```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 -``` - -### ساخت - -```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 -``` - -## نحوه ساخت با داکر - -این مخزن Git را دریافت کنید و کانتینر را به روش زیر بسازید - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -سپس، هر بار که نیاز به ساخت نرم‌افزار داشتید، دستور زیر را اجرا کنید: - -```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 -``` - -توجه داشته باشید که نخستین ساخت ممکن است به دلیل محلی نبودن وابستگی‌ها بیشتر طول بکشد. اما دفعات بعدی سریعتر خواهند بود. علاوه بر این، اگر نیاز به تعیین آرگومان های مختلف برای دستور ساخت دارید، می توانید این کار را در انتهای دستور ساخت و از طریق `` انجام دهید. به عنوان مثال، اگر می خواهید یک نسخه نهایی بهینه سازی شده ایجاد کنید، دستور بالا را تایپ کنید و در انتها `release--` را اضافه کنید. فایل اجرایی به دست آمده در پوشه مقصد در سیستم شما در دسترس خواهد بود و می تواند با دستور: - -```sh -target/debug/rustdesk -``` - -یا برای نسخه بهینه سازی شده دستور زیر را اجرا کنید: - -```sh -target/release/rustdesk -``` - -لطفاً اطمینان حاصل کنید که این دستورات را از پوشه مخزن RustDesk اجرا می کنید، در غیر این صورت ممکن است برنامه نتواند منابع مورد نیاز را پیدا کند. همچنین توجه داشته باشید که سایر دستورات فرعی Cargo مانند `install` یا `run` در حال حاضر از طریق این روش پشتیبانی نمی شوند زیرا برنامه به جای سیستم عامل میزبان, در داخل کانتینر نصب و اجرا میشود. - -## ساختار پوشه ها - -- **[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-GR.md b/docs/README-GR.md deleted file mode 100644 index 8b0276bf8..000000000 --- a/docs/README-GR.md +++ /dev/null @@ -1,171 +0,0 @@ -

- 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) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%CE%A0%CF%81%CE%BF%CE%B7%CE%B3%CE%BC%CE%AD%CE%BD%CE%B5%CF%82%20%CE%94%CF%85%CE%BD%CE%B1%CF%84%CF%8C%CF%84%CE%B7%CF%84%CE%B5%CF%82-blue)](https://rustdesk.com/pricing.html) - -Ένα λογισμικό απομακρυσμένης επιφάνειας εργασίας, γραμμένο σε γλώσσα 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`](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) - -## Προαπαιτούμενα για 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 aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- Εκτελέστε `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 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Διόρθωση 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 -``` - -## Πως να κάνετε 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 deleted file mode 100644 index 82d1d5550..000000000 --- a/docs/README-HU.md +++ /dev/null @@ -1,163 +0,0 @@ -

- RustDesk - Your remote desktop
- Szerverek • - Építés • - Docker • - Struktúra • - Képernyőképek
- [English] | [Українська] | [česky] | [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
- Kell a segítséged, hogy lefordítsuk ezt a README-t, a RustDesk UI-t és a Dokumentációt az anyanyelvedre -

- -Beszélgess velünk: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Speci%C3%A1lis%20Funkci%C3%B3k-blue)](https://rustdesk.com/pricing.html) - -A RustDesk egy távoli elérésű asztali szoftver, Rust-ban írva. Működik mindenféle konfiguráció nélkül, feltelepítéssel, vagy anélkül. Az adataidat teljesen te kezeled, nincs szükség aggódásra a harmadik felek miatt. Használhatod a RustDesk punblikus randevú/relay szervereit, [hostolhatsz sajátot](https://rustdesk.com/server), vagy akár [írhatsz is egyet](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -A RustDesk szívesen fogad minden contributiont, támogatást mindenkitől. Lásd a [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) fájlt a kezdéshez. - -[**Hogyan működik a RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) - -[**BINARY LELTÖLTÉS**](https://github.com/rustdesk/rustdesk/releases) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## Dependencies - -Az asztali verziók [sciter](https://sciter.com/)-t használnak a GUI-hoz, kérlek telepítsd a dynamikus könyvtárat magad. - -[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) - -A telefonos verziók Flutter-t hasznának. Később lehetséges hogy Sciterről Flutterre migrálunk az asztali verziókban is. - -## Építési pontok - -- Készítsd elő a Rust, C++ fejlesztői környezetet (env) - -- Telepítsd a [vcpkg](https://github.com/microsoft/vcpkg)-t, és állítsd be a `VCPKG_ROOT` környezeti változót helyesen - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- Futtasd a `cargo run` parancsot - -## [Építés](https://rustdesk.com/docs/hu/dev/build/) - -## Hogyan építs Linuxon - -### 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 -``` - -### 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 -``` - -### Telepítsd a vcpkg-t - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Fixeld a libvpx-t (Fedora-n csak) - -```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 -``` - -### Építés - -```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 -``` - -## Hogyan építs Dockerrel - -Kezdjünk a repo clónozásával, majd pedig a Docker container megépítésével: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Ezután, minden egyes alkalommal amikor meg kell építened a RustDesk-et, futtasd a kövezkező parancsot: - -```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 -``` - -Fontos, hogy az első építés lehet hogy több ideig fog tartani mint a következőek, mivel a dependenciek még nincsenek cachelve. Emelett, ha esetleg szeretnél valamilyen argumentumot hozzáadni az építő parancshoz, akkor megteheted a paracssor végén, a `` argumentum használatával. Például ha egy optimalizált release éptést szeretnél megépíteni, akkor add hozzá a fenti parancsorhoz a `--release` opciót. A futtatható binary elérhető lesz a target mappában a rendszereden, futtatni a következőképpen tudod: - -```sh -target/debug/rustdesk -``` - -Vagy ha release binary, akkor: - -```sh -target/release/rustdesk -``` - -Kérlek mindenképpen nézd meg hogy ezeket a parancsokat a root RustDesk mappában futtatod e, különben a RustDesk lehet hogy nem fogja megtalálni az építéshez szükséges elemeket. Fontos az is, hogy jelenleg más cargo subparancsok, például `install`vagy `run` nem támogatottak, mivel egy Dockeres építés esetén elindítanák a programot a containeren belül. - - -## Fájl Struktúra - -- **[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 - -## Képernyőképek - -![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-ID.md b/docs/README-ID.md deleted file mode 100644 index 7b63d0e7e..000000000 --- a/docs/README-ID.md +++ /dev/null @@ -1,166 +0,0 @@ -

- RustDesk - Your remote desktop
- Servers • - Build • - Docker • - Structure • - Snapshot
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
- Kami membutuhkan bantuanmu untuk menterjemahkan file README dan RustDesk UI ke Bahasa Indonesia -

- -Mari mengobrol bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Fitur%20Lanjutan-blue)](https://rustdesk.com/pricing.html) - -[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open) - -Merupakan perangkat lunak Remote Desktop yang baru, dan dibangun dengan Rust. Bahkan kamu bisa langsung menggunakannya tanpa perlu melakukan konfigurasi tambahan. Serta memiliki kontrol penuh terhadap semua data, tanpa perlu merasa was-was tentang isu keamanan, dan yang lebih menarik adalah memiliki opsi untuk menggunakan server rendezvous/relay milik kami, [konfigurasi server sendiri](https://rustdesk.com/server), atau [tulis rendezvous/relay server anda sendiri](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk mengajak semua orang untuk ikut berkontribusi. Lihat [`docs/CONTRIBUTING-ID.md`](CONTRIBUTING-ID.md) untuk melihat panduan. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**UNDUH BINARY**](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) - -## Dependensi - -Pada versi desktop, antarmuka pengguna (GUI) menggunakan [Sciter](https://sciter.com/) atau flutter - -Kamu bisa mengunduh Sciter dynamic library disini. - -[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) - -## Langkah awal untuk memulai - -- Siapkan env development Rust dan env build C++ - -- Install [vcpkg](https://github.com/microsoft/vcpkg), dan atur variabel env `VCPKG_ROOT` dengan benar - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- jalankan `cargo run` - -## [Build](https://rustdesk.com/docs/en/dev/build/) - -## Cara Build di 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 -``` - -### 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 -``` - -### Install vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Mengatasi masalah libvpx (Untuk 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 -``` - -## Cara Build dengan Docker - -Mulailah dengan melakukan kloning (clone) repositori dan build dengan docker container: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Selanjutnya, setiap kali ketika kamu akan melakukan build aplikasi, jalankan perintah berikut: - -```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 -``` - -Perlu diingat bahwa pada saat build pertama kali, mungkin memerlukan waktu lebih lama sebelum dependensi di-cache, build berikutnya akan lebih cepat. Selain itu, jika perlu menentukan argumen yang berbeda untuk perintah build, kamu dapat melakukannya di akhir perintah di posisi ``. Misalnya, jika ingin membangun versi rilis yang dioptimalkan, jalankan perintah di atas dan tambahkan `--release`. Hasil eksekusi perintah tersebut akan tersimpan pada target folder di sistem kamu, dan dapat dijalankan dengan: - -```sh -target/debug/rustdesk -``` - -Atau, jika kamu menjalankan rilis yang dapat dieksekusi: - -```sh -target/release/rustdesk -``` - -Harap pastikan bahwa kamu menjalankan perintah ini dari repositori root RustDesk, jika tidak demikian, aplikasi mungkin tidak dapat menemukan sumber yang diperlukan. Dan juga, perintah cargo seperti `install` atau `run` saat ini tidak didukung melalui metode ini karena, proses menginstal atau menjalankan program terjadi di dalam container bukan pada host. - -## Struktur File - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions untuk transfer file, dan beberapa fungsi utilitas lainnya -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: spesifikasi platform 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, dan 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)**: Komunikasi dengan [rustdesk-server](https://github.com/rustdesk/rustdesk-server), menunggu untuk remote direct (TCP hole punching) atau relayed connection -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: kode khusus platform - -## Snapshots - -![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-IT.md b/docs/README-IT.md deleted file mode 100644 index 0393ee6c7..000000000 --- a/docs/README-IT.md +++ /dev/null @@ -1,179 +0,0 @@ -

- RustDesk - il tuo desktop remoto
- Server • - Compilazione • - Docker • - Struttura • - Schermate
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe]
- Abbiamo bisogno del tuo aiuto per tradurre questo file README e la UI RustDesk nella tua lingua nativa -

- -Chatta con noi su: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Funzionalit%C3%A0%20Avanzate-blue)](https://rustdesk.com/pricing.html) - -[![Bounties aperti](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open) - -Ancora un altro software per il controllo remoto del desktop, scritto in Rust. Funziona immediatamente, nessuna configurazione richiesta. Hai il pieno controllo dei tuoi dati, senza preoccupazioni per la sicurezza. Puoi usare il nostro server rendezvous/relay, [configurare il tuo server](https://rustdesk.com/server) o [realizzare il tuo server rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk accoglie il contributo di tutti. Per ulteriori informazioni su come iniziare a contribuire, vedi [CONTRIBUTING.md](CONTRIBUTING-IT.md). - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**SCARICA PROGRAMMA**](https://github.com/rustdesk/rustdesk/releases) - -[**SCARICA NIGHTLY**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## Dipendenze - -Le versioni desktop utilizzano Flutter o Sciter (deprecato) per l'interfaccia utente, questo tutorial è solo per Sciter, poiché è più facile per iniziare. Controlla il nostro [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) per la compilazione della versione Flutter. - -Scarica la libreria dinamica 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) - -## Passaggi per la compilazione - -- Prepara l'ambiente per lo sviluppo e compilazione in Rust e C++ - -- Installa [vcpkg](https://github.com/microsoft/vcpkg), e imposta correttamente la variabile d'ambiente `VCPKG_ROOT` - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- Esegui `cargo run` - -## [Build](https://rustdesk.com/docs/en/dev/build/) - -## Come compilare in 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 -``` - -### Installa vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Correzione libvpx (per 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 -``` - -### Compilazione - -```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 -``` - -## Come compilare con Docker - -Clona il repository e compila i container docker: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Quindi, ogni volta che devi compilare l'applicazione, esegui il seguente comando: - -```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 -``` - -Tieni presente che la prima build potrebbe richiedere più tempo prima che le dipendenze vengano memorizzate nella cache, le build successive saranno più veloci. Inoltre, se hai bisogno di specificare argomenti diversi per il comando build, puoi farlo alla fine del comando nella posizione ``. Ad esempio, se vuoi creare una versione di rilascio ottimizzata, esegui il comando precedentemente indicato seguito da `--release`. L'eseguibile generato sarà creato nella cartella destinazione del sistema e può essere eseguito con: - -```sh -target/debug/rustdesk -``` - -Oppure, se stai avviando un eseguibile di rilascio: - -```sh -target/release/rustdesk -``` - -Assicurati di eseguire questi comandi dalla radice del repository RustDesk, altrimenti l'applicazione potrebbe non essere in grado di trovare le risorse richieste. Nota inoltre che altri sottocomandi cargo come `install` o `run` non sono attualmente supportati tramite questo metodo poiché installerebbero o eseguirebbero il programma all'interno del container anziché nell'host. - -## Struttura dei file - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec video, config, wrapper tcp/udp, protobuf, funzioni per il trasferimento file, e altre funzioni utili. -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: cattura dello schermo -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: controllo tastiera/mouse specifico della piattaforma -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: implementazione del copia e incolla dei file per Windows, Linux, macOS. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: Sciter UI obsoleto (deprecato) -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: servizi audio/appunti/input/video e connessioni di rete -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: avvio di una connessione peer -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: comunica con [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attende la connessione remota diretta (TCP hole punching) oppure indiretta (relayed) -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: codice specifico della piattaforma -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: codice Flutter per desktop e mobile -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript per client web Flutter - -> [!Attenzione] -> **Dichiarazione di non responsabilità per uso improprio:**
-> Gli sviluppatori di RustDesk non approvano né supportano alcun uso non etico o illegale di questo software. L'uso improprio, come l'accesso non autorizzato, il controllo o l'invasione della privacy, è strettamente contro le nostre linee guida. Gli autori non sono responsabili per qualsiasi uso improprio dell'applicazione. - -## Schermate - -![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-JP.md b/docs/README-JP.md deleted file mode 100644 index c9f75640b..000000000 --- a/docs/README-JP.md +++ /dev/null @@ -1,183 +0,0 @@ -

- RustDesk - あなたのためのリモートデスクトップ
- Servers • - Build • - Docker • - Structure • - Snapshot
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe]
- READMEやRustDesk UIRustDesk Docの翻訳者を歓迎します! -

- -私たちと話す: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%E9%AB%98%E5%BA%A6%E3%81%AA%E6%A9%9F%E8%83%BD-blue)](https://rustdesk.com/pricing.html) - -Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分でサーバーをセットアップする](https://rustdesk.com/server) ことも、 [自分でランデブー/リレーサーバを作成する](https://github.com/rustdesk/rustdesk-server-demo)こともできます。 - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDeskは皆さんの貢献を歓迎します。 -貢献の方法については[CONTRIBUTING.md](CONTRIBUTING.md)をご確認ください。 - -[**よくある質問**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**パッケージのダウンロード**](https://github.com/rustdesk/rustdesk/releases) - -[**ナイトリービルド**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[F-Droidで入手する](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## 依存関係 - -デスクトップ版ではGUIにFlutterまたはSciter(非推奨)を使用しますが、チュートリアルでは分かりやすく、簡単なSciterのみを対象に解説しています。Flutterでのビルド方法については[CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)をご覧ください。 - -Sciter dynamic libraryを事前にダウンロードしてください。 - -[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) - -## ビルド手順 - -- 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 aom:x64-windows-static - - Linux/macOSの場合: vcpkg install libvpx libyuv opus aom - -- `cargo run`を実行します。 - -## [ビルド](https://rustdesk.com/docs/en/dev/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 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### 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 -``` - -### ビルド - -```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 -``` - -## Dockerでのビルド方法 - -リポジトリをクローンし、Dockerコンテナを構築します: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -以下のコマンドを実行します: - -```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 -``` -このコマンドはRustDeskをビルドする度に実行する必要があります。 - -初回ビルドは時間がかかるかもしれませんが、2回目以降は依存関係がキャッシュされるため、ビルドにかかる時間が短くなります。 -ビルドコマンドに追加の引数を指定する必要がある場合は、コマンドの最後(``の位置)で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に `--release` を追記し実行します。ビルドされた実行ファイルはあなたのシステムのターゲットフォルダに保存され、下記のコマンドで実行することができます。 - -デバッグビルドを起動する場合: -```sh -target/debug/rustdesk -``` - -リリースビルドを起動する場合: - -```sh -target/release/rustdesk -``` - -コマンドをRustDeskリポジトリのルートから実行していることを確認してください。また、`install` や `run` などの他のcargoサブコマンドは、ホストではなくコンテナ内でプログラムをインストール、実行するため、現在の方法ではサポートされていません。 - -## ファイル構造 - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: ビデオコーデック、設定、tcp/udpラッパー、protobuf、ファイル転送に利用されるfs関数やその他のユーティリティ関数 -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: スクリーンキャプチャ -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: プラットフォーム固有のキーボード/マウス操作 -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows、Linux、macOS向けのファイルのコピーと貼り付けの実装 -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 廃止された Sciter 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ホールパンチング)や中継接続を担う。 -- **[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)**: Flutterウェブクライアント向けのJavaScript - -> [!注意] -> **:不正使用に関する免責事項**
-> RustDeskの開発者は、このソフトウェアの非倫理的または違法な使用を容認または支持しません。不正アクセス、不正な制御、またはプライバシーの侵害などの不正使用は、当社のガイドラインに厳密に違反します。開発者は、アプリケーションの不正使用に対して一切の責任を負いません。 - -## スクリーンショット - -![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) diff --git a/docs/README-KR.md b/docs/README-KR.md deleted file mode 100644 index c301fde05..000000000 --- a/docs/README-KR.md +++ /dev/null @@ -1,182 +0,0 @@ -

- RustDesk - Your remote desktop
- 빌드 • - Docker • - 구조 • - 스냇샷
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe] | [Norsk]
- 이 README, RustDesk UIRustDesk 문서를 귀하의 모국어로 번역하는 데 도움이 필요합니다 -

- -> [!Caution] -> **오용 면책 조항:**
-> RustDesk의 개발자는 이 소프트웨어의 비윤리적 또는 불법적인 사용을 묵인하거나 지원하지 않습니다. 무단 액세스, 제어 또는 개인정보 침해와 같은 오용은 엄격하게 당사의 지침에 위배됩니다. 작성자는 응용 프로그램의 오용에 대해 책임을 지지 않습니다. - - -우리와 채팅: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%EA%B3%A0%EA%B8%89%20%EA%B8%B0%EB%8A%A5-blue)](https://rustdesk.com/pricing.html) - -또 하나의 원격 데스크톱 솔루션으로, Rust로 작성되었습니다. 별도의 설정 없이 바로 사용할 수 있습니다. 데이터에 대한 완전한 통제권을 가지며 보안에 대한 걱정이 없습니다. 저희 랑데부/릴레이 서버를 사용하거나, [직접 설정](https://rustdesk.com/server)하거나, [자신만의 랑데부/릴레이 서버를 작성](https://github.com/rustdesk/rustdesk-server-demo)할 수 있습니다. - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk는 모든 분들의 기여를 환영합니다. 시작하는 데 도움이 필요하면 [CONTRIBUTING-KR.md](CONTRIBUTING-KR.md)를 참조하세요. - -[**자주 묻는 질문**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**바이너리 다운로드**](https://github.com/rustdesk/rustdesk/releases) - -[**개발자 빌드**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## 종속성 - -데스크톱 버전은 GUI로 Flutter 또는 Sciter (더 이상 지원되지 않음)를 사용하며, 이 자습서는 시작하기 더 쉽고 친숙한 Sciter 전용입니다. Flutter 버전 빌드는 [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)을 확인하세요. - -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) - -## 빌드를 위한 원시 단계 - -- 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 aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- `cargo run` 실행 - -## [빌드](https://rustdesk.com/docs/en/dev/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 libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### vcpkg 설치 - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### 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 -``` - -### 빌드 - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -git clone --recurse-submodules 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 -``` - -## Docker로 빌드하는 방법 - -먼저 리포지토리를 복제하고 Docker 컨테이너를 빌드합니다: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -git submodule update --init --recursive -docker build -t "rustdesk-builder" . -``` - -그런 다음 응용 프로그램을 빌드해야 할 때마다 다음 명령을 실행합니다: - -```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 -``` - -첫 번째 빌드는 종속성이 캐시되기까지 시간이 오래 걸릴 수 있으며, 이후 빌드는 더 빨라집니다. 또한 빌드 명령에 다른 인수를 지정해야 하는 경우 명령 끝의 `` 위치에 인수를 지정할 수 있습니다. 예를 들어 최적화된 릴리스 버전을 빌드하려면 위의 명령 뒤에 `--release`를 추가하면 됩니다. 결과 실행 파일은 시스템의 대상 폴더에서 사용할 수 있으며 실행할 수 있습니다:: - -```sh -target/debug/rustdesk -``` - -또는 릴리스 실행 파일을 실행하는 경우: - -```sh -target/release/rustdesk -``` - -RustDesk 리포지토리의 루트에서 이러한 명령을 실행하고 있는지 확인하세요. 그렇지 않으면 응용 프로그램이 필요한 리소스를 찾지 못할 수 있습니다. 또한 `install` 또는 `run` 과 같은 다른 cargo 하위 명령은 호스트가 아닌 컨테이너 내부에 프로그램을 설치하거나 실행하므로 현재 이 방법을 통해 지원되지 않는다는 점에 유의하세요. - -## 파일 구조 - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 비디오 코덱, 구성, tcp/udp wrapper, protobuf, 파일 전송을 위한 fs 함수 및 기타 유틸리티 함수 -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡쳐 -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 플랫폼별 키보드/마우스 제어 -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows, Linux, macOS용 파일 복사 및 붙여넣기 구현 -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 더 이상 사용되지 않는 Sciter 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 홀 펀칭) 또는 릴레이 연결 대기 -- **[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/v1/js)**: Flutter 웹 클라이언트용 JavaScript - -## 스크린샷 - -![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) - diff --git a/docs/README-NO.md b/docs/README-NO.md deleted file mode 100644 index 1352e8aed..000000000 --- a/docs/README-NO.md +++ /dev/null @@ -1,177 +0,0 @@ -

- RustDesk - Your remote desktop
- Servere • - Build • - Docker • - Struktur • - Snapshot
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe] | [Norsk
- Vi trenger din hjelp til å oversette denne README-en, RustDesk UI og RustDesk Doc tid ditt morsmål -

- -Snakk med oss: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Avanserte%20Funksjoner-blue)](https://rustdesk.com/pricing.html) - -Enda en annen fjernstyrt desktop programvare, skrevet i Rust. Virker rett ut av pakken, ingen konfigurasjon nødvendig. Du har full kontroll over din data, uten beskymring for sikkerhet. Du kan bruke vår rendezvous_mediator/relay server, [sett opp din egen](https://rustdesk.com/server), eller [skriv din egen rendezvous_mediator/relay server](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk er velkommen for bidrag fra alle. Se [CONTRIBUTING.md](CONTRIBUTING-NO.md) for hjelp med oppstart. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**BINARY NEDLASTING**](https://github.com/rustdesk/rustdesk/releases) - -[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Få det på F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Få det på Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## Avhengigheter - -Desktop versjoner bruker Flutter eller Sciter (avviklet) for GUI, denne veiledningen er bare for Sciter, grunnet att det er letter og en mer venlig start. Skjekk ut vår [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for bygging av Flutter versjonen. - -Venligst last ned Sciters dynamiske bibliotek selv. - -[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | -[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | -[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) - -## Rå steg for bygging - -- Klargjør ditt Rust development env og C++ build env - -- Installer [vcpkg](https://github.com/microsoft/vcpkg), og koriger `VCPKG_ROOT` env vaiabelen - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- Kjør `cargo run` - -## [Bygg](https://rustdesk.com/docs/en/dev/build/) - -## Hvordan Bygge til Linux - -### Ubuntu 18 (Debian 10) - -```sh -sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ - libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### Installer vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Fiks libvpx (For Fedora) - -```sh -cd vcpkg/buildtrees/libvpx/src -cd * -./configure -sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile -sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile -make -cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ -cd -``` - -### Bygg - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -mkdir -p target/debug -wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so -mv libsciter-gtk.so target/debug -VCPKG_ROOT=$HOME/vcpkg cargo run -``` - -## Hvordan bygge med Docker - -Start med å klone repositoret og bygg Docker konteineren: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Deretter, hver gang du trenger å bygge applikasjonen, kjør følgene kommando: - -```sh -docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder -``` - -Det kan ta lengere tid før avhengighetene blir bufret første gang du bygger, senere bygg er raskere. Hvis du trenger å spesifisere forkjellige argumenter til bygge kommandoen, kan du gjøre det på slutten av kommandoen ved `` feltet. For eksempel, hvis du ville bygge en optimalisert release versjon, ville du kjørt kommandoen over fulgt `--release`. Den kjørbare filen vill være tilgjengelig i mål direktive på ditt system, og kan bli kjørt med: - -```sh -target/debug/rustdesk -``` - -Eller, hvis du kjører ett release program: - -```sh -target/release/rustdesk -``` - -Venligst pass på att du kjører disse kommandoene fra roten av RustDesk repositoret, eller kan det hende att applikasjon ikke finner de riktige ressursene. Pass også på att andre cargo subkommandoer som for eksempel `install` eller `run` ikke støttes med denne metoden da de vill installere eller kjøre programmet i konteineren istedet for verten. - -## Fil Struktur - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video kodek, configurasjon, tcp/udp innpakning, protobuf, fs funksjon for fil overføring, og noen andre verktøy funksjoner -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: skjermfangst -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform spesefik keyboard/mus kontroll -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: fil kopi og innliming implementasjon for Windows, Linux, macOS. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: foreldret Sciter UI (avviklet) -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: lyd/utklippstavle/input/video tjenester, og internett tilkobling -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start en peer tilkobling -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Kommunikasjon med [rustdesk-server](https://github.com/rustdesk/rustdesk-server), vent på direkte fjernstyring (TCP hulling) eller vidresendt tilkobling -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform spesefik kode -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter kode for desktop og mobil -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter nettsted klient - -## Skjermbilder - -![Tilkoblings Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Koble til Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![Fil Overføring](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) - diff --git a/docs/README-PTBR.md b/docs/README-PTBR.md deleted file mode 100644 index 6c3e6b99f..000000000 --- a/docs/README-PTBR.md +++ /dev/null @@ -1,152 +0,0 @@ -

- RustDesk - Seu desktop remoto
- Servidores • - Compilar • - Docker • - Estrutura • - Screenshots
- [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 -

- -Converse conosco: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Recursos%20Avan%C3%A7ados-blue)](https://rustdesk.com/pricing.html) - -Mais um software de desktop remoto, escrito em Rust. Funciona por padrão, sem necessidade de configuração. Você tem completo controle de seus dados, sem se preocupar com segurança. Você pode usar nossos servidores de rendezvous/relay, [configurar seu próprio](https://rustdesk.com/server), ou [escrever seu próprio servidor de rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo). - -RustDesk acolhe contribuições de todos. Leia [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) para ver como começar. - -[**DOWNLOAD DE BINÁRIOS**](https://github.com/rustdesk/rustdesk/releases) - -## Dependências - -Versões de desktop utilizam [sciter](https://sciter.com/) para a GUI, por favor baixe a biblioteca dinâmica sciter por conta própria. - -[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) - -## Compilação crua - -- Prepare seu ambiente de desenvolvimento Rust e ambiente de compilação C++ - -- Instale [vcpkg](https://github.com/microsoft/vcpkg), e configure a variável de ambiente `VCPKG_ROOT` corretamente - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/MacOS: vcpkg install libvpx libyuv opus aom - -- Execute `cargo run` - -## Como compilar no Linux - -### 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 -``` - -### 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 -``` - -### Instale vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Conserte libvpx (Para o 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 -``` - -### Compile - -```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 -``` - -## Como compilar com Docker - -Comece clonando o repositório e montando o container docker: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Então, sempre que precisar compilar a aplicação, execute este comando: - -```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 -``` - -Note que a primeira compilação pode demorar mais antes que as dependências sejam armazenadas em cache, as compilações subsequentes serão mais rápidas. Adicionalmente, se você precisar especificar argumentos diferentes para o comando de compilação, você pode fazê-lo ao final do comando na posição do ``. Por exemplo, se você gostaria de compilar uma versão de release otimizada, você executaria o comando acima seguido de `--release`. O executável gerado estará disponível no diretório alvo no seu sistema, e pode ser executado com: - -```sh -target/debug/rustdesk -``` - -Ou, se estiver rodando um executável de release: - -```sh -target/release/rustdesk -``` - -Por favor verifique que está executando estes comandos da raiz do repositório do RustDesk, senão a aplicação pode não encontrar os recursos necessários. Note também que outros subcomandos do cargo como `install` ou `run` não são suportados atualmente via este método, já que eles iriam instalar ou rodar o programa dentro do container ao invés do host. - -## Estrutura de arquivos - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec de vídeo, configurações, wrapper de tcp/udp, protobuf, funções de sistema de arquivos para transferência de arquivos, e outras funções utilitárias -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: captura de tela -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: controle de teclado/mouse específico a cada plataforma -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: serviços de áudio/área de transferência/entrada/vídeo, e conexões de rede -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: iniciar uma conexão "peer to peer" -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicação com [rustdesk-server](https://github.com/rustdesk/rustdesk-server), aguardar pela conexão remota direta (TCP hole punching) ou conexão indireta (relayed) -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico a cada plataforma - -> [!Cuidadob] -> **Aviso de uso indevido:**
-> Os desenvolvedores do RustDesk não aprovam nem apoiam qualquer uso antiético ou ilegal deste software. O uso indevido, como acesso não autorizado, controle ou invasão de privacidade, é estritamente contra nossas diretrizes. Os autores não são responsáveis por qualquer uso indevido da aplicação. - -## Screenshots - -![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-RO.md b/docs/README-RO.md deleted file mode 100644 index be7ecf164..000000000 --- a/docs/README-RO.md +++ /dev/null @@ -1,181 +0,0 @@ -

- RustDesk - desktopul tău la distanță
- Construire • - Docker • - Structură • - Capturi
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe] | [Norsk] | [Română]
- Avem nevoie de ajutorul tău pentru a traduce acest README, RustDesk UI și RustDesk Doc în limba ta maternă -

- -> [!Atenție] -> **Declinare de responsabilitate privind utilizarea abuzivă:**
-> Dezvoltatorii RustDesk nu susțin sau aprobă utilizarea neetică sau ilegală a acestui software. Utilizarea abuzivă, cum ar fi accesul neautorizat, controlul sau invadarea intimității, este strict împotriva regulilor noastre. Autorii nu sunt responsabili pentru utilizarea necorespunzătoare a aplicației. - - -Conversați cu noi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Advanced%20Features-blue)](https://rustdesk.com/pricing.html) - -Încă o soluție de desktop la distanță scrisă în Rust. Funcționează imediat, fără configurare necesară. Ai control total asupra datelor tale, fără probleme de securitate. Poți folosi serverul nostru de rendezvous/relay, [să-ți configurezi propriul server](https://rustdesk.com/server) sau [să scrii propriul server de rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo). - -![imagine](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk primește contribuții de la oricine. Vezi [CONTRIBUTING.md](../docs/CONTRIBUTING.md) pentru ajutor la început. - -[**ÎNTREBĂRI FRECVENTE (FAQ)**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**DESCĂRCARE BINARE**](https://github.com/rustdesk/rustdesk/releases) - -[**BUILD NIGHTLY**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## Dependențe - -Versiunile desktop folosesc Flutter sau Sciter (depreciat) pentru interfață; acest ghid este pentru Sciter doar, deoarece este mai ușor și mai prietenos pentru început. Vezi [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) pentru construire cu Flutter. - -Te rugăm să descarci singur librăria dinamică 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) - -## Pași pentru construire (Raw Steps to build) - -- Pregătește mediul de dezvoltare Rust și mediul de construire C++ - -- Instalează [vcpkg](https://github.com/microsoft/vcpkg) și setează corect variabila de mediu `VCPKG_ROOT` - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- rulează `cargo run` - -## [Construire](https://rustdesk.com/docs/en/dev/build/) - -## Cum se construiește pe Linux - -### Ubuntu 18 (Debian 10) - -```sh -sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ - libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ - libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### Instalează vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Repară libvpx (Pentru 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 --recurse-submodules 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 -``` - -## Cum să construiești cu Docker - -Începe prin clonarea repository-ului și construirea imaginii Docker: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -git submodule update --init --recursive -docker build -t "rustdesk-builder" . -``` - -Apoi, de fiecare dată când trebuie să construiești aplicația, rulează comanda următoare: - -```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 -``` - -Reține că prima construire poate dura mai mult până când dependențele sunt în cache; construirile ulterioare vor fi mai rapide. De asemenea, dacă trebuie să specifici argumente diferite comenzii de build, le poți adăuga la finalul comenzii în poziția ``. De exemplu, pentru a construi o versiune optimizată de release, adaugă `--release`. Executabilul rezultat va fi disponibil în folderul `target` pe sistemul tău, și poate fi rulat cu: - -```sh -target/debug/rustdesk -``` - -Sau, dacă rulezi un executabil release: - -```sh -target/release/rustdesk -``` - -Asigură-te că rulezi aceste comenzi din rădăcina repository-ului RustDesk, altfel aplicația poate să nu găsească resursele necesare. De asemenea, reține că alte subcomenzi cargo, cum ar fi `install` sau `run`, nu sunt acceptate în prezent prin această metodă, deoarece ar instala sau rula programul în interiorul containerului în loc de gazdă. - -## Structura fișierelor - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec video, config, wrapper tcp/udp, protobuf, funcții fs pentru transfer de fișiere și alte funcții utilitare -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: capturare ecran -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control tastatură/mouse specific platformei -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: implementare copy/paste pentru fișiere pentru Windows, Linux, macOS. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: interfață Sciter învechită (depreciată) -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: servicii audio/clipboard/input/video și conexiuni de rețea -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: inițiază o conexiune peer -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: comunică cu [rustdesk-server](https://github.com/rustdesk/rustdesk-server), așteaptă conexiune directă remote (TCP hole punching) sau prin relay -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: cod specific platformei -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: cod Flutter pentru desktop și mobil -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: JavaScript pentru clientul Flutter web - -## Capturi de ecran - -![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) diff --git a/docs/README-RU.md b/docs/README-RU.md deleted file mode 100644 index 928faad07..000000000 --- a/docs/README-RU.md +++ /dev/null @@ -1,183 +0,0 @@ -

- RustDesk - Ваш удаленый рабочий стол
- Первичные шаги для сборки • - Как собрать с помощью Docker • - Структура файлов • - Скриншоты
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
- Нам нужна ваша помощь в переводе этого README, интерфейса RustDesk - и документации RustDesk на ваш родной язык. -

- -> [!Caution] -> **Отказ от ответственности за неправомерное использование**
-> Разработчики RustDesk не одобряют и не поддерживают какое-либо неэтичное или незаконное использование данного программного обеспечения. Неправомерное использование (несанкционированный доступ, контроль или вторжение в частную жизнь) строго противоречит нашим правилам. Авторы не несут ответственности за любое неправомерное использование приложения. - -Общение с нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5%20%D0%92%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D0%B8-blue)](https://rustdesk.com/pricing.html) - -Ещё одно программное обеспечение для удаленного рабочего стола, написанное на Rust. Работает из коробки, настройки не требует. Вы полностью контролируете свои данные, не беспокоясь о безопасности. Вы можете использовать наш сервер ретрансляции, [настроить свой собственный](https://rustdesk.com/server), или [написать свой](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk приветствует вклад каждого. Ознакомьтесь с [`docs/CONTRIBUTING-RU.md`](CONTRIBUTING-RU.md) в начале работы для понимания. - -[**Как работает RustDesk?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) (Документация на английском языке) - -[**Часто задаваемые вопросы**](https://github.com/rustdesk/rustdesk/wiki/FAQ) (Страница на английском языке) - -[**СКАЧАТЬ ПРИЛОЖЕНИЕ**](https://github.com/rustdesk/rustdesk/releases) - -[**НОЧНЫЕ СБОРКИ (Актуальные)**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) -[Get it on Flathub](https://flathub.org/apps/com.rustdesk.RustDesk) - -## Зависимости - -Для ПК-версии используются библиотеки Flutter или Sciter (устаревшее) для графического интерфейса. Данное руководство подразумевает работу с Sciter, так как он более простой в использовании и с ним легче начать работу. Вы можете также посмотреть на механизм нашего [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) для сборок на Flutter. - -Загрузите динамическую библиотеку Flutter самостоятельно. - -[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) - -## Первичные шаги для сборки - -- Подготовьте среду разработки 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 aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- Выполните команду `cargo run` - -## [Сборка](https://rustdesk.com/docs/ru/dev/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 libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### Установка vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Исправление 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 -``` - -### Сборка - -```sh -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -source $HOME/.cargo/env -git clone --recurse-submodules 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 -``` - -## Как собрать с помощью Docker - -Начните с клонирования репозитория и создания docker-контейнера: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -git submodule update --init --recursive -docker build -t "rustdesk-builder" . -``` - -Затем при каждой сборке приложения выполняйте следующую команду: - -```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 -``` - -Обратите внимание, что первая сборка может занять больше времени, прежде чем зависимости будут кэшированы, но последующие сборки будут выполняться быстрее. Кроме того, если вам нужно указать другие аргументы для команды сборки, вы можете сделать это в конце команды в переменной ``. Например, если вы хотите создать оптимизированную версию, вы должны выполнить приведенную выше команду и в конце строки добавить `--release`. Полученный исполняемый файл будет доступен в целевой папке вашей системы и может быть запущен с помощью следующей команды: - -```sh -target/debug/rustdesk -``` - -Или, если вы используете исполняемый файл релиза: - -```sh -target/release/rustdesk -``` - -Пожалуйста, убедитесь, что вы запускаете эти команды из корня репозитория RustDesk, иначе приложение не сможет найти необходимые ресурсы. Также обратите внимание, что другие подкоманды Cargo, такие как `install` или `run`, в настоящее время не поддерживаются этим методом, поскольку они будут устанавливать или запускать программу внутри контейнера, а не на хосте. - -## Структура файлов - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: видеокодек, конфигурация, враппер TCP/UDP, protobuf, функции файловой системы для передачи файлов и некоторые другие служебные функции -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: захват экрана -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: специфичное для платформы управление клавиатурой/мышью -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: функционал буфера обмена файлами для Windows, Linux, и macOS -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графический пользовательский интерфейс на Sciter (устаревшее) -- **[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](https://github.com/rustdesk/rustdesk-server), ожидает удаленного прямого (через TCP hole punching) или ретранслируемого соединения -- **[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/v1/js)**: JavaScript для Web-клиента Flutter - -## Скриншоты - -![Менеджер соединений](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Подключение к удалённому рабочему столу на Windows](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![Передача файлов](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![TCP-туннелирование](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) \ No newline at end of file diff --git a/docs/README-TR.md b/docs/README-TR.md deleted file mode 100644 index 99c961e8b..000000000 --- a/docs/README-TR.md +++ /dev/null @@ -1,181 +0,0 @@ - -

- RustDesk - Uzak masaüstü uygulamanız
- Sunucular • - Derleme • - Docker ile Derleme • - Dosya Yapısı • - Ekran Görüntüleri
- [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά]
- README, RustDesk UI ve RustDesk Dökümantasyonu'nu ana dilinize çevirmemiz için yardımınıza ihtiyacımız var -

- - -> [!Dikkat] -> **Yanlış Kullanım Uyarısı:**
-> RustDesk geliştiricileri, bu yazılımın etik olmayan veya yasa dışı kullanımını onaylamaz veya desteklemez. Yetkisiz erişim, kontrol veya gizlilik ihlali gibi kötüye kullanımlar kesinlikle yönergelerimize aykırıdır. Yazarlar, uygulamanın herhangi bir yanlış kullanımından sorumlu değildir. - -Bizimle sohbet edin: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Geli%C5%9Fmi%C5%9F%20%C3%96zellikler-blue)](https://rustdesk.com/pricing.html) - -Rust dilinde yazılmış, başka bir uzak masaüstü yazılımı daha. Hiçbir yapılandırma gerekmeksizin, hemen kullanıma hazır. Güvenlik konusunda hiçbir endişe duymadan, verileriniz üzerinde tam kontrole sahip olun. Kendi rendezvous/relay sunucumuzu kullanabilirsiniz, [kendi sunucunuzu kurabilirsiniz](https://rustdesk.com/server) veya [kendi rendezvous/relay sunucunuzu yazabilirsiniz](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk, herkesin katkısına açıktır. Başlamak için [CONTRIBUTING.md](CONTRIBUTING-TR.md) belgesine göz atın. - -[**SSS**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**BINARY İNDİR**](https://github.com/rustdesk/rustdesk/releases) - -[**NIGHTLY DERLEME**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[F-Droid'de Alın](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## Gereksinimler - -Masaüstü sürümleri GUI için; [Sciter](https://sciter.com/)(kaldırılacak) veya Flutter kullanır. Sciter daha kolay ve başlamak için daha dostcanlısı, bundan dolayı bu kılavuz sadece Sciter içindir. Flutter sürümünü derlemek için [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)'ımıza bakın. - -Lütfen Sciter dinamik kütüphanesini kendiniz indirin. - -[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) - -## Temel Derleme Adımları - -- Rust geliştirme ortamınızı ve C++ derleme ortamınızı hazırlayın. - -- [vcpkg](https://github.com/microsoft/vcpkg) yükleyin ve `VCPKG_ROOT` ortam değişkenini doğru bir şekilde ayarlayın. - - - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- `cargo run` komutunu çalıştırın. - -## [Derleme](https://rustdesk.com/docs/en/dev/build/) - -## Linux Üzerinde Derleme Nasıl Yapılır - -### 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'yi Yükleyin - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### libvpx'i Düzeltin (Fedora için) - -```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 -``` - -### Derleme - -```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 -``` - -## Docker ile Derleme Nasıl Yapılır - -Önce repository'i klonlayın ve Docker container'ını oluşturun. - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Ardından, uygulamayı her derlemeniz gerektiğinde aşağıdaki komutu çalıştırın: - -```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 -``` - -Bilin ki ilk derlemeniz gereksinimlerin önbelleği yüklenmesinden ötürü uzun sürebilir, sonraki derlemeleriniz daha hızlı olacaktır. Ayrıca, derleme komutuna isteğe bağlı argümanlar belirtmeniz gerekiyorsa, bunu komutun sonunda ki `` yerine yazabilirsiniz. Örneğin, optimize edilmiş bir sürümü derlemek isterseniz, yukarıdaki komutu çalıştırdıktan sonra `--release` ekleyebilirsiniz. Oluşan çalıştırılabilir dosya sisteminizdeki hedef klasöründe bulunacak ve şu komutla çalıştırılabilir olacaktır: - -```sh -target/debug/rustdesk -``` - -Veya, yayım çalıştırılabilir dosyası için: - -```sh -target/release/rustdesk -``` - -Lütfen bu komutları RustDesk reposunun root klasöründe çalıştırdığınızdan emin olun, aksi takdirde uygulama gereken kaynakları bulamayabilir. Ayrıca, `install` veya `run` gibi diğer cargo altkomutları şu anda bu yöntem aracılığıyla desteklenmemektedir, çünkü bunlar programı konteyner içinde kurar veya çalıştırır, ana makinede değil. - -## Dosya Yapısı - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, dosya transferi için fs fonksiyonları ve diğer bazı yardımcı işlevler -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ekran yakalama -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platforma özgü klavye/fare kontrolü -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: platforma özgü kopyala/yapıştır implementasyonları. -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: Eski Sciter UI (kaldırılacak) -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: ses/pano/input/video servisleri ve ağ bağlantıları -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: Eşli bağlantı başlat -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server) ile iletişime gir, remote direct(TCP delik açma) yada relay bağlantısı için bekle -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platforma özgü kod -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Masaüstü ve mobil için Flutter kodu -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: Flutter web istemcisi için JavaScript - - -## Ekran Görüntüleri - -![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-UA.md b/docs/README-UA.md deleted file mode 100644 index eb4c9edec..000000000 --- a/docs/README-UA.md +++ /dev/null @@ -1,174 +0,0 @@ -

- RustDesk - Ваша віддалена стільниця
- Сервери • - Збирання • - Docker • - Структура • - Знімки екрана
- [English] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Dansk] | [Ελληνικά] | [Türkçe]
- Нам потрібна ваша допомога для перекладу цього README, інтерфейсу та документації RustDesk вашою рідною мовою -

- -Спілкування з нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%D0%A0%D0%BE%D0%B7%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D1%96%20%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D1%96%D1%97-blue)](https://rustdesk.com/pricing.html) - -Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk вітає внесок кожного. Ознайомтеся з [CONTRIBUTING.md](CONTRIBUTING.md), щоб отримати допомогу на початковому етапі. - -[**ЧаПи**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**ЗАВАНТАЖЕННЯ ЗАСТОСУНКУ**](https://github.com/rustdesk/rustdesk/releases) - -[**НІЧНІ ЗБІРКИ**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## Залежності - -Стільничні версії використовують Flutter чи Sciter (застаріле) для графічного інтерфейсу. Ця інструкція лише для Sciter, оскільки він є більш простим та дружнім для початківців. Перегляньте [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) для збірки версії на Flutter. - -Будь ласка, завантажте динамічну бібліотеку 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) - -## Кроки для збірки - -- Підготуйте середовище розробки 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 aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- Запустіть `cargo run` - -## [Збирання](https://rustdesk.com/docs/en/dev/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 libpam0g-dev -``` - -### openSUSE Tumbleweed - -```sh -sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel -``` - -### Fedora 28 (CentOS 8) - -```sh -sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel -``` - -### Arch (Manjaro) - -```sh -sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire -``` - -### Встановлення vcpkg - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Виправлення 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 -``` - -### Збирання - -```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 -``` - -## Як зібрати за допомогою Docker - -Почніть з клонування сховища та створення docker-контейнера: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Надалі щоразу, коли вам буде потрібно зібрати застосунок, запускайте таку команду: - -```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 -``` - -Зверніть увагу, що перша збірка може зайняти більше часу, перш ніж залежності будуть кешовані, але наступні збірки будуть виконуватися швидше. Крім того, якщо вам потрібно вказати інші аргументи для команди збірки, ви можете зробити це в кінці команди у змінній ``. Наприклад, якщо ви хочете створити оптимізовану версію, ви маєте запустити наведену вище команду і в кінці рядка додати `--release`. Отриманий виконуваний файл буде доступний у цільовій папці вашої системи і може бути запущений за допомогою: - -```sh -target/debug/rustdesk -``` - -Або, якщо ви використовуєте виконуваний файл релізу: - -```sh -target/release/rustdesk -``` - -Будь ласка, переконайтеся, що ви запускаєте ці команди з кореня сховища RustDesk, інакше додаток не зможе знайти необхідні ресурси. Також зверніть увагу, що інші cargo підкоманди, такі як `install` або `run`, наразі не підтримуються цим методом, оскільки вони будуть встановлювати або запускати програму всередині контейнера, а не на хості. - -## Структура файлів - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: відеокодек, конфіг, обгортка tcp/udp, protobuf, функції fs для передавання файлів і деякі інші службові функції -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: захоплення екрана -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: специфічне для платформи керування клавіатурою/мишею -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: реалізація копіювання та вставлення файлів для Windows, Linux, macOS. -- **[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/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 - -## Знімки екрана - -![Менеджер зʼєднань](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) - -![Підключення до ПК з Windows](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea) - -![Передача файлів](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) - -![Тунелювання TCP](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) - diff --git a/docs/README-VN.md b/docs/README-VN.md deleted file mode 100644 index 38cdc10fb..000000000 --- a/docs/README-VN.md +++ /dev/null @@ -1,161 +0,0 @@ - - -

- RustDesk - Your remote desktop
- Server • - Build • - Docker • - Structure • - Snapshot
- [English] | [Українська] | [česky] | [中文] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Ελληνικά]
- Chúng tôi rất hoan nghênh sự hỗ trợ của bạn trong việc dịch trang README, trang giao diện người dùng của RustDesk - RustDesk UI và trang tài liệu của RustDesk - RustDesk Doc sang Tiếng Việt -

- -Hãy trao đổi với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-T%C3%ADnh%20N%C4%83ng%20N%C3%A2ng%20Cao-blue)](https://rustdesk.com/pricing.html) - -RustDesk là một phần mềm điểu khiển máy tính từ xa mã nguồn mở, được viết bằng Rust. Nó hoạt động ngay sau khi cài đặt, không yêu cầu cấu hình phức tạp. Bạn có toàn quyền kiểm soát với dữ liệu của mình mà không cần phải lo lắng về vấn đề bảo mật. Bạn có thể sử dụng máy chủ rendezvous/relay của chúng tôi hoặc [tự cài đặt máy chủ của riêng mình](https://rustdesk.com/server) hay thậm chí [tự tạo máy chủ rendezvous/relay cho riêng bạn](https://github.com/rustdesk/rustdesk-server-demo). - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -**RustDesk** luôn hoan nghênh mọi đóng góp từ mọi người. Hãy xem tệp [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) để bắt đầu. - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) -[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) -[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/FAQreleases/tag/nightly) - -[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) - -## Dependencies - -Phiên bản máy tính sử dụng __Flutter__ hoặc __Sciter__ (đã lỗi thời) cho giao diện người dùng (GUI). Hướng dẫn này chỉ áp dụng cho phiên bản Sciter, vì nó thân thiện và dễ bắt đầu hơn. Hãy kiểm tra [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) của chúng tôi để xây dựng phiên bản Flutter. - -Vui lòng tự tải thư viện `Sciter` về máy theo hướng dẫn cho từng hệ điều hành. - -[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) - -## Các bước build cơ bản - -- Chuẩn bị môi trường phát triển Rust và môi trường biên dịch C++ - -- Tải và cài đặt [`vcpkg`](https://github.com/microsoft/vcpkg), và thiết lập biến môi trường `VCPKG_ROOT`. - - - Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static` - - Linux/MacOS: `vcpkg install libvpx libyuv opus aom` -- Chạy lệnh `cargo run` - -## [Build](https://rustdesk.com/docs/en/dev/build/) - -## Cách build cho Linux - -### 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 -``` - -### 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 -``` - -### Cách cài đặt `vcpkg` - -```sh -git clone https://github.com/microsoft/vcpkg -cd vcpkg -git checkout 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### Cách sửa lỗi `libvpx` (Dành cho hệ điều hành 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 -``` - -## Cách build bằng Docker - -Bắt đầu bằng cách sao chép repo này về máy tính của bạn và tạo Docker container: - -```sh -git clone https://github.com/rustdesk/rustdesk -cd rustdesk -docker build -t "rustdesk-builder" . -``` - -Sau đó, mỗi khi bạn chạy ứng dụng, thì hãy chạy dòng lệnh sau: - -```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 -``` - -Lưu ý rằng **lần build đầu tiên có thể mất thời gian hơn trước khi các dependencies được lưu vào bộ nhớ cache**, nhưng các lần build sau sẽ nhanh hơn. Ngoài ra, nếu bạn cần chỉ định các đối số khác cho lệnh build, bạn có thể thêm chúng vào cuối lệnh ở phần ``. Ví dụ, nếu bạn muốn build phiên bản tối ưu hóa, bạn sẽ chạy lệnh trên với tùy chọn `--release`. Kết quả biên dịch sẽ được lưu trong thư mục target trên máy tính của bạn, và có thể chạy với lệnh: - -```sh -target/debug/rustdesk -``` - -Nếu bạn đang chạy bản build được tối ưu hóa, thì bạn có thể chạy với lệnh: - -```sh -target/release/rustdesk -``` - -Hãy đảm bảo rằng bạn đang chạy các lệnh này từ gốc của thư mục **RustDesk**, nếu không, ứng dụng có thể không thể tìm thấy các tệp tài nguyên cần thiết. Hãy lưu ý rằng các câu lệnh con khác của **cargo** như **install** hoặc **run** hiện không được hỗ trợ qua phương pháp này, vì chúng sẽ cài đặt hoặc chạy chương trình bên trong **container** thay vì trên máy tính của bạn. - -## Cấu trúc tệp tin - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, cấu hình, tcp/udp wrapper, protobuf, fs functions để truyền file, và một số hàm tiện ích khác -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: ghi lại màn hình -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: điều khiển máy tính/chuột trên các nền tảng khác nhau -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: giao diện người dùng -- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: các dịch vụ âm thanh, clipboard, đầu vào, video và các kết nối mạng -- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: bắt đầu kết nối với một peer -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: giao tiếp với [rustdesk-server](https://github.com/rustdesk/rustdesk-server), đợi kết nối trực tiếp (TCP hole punching) hoặc kết nối được chuyển tiếp. -- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: mã nguồn riêng cho mỗi nền tảng -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Mã Flutter dành máy tính và điện thoại -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Mã JavaScript dành cho giao diện trên web bằng Flutter - -## Snapshot - -![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-ZH.md b/docs/README-ZH.md deleted file mode 100644 index 9328e52e9..000000000 --- a/docs/README-ZH.md +++ /dev/null @@ -1,233 +0,0 @@ -

- RustDesk - Your remote desktop
- 服务器 • - 编译 • - Docker • - 结构 • - 截图
- [English] | [Українська] | [česky] | [Magyar] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي] | [Tiếng Việt] | [Ελληνικά]
-

- -> [!CAUTION] -> **免责声明:**
-> RustDesk 的开发人员不纵容或支持任何不道德或非法的软件使用行为。滥用行为,例如未经授权的访问、控制或侵犯隐私,严格违反我们的准则。作者对应用程序的任何滥用行为概不负责。 - -与我们交流: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) - -[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-%E9%AB%98%E7%BA%A7%E5%8A%9F%E8%83%BD-blue)](https://rustdesk.com/pricing.html) - -远程桌面软件,开箱即用,无需任何配置。您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器, -或者[自己设置](https://rustdesk.com/server), -亦或者[开发您的版本](https://github.com/rustdesk/rustdesk-server-demo)。 - -![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) - -RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING-ZH.md](CONTRIBUTING-ZH.md). - -[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) - -[**BINARY DOWNLOAD**](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) - -## 依赖 - -桌面版本使用 Flutter 或 Sciter(已弃用)作为 GUI,本教程仅适用于 Sciter,因为它更简单且更易于上手。查看我们的[CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)以构建 Flutter 版本。 - -请自行下载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) - -## 基本构建步骤 - -- 请准备好 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 aom:x64-windows-static - - Linux/macOS: vcpkg install libvpx libyuv opus aom - -- 运行 `cargo run` - -## [构建](https://rustdesk.com/docs/en/dev/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 2023.04.15 -cd .. -vcpkg/bootstrap-vcpkg.sh -export VCPKG_ROOT=$HOME/vcpkg -vcpkg/vcpkg install libvpx libyuv opus aom -``` - -### 修复 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 -``` - -### 构建 - -```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 -``` - -## 使用 Docker 编译 - -克隆版本库并构建 Docker 容器: - -```sh -git clone https://github.com/rustdesk/rustdesk # 克隆Github存储库 -cd rustdesk # 进入文件夹 -docker build -t "rustdesk-builder" . # 构建容器 -``` - -请注意: -* 针对国内网络访问问题,可以做以下几点优化: - 1. Dockerfile 中修改系统的源到国内镜像 - ``` - 在Dockerfile的RUN apt update之前插入两行: - - RUN sed -i "s|deb.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list && \ - sed -i "s|security.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list - ``` - - 2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码: - - ``` - RUN echo '[source.crates-io]' > ~/.cargo/config \ - && echo 'registry = "https://github.com/rust-lang/crates.io-index"' >> ~/.cargo/config \ - && echo '# 替换成你偏好的镜像源' >> ~/.cargo/config \ - && echo "replace-with = 'sjtu'" >> ~/.cargo/config \ - && echo '# 上海交通大学' >> ~/.cargo/config \ - && echo '[source.sjtu]' >> ~/.cargo/config \ - && echo 'registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"' >> ~/.cargo/config \ - && echo '' >> ~/.cargo/config - ``` - - 3. Dockerfile 中加入代理的 env - - ``` - 在User root后插入两行 - - ENV http_proxy=http://host:port - ENV https_proxy=http://host:port - ``` - - 4. docker build 命令后面加上 proxy 参数 - - ``` - docker build -t "rustdesk-builder" . --build-arg http_proxy=http://host:port --build-arg https_proxy=http://host:port - ``` - -### 构建 RustDesk 程序 - -然后, 每次需要构建应用程序时, 运行以下命令: - -```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 -``` - -请注意: -* 因为需要缓存依赖项,首次构建一般很慢(国内网络会经常出现拉取失败,可以多试几次)。 -* 如果您需要添加不同的构建参数,可以在指令末尾的`` 位置进行修改。例如构建一个"Release"版本,在指令后面加上` --release`即可。 -* 如果出现以下的提示,则是无权限问题,可以尝试把`-e PUID="$(id -u)" -e PGID="$(id -g)"`参数去掉。 - ``` - usermod: user user is currently used by process 1 - groupmod: Permission denied. - groupmod: cannot lock /etc/group; try again later. - ``` - > **原因:** 容器的 entrypoint 脚本会检测 UID 和 GID,在度判和给定的环境变量的不一致时,会强行修改 user 的 UID 和 GID 并重新运行。但在重启后读不到环境中的 UID 和 GID,然后再次进入判错重启环节 - - -### 运行 RustDesk 程序 - -生成的可执行程序在 target 目录下,可直接通过指令运行调试 (Debug) 版本的 RustDesk: -```sh -target/debug/rustdesk -``` - -或者您想运行发行 (Release) 版本: - -```sh -target/release/rustdesk -``` - -请注意: -* 请保证您运行的目录是在 RustDesk 库的根目录内,否则软件会读不到文件。 -* `install`、`run`等 Cargo 的子指令在容器内不可用,宿主机才行。 - -## 文件结构 - -- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数 -- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 屏幕截取 -- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入 -- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows、Linux、macOS 的文件复制和粘贴实现 -- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 过时的 Sciter 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)保持UDP通讯, 等待远程连接(通过打洞直连或者中继) -- **[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)**: Flutter Web版本中的Javascript代码 - -## 截图 - -![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/SECURITY-DE.md b/docs/SECURITY-DE.md deleted file mode 100644 index 65f4f79c1..000000000 --- a/docs/SECURITY-DE.md +++ /dev/null @@ -1,9 +0,0 @@ -# Sicherheitsrichtlinie - -## Melden einer Schwachstelle - -Wir legen großen Wert auf die Sicherheit des Projekts. Wir ermutigen alle Benutzer, uns alle Sicherheitslücken zu melden, die sie entdecken. -Wenn Sie eine Sicherheitslücke im RustDesk-Projekt finden, melden Sie diese bitte verantwortungsbewusst per E-Mail an info@rustdesk.com. - -Zum jetzigen Zeitpunkt haben wir kein Bug-Bounty-Programm. Wir sind ein kleines Team, das versucht, ein großes Problem zu lösen. Wir bitten Sie dringend, -alle Schwachstellen verantwortungsbewusst zu melden, damit wir weiterhin eine sichere Anwendung für die ganze Gemeinschaft entwickeln können. diff --git a/docs/SECURITY-FR.md b/docs/SECURITY-FR.md deleted file mode 100644 index 1cf2c6167..000000000 --- a/docs/SECURITY-FR.md +++ /dev/null @@ -1,16 +0,0 @@ - -# Politique de sécurité - -## Signaler une vulnérabilité - -Nous accordons une très grande importance à la sécurité du projet. Nous -encourageons tous les utilisateurs à nous signaler toute vulnérabilité qu'ils -découvrent. - -Si vous trouvez une vulnérabilité de sécurité dans le projet RustDesk, veuillez -la signaler de manière responsable en envoyant un e-mail à info@rustdesk.com. - -À ce stade, nous n'avons pas de programme de bug bounty. Nous sommes une petite -équipe qui s'attaque à un grand défi. Nous vous encourageons vivement à signaler -toute vulnérabilité de manière responsable afin que nous puissions continuer à -développer une application sécurisée pour l'ensemble de la communauté. diff --git a/docs/SECURITY-IT.md b/docs/SECURITY-IT.md deleted file mode 100644 index 91573dcf7..000000000 --- a/docs/SECURITY-IT.md +++ /dev/null @@ -1,11 +0,0 @@ -# Policy sicurezza - -## Segnalazione di una vulnerabilità - -Attribuiamo grande importanza alla sicurezza del progetto. -Incoraggiamo tutti gli utenti a segnalare eventuali vulnerabilità di sicurezza che ci scoprono. -Se trovi una vulnerabilità nel progetto RustDesk, segnalala responsabilmente inviando un'email a info@rustdesk.com. - -Al momento non abbiamo un programma di taglia sui bug. -Siamo una piccola squadra che cerca di risolvere un grosso problema. -Ti esortiamo a segnalare responsabilmente tutte le vulnerabilità in modo da poter continuare a sviluppare un'applicazione sicura per l'intera comunità. diff --git a/docs/SECURITY-JP.md b/docs/SECURITY-JP.md deleted file mode 100644 index bddf6d993..000000000 --- a/docs/SECURITY-JP.md +++ /dev/null @@ -1,9 +0,0 @@ -# セキュリティポリシー - -## 脆弱性の報告 - -私たちはプロジェクトのセキュリティを非常に重視しています。私たちは、すべてのユーザーが脆弱性を発見した場合、私たちに報告することを奨励しています。 -RustDesk プロジェクトにセキュリティの脆弱性を発見した場合は、info@rustdesk.com までメールで責任を持って報告してください。 - -現時点では、バグ報奨金制度はありません。私たちは大きな問題を解決しようとしている小さなチームです。コミュニティ全体のために安全なアプリケーションを作り続けることができるよう、 -責任を持って脆弱性を報告してください。 diff --git a/docs/SECURITY-KR.md b/docs/SECURITY-KR.md deleted file mode 100644 index 94ce8f2ba..000000000 --- a/docs/SECURITY-KR.md +++ /dev/null @@ -1,7 +0,0 @@ -# 보안 정책 - -## 취약점 보고 - -저희는 프로젝트의 보안을 매우 중요하게 생각합니다. 모든 사용자가 발견한 취약점을 저희에게 보고할 것을 권장합니다. RustDesk 프로젝트에서 보안 취약점이 발견되면 info@rustdesk.com으로 이메일을 보내 책임감 있게 보고해 주시기 바랍니다. - -현재로서는 버그 현상금 프로그램이 없습니다. 저희는 큰 문제를 해결하기 위해 노력하는 소규모 팀입니다. 전체 커뮤니티를 위한 안전한 응용 프로그램을 계속 구축할 수 있도록 취약점을 책임감 있게 신고해 주시기 바랍니다. diff --git a/docs/SECURITY-NL.md b/docs/SECURITY-NL.md deleted file mode 100644 index 463b3228e..000000000 --- a/docs/SECURITY-NL.md +++ /dev/null @@ -1,11 +0,0 @@ -# Veiligheidsbeleid - -## Een Kwetsbaarheid Melden - -Wij hechten veel waarde aan de veiligheid van het project. We moedigen alle gebruikers aan om kwetsbaarheden die ze ontdekken -aan ons te melden. Als u een beveiligingslek in het RustDesk project vindt, meld dit dan op verantwoorde wijze door -een e-mail te sturen naar info@rustdesk.com. - -Op dit moment hebben we geen bug premie programma. We zijn een klein team dat een groot probleem probeert op te lossen. -We verzoeken u dringend om alle kwetsbaarheden op verantwoorde wijze te melden, zodat we verder kunnen bouwen aan -een veilige applicatie voor de hele gemeenschap. diff --git a/docs/SECURITY-NO.md b/docs/SECURITY-NO.md deleted file mode 100644 index 1f8dcb411..000000000 --- a/docs/SECURITY-NO.md +++ /dev/null @@ -1,9 +0,0 @@ -# Sikkerhets Rettningslinjer - -## Reportering av en Sårbarhet - -Vi verdsetter pris på sikkerhet for prosjektet høyt. Og oppmunterer alle brukere til å rapportere sårbarheter de oppdager til oss. -Om du finner en sikkerhets sårbarhet i RustDesk prosjektet, venligst raportere det ansvarsfult ved å sende oss en email til info@rustdesk.com. - -På dette tidspunktet har vi ingen bug dusør program. Vi er ett lite team som prøver å løse ett stort problem. Vi trenger att du raporterer alle sårbarhetene -annsvarfult så vi kan fortsettte å bygge ett en sikker applikasjon for hele felleskapet. diff --git a/docs/SECURITY-PL.md b/docs/SECURITY-PL.md deleted file mode 100644 index 0d4975ba0..000000000 --- a/docs/SECURITY-PL.md +++ /dev/null @@ -1,9 +0,0 @@ -# Polityka bezpieczeństwa - -## Zgłaszanie podatności - -Bardzo cenimy sobie bezpieczeństwo projektu. Zachęcamy wszystkich użytkowników do zgłaszania nam wszelkich wykrytych luk. -Jeżeli znajdziesz lukę w projekcie RustDesk, proszę zgłosić ją jak najszybciej wysyłając e-mail na adres info@rustdesk.com. - -W tym momencie, nie mamy uruchomionego programu nagradzania za wykryte błędy. Jesteśmy małym zespołem próbującym rozwiązywać duże problemy. -Prosimy o odpowidzialne zgłaszanie wszelkich podatności w zabezpieczeniach, abyśmy mogli kontynuować tworzenie bezpiecznej aplikacji dla całej społeczności. diff --git a/docs/SECURITY-RO.md b/docs/SECURITY-RO.md deleted file mode 100644 index 029e01d53..000000000 --- a/docs/SECURITY-RO.md +++ /dev/null @@ -1,9 +0,0 @@ -# Politica de Securitate - -## Raportarea unei Vulnerabilități - -Acordăm o mare importanță securității proiectului. Încurajăm toți utilizatorii să ne raporteze orice vulnerabilități pe care le descoperă. -Dacă găsești o vulnerabilitate de securitate în proiectul RustDesk, te rugăm să o raportezi responsabil trimițând un e-mail la info@rustdesk.com. - -În acest moment, nu avem un program de recompense pentru descoperirea de bug-uri. Suntem o echipă mică care încearcă să rezolve o problemă mare. -Te rugăm să raportezi orice vulnerabilitate în mod responsabil, astfel încât să putem continua să construim o aplicație sigură pentru întreaga comunitate. diff --git a/docs/SECURITY-TR.md b/docs/SECURITY-TR.md deleted file mode 100644 index 88037acb2..000000000 --- a/docs/SECURITY-TR.md +++ /dev/null @@ -1,9 +0,0 @@ -# Güvenlik Politikası - -## Bir Güvenlik Açığı Bildirme - -Projemiz için güvenliği çok önemsiyoruz. Kullanıcıların keşfettikleri herhangi bir güvenlik açığını bize bildirmelerini teşvik ediyoruz. -Eğer RustDesk projesinde bir güvenlik açığı bulursanız, lütfen info@rustdesk.com adresine sorumlu bir şekilde bildirin. - -Şu an için bir hata ödül programımız bulunmamaktadır. Büyük bir sorunu çözmeye çalışan küçük bir ekibiz. Herhangi bir güvenlik açığını sorumlu bir şekilde bildirmenizi rica ederiz, -böylece tüm topluluk için güvenli bir uygulama oluşturmaya devam edebiliriz. diff --git a/docs/SECURITY.md b/docs/SECURITY.md deleted file mode 100644 index c595885f2..000000000 --- a/docs/SECURITY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -We value security for the project very highly. We encourage all users to report any vulnerabilities they discover to us. -If you find a security vulnerability in the RustDesk project, please report it responsibly by sending an email to info@rustdesk.com. - -At this juncture, we don't have a bug bounty program. We are a small team trying to solve a big problem. We urge you to report any vulnerabilities responsibly -so that we can continue building a secure application for the entire community. diff --git a/entrypoint b/entrypoint new file mode 100755 index 000000000..514de9b9d --- /dev/null +++ b/entrypoint @@ -0,0 +1,34 @@ +#!/bin/sh + +cd $HOME/rustdesk +. $HOME/.cargo/env + +argv=$@ + +while test $# -gt 0; do + case "$1" in + --release) + mkdir -p target/release + test -f target/release/libsciter-gtk.so || cp $HOME/libsciter-gtk.so target/release/ + release=1 + shift + ;; + --target) + shift + if test $# -gt 0; then + rustup target add $1 + shift + fi + ;; + *) + shift + ;; + esac +done + +if [ -z $release ]; then + mkdir -p target/debug + test -f target/debug/libsciter-gtk.so || cp $HOME/libsciter-gtk.so target/debug/ +fi + +VCPKG_ROOT=/vcpkg cargo build $argv diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 8c7be0786..000000000 --- a/entrypoint.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -cd "$HOME"/rustdesk || exit 1 -# shellcheck source=/dev/null -. "$HOME"/.cargo/env - -argv=$* - -while test $# -gt 0; do - case "$1" in - --release) - mkdir -p target/release - test -f target/release/libsciter-gtk.so || cp "$HOME"/libsciter-gtk.so target/release/ - release=1 - shift - ;; - --target) - shift - if test $# -gt 0; then - rustup target add "$1" - shift - fi - ;; - *) - shift - ;; - esac -done - -if [ -z $release ]; then - mkdir -p target/debug - test -f target/debug/libsciter-gtk.so || cp "$HOME"/libsciter-gtk.so target/debug/ -fi -set -f -#shellcheck disable=2086 -VCPKG_ROOT=/vcpkg cargo build $argv diff --git a/examples/ipc.rs b/examples/ipc.rs deleted file mode 100644 index bca2321b1..000000000 --- a/examples/ipc.rs +++ /dev/null @@ -1,90 +0,0 @@ -use docopt::Docopt; -use hbb_common::{ - env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV}, - log, tokio, -}; -use librustdesk::{ipc::Data, *}; - -const USAGE: &'static str = " -IPC test program. - -Usage: - ipc (-s | --server | -c | --client) [-p | --postfix=] - ipc (-h | --help) - -Options: - -h --help Show this screen. - -s --server Run as IPC server. - -c --client Run as IPC client. - -p --postfix= IPC path postfix [default: ]. -"; - -#[derive(Debug, serde::Deserialize)] -struct Args { - flag_server: bool, - flag_client: bool, - flag_postfix: String, -} - -#[tokio::main] -async 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()); - - if args.flag_server { - if args.flag_postfix.is_empty() { - log::info!("Starting IPC server..."); - } else { - log::info!( - "Starting IPC server with postfix: '{}'...", - args.flag_postfix - ); - } - ipc_server(&args.flag_postfix).await; - } else if args.flag_client { - if args.flag_postfix.is_empty() { - log::info!("Starting IPC client..."); - } else { - log::info!( - "Starting IPC client with postfix: '{}'...", - args.flag_postfix - ); - } - ipc_client(&args.flag_postfix).await; - } -} - -async fn ipc_server(postfix: &str) { - let postfix = postfix.to_string(); - let postfix2 = postfix.clone(); - std::thread::spawn(move || { - if let Err(err) = crate::ipc::start(&postfix) { - log::error!("Failed to start ipc: {}", err); - std::process::exit(-1); - } - }); - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - ipc_client(&postfix2).await; -} - -async fn ipc_client(postfix: &str) { - loop { - match crate::ipc::connect(1000, postfix).await { - Ok(mut conn) => match conn.send(&Data::Empty).await { - Ok(_) => { - log::info!("send message to ipc server success"); - } - Err(e) => { - log::error!("Failed to send message to ipc server: {}", e); - } - }, - Err(e) => { - log::error!("Failed to connect to ipc server: {}", e); - } - } - tokio::time::sleep(std::time::Duration::from_secs(6)).await; - } -} diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt deleted file mode 100644 index f78b3a20b..000000000 --- a/fastlane/metadata/android/en-US/full_description.txt +++ /dev/null @@ -1,11 +0,0 @@ -An open-source remote desktop application, the open source TeamViewer alternative. -Source code: https://github.com/rustdesk/rustdesk -Doc: https://rustdesk.com/docs/en/manual/mobile/ - -In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the "Accessibility" service, RustDesk uses AccessibilityService API to implement Android remote control. - -In addition to remote control, you can also transfer files between Android devices and PCs easily with RustDesk. - -You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, or self-hosting, or write your own rendezvous/relay server. Self-hosting server is free and open source: https://github.com/rustdesk/rustdesk-server - -Please download and install desktop version from: https://rustdesk.com, then you can access and control your desktop from your mobile, or control your mobile from desktop. diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png deleted file mode 100644 index 3668c7106..000000000 Binary files a/fastlane/metadata/android/en-US/images/icon.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png deleted file mode 100644 index 9b0125f21..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png deleted file mode 100644 index caffe504b..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png deleted file mode 100644 index 05b6afabb..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png deleted file mode 100644 index 39a15ba77..000000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png deleted file mode 100644 index 5574ee7dc..000000000 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png deleted file mode 100644 index bbef90fb9..000000000 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png deleted file mode 100644 index 132736514..000000000 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png deleted file mode 100644 index 946956b2b..000000000 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt deleted file mode 100644 index 91e796ada..000000000 --- a/fastlane/metadata/android/en-US/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -An open-source remote desktop application, the TeamViewer alternative diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt deleted file mode 100644 index effb820d6..000000000 --- a/fastlane/metadata/android/fr-FR/full_description.txt +++ /dev/null @@ -1,11 +0,0 @@ -Une application de bureau à distance open source, l'alternative open source à TeamViewer. -Code source : https://github.com/rustdesk/rustdesk -Doc : https://rustdesk.com/docs/en/manual/mobile/ - -Pour qu'un appareil distant puisse contrôler votre appareil Android via la souris ou le toucher, vous devez autoriser RustDesk à utiliser le service "Accessibilité", RustDesk utilise l'API AccessibilityService pour implémenter la télécommande Addroid. - -En plus du contrôle à distance, vous pouvez également transférer facilement des fichiers entre des appareils Android et des PC avec RustDesk. - -Vous avez le contrôle total de vos données, sans aucun souci de sécurité. Vous pouvez utiliser notre serveur de rendez-vous/relais, ou l'auto-hébergement, ou écrire votre propre serveur de rendez-vous/relais. Le serveur auto-hébergé est gratuit et open source : https://github.com/rustdesk/rustdesk-server - -Veuillez télécharger et installer la version de bureau à partir de : https://rustdesk.com, vous pourrez alors accéder et contrôler votre bureau à partir de votre mobile, ou contrôler votre mobile à partir du bureau. diff --git a/fastlane/metadata/android/fr-FR/short_description.txt b/fastlane/metadata/android/fr-FR/short_description.txt deleted file mode 100644 index e1f4b4b0f..000000000 --- a/fastlane/metadata/android/fr-FR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Une application de bureau à distance open source, l'alternative open source à TeamViewer. diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt deleted file mode 100644 index b60d52ceb..000000000 --- a/fastlane/metadata/android/nl-NL/full_description.txt +++ /dev/null @@ -1,11 +0,0 @@ -Een open-source toepassing voor bureaublad op afstand, het open-source alternatief voor TeamViewer. -Bron code: https://github.com/rustdesk/rustdesk -Doc: https://rustdesk.com/docs/en/manual/mobile/ - -Om ervoor te zorgen dat een extern apparaat uw Android-apparaat via muis of aanraking kan besturen, moet u RustDesk toestaan de "Toegankelijkheid" service te gebruiken. RustDesk gebruikt AccessibilityService API om Android afstandsbediening te kunnen implementeren. - -Naast bediening op afstand kunt u met RustDesk ook eenvoudig bestanden overzetten tussen Android-apparaten en pc's. - -U hebt volledige controle over uw gegevens, en u hoeft zich geen zorgen te maken over de veiligheid. U kunt onze rendez-vous/relay server gebruiken, of zelf hosten, of uw eigen rendez-vous/relay server schrijven. Self-hosting server is gratis en open source: https://github.com/rustdesk/rustdesk-server - -Download en installeer de desktop versie vanaf: https://rustdesk.com, dan kunt u uw desktop benaderen en bedienen vanaf uw mobiel, of uw mobiel bedienen vanaf uw desktop. diff --git a/fastlane/metadata/android/nl-NL/short_description.txt b/fastlane/metadata/android/nl-NL/short_description.txt deleted file mode 100644 index 18a46000b..000000000 --- a/fastlane/metadata/android/nl-NL/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Een open-source toepassing voor bureaublad op afstand, het open-source alternatief voor TeamViewer. diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt deleted file mode 100644 index f1f44057e..000000000 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ /dev/null @@ -1,12 +0,0 @@ -开源远程桌面应用,开源 TeamViewer 替代方案。 -源代码:https://github.com/rustdesk/rustdesk -文档:https://rustdesk.com/docs/en/manual/mobile/ - -为了让远程设备通过鼠标或触摸控制您的 Android 设备,您需要允许 RustDesk 使用“Accessibility”服务,RustDesk 使用 AccessibilityService API 来实现 Addroid 远程控制。 - -除了远程控制,您还可以使用 RustDesk 在 Android 设备和 PC 之间轻松传输文件。 - -您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,或者自建,亦或者开发您的版本。 -自托管服务器是免费和开源的:https://github.com/rustdesk/rustdesk-server - -请从:https://rustdesk.com 下载并安装桌面版,然后您可以通过手机访问和控制您的桌面,或从桌面控制您的手机。 diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt deleted file mode 100644 index 69a4a5b52..000000000 --- a/fastlane/metadata/android/zh-CN/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -开源远程桌面应用,开源 TeamViewer 替代方案 diff --git a/flatpak/com.rustdesk.RustDesk.metainfo.xml b/flatpak/com.rustdesk.RustDesk.metainfo.xml deleted file mode 100644 index 90bdafcb5..000000000 --- a/flatpak/com.rustdesk.RustDesk.metainfo.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - com.rustdesk.RustDesk - - RustDesk - - com.rustdesk.RustDesk.desktop - CC0-1.0 - AGPL-3.0-only - RustDesk - Secure remote desktop access - -

- RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration. -

-
    -
  • Works on Windows, macOS, Linux, iOS, Android, Web.
  • -
  • Supports VP8 / VP9 / AV1 software codecs, and H264 / H265 hardware codecs.
  • -
  • Own your data, easily set up self-hosting solution on your infrastructure.
  • -
  • P2P connection with end-to-end encryption based on NaCl.
  • -
  • No administrative privileges or installation needed for Windows, elevate privilege locally or from remote on demand.
  • -
  • We like to keep things simple and will strive to make simpler where possible.
  • -
-

- For self-hosting setup instructions please go to our home page. -

-
- - Utility - - - - Remote desktop session - https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png - - - - #d9eaf8 - #0160ee - - https://rustdesk.com - https://github.com/rustdesk/rustdesk/issues - https://github.com/rustdesk/rustdesk/wiki/FAQ - https://rustdesk.com/docs - https://ko-fi.com/rustdesk - https://github.com/rustdesk/rustdesk - https://github.com/rustdesk/rustdesk/tree/master/src/lang - https://github.com/rustdesk/rustdesk/blob/master/docs/CONTRIBUTING.md - https://rustdesk.com/docs/en/technical-support - - 600 - always - - - keyboard - pointing - - -
diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json deleted file mode 100644 index 2418ac2a6..000000000 --- a/flatpak/rustdesk.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "id": "com.rustdesk.RustDesk", - "runtime": "org.freedesktop.Platform", - "runtime-version": "24.08", - "sdk": "org.freedesktop.Sdk", - "command": "rustdesk", - "cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"], - "rename-desktop-file": "rustdesk.desktop", - "rename-icon": "rustdesk", - "modules": [ - "shared-modules/libappindicator/libappindicator-gtk3-12.10.json", - { - "name": "xdotool", - "no-autogen": true, - "make-install-args": ["PREFIX=${FLATPAK_DEST}"], - "sources": [ - { - "type": "archive", - "url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz", - "sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada" - } - ] - }, - { - "name": "pam", - "buildsystem": "autotools", - "config-opts": ["--disable-selinux"], - "sources": [ - { - "type": "archive", - "url": "https://github.com/linux-pam/linux-pam/releases/download/v1.3.1/Linux-PAM-1.3.1.tar.xz", - "sha256": "eff47a4ecd833fbf18de9686632a70ee8d0794b79aecb217ebd0ce11db4cd0db" - } - ] - }, - { - "name": "rustdesk", - "buildsystem": "simple", - "build-commands": [ - "bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -", - "cp -r usr/* /app/", - "mkdir -p /app/bin && ln -s /app/share/rustdesk/rustdesk /app/bin/rustdesk" - ], - "sources": [ - { - "type": "file", - "path": "rustdesk.deb" - }, - { - "type": "file", - "path": "com.rustdesk.RustDesk.metainfo.xml" - } - ] - } - ], - "finish-args": [ - "--share=ipc", - "--socket=wayland", - "--socket=x11", - "--share=network", - "--filesystem=home", - "--device=dri", - "--socket=pulseaudio", - "--talk-name=org.freedesktop.Flatpak" - ] -} \ No newline at end of file diff --git a/flutter/.gitattributes b/flutter/.gitattributes deleted file mode 100644 index 176a458f9..000000000 --- a/flutter/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto diff --git a/flutter/.gitignore b/flutter/.gitignore deleted file mode 100644 index ee7e42c6c..000000000 --- a/flutter/.gitignore +++ /dev/null @@ -1,56 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related - -# Symbolication related -app.*.symbols -# Obfuscation related -app.*.map.json -jniLibs -.vscode - -# flutter rust bridge -lib/generated_bridge.dart -lib/generated_bridge.freezed.dart - -# Flutter Generated Files -**/GeneratedPluginRegistrant.swift -**/flutter/generated_plugin_registrant.cc -**/flutter/generated_plugin_registrant.h -**/flutter/generated_plugins.cmake -**/Runner/bridge_generated.h -flutter_export_environment.sh -Flutter-Generated.xcconfig -key.jks -macos/rustdesk.xcodeproj/project.xcworkspace/ diff --git a/flutter/.metadata b/flutter/.metadata deleted file mode 100644 index 8b4892cfb..000000000 --- a/flutter/.metadata +++ /dev/null @@ -1,36 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - channel: stable - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - - platform: linux - create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - - platform: macos - create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - - platform: windows - create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter/README.md b/flutter/README.md deleted file mode 100644 index 519b85e38..000000000 --- a/flutter/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# flutter_hbb - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples and guidance on mobile development, and a full API reference. diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml deleted file mode 100644 index a679f5774..000000000 --- a/flutter/analysis_options.yaml +++ /dev/null @@ -1,6 +0,0 @@ -include: package:lints/recommended.yaml - -linter: - rules: - non_constant_identifier_names: false - sort_child_properties_last: false diff --git a/flutter/android/.gitignore b/flutter/android/.gitignore deleted file mode 100644 index 0a741cb43..000000000 --- a/flutter/android/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java - -# Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app -key.properties diff --git a/flutter/android/app/build.gradle b/flutter/android/app/build.gradle deleted file mode 100644 index 830cbc2dd..000000000 --- a/flutter/android/app/build.gradle +++ /dev/null @@ -1,137 +0,0 @@ -import com.google.protobuf.gradle.* -import groovy.json.JsonSlurper - -plugins { - id "com.google.protobuf" version "0.9.4" - id "com.android.application" - id "kotlin-android" - id "dev.flutter.flutter-gradle-plugin" -} - -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -// Add rustls-platform-verifier Android support -String findRustlsPlatformVerifierMavenDir() { - def dependencyText = providers.exec { - it.workingDir = new File("../..") - commandLine("cargo", "metadata", "--format-version", "1") - }.standardOutput.asText.get() - - def dependencyJson = new JsonSlurper().parseText(dependencyText) - def pkg = dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" } - - if (pkg == null) { - throw new GradleException("rustls-platform-verifier-android package not found in cargo metadata!") - } - - def manifestPath = file(pkg.manifest_path) - def mavenDir = new File(manifestPath.parentFile, "maven") - - if (!mavenDir.exists()) { - throw new GradleException("Maven directory not found at: ${mavenDir.path}") - } - - println("✓ Found rustls-platform-verifier maven repo at: ${mavenDir.path}") - return mavenDir.path -} - - -repositories { - maven { - url = findRustlsPlatformVerifierMavenDir() - metadataSources.artifact() - } -} - -protobuf { - protoc { - artifact = 'com.google.protobuf:protoc:3.20.1' - } - - generateProtoTasks { - all().configureEach { task -> - task.builtins { - java { - option "lite" - } - } - } - } -} - -android { - compileSdkVersion 34 - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - - main.proto.srcDirs += '../../../libs/hbb_common/protos' - main.proto.includes += "message.proto" - } - - compileOptions { - targetCompatibility JavaVersion.VERSION_1_8 - sourceCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.carriez.flutter_hbb" - minSdkVersion 22 - targetSdkVersion 33 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - signingConfigs { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null - storePassword keystoreProperties['storePassword'] - } - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.release - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules' - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation 'com.google.protobuf:protobuf-javalite:3.20.1' - implementation "androidx.media:media:1.6.0" - implementation 'com.github.getActivity:XXPermissions:18.5' - implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("1.9.10") } } - implementation 'com.caverock:androidsvg-aar:1.4' - implementation "rustls:rustls-platform-verifier:0.1.1" -} diff --git a/flutter/android/app/proguard-rules b/flutter/android/app/proguard-rules deleted file mode 100644 index 517402567..000000000 --- a/flutter/android/app/proguard-rules +++ /dev/null @@ -1,7 +0,0 @@ -# Keep class members from protobuf generated code. --keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { - ; -} - -# Keep rustls-platform-verifier classes for JNI --keep, includedescriptorclasses class org.rustls.platformverifier.** { *; } \ No newline at end of file diff --git a/flutter/android/app/src/debug/AndroidManifest.xml b/flutter/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 64d68a588..000000000 --- a/flutter/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index f4788af4c..000000000 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt deleted file mode 100644 index 05742d7fd..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/AudioRecordHandle.kt +++ /dev/null @@ -1,199 +0,0 @@ -package com.carriez.flutter_hbb - -import ffi.FFI - -import android.Manifest -import android.content.Context -import android.media.* -import android.content.pm.PackageManager -import android.media.projection.MediaProjection -import androidx.annotation.RequiresApi -import androidx.core.app.ActivityCompat -import android.os.Build -import android.util.Log -import kotlin.concurrent.thread - -const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_FLOAT // ENCODING_OPUS need API 30 -const val AUDIO_SAMPLE_RATE = 48000 -const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO - -class AudioRecordHandle(private var context: Context, private var isVideoStart: ()->Boolean, private var isAudioStart: ()->Boolean) { - private val logTag = "LOG_AUDIO_RECORD_HANDLE" - - private var audioRecorder: AudioRecord? = null - private var audioReader: AudioReader? = null - private var minBufferSize = 0 - private var audioRecordStat = false - private var audioThread: Thread? = null - - @RequiresApi(Build.VERSION_CODES.M) - fun createAudioRecorder(inVoiceCall: Boolean, mediaProjection: MediaProjection?): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return false - } - if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.RECORD_AUDIO - ) != PackageManager.PERMISSION_GRANTED - ) { - Log.d(logTag, "createAudioRecorder failed, no RECORD_AUDIO permission") - return false - } - - var builder = AudioRecord.Builder() - .setAudioFormat( - AudioFormat.Builder() - .setEncoding(AUDIO_ENCODING) - .setSampleRate(AUDIO_SAMPLE_RATE) - .setChannelMask(AUDIO_CHANNEL_MASK).build() - ); - if (inVoiceCall) { - builder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) - } else { - mediaProjection?.let { - var apcc = AudioPlaybackCaptureConfiguration.Builder(it) - .addMatchingUsage(AudioAttributes.USAGE_MEDIA) - .addMatchingUsage(AudioAttributes.USAGE_ALARM) - .addMatchingUsage(AudioAttributes.USAGE_GAME) - .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build(); - builder.setAudioPlaybackCaptureConfig(apcc); - } ?: let { - Log.d(logTag, "createAudioRecorder failed, mediaProjection null") - return false - } - } - val recorder = try { - builder.build() - } catch (e: Exception) { - Log.e(logTag, "createAudioRecorder failed", e) - return false - } - audioRecorder = recorder - Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize") - return true - } - - @RequiresApi(Build.VERSION_CODES.M) - private fun checkAudioReader() { - if (audioReader != null && minBufferSize != 0) { - return - } - // read f32 to byte , length * 4 - minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize( - AUDIO_SAMPLE_RATE, - AUDIO_CHANNEL_MASK, - AUDIO_ENCODING - ) - if (minBufferSize == 0) { - Log.d(logTag, "get min buffer size fail!") - return - } - audioReader = AudioReader(minBufferSize, 4) - Log.d(logTag, "init audioData len:$minBufferSize") - } - - @RequiresApi(Build.VERSION_CODES.M) - fun startAudioRecorder() { - checkAudioReader() - if (audioReader != null && audioRecorder != null && minBufferSize != 0) { - try { - FFI.setFrameRawEnable("audio", true) - audioRecorder!!.startRecording() - audioRecordStat = true - audioThread = thread { - while (audioRecordStat) { - audioReader!!.readSync(audioRecorder!!)?.let { - FFI.onAudioFrameUpdate(it) - } - } - // let's release here rather than onDestroy to avoid threading issue - audioRecorder?.release() - audioRecorder = null - minBufferSize = 0 - FFI.setFrameRawEnable("audio", false) - Log.d(logTag, "Exit audio thread") - } - } catch (e: Exception) { - Log.d(logTag, "startAudioRecorder fail:$e") - } - } else { - Log.d(logTag, "startAudioRecorder fail") - } - } - - fun onVoiceCallStarted(mediaProjection: MediaProjection?): Boolean { - if (!isSupportVoiceCall()) { - return false - } - // No need to check if video or audio is started here. - if (!switchToVoiceCall(mediaProjection)) { - return false - } - return true - } - - fun onVoiceCallClosed(mediaProjection: MediaProjection?): Boolean { - // Return true if not supported, because is was not started. - if (!isSupportVoiceCall()) { - return true - } - if (isVideoStart()) { - switchOutVoiceCall(mediaProjection) - } - tryReleaseAudio() - return true - } - - @RequiresApi(Build.VERSION_CODES.M) - fun switchToVoiceCall(mediaProjection: MediaProjection?): Boolean { - audioRecorder?.let { - if (it.getAudioSource() == MediaRecorder.AudioSource.VOICE_COMMUNICATION) { - return true - } - } - audioRecordStat = false - audioThread?.join() - audioThread = null - - if (!createAudioRecorder(true, mediaProjection)) { - Log.e(logTag, "createAudioRecorder fail") - return false - } - startAudioRecorder() - return true - } - - @RequiresApi(Build.VERSION_CODES.M) - fun switchOutVoiceCall(mediaProjection: MediaProjection?): Boolean { - audioRecorder?.let { - if (it.getAudioSource() != MediaRecorder.AudioSource.VOICE_COMMUNICATION) { - return true - } - } - audioRecordStat = false - audioThread?.join() - - if (!createAudioRecorder(false, mediaProjection)) { - Log.e(logTag, "createAudioRecorder fail") - return false - } - startAudioRecorder() - return true - } - - fun tryReleaseAudio() { - if (isAudioStart() || isVideoStart()) { - return - } - audioRecordStat = false - audioThread?.join() - audioThread = null - } - - fun destroy() { - Log.d(logTag, "destroy audio record handle") - - audioRecordStat = false - audioThread?.join() - } -} 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 deleted file mode 100644 index 71bbba754..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/BootReceiver.kt +++ /dev/null @@ -1,47 +0,0 @@ -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) { - 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 - } - // 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 { - context.startService(it) - } - } - } -} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt deleted file mode 100644 index 6dd4a2f61..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt +++ /dev/null @@ -1,394 +0,0 @@ -package com.carriez.flutter_hbb - -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.app.Service -import android.content.Intent -import android.content.res.Configuration -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.PixelFormat -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import android.os.Build -import android.os.Handler -import android.os.IBinder -import android.os.Looper -import android.util.Log -import android.view.Gravity -import android.view.MotionEvent -import android.view.View -import android.view.WindowManager -import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN -import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE -import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL -import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON -import android.widget.ImageView -import android.widget.PopupMenu -import com.caverock.androidsvg.SVG -import ffi.FFI -import kotlin.math.abs - -class FloatingWindowService : Service(), View.OnTouchListener { - - private lateinit var windowManager: WindowManager - private lateinit var layoutParams: WindowManager.LayoutParams - private lateinit var floatingView: ImageView - private lateinit var originalDrawable: Drawable - private lateinit var leftHalfDrawable: Drawable - private lateinit var rightHalfDrawable: Drawable - - private var dragging = false - private var lastDownX = 0f - private var lastDownY = 0f - private var viewCreated = false; - private var keepScreenOn = KeepScreenOn.DURING_CONTROLLED - - companion object { - private val logTag = "floatingService" - private var firstCreate = true - private var viewWidth = 120 - private var viewHeight = 120 - private const val MIN_VIEW_SIZE = 32 // size 0 does not help prevent the service from being killed - private const val MAX_VIEW_SIZE = 320 - private var viewUntouchable = false - private var viewTransparency = 1f // 0 means invisible but can help prevent the service from being killed - private var customSvg = "" - private var lastLayoutX = 0 - private var lastLayoutY = 0 - private var lastOrientation = Configuration.ORIENTATION_UNDEFINED - } - - override fun onBind(intent: Intent): IBinder? { - return null - } - - override fun onCreate() { - super.onCreate() - windowManager = getSystemService(WINDOW_SERVICE) as WindowManager - try { - if (firstCreate) { - firstCreate = false - onFirstCreate(windowManager) - } - Log.d(logTag, "floating window size: $viewWidth x $viewHeight, transparency: $viewTransparency, lastLayoutX: $lastLayoutX, lastLayoutY: $lastLayoutY, customSvg: $customSvg") - createView(windowManager) - handler.postDelayed(runnable, 1000) - Log.d(logTag, "onCreate success") - } catch (e: Exception) { - Log.d(logTag, "onCreate failed: $e") - } - } - - override fun onDestroy() { - super.onDestroy() - if (viewCreated) { - windowManager.removeView(floatingView) - } - handler.removeCallbacks(runnable) - } - - @SuppressLint("ClickableViewAccessibility") - private fun createView(windowManager: WindowManager) { - floatingView = ImageView(this) - viewCreated = true - originalDrawable = resources.getDrawable(R.drawable.floating_window, null) - if (customSvg.isNotEmpty()) { - try { - val svg = SVG.getFromString(customSvg) - Log.d(logTag, "custom svg info: ${svg.documentWidth} x ${svg.documentHeight}"); - // This make the svg render clear - svg.documentWidth = viewWidth * 1f - svg.documentHeight = viewHeight * 1f - originalDrawable = svg.renderToPicture().let { - BitmapDrawable( - resources, - Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888) - .also { bitmap -> - it.draw(Canvas(bitmap)) - }) - } - floatingView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - Log.d(logTag, "custom svg loaded") - } catch (e: Exception) { - e.printStackTrace() - } - } - val originalBitmap = Bitmap.createBitmap( - originalDrawable.intrinsicWidth, - originalDrawable.intrinsicHeight, - Bitmap.Config.ARGB_8888 - ) - val canvas = Canvas(originalBitmap) - originalDrawable.setBounds( - 0, - 0, - originalDrawable.intrinsicWidth, - originalDrawable.intrinsicHeight - ) - originalDrawable.draw(canvas) - val leftHalfBitmap = Bitmap.createBitmap( - originalBitmap, - 0, - 0, - originalDrawable.intrinsicWidth / 2, - originalDrawable.intrinsicHeight - ) - val rightHalfBitmap = Bitmap.createBitmap( - originalBitmap, - originalDrawable.intrinsicWidth / 2, - 0, - originalDrawable.intrinsicWidth / 2, - originalDrawable.intrinsicHeight - ) - leftHalfDrawable = BitmapDrawable(resources, leftHalfBitmap) - rightHalfDrawable = BitmapDrawable(resources, rightHalfBitmap) - - floatingView.setImageDrawable(rightHalfDrawable) - floatingView.setOnTouchListener(this) - floatingView.alpha = viewTransparency * 1f - - var flags = FLAG_LAYOUT_IN_SCREEN or FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE - if (viewUntouchable || viewTransparency == 0f) { - flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - } - layoutParams = WindowManager.LayoutParams( - viewWidth / 2, - viewHeight, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE, - flags, - PixelFormat.TRANSLUCENT - ) - - layoutParams.gravity = Gravity.TOP or Gravity.START - layoutParams.x = lastLayoutX - layoutParams.y = lastLayoutY - - val keepScreenOnOption = FFI.getLocalOption("keep-screen-on").lowercase() - keepScreenOn = when (keepScreenOnOption) { - "never" -> KeepScreenOn.NEVER - "service-on" -> KeepScreenOn.SERVICE_ON - else -> KeepScreenOn.DURING_CONTROLLED - } - Log.d(logTag, "keepScreenOn option: $keepScreenOnOption, value: $keepScreenOn") - updateKeepScreenOnLayoutParams() - - windowManager.addView(floatingView, layoutParams) - moveToScreenSide() - } - - private fun onFirstCreate(windowManager: WindowManager) { - val wh = getScreenSize(windowManager) - val w = wh.first - val h = wh.second - // size - FFI.getLocalOption("floating-window-size").let { - if (it.isNotEmpty()) { - try { - val size = it.toInt() - if (size in MIN_VIEW_SIZE..MAX_VIEW_SIZE && size <= w / 2 && size <= h / 2) { - viewWidth = size - viewHeight = size - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - // untouchable - viewUntouchable = FFI.getLocalOption("floating-window-untouchable") == "Y" - // transparency - FFI.getLocalOption("floating-window-transparency").let { - if (it.isNotEmpty()) { - try { - val transparency = it.toInt() - if (transparency in 0..10) { - viewTransparency = transparency * 1f / 10 - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - // custom svg - FFI.getLocalOption("floating-window-svg").let { - if (it.isNotEmpty()) { - customSvg = it - } - } - // position - lastLayoutX = 0 - lastLayoutY = (wh.second - viewHeight) / 2 - lastOrientation = resources.configuration.orientation - } - - - - private fun performClick() { - showPopupMenu() - } - - override fun onTouch(view: View?, event: MotionEvent?): Boolean { - when (event?.action) { - MotionEvent.ACTION_DOWN -> { - dragging = false - lastDownX = event.rawX - lastDownY = event.rawY - } - MotionEvent.ACTION_UP -> { - val clickDragTolerance = 10f - if (abs(event.rawX - lastDownX) < clickDragTolerance && abs(event.rawY - lastDownY) < clickDragTolerance) { - performClick() - } else { - moveToScreenSide() - } - } - MotionEvent.ACTION_MOVE -> { - val dx = event.rawX - lastDownX - val dy = event.rawY - lastDownY - // ignore too small fist start moving(some time is click) - if (!dragging && dx*dx+dy*dy < 25) { - return false - } - dragging = true - layoutParams.x = event.rawX.toInt() - layoutParams.y = event.rawY.toInt() - layoutParams.width = viewWidth - floatingView.setImageDrawable(originalDrawable) - windowManager.updateViewLayout(view, layoutParams) - lastLayoutX = layoutParams.x - lastLayoutY = layoutParams.y - } - } - return false - } - - private fun moveToScreenSide(center: Boolean = false) { - val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager - val wh = getScreenSize(windowManager) - val w = wh.first - if (layoutParams.x < w / 2) { - layoutParams.x = 0 - floatingView.setImageDrawable(rightHalfDrawable) - } else { - layoutParams.x = w - viewWidth / 2 - floatingView.setImageDrawable(leftHalfDrawable) - } - if (center) { - layoutParams.y = (wh.second - viewHeight) / 2 - } - layoutParams.width = viewWidth / 2 - windowManager.updateViewLayout(floatingView, layoutParams) - lastLayoutX = layoutParams.x - lastLayoutY = layoutParams.y - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - if (newConfig.orientation != lastOrientation) { - lastOrientation = newConfig.orientation - val wh = getScreenSize(windowManager) - Log.d(logTag, "orientation: $lastOrientation, screen size: ${wh.first} x ${wh.second}") - val newW = wh.first - val newH = wh.second - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - // Proportional change - layoutParams.x = (layoutParams.x.toFloat() / newH.toFloat() * newW.toFloat()).toInt() - layoutParams.y = (layoutParams.y.toFloat() / newW.toFloat() * newH.toFloat()).toInt() - } - moveToScreenSide() - } - } - - private fun showPopupMenu() { - val popupMenu = PopupMenu(this, floatingView) - val idShowRustDesk = 0 - popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk")) - // For host side, clipboard sync - val idSyncClipboard = 1 - val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled() - if (isServiceSyncEnabled) { - popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard")) - } - val idStopService = 2 - val hideStopService = FFI.getBuildinOption("hide-stop-service") == "Y" - if (!hideStopService) { - popupMenu.menu.add(0, idStopService, 0, translate("Stop service")) - } - popupMenu.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - idShowRustDesk -> { - openMainActivity() - true - } - idSyncClipboard -> { - syncClipboard() - true - } - idStopService -> { - stopMainService() - true - } - else -> false - } - } - popupMenu.setOnDismissListener { - moveToScreenSide() - } - popupMenu.show() - } - - - private fun openMainActivity() { - val intent = Intent(this, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val pendingIntent = PendingIntent.getActivity( - this, 0, intent, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT - ) - try { - pendingIntent.send() - } catch (e: PendingIntent.CanceledException) { - e.printStackTrace() - } - } - - private fun syncClipboard() { - MainActivity.rdClipboardManager?.syncClipboard(false) - } - - private fun stopMainService() { - MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null) - } - - enum class KeepScreenOn { - NEVER, - DURING_CONTROLLED, - SERVICE_ON, - } - - private val handler = Handler(Looper.getMainLooper()) - private val runnable = object : Runnable { - override fun run() { - if (updateKeepScreenOnLayoutParams()) { - windowManager.updateViewLayout(floatingView, layoutParams) - } - handler.postDelayed(this, 1000) // 1000 milliseconds = 1 second - } - } - - private fun updateKeepScreenOnLayoutParams(): Boolean { - val oldOn = layoutParams.flags and FLAG_KEEP_SCREEN_ON != 0 - val newOn = keepScreenOn == KeepScreenOn.SERVICE_ON || (keepScreenOn == KeepScreenOn.DURING_CONTROLLED && MainService.isStart) - if (oldOn != newOn) { - Log.d(logTag, "change keep screen on to $newOn") - if (newOn) { - layoutParams.flags = layoutParams.flags or FLAG_KEEP_SCREEN_ON - } else { - layoutParams.flags = layoutParams.flags and FLAG_KEEP_SCREEN_ON.inv() - } - return true - } - return false - } -} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt deleted file mode 100644 index 3ca83fbac..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/InputService.kt +++ /dev/null @@ -1,741 +0,0 @@ -package com.carriez.flutter_hbb - -/** - * Handle remote input and dispatch android gesture - * - * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG - */ - -import android.accessibilityservice.AccessibilityService -import android.accessibilityservice.GestureDescription -import android.graphics.Path -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.util.Log -import android.widget.EditText -import android.view.accessibility.AccessibilityEvent -import android.view.ViewGroup.LayoutParams -import android.view.accessibility.AccessibilityNodeInfo -import android.view.KeyEvent as KeyEventAndroid -import android.view.ViewConfiguration -import android.graphics.Rect -import android.media.AudioManager -import android.accessibilityservice.AccessibilityServiceInfo -import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR -import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS -import android.view.inputmethod.EditorInfo -import androidx.annotation.RequiresApi -import java.util.* -import java.lang.Character -import kotlin.math.abs -import kotlin.math.max -import hbb.MessageOuterClass.KeyEvent -import hbb.MessageOuterClass.KeyboardMode -import hbb.KeyEventConverter - -// const val BUTTON_UP = 2 -// const val BUTTON_BACK = 0x08 - -const val LEFT_DOWN = 9 -const val LEFT_MOVE = 8 -const val LEFT_UP = 10 -const val RIGHT_UP = 18 -// (BUTTON_BACK << 3) | BUTTON_UP -const val BACK_UP = 66 -const val WHEEL_BUTTON_DOWN = 33 -const val WHEEL_BUTTON_UP = 34 -const val WHEEL_DOWN = 523331 -const val WHEEL_UP = 963 - -const val TOUCH_SCALE_START = 1 -const val TOUCH_SCALE = 2 -const val TOUCH_SCALE_END = 3 -const val TOUCH_PAN_START = 4 -const val TOUCH_PAN_UPDATE = 5 -const val TOUCH_PAN_END = 6 - -const val WHEEL_STEP = 120 -const val WHEEL_DURATION = 50L -const val LONG_TAP_DELAY = 200L - -class InputService : AccessibilityService() { - - companion object { - var ctx: InputService? = null - val isOpen: Boolean - get() = ctx != null - } - - private val logTag = "input service" - private var leftIsDown = false - private var touchPath = Path() - private var stroke: GestureDescription.StrokeDescription? = null - private var lastTouchGestureStartTime = 0L - private var mouseX = 0 - private var mouseY = 0 - private var timer = Timer() - private var recentActionTask: TimerTask? = null - // 100(tap timeout) + 400(long press timeout) - private val longPressDuration = ViewConfiguration.getTapTimeout().toLong() + ViewConfiguration.getLongPressTimeout().toLong() - - private val wheelActionsQueue = LinkedList() - private var isWheelActionsPolling = false - private var isWaitingLongPress = false - - private var fakeEditTextForTextStateCalculation: EditText? = null - - private var lastX = 0 - private var lastY = 0 - - private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) } - - @RequiresApi(Build.VERSION_CODES.N) - fun onMouseInput(mask: Int, _x: Int, _y: Int) { - val x = max(0, _x) - val y = max(0, _y) - - if (mask == 0 || mask == LEFT_MOVE) { - val oldX = mouseX - val oldY = mouseY - mouseX = x * SCREEN_INFO.scale - mouseY = y * SCREEN_INFO.scale - if (isWaitingLongPress) { - val delta = abs(oldX - mouseX) + abs(oldY - mouseY) - Log.d(logTag,"delta:$delta") - if (delta > 8) { - isWaitingLongPress = false - } - } - } - - // left button down, was up - if (mask == LEFT_DOWN) { - isWaitingLongPress = true - timer.schedule(object : TimerTask() { - override fun run() { - if (isWaitingLongPress) { - isWaitingLongPress = false - continueGesture(mouseX, mouseY) - } - } - }, longPressDuration) - - leftIsDown = true - startGesture(mouseX, mouseY) - return - } - - // left down, was down - if (leftIsDown) { - continueGesture(mouseX, mouseY) - } - - // left up, was down - if (mask == LEFT_UP) { - if (leftIsDown) { - leftIsDown = false - isWaitingLongPress = false - endGesture(mouseX, mouseY) - return - } - } - - if (mask == RIGHT_UP) { - longPress(mouseX, mouseY) - return - } - - if (mask == BACK_UP) { - performGlobalAction(GLOBAL_ACTION_BACK) - return - } - - // long WHEEL_BUTTON_DOWN -> GLOBAL_ACTION_RECENTS - if (mask == WHEEL_BUTTON_DOWN) { - timer.purge() - recentActionTask = object : TimerTask() { - override fun run() { - performGlobalAction(GLOBAL_ACTION_RECENTS) - recentActionTask = null - } - } - timer.schedule(recentActionTask, LONG_TAP_DELAY) - } - - // wheel button up - if (mask == WHEEL_BUTTON_UP) { - if (recentActionTask != null) { - recentActionTask!!.cancel() - performGlobalAction(GLOBAL_ACTION_HOME) - } - return - } - - if (mask == WHEEL_DOWN) { - if (mouseY < WHEEL_STEP) { - return - } - val path = Path() - path.moveTo(mouseX.toFloat(), mouseY.toFloat()) - path.lineTo(mouseX.toFloat(), (mouseY - WHEEL_STEP).toFloat()) - val stroke = GestureDescription.StrokeDescription( - path, - 0, - WHEEL_DURATION - ) - val builder = GestureDescription.Builder() - builder.addStroke(stroke) - wheelActionsQueue.offer(builder.build()) - consumeWheelActions() - - } - - if (mask == WHEEL_UP) { - if (mouseY < WHEEL_STEP) { - return - } - val path = Path() - path.moveTo(mouseX.toFloat(), mouseY.toFloat()) - path.lineTo(mouseX.toFloat(), (mouseY + WHEEL_STEP).toFloat()) - val stroke = GestureDescription.StrokeDescription( - path, - 0, - WHEEL_DURATION - ) - val builder = GestureDescription.Builder() - builder.addStroke(stroke) - wheelActionsQueue.offer(builder.build()) - consumeWheelActions() - } - } - - @RequiresApi(Build.VERSION_CODES.N) - fun onTouchInput(mask: Int, _x: Int, _y: Int) { - when (mask) { - TOUCH_PAN_UPDATE -> { - mouseX -= _x * SCREEN_INFO.scale - mouseY -= _y * SCREEN_INFO.scale - mouseX = max(0, mouseX); - mouseY = max(0, mouseY); - continueGesture(mouseX, mouseY) - } - TOUCH_PAN_START -> { - mouseX = max(0, _x) * SCREEN_INFO.scale - mouseY = max(0, _y) * SCREEN_INFO.scale - startGesture(mouseX, mouseY) - } - TOUCH_PAN_END -> { - endGesture(mouseX, mouseY) - mouseX = max(0, _x) * SCREEN_INFO.scale - mouseY = max(0, _y) * SCREEN_INFO.scale - } - else -> {} - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun consumeWheelActions() { - if (isWheelActionsPolling) { - return - } else { - isWheelActionsPolling = true - } - wheelActionsQueue.poll()?.let { - dispatchGesture(it, null, null) - timer.purge() - timer.schedule(object : TimerTask() { - override fun run() { - isWheelActionsPolling = false - consumeWheelActions() - } - }, WHEEL_DURATION + 10) - } ?: let { - isWheelActionsPolling = false - return - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun performClick(x: Int, y: Int, duration: Long) { - val path = Path() - path.moveTo(x.toFloat(), y.toFloat()) - try { - val longPressStroke = GestureDescription.StrokeDescription(path, 0, duration) - val builder = GestureDescription.Builder() - builder.addStroke(longPressStroke) - Log.d(logTag, "performClick x:$x y:$y time:$duration") - dispatchGesture(builder.build(), null, null) - } catch (e: Exception) { - Log.e(logTag, "performClick, error:$e") - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun longPress(x: Int, y: Int) { - performClick(x, y, longPressDuration) - } - - private fun startGesture(x: Int, y: Int) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - touchPath.reset() - } else { - touchPath = Path() - } - touchPath.moveTo(x.toFloat(), y.toFloat()) - lastTouchGestureStartTime = System.currentTimeMillis() - lastX = x - lastY = y - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun doDispatchGesture(x: Int, y: Int, willContinue: Boolean) { - touchPath.lineTo(x.toFloat(), y.toFloat()) - var duration = System.currentTimeMillis() - lastTouchGestureStartTime - if (duration <= 0) { - duration = 1 - } - try { - if (stroke == null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stroke = GestureDescription.StrokeDescription( - touchPath, - 0, - duration, - willContinue - ) - } else { - stroke = GestureDescription.StrokeDescription( - touchPath, - 0, - duration - ) - } - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stroke = stroke?.continueStroke(touchPath, 0, duration, willContinue) - } else { - stroke = null - stroke = GestureDescription.StrokeDescription( - touchPath, - 0, - duration - ) - } - } - stroke?.let { - val builder = GestureDescription.Builder() - builder.addStroke(it) - Log.d(logTag, "doDispatchGesture x:$x y:$y time:$duration") - dispatchGesture(builder.build(), null, null) - } - } catch (e: Exception) { - Log.e(logTag, "doDispatchGesture, willContinue:$willContinue, error:$e") - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun continueGesture(x: Int, y: Int) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - doDispatchGesture(x, y, true) - touchPath.reset() - touchPath.moveTo(x.toFloat(), y.toFloat()) - lastTouchGestureStartTime = System.currentTimeMillis() - lastX = x - lastY = y - } else { - touchPath.lineTo(x.toFloat(), y.toFloat()) - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun endGestureBelowO(x: Int, y: Int) { - try { - touchPath.lineTo(x.toFloat(), y.toFloat()) - var duration = System.currentTimeMillis() - lastTouchGestureStartTime - if (duration <= 0) { - duration = 1 - } - val stroke = GestureDescription.StrokeDescription( - touchPath, - 0, - duration - ) - val builder = GestureDescription.Builder() - builder.addStroke(stroke) - Log.d(logTag, "end gesture x:$x y:$y time:$duration") - dispatchGesture(builder.build(), null, null) - } catch (e: Exception) { - Log.e(logTag, "endGesture error:$e") - } - } - - @RequiresApi(Build.VERSION_CODES.N) - private fun endGesture(x: Int, y: Int) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - doDispatchGesture(x, y, false) - touchPath.reset() - stroke = null - } else { - endGestureBelowO(x, y) - } - } - - @RequiresApi(Build.VERSION_CODES.N) - fun onKeyEvent(data: ByteArray) { - val keyEvent = KeyEvent.parseFrom(data) - val keyboardMode = keyEvent.getMode() - - var textToCommit: String? = null - - // [down] indicates the key's state(down or up). - // [press] indicates a click event(down and up). - // https://github.com/rustdesk/rustdesk/blob/3a7594755341f023f56fa4b6a43b60d6b47df88d/flutter/lib/models/input_model.dart#L688 - if (keyEvent.hasSeq()) { - textToCommit = keyEvent.getSeq() - } else if (keyboardMode == KeyboardMode.Legacy) { - if (keyEvent.hasChr() && (keyEvent.getDown() || keyEvent.getPress())) { - val chr = keyEvent.getChr() - if (chr != null) { - textToCommit = String(Character.toChars(chr)) - } - } - } else if (keyboardMode == KeyboardMode.Translate) { - } else { - } - - Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit") - - var ke: KeyEventAndroid? = null - if (Build.VERSION.SDK_INT < 33 || textToCommit == null) { - ke = KeyEventConverter.toAndroidKeyEvent(keyEvent) - } - ke?.let { event -> - if (tryHandleVolumeKeyEvent(event)) { - return - } else if (tryHandlePowerKeyEvent(event)) { - return - } - } - - if (Build.VERSION.SDK_INT >= 33) { - getInputMethod()?.let { inputMethod -> - inputMethod.getCurrentInputConnection()?.let { inputConnection -> - if (textToCommit != null) { - textToCommit?.let { text -> - inputConnection.commitText(text, 1, null) - } - } else { - ke?.let { event -> - inputConnection.sendKeyEvent(event) - if (keyEvent.getPress()) { - val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode) - inputConnection.sendKeyEvent(actionUpEvent) - } - } - } - } - } - } else { - val handler = Handler(Looper.getMainLooper()) - handler.post { - ke?.let { event -> - val possibleNodes = possibleAccessibiltyNodes() - Log.d(logTag, "possibleNodes:$possibleNodes") - for (item in possibleNodes) { - val success = trySendKeyEvent(event, item, textToCommit) - if (success) { - if (keyEvent.getPress()) { - val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode) - trySendKeyEvent(actionUpEvent, item, textToCommit) - } - break - } - } - } - } - } - } - - private fun tryHandleVolumeKeyEvent(event: KeyEventAndroid): Boolean { - when (event.keyCode) { - KeyEventAndroid.KEYCODE_VOLUME_UP -> { - if (event.action == KeyEventAndroid.ACTION_DOWN) { - volumeController.raiseVolume(null, true, AudioManager.STREAM_SYSTEM) - } - return true - } - KeyEventAndroid.KEYCODE_VOLUME_DOWN -> { - if (event.action == KeyEventAndroid.ACTION_DOWN) { - volumeController.lowerVolume(null, true, AudioManager.STREAM_SYSTEM) - } - return true - } - KeyEventAndroid.KEYCODE_VOLUME_MUTE -> { - if (event.action == KeyEventAndroid.ACTION_DOWN) { - volumeController.toggleMute(true, AudioManager.STREAM_SYSTEM) - } - return true - } - else -> { - return false - } - } - } - - private fun tryHandlePowerKeyEvent(event: KeyEventAndroid): Boolean { - if (event.keyCode == KeyEventAndroid.KEYCODE_POWER) { - // Perform power dialog action when action is up - if (event.action == KeyEventAndroid.ACTION_UP) { - performGlobalAction(GLOBAL_ACTION_POWER_DIALOG); - } - return true - } - return false - } - - private fun insertAccessibilityNode(list: LinkedList, node: AccessibilityNodeInfo) { - if (node == null) { - return - } - if (list.contains(node)) { - return - } - list.add(node) - } - - private fun findChildNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? { - if (node == null) { - return null - } - if (node.isEditable() && node.isFocusable()) { - return node - } - val childCount = node.getChildCount() - for (i in 0 until childCount) { - val child = node.getChild(i) - if (child != null) { - if (child.isEditable() && child.isFocusable()) { - return child - } - if (Build.VERSION.SDK_INT < 33) { - child.recycle() - } - } - } - for (i in 0 until childCount) { - val child = node.getChild(i) - if (child != null) { - val result = findChildNode(child) - if (Build.VERSION.SDK_INT < 33) { - if (child != result) { - child.recycle() - } - } - if (result != null) { - return result - } - } - } - return null - } - - private fun possibleAccessibiltyNodes(): LinkedList { - val linkedList = LinkedList() - val latestList = LinkedList() - - val focusInput = findFocus(AccessibilityNodeInfo.FOCUS_INPUT) - var focusAccessibilityInput = findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) - - val rootInActiveWindow = getRootInActiveWindow() - - Log.d(logTag, "focusInput:$focusInput focusAccessibilityInput:$focusAccessibilityInput rootInActiveWindow:$rootInActiveWindow") - - if (focusInput != null) { - if (focusInput.isFocusable() && focusInput.isEditable()) { - insertAccessibilityNode(linkedList, focusInput) - } else { - insertAccessibilityNode(latestList, focusInput) - } - } - - if (focusAccessibilityInput != null) { - if (focusAccessibilityInput.isFocusable() && focusAccessibilityInput.isEditable()) { - insertAccessibilityNode(linkedList, focusAccessibilityInput) - } else { - insertAccessibilityNode(latestList, focusAccessibilityInput) - } - } - - val childFromFocusInput = findChildNode(focusInput) - Log.d(logTag, "childFromFocusInput:$childFromFocusInput") - - if (childFromFocusInput != null) { - insertAccessibilityNode(linkedList, childFromFocusInput) - } - - val childFromFocusAccessibilityInput = findChildNode(focusAccessibilityInput) - if (childFromFocusAccessibilityInput != null) { - insertAccessibilityNode(linkedList, childFromFocusAccessibilityInput) - } - Log.d(logTag, "childFromFocusAccessibilityInput:$childFromFocusAccessibilityInput") - - if (rootInActiveWindow != null) { - insertAccessibilityNode(linkedList, rootInActiveWindow) - } - - for (item in latestList) { - insertAccessibilityNode(linkedList, item) - } - - return linkedList - } - - private fun trySendKeyEvent(event: KeyEventAndroid, node: AccessibilityNodeInfo, textToCommit: String?): Boolean { - node.refresh() - this.fakeEditTextForTextStateCalculation?.setSelection(0,0) - this.fakeEditTextForTextStateCalculation?.setText(null) - - val text = node.getText() - var isShowingHint = false - if (Build.VERSION.SDK_INT >= 26) { - isShowingHint = node.isShowingHintText() - } - - var textSelectionStart = node.textSelectionStart - var textSelectionEnd = node.textSelectionEnd - - if (text != null) { - if (textSelectionStart > text.length) { - textSelectionStart = text.length - } - if (textSelectionEnd > text.length) { - textSelectionEnd = text.length - } - if (textSelectionStart > textSelectionEnd) { - textSelectionStart = textSelectionEnd - } - } - - var success = false - - Log.d(logTag, "existing text:$text textToCommit:$textToCommit textSelectionStart:$textSelectionStart textSelectionEnd:$textSelectionEnd") - - if (textToCommit != null) { - if ((textSelectionStart == -1) || (textSelectionEnd == -1)) { - val newText = textToCommit - this.fakeEditTextForTextStateCalculation?.setText(newText) - success = updateTextForAccessibilityNode(node) - } else if (text != null) { - this.fakeEditTextForTextStateCalculation?.setText(text) - this.fakeEditTextForTextStateCalculation?.setSelection( - textSelectionStart, - textSelectionEnd - ) - this.fakeEditTextForTextStateCalculation?.text?.insert(textSelectionStart, textToCommit) - success = updateTextAndSelectionForAccessibiltyNode(node) - } - } else { - if (isShowingHint) { - this.fakeEditTextForTextStateCalculation?.setText(null) - } else { - this.fakeEditTextForTextStateCalculation?.setText(text) - } - if (textSelectionStart != -1 && textSelectionEnd != -1) { - Log.d(logTag, "setting selection $textSelectionStart $textSelectionEnd") - this.fakeEditTextForTextStateCalculation?.setSelection( - textSelectionStart, - textSelectionEnd - ) - } - - this.fakeEditTextForTextStateCalculation?.let { - // This is essiential to make sure layout object is created. OnKeyDown may not work if layout is not created. - val rect = Rect() - node.getBoundsInScreen(rect) - - it.layout(rect.left, rect.top, rect.right, rect.bottom) - it.onPreDraw() - if (event.action == KeyEventAndroid.ACTION_DOWN) { - val succ = it.onKeyDown(event.getKeyCode(), event) - Log.d(logTag, "onKeyDown $succ") - } else if (event.action == KeyEventAndroid.ACTION_UP) { - val success = it.onKeyUp(event.getKeyCode(), event) - Log.d(logTag, "keyup $success") - } else {} - } - - success = updateTextAndSelectionForAccessibiltyNode(node) - } - return success - } - - fun updateTextForAccessibilityNode(node: AccessibilityNodeInfo): Boolean { - var success = false - this.fakeEditTextForTextStateCalculation?.text?.let { - val arguments = Bundle() - arguments.putCharSequence( - AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, - it.toString() - ) - success = node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) - } - return success - } - - fun updateTextAndSelectionForAccessibiltyNode(node: AccessibilityNodeInfo): Boolean { - var success = updateTextForAccessibilityNode(node) - - if (success) { - val selectionStart = this.fakeEditTextForTextStateCalculation?.selectionStart - val selectionEnd = this.fakeEditTextForTextStateCalculation?.selectionEnd - - if (selectionStart != null && selectionEnd != null) { - val arguments = Bundle() - arguments.putInt( - AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, - selectionStart - ) - arguments.putInt( - AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, - selectionEnd - ) - success = node.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments) - Log.d(logTag, "Update selection to $selectionStart $selectionEnd success:$success") - } - } - - return success - } - - - override fun onAccessibilityEvent(event: AccessibilityEvent) { - } - - override fun onServiceConnected() { - super.onServiceConnected() - ctx = this - val info = AccessibilityServiceInfo() - if (Build.VERSION.SDK_INT >= 33) { - info.flags = FLAG_INPUT_METHOD_EDITOR or FLAG_RETRIEVE_INTERACTIVE_WINDOWS - } else { - info.flags = FLAG_RETRIEVE_INTERACTIVE_WINDOWS - } - setServiceInfo(info) - fakeEditTextForTextStateCalculation = EditText(this) - // Size here doesn't matter, we won't show this view. - fakeEditTextForTextStateCalculation?.layoutParams = LayoutParams(100, 100) - fakeEditTextForTextStateCalculation?.onPreDraw() - val layout = fakeEditTextForTextStateCalculation?.getLayout() - Log.d(logTag, "fakeEditTextForTextStateCalculation layout:$layout") - Log.d(logTag, "onServiceConnected!") - } - - override fun onDestroy() { - ctx = null - super.onDestroy() - } - - override fun onInterrupt() {} -} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt deleted file mode 100644 index ccb33195e..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/KeyboardKeyEventMapper.kt +++ /dev/null @@ -1,122 +0,0 @@ -package hbb; -import android.view.KeyEvent -import android.view.KeyCharacterMap -import hbb.MessageOuterClass.KeyboardMode -import hbb.MessageOuterClass.ControlKey - -object KeyEventConverter { - fun toAndroidKeyEvent(keyEventProto: hbb.MessageOuterClass.KeyEvent): KeyEvent { - var chrValue = 0 - var modifiers = 0 - - val keyboardMode = keyEventProto.getMode() - - if (keyEventProto.hasChr()) { - if (keyboardMode == KeyboardMode.Map || keyboardMode == KeyboardMode.Translate) { - chrValue = keyEventProto.getChr() - } else { - chrValue = convertUnicodeToKeyCode(keyEventProto.getChr() as Int) - } - } else if (keyEventProto.hasControlKey()) { - chrValue = convertControlKeyToKeyCode(keyEventProto.getControlKey()) - } - - var modifiersList = keyEventProto.getModifiersList() - - if (modifiersList != null) { - for (modifier in keyEventProto.getModifiersList()) { - val modifierValue = convertModifier(modifier) - modifiers = modifiers or modifierValue - } - } - - var action = 0 - if (keyEventProto.getDown() || keyEventProto.getPress()) { - action = KeyEvent.ACTION_DOWN - } else { - action = KeyEvent.ACTION_UP - } - - return KeyEvent(0, 0, action, chrValue, 0, modifiers) - } - - private fun convertModifier(controlKey: hbb.MessageOuterClass.ControlKey): Int { - // Add logic to map ControlKey values to Android KeyEvent key codes. - // You'll need to provide the mapping for each key. - return when (controlKey) { - ControlKey.Alt -> KeyEvent.META_ALT_ON - ControlKey.Control -> KeyEvent.META_CTRL_ON - ControlKey.CapsLock -> KeyEvent.META_CAPS_LOCK_ON - ControlKey.Meta -> KeyEvent.META_META_ON - ControlKey.NumLock -> KeyEvent.META_NUM_LOCK_ON - ControlKey.RShift -> KeyEvent.META_SHIFT_RIGHT_ON - ControlKey.Shift -> KeyEvent.META_SHIFT_ON - ControlKey.RAlt -> KeyEvent.META_ALT_RIGHT_ON - ControlKey.RControl -> KeyEvent.META_CTRL_RIGHT_ON - else -> 0 // Default to unknown. - } - } - - private val tag = "KeyEventConverter" - - private fun convertUnicodeToKeyCode(unicode: Int): Int { - val charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD) - android.util.Log.d(tag, "unicode: $unicode") - val events = charMap.getEvents(charArrayOf(unicode.toChar())) - if (events != null && events.size > 0) { - android.util.Log.d(tag, "keycode ${events[0].keyCode}") - return events[0].keyCode - } - return 0 - } - - private fun convertControlKeyToKeyCode(controlKey: hbb.MessageOuterClass.ControlKey): Int { - // Add logic to map ControlKey values to Android KeyEvent key codes. - // You'll need to provide the mapping for each key. - return when (controlKey) { - ControlKey.Alt -> KeyEvent.KEYCODE_ALT_LEFT - ControlKey.Backspace -> KeyEvent.KEYCODE_DEL - ControlKey.Control -> KeyEvent.KEYCODE_CTRL_LEFT - ControlKey.CapsLock -> KeyEvent.KEYCODE_CAPS_LOCK - ControlKey.Meta -> KeyEvent.KEYCODE_META_LEFT - ControlKey.NumLock -> KeyEvent.KEYCODE_NUM_LOCK - ControlKey.RShift -> KeyEvent.KEYCODE_SHIFT_RIGHT - ControlKey.Shift -> KeyEvent.KEYCODE_SHIFT_LEFT - ControlKey.RAlt -> KeyEvent.KEYCODE_ALT_RIGHT - ControlKey.RControl -> KeyEvent.KEYCODE_CTRL_RIGHT - ControlKey.DownArrow -> KeyEvent.KEYCODE_DPAD_DOWN - ControlKey.LeftArrow -> KeyEvent.KEYCODE_DPAD_LEFT - ControlKey.RightArrow -> KeyEvent.KEYCODE_DPAD_RIGHT - ControlKey.UpArrow -> KeyEvent.KEYCODE_DPAD_UP - ControlKey.End -> KeyEvent.KEYCODE_MOVE_END - ControlKey.Home -> KeyEvent.KEYCODE_MOVE_HOME - ControlKey.PageUp -> KeyEvent.KEYCODE_PAGE_UP - ControlKey.PageDown -> KeyEvent.KEYCODE_PAGE_DOWN - ControlKey.Insert -> KeyEvent.KEYCODE_INSERT - ControlKey.Escape -> KeyEvent.KEYCODE_ESCAPE - ControlKey.F1 -> KeyEvent.KEYCODE_F1 - ControlKey.F2 -> KeyEvent.KEYCODE_F2 - ControlKey.F3 -> KeyEvent.KEYCODE_F3 - ControlKey.F4 -> KeyEvent.KEYCODE_F4 - ControlKey.F5 -> KeyEvent.KEYCODE_F5 - ControlKey.F6 -> KeyEvent.KEYCODE_F6 - ControlKey.F7 -> KeyEvent.KEYCODE_F7 - ControlKey.F8 -> KeyEvent.KEYCODE_F8 - ControlKey.F9 -> KeyEvent.KEYCODE_F9 - ControlKey.F10 -> KeyEvent.KEYCODE_F10 - ControlKey.F11 -> KeyEvent.KEYCODE_F11 - ControlKey.F12 -> KeyEvent.KEYCODE_F12 - ControlKey.Space -> KeyEvent.KEYCODE_SPACE - ControlKey.Tab -> KeyEvent.KEYCODE_TAB - ControlKey.Return -> KeyEvent.KEYCODE_ENTER - ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL - ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR - ControlKey.Pause -> KeyEvent.KEYCODE_BREAK - ControlKey.VolumeMute -> KeyEvent.KEYCODE_VOLUME_MUTE - ControlKey.VolumeUp -> KeyEvent.KEYCODE_VOLUME_UP - ControlKey.VolumeDown -> KeyEvent.KEYCODE_VOLUME_DOWN - ControlKey.Power -> KeyEvent.KEYCODE_POWER - else -> 0 // Default to unknown. - } - } -} 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 deleted file mode 100644 index fea8e5519..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ /dev/null @@ -1,414 +0,0 @@ -package com.carriez.flutter_hbb - -/** - * Handle events from flutter - * Request MediaProjection permission - * - * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG - */ - -import ffi.FFI - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.content.ClipboardManager -import android.os.Bundle -import android.os.Build -import android.os.IBinder -import android.util.Log -import android.view.WindowManager -import android.media.MediaCodecInfo -import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface -import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar -import android.media.MediaCodecList -import android.media.MediaFormat -import android.util.DisplayMetrics -import androidx.annotation.RequiresApi -import org.json.JSONArray -import org.json.JSONObject -import com.hjq.permissions.XXPermissions -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodChannel -import kotlin.concurrent.thread - - -class MainActivity : FlutterActivity() { - companion object { - var flutterMethodChannel: MethodChannel? = null - private var _rdClipboardManager: RdClipboardManager? = null - val rdClipboardManager: RdClipboardManager? - get() = _rdClipboardManager; - } - - private val channelTag = "mChannel" - private val logTag = "mMainActivity" - private var mainService: MainService? = null - - private var isAudioStart = false - private val audioRecordHandle = AudioRecordHandle(this, { false }, { isAudioStart }) - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - if (MainService.isReady) { - Intent(activity, MainService::class.java).also { - bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) - } - } - flutterMethodChannel = MethodChannel( - flutterEngine.dartExecutor.binaryMessenger, - channelTag - ) - initFlutterChannel(flutterMethodChannel!!) - thread { - try { - setCodecInfo() - } catch (e: Exception) { - Log.e("MainActivity", "Failed to setCodecInfo: ${e.message}", e) - } - } - } - - override fun onResume() { - super.onResume() - val inputPer = InputService.isOpen - activity.runOnUiThread { - 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 == REQ_INVOKE_PERMISSION_ACTIVITY_MEDIA_PROJECTION && resultCode == RES_FAILED) { - flutterMethodChannel?.invokeMethod("on_media_projection_canceled", null) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (_rdClipboardManager == null) { - _rdClipboardManager = RdClipboardManager(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) - FFI.setClipboardManager(_rdClipboardManager!!) - } - } - - override fun onDestroy() { - Log.e(logTag, "onDestroy") - mainService?.let { - unbindService(serviceConnection) - } - super.onDestroy() - } - - private val serviceConnection = object : ServiceConnection { - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - Log.d(logTag, "onServiceConnected") - val binder = service as MainService.LocalBinder - mainService = binder.getService() - } - - override fun onServiceDisconnected(name: ComponentName?) { - Log.d(logTag, "onServiceDisconnected") - 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) - - } - "try_sync_clipboard" -> { - rdClipboardManager?.syncClipboard(true) - 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) - } - } - GET_VALUE -> { - if (call.arguments is String) { - if (call.arguments == KEY_IS_SUPPORT_VOICE_CALL) { - result.success(isSupportVoiceCall()) - } else { - result.error("-1", "No such key", null) - } - } else { - result.success(null) - } - } - "on_voice_call_started" -> { - onVoiceCallStarted() - } - "on_voice_call_closed" -> { - onVoiceCallClosed() - } - else -> { - result.error("-1", "No such method", null) - } - } - } - } - - private fun setCodecInfo() { - val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS) - val codecs = codecList.codecInfos - val codecArray = JSONArray() - - val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager - val wh = getScreenSize(windowManager) - var w = wh.first - var h = wh.second - val align = 64 - w = (w + align - 1) / align * align - h = (h + align - 1) / align * align - codecs.forEach { codec -> - val codecObject = JSONObject() - codecObject.put("name", codec.name) - codecObject.put("is_encoder", codec.isEncoder) - var hw: Boolean? = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - hw = codec.isHardwareAccelerated - } else { - // https://chromium.googlesource.com/external/webrtc/+/HEAD/sdk/android/src/java/org/webrtc/MediaCodecUtils.java#29 - // https://chromium.googlesource.com/external/webrtc/+/master/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java#229 - if (listOf("OMX.google.", "OMX.SEC.", "c2.android").any { codec.name.startsWith(it, true) }) { - hw = false - } else if (listOf("c2.qti", "OMX.qcom.video", "OMX.Exynos", "OMX.hisi", "OMX.MTK", "OMX.Intel", "OMX.Nvidia").any { codec.name.startsWith(it, true) }) { - hw = true - } - } - if (hw != true) { - return@forEach - } - codecObject.put("hw", hw) - var mime_type = "" - codec.supportedTypes.forEach { type -> - if (listOf("video/avc", "video/hevc").contains(type)) { // "video/x-vnd.on2.vp8", "video/x-vnd.on2.vp9", "video/av01" - mime_type = type; - } - } - if (mime_type.isNotEmpty()) { - codecObject.put("mime_type", mime_type) - val caps = codec.getCapabilitiesForType(mime_type) - if (codec.isEncoder) { - // Encoder's max_height and max_width are interchangeable - if (!caps.videoCapabilities.isSizeSupported(w,h) && !caps.videoCapabilities.isSizeSupported(h,w)) { - return@forEach - } - } - codecObject.put("min_width", caps.videoCapabilities.supportedWidths.lower) - codecObject.put("max_width", caps.videoCapabilities.supportedWidths.upper) - codecObject.put("min_height", caps.videoCapabilities.supportedHeights.lower) - codecObject.put("max_height", caps.videoCapabilities.supportedHeights.upper) - val surface = caps.colorFormats.contains(COLOR_FormatSurface); - codecObject.put("surface", surface) - val nv12 = caps.colorFormats.contains(COLOR_FormatYUV420SemiPlanar) - codecObject.put("nv12", nv12) - if (!(nv12 || surface)) { - return@forEach - } - codecObject.put("min_bitrate", caps.videoCapabilities.bitrateRange.lower / 1000) - codecObject.put("max_bitrate", caps.videoCapabilities.bitrateRange.upper / 1000) - if (!codec.isEncoder) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - codecObject.put("low_latency", caps.isFeatureSupported(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency)) - } - } - if (!codec.isEncoder) { - return@forEach - } - codecArray.put(codecObject) - } - } - val result = JSONObject() - result.put("version", Build.VERSION.SDK_INT) - result.put("w", w) - result.put("h", h) - result.put("codecs", codecArray) - FFI.setCodecInfo(result.toString()) - } - - private fun onVoiceCallStarted() { - var ok = false - mainService?.let { - ok = it.onVoiceCallStarted() - } ?: let { - isAudioStart = true - ok = audioRecordHandle.onVoiceCallStarted(null) - } - if (!ok) { - // Rarely happens, So we just add log and msgbox here. - Log.e(logTag, "onVoiceCallStarted fail") - flutterMethodChannel?.invokeMethod("msgbox", mapOf( - "type" to "custom-nook-nocancel-hasclose-error", - "title" to "Voice call", - "text" to "Failed to start voice call.")) - } else { - Log.d(logTag, "onVoiceCallStarted success") - } - } - - private fun onVoiceCallClosed() { - var ok = false - mainService?.let { - ok = it.onVoiceCallClosed() - } ?: let { - isAudioStart = false - ok = audioRecordHandle.onVoiceCallClosed(null) - } - if (!ok) { - // Rarely happens, So we just add log and msgbox here. - Log.e(logTag, "onVoiceCallClosed fail") - flutterMethodChannel?.invokeMethod("msgbox", mapOf( - "type" to "custom-nook-nocancel-hasclose-error", - "title" to "Voice call", - "text" to "Failed to stop voice call.")) - } else { - Log.d(logTag, "onVoiceCallClosed success") - } - } - - override fun onStop() { - super.onStop() - val disableFloatingWindow = FFI.getLocalOption("disable-floating-window") == "Y" - if (!disableFloatingWindow && MainService.isReady) { - startService(Intent(this, FloatingWindowService::class.java)) - } - } - - override fun onStart() { - super.onStart() - stopService(Intent(this, FloatingWindowService::class.java)) - } -} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainApplication.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainApplication.kt deleted file mode 100644 index 59a3b0fb4..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainApplication.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.carriez.flutter_hbb - -import android.app.Application -import android.util.Log -import ffi.FFI - -class MainApplication : Application() { - companion object { - private const val TAG = "MainApplication" - } - - override fun onCreate() { - super.onCreate() - Log.d(TAG, "App start") - FFI.onAppStart(applicationContext) - } -} 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 deleted file mode 100644 index 7bb16a00a..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ /dev/null @@ -1,729 +0,0 @@ -package com.carriez.flutter_hbb - -import ffi.FFI - -/** - * Capture screen,get video and audio,send to rust. - * Dispatch notifications - * - * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG - */ - -import android.Manifest -import android.annotation.SuppressLint -import android.app.* -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.content.res.Configuration.ORIENTATION_LANDSCAPE -import android.graphics.Color -import android.graphics.PixelFormat -import android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR -import android.hardware.display.VirtualDisplay -import android.media.* -import android.media.projection.MediaProjection -import android.media.projection.MediaProjectionManager -import android.os.* -import android.util.DisplayMetrics -import android.util.Log -import android.view.Surface -import android.view.Surface.FRAME_RATE_COMPATIBILITY_DEFAULT -import android.view.WindowManager -import androidx.annotation.Keep -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 -import org.json.JSONObject -import java.nio.ByteBuffer -import kotlin.math.max -import kotlin.math.min - -const val DEFAULT_NOTIFY_TITLE = "RustDesk" -const val DEFAULT_NOTIFY_TEXT = "Service is running" -const val DEFAULT_NOTIFY_ID = 1 -const val NOTIFY_ID_OFFSET = 100 - -const val MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_VP9 - -// video const - -const val MAX_SCREEN_SIZE = 1200 - -const val VIDEO_KEY_BIT_RATE = 1024_000 -const val VIDEO_KEY_FRAME_RATE = 30 - -class MainService : Service() { - - @Keep - @RequiresApi(Build.VERSION_CODES.N) - fun rustPointerInput(kind: Int, mask: Int, x: Int, y: Int) { - // turn on screen with LEFT_DOWN when screen off - if (!powerManager.isInteractive && (kind == 0 || mask == LEFT_DOWN)) { - if (wakeLock.isHeld) { - Log.d(logTag, "Turn on Screen, WakeLock release") - wakeLock.release() - } - Log.d(logTag,"Turn on Screen") - wakeLock.acquire(5000) - } else { - when (kind) { - 0 -> { // touch - InputService.ctx?.onTouchInput(mask, x, y) - } - 1 -> { // mouse - InputService.ctx?.onMouseInput(mask, x, y) - } - else -> { - } - } - } - } - - @Keep - @RequiresApi(Build.VERSION_CODES.N) - fun rustKeyEventInput(input: ByteArray) { - InputService.ctx?.onKeyEvent(input) - } - - @Keep - fun rustGetByName(name: String): String { - return when (name) { - "screen_size" -> { - JSONObject().apply { - put("width",SCREEN_INFO.width) - put("height",SCREEN_INFO.height) - put("scale",SCREEN_INFO.scale) - }.toString() - } - "is_start" -> { - isStart.toString() - } - else -> "" - } - } - - @Keep - fun rustSetByName(name: String, arg1: String, arg2: String) { - when (name) { - "add_connection" -> { - try { - val jsonObject = JSONObject(arg1) - val id = jsonObject["id"] as Int - val username = jsonObject["name"] as String - val peerId = jsonObject["peer_id"] as String - val authorized = jsonObject["authorized"] as Boolean - val isFileTransfer = jsonObject["is_file_transfer"] as Boolean - val type = if (isFileTransfer) { - translate("Transfer file") - } else { - translate("Share screen") - } - if (authorized) { - if (!isFileTransfer && !isStart) { - startCapture() - } - onClientAuthorizedNotification(id, type, username, peerId) - } else { - loginRequestNotification(id, type, username, peerId) - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - "update_voice_call_state" -> { - try { - val jsonObject = JSONObject(arg1) - val id = jsonObject["id"] as Int - val username = jsonObject["name"] as String - val peerId = jsonObject["peer_id"] as String - val inVoiceCall = jsonObject["in_voice_call"] as Boolean - val incomingVoiceCall = jsonObject["incoming_voice_call"] as Boolean - if (!inVoiceCall) { - if (incomingVoiceCall) { - voiceCallRequestNotification(id, "Voice Call Request", username, peerId) - } else { - if (!audioRecordHandle.switchOutVoiceCall(mediaProjection)) { - Log.e(logTag, "switchOutVoiceCall fail") - MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf( - "type" to "custom-nook-nocancel-hasclose-error", - "title" to "Voice call", - "text" to "Failed to switch out voice call.")) - } - } - } else { - if (!audioRecordHandle.switchToVoiceCall(mediaProjection)) { - Log.e(logTag, "switchToVoiceCall fail") - MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf( - "type" to "custom-nook-nocancel-hasclose-error", - "title" to "Voice call", - "text" to "Failed to switch to voice call.")) - } - } - } catch (e: JSONException) { - e.printStackTrace() - } - } - "stop_capture" -> { - Log.d(logTag, "from rust:stop_capture") - stopCapture() - } - "half_scale" -> { - val halfScale = arg1.toBoolean() - if (isHalfScale != halfScale) { - isHalfScale = halfScale - updateScreenInfo(resources.configuration.orientation) - } - - } - else -> { - } - } - } - - private var serviceLooper: Looper? = null - private var serviceHandler: Handler? = null - - private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager } - private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")} - - companion object { - private var _isReady = false // media permission ready status - private var _isStart = false // screen capture start status - private var _isAudioStart = false // audio capture start status - val isReady: Boolean - get() = _isReady - val isStart: Boolean - get() = _isStart - val isAudioStart: Boolean - get() = _isAudioStart - } - - private val logTag = "LOG_SERVICE" - private val useVP9 = false - private val binder = LocalBinder() - - private var reuseVirtualDisplay = Build.VERSION.SDK_INT > 33 - - // video - private var mediaProjection: MediaProjection? = null - private var surface: Surface? = null - private val sendVP9Thread = Executors.newSingleThreadExecutor() - private var videoEncoder: MediaCodec? = null - private var imageReader: ImageReader? = null - private var virtualDisplay: VirtualDisplay? = null - - // audio - private val audioRecordHandle = AudioRecordHandle(this, { isStart }, { isAudioStart }) - - // notification - private lateinit var notificationManager: NotificationManager - private lateinit var notificationChannel: String - private lateinit var notificationBuilder: NotificationCompat.Builder - - override fun onCreate() { - super.onCreate() - Log.d(logTag,"MainService onCreate, sdk int:${Build.VERSION.SDK_INT} reuseVirtualDisplay:$reuseVirtualDisplay") - FFI.init(this) - HandlerThread("Service", Process.THREAD_PRIORITY_BACKGROUND).apply { - start() - serviceLooper = looper - serviceHandler = Handler(looper) - } - updateScreenInfo(resources.configuration.orientation) - initNotification() - - // 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, "") ?: "" - FFI.startServer(configPath, "") - - createForegroundNotification() - } - - override fun onDestroy() { - checkMediaPermission() - stopService(Intent(this, FloatingWindowService::class.java)) - super.onDestroy() - } - - private var isHalfScale: Boolean? = null; - private fun updateScreenInfo(orientation: Int) { - var w: Int - var h: Int - var dpi: Int - val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager - - @Suppress("DEPRECATION") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val m = windowManager.maximumWindowMetrics - w = m.bounds.width() - h = m.bounds.height() - dpi = resources.configuration.densityDpi - } else { - val dm = DisplayMetrics() - windowManager.defaultDisplay.getRealMetrics(dm) - w = dm.widthPixels - h = dm.heightPixels - dpi = dm.densityDpi - } - - val max = max(w,h) - val min = min(w,h) - if (orientation == ORIENTATION_LANDSCAPE) { - w = max - h = min - } else { - w = min - h = max - } - Log.d(logTag,"updateScreenInfo:w:$w,h:$h") - var scale = 1 - if (w != 0 && h != 0) { - if (isHalfScale == true && (w > MAX_SCREEN_SIZE || h > MAX_SCREEN_SIZE)) { - scale = 2 - w /= scale - h /= scale - dpi /= scale - } - if (SCREEN_INFO.width != w) { - SCREEN_INFO.width = w - SCREEN_INFO.height = h - SCREEN_INFO.scale = scale - SCREEN_INFO.dpi = dpi - if (isStart) { - stopCapture() - FFI.refreshScreen() - startCapture() - } else { - FFI.refreshScreen() - } - } - - } - } - - override fun onBind(intent: Intent): IBinder { - Log.d(logTag, "service onBind") - return binder - } - - inner class LocalBinder : Binder() { - init { - Log.d(logTag, "LocalBinder init") - } - - fun getService(): MainService = this@MainService - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.d("whichService", "this service: ${Thread.currentThread()}") - super.onStartCommand(intent, flags, startId) - if (intent?.action == ACT_INIT_MEDIA_PROJECTION_AND_SERVICE) { - createForegroundNotification() - - if (intent.getBooleanExtra(EXT_INIT_FROM_BOOT, false)) { - FFI.startService() - } - Log.d(logTag, "service starting: ${startId}:${Thread.currentThread()}") - val mediaProjectionManager = - getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager - - intent.getParcelableExtra(EXT_MEDIA_PROJECTION_RES_INTENT)?.let { - mediaProjection = - mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, it) - checkMediaPermission() - _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 - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - 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) { - // TODO - null - } else { - Log.d(logTag, "ImageReader.newInstance:INFO:$SCREEN_INFO") - imageReader = - ImageReader.newInstance( - SCREEN_INFO.width, - SCREEN_INFO.height, - PixelFormat.RGBA_8888, - 4 - ).apply { - setOnImageAvailableListener({ imageReader: ImageReader -> - try { - // If not call acquireLatestImage, listener will not be called again - imageReader.acquireLatestImage().use { image -> - if (image == null || !isStart) return@setOnImageAvailableListener - val planes = image.planes - val buffer = planes[0].buffer - buffer.rewind() - FFI.onVideoFrameUpdate(buffer) - } - } catch (ignored: java.lang.Exception) { - } - }, serviceHandler) - } - Log.d(logTag, "ImageReader.setOnImageAvailableListener done") - imageReader?.surface - } - } - - fun onVoiceCallStarted(): Boolean { - return audioRecordHandle.onVoiceCallStarted(mediaProjection) - } - - fun onVoiceCallClosed(): Boolean { - return audioRecordHandle.onVoiceCallClosed(mediaProjection) - } - - fun startCapture(): Boolean { - if (isStart) { - return true - } - if (mediaProjection == null) { - Log.w(logTag, "startCapture fail,mediaProjection is null") - return false - } - - updateScreenInfo(resources.configuration.orientation) - Log.d(logTag, "Start Capture") - surface = createSurface() - - if (useVP9) { - startVP9VideoRecorder(mediaProjection!!) - } else { - startRawVideoRecorder(mediaProjection!!) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (!audioRecordHandle.createAudioRecorder(false, mediaProjection)) { - Log.d(logTag, "createAudioRecorder fail") - } else { - Log.d(logTag, "audio recorder start") - audioRecordHandle.startAudioRecorder() - } - } - checkMediaPermission() - _isStart = true - FFI.setFrameRawEnable("video",true) - MainActivity.rdClipboardManager?.setCaptureStarted(_isStart) - return true - } - - @Synchronized - fun stopCapture() { - Log.d(logTag, "Stop Capture") - FFI.setFrameRawEnable("video",false) - _isStart = false - MainActivity.rdClipboardManager?.setCaptureStarted(_isStart) - // release video - if (reuseVirtualDisplay) { - // The virtual display video projection can be paused by calling `setSurface(null)`. - // https://developer.android.com/reference/android/hardware/display/VirtualDisplay.Callback - // https://learn.microsoft.com/en-us/dotnet/api/android.hardware.display.virtualdisplay.callback.onpaused?view=net-android-34.0 - virtualDisplay?.setSurface(null) - } else { - virtualDisplay?.release() - } - // suface needs to be release after `imageReader.close()` to imageReader access released surface - // https://github.com/rustdesk/rustdesk/issues/4118#issuecomment-1515666629 - imageReader?.close() - imageReader = null - videoEncoder?.let { - it.signalEndOfInputStream() - it.stop() - it.release() - } - if (!reuseVirtualDisplay) { - virtualDisplay = null - } - videoEncoder = null - // suface needs to be release after `imageReader.close()` to imageReader access released surface - // https://github.com/rustdesk/rustdesk/issues/4118#issuecomment-1515666629 - surface?.release() - - // release audio - _isAudioStart = false - audioRecordHandle.tryReleaseAudio() - } - - fun destroy() { - Log.d(logTag, "destroy service") - _isReady = false - _isAudioStart = false - - stopCapture() - - if (reuseVirtualDisplay) { - virtualDisplay?.release() - virtualDisplay = null - } - - mediaProjection = null - checkMediaPermission() - stopForeground(true) - stopService(Intent(this, FloatingWindowService::class.java)) - stopSelf() - } - - fun checkMediaPermission(): Boolean { - Handler(Looper.getMainLooper()).post { - MainActivity.flutterMethodChannel?.invokeMethod( - "on_state_changed", - mapOf("name" to "media", "value" to isReady.toString()) - ) - } - Handler(Looper.getMainLooper()).post { - MainActivity.flutterMethodChannel?.invokeMethod( - "on_state_changed", - mapOf("name" to "input", "value" to InputService.isOpen.toString()) - ) - } - return isReady - } - - private fun startRawVideoRecorder(mp: MediaProjection) { - Log.d(logTag, "startRawVideoRecorder,screen info:$SCREEN_INFO") - if (surface == null) { - Log.d(logTag, "startRawVideoRecorder failed,surface is null") - return - } - createOrSetVirtualDisplay(mp, surface!!) - } - - private fun startVP9VideoRecorder(mp: MediaProjection) { - createMediaCodec() - videoEncoder?.let { - surface = it.createInputSurface() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - surface!!.setFrameRate(1F, FRAME_RATE_COMPATIBILITY_DEFAULT) - } - it.setCallback(cb) - it.start() - createOrSetVirtualDisplay(mp, surface!!) - } - } - - // https://github.com/bk138/droidVNC-NG/blob/b79af62db5a1c08ed94e6a91464859ffed6f4e97/app/src/main/java/net/christianbeier/droidvnc_ng/MediaProjectionService.java#L250 - // Reuse virtualDisplay if it exists, to avoid media projection confirmation dialog every connection. - private fun createOrSetVirtualDisplay(mp: MediaProjection, s: Surface) { - try { - virtualDisplay?.let { - it.resize(SCREEN_INFO.width, SCREEN_INFO.height, SCREEN_INFO.dpi) - it.setSurface(s) - } ?: let { - virtualDisplay = mp.createVirtualDisplay( - "RustDeskVD", - SCREEN_INFO.width, SCREEN_INFO.height, SCREEN_INFO.dpi, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - s, null, null - ) - } - } catch (e: SecurityException) { - Log.w(logTag, "createOrSetVirtualDisplay: got SecurityException, re-requesting confirmation"); - // This initiates a prompt dialog for the user to confirm screen projection. - requestMediaProjection() - } - } - - private val cb: MediaCodec.Callback = object : MediaCodec.Callback() { - override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {} - override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {} - - override fun onOutputBufferAvailable( - codec: MediaCodec, - index: Int, - info: MediaCodec.BufferInfo - ) { - codec.getOutputBuffer(index)?.let { buf -> - sendVP9Thread.execute { - val byteArray = ByteArray(buf.limit()) - buf.get(byteArray) - // sendVp9(byteArray) - codec.releaseOutputBuffer(index, false) - } - } - } - - override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { - Log.e(logTag, "MediaCodec.Callback error:$e") - } - } - - private fun createMediaCodec() { - Log.d(logTag, "MediaFormat.MIMETYPE_VIDEO_VP9 :$MIME_TYPE") - videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE) - val mFormat = - MediaFormat.createVideoFormat(MIME_TYPE, SCREEN_INFO.width, SCREEN_INFO.height) - mFormat.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_KEY_BIT_RATE) - mFormat.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_KEY_FRAME_RATE) - mFormat.setInteger( - MediaFormat.KEY_COLOR_FORMAT, - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible - ) - mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5) - try { - videoEncoder!!.configure(mFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) - } catch (e: Exception) { - Log.e(logTag, "mEncoder.configure fail!") - } - } - - private fun initNotification() { - notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channelId = "RustDesk" - val channelName = "RustDesk Service" - val channel = NotificationChannel( - channelId, - channelName, NotificationManager.IMPORTANCE_HIGH - ).apply { - description = "RustDesk Service Channel" - } - channel.lightColor = Color.BLUE - channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE - notificationManager.createNotificationChannel(channel) - channelId - } else { - "" - } - notificationBuilder = NotificationCompat.Builder(this, notificationChannel) - } - - @SuppressLint("UnspecifiedImmutableFlag") - private fun createForegroundNotification() { - val intent = Intent(this, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - action = Intent.ACTION_MAIN - addCategory(Intent.CATEGORY_LAUNCHER) - putExtra("type", type) - } - val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.getActivity(this, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) - } else { - PendingIntent.getActivity(this, 0, intent, FLAG_UPDATE_CURRENT) - } - val notification = notificationBuilder - .setOngoing(true) - .setSmallIcon(R.mipmap.ic_stat_logo) - .setDefaults(Notification.DEFAULT_ALL) - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentTitle(DEFAULT_NOTIFY_TITLE) - .setContentText(translate(DEFAULT_NOTIFY_TEXT)) - .setOnlyAlertOnce(true) - .setContentIntent(pendingIntent) - .setColor(ContextCompat.getColor(this, R.color.primary)) - .setWhen(System.currentTimeMillis()) - .build() - startForeground(DEFAULT_NOTIFY_ID, notification) - } - - private fun loginRequestNotification( - clientID: Int, - type: String, - username: String, - peerId: String - ) { - val notification = notificationBuilder - .setOngoing(false) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setContentTitle(translate("Do you accept?")) - .setContentText("$type:$username-$peerId") - // .setStyle(MediaStyle().setShowActionsInCompactView(0, 1)) - // .addAction(R.drawable.check_blue, "check", genLoginRequestPendingIntent(true)) - // .addAction(R.drawable.close_red, "close", genLoginRequestPendingIntent(false)) - .build() - notificationManager.notify(getClientNotifyID(clientID), notification) - } - - private fun onClientAuthorizedNotification( - clientID: Int, - type: String, - username: String, - peerId: String - ) { - cancelNotification(clientID) - val notification = notificationBuilder - .setOngoing(false) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setContentTitle("$type ${translate("Established")}") - .setContentText("$username - $peerId") - .build() - notificationManager.notify(getClientNotifyID(clientID), notification) - } - - private fun voiceCallRequestNotification( - clientID: Int, - type: String, - username: String, - peerId: String - ) { - val notification = notificationBuilder - .setOngoing(false) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setContentTitle(translate("Do you accept?")) - .setContentText("$type:$username-$peerId") - .build() - notificationManager.notify(getClientNotifyID(clientID), notification) - } - - private fun getClientNotifyID(clientID: Int): Int { - return clientID + NOTIFY_ID_OFFSET - } - - fun cancelNotification(clientID: Int) { - notificationManager.cancel(getClientNotifyID(clientID)) - } - - @SuppressLint("UnspecifiedImmutableFlag") - private fun genLoginRequestPendingIntent(res: Boolean): PendingIntent { - val intent = Intent(this, MainService::class.java).apply { - 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) - } else { - PendingIntent.getService(this, 111, intent, FLAG_UPDATE_CURRENT) - } - } - - private fun setTextNotification(_title: String?, _text: String?) { - val title = _title ?: DEFAULT_NOTIFY_TITLE - val text = _text ?: translate(DEFAULT_NOTIFY_TEXT) - val notification = notificationBuilder - .clearActions() - .setStyle(null) - .setContentTitle(title) - .setContentText(text) - .build() - notificationManager.notify(DEFAULT_NOTIFY_ID, notification) - } -} 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 deleted file mode 100644 index 3beb7ec6b..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/PermissionRequestTransparentActivity.kt +++ /dev/null @@ -1,54 +0,0 @@ -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/RdClipboardManager.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt deleted file mode 100644 index 8c9d85028..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt +++ /dev/null @@ -1,197 +0,0 @@ -package com.carriez.flutter_hbb - -import java.nio.ByteBuffer -import java.util.Timer -import java.util.TimerTask - -import android.content.ClipData -import android.content.ClipDescription -import android.content.ClipboardManager -import android.util.Log -import androidx.annotation.Keep - -import hbb.MessageOuterClass.ClipboardFormat -import hbb.MessageOuterClass.Clipboard -import hbb.MessageOuterClass.MultiClipboards - -import ffi.FFI - -class RdClipboardManager(private val clipboardManager: ClipboardManager) { - private val logTag = "RdClipboardManager" - private val supportedMimeTypes = arrayOf( - ClipDescription.MIMETYPE_TEXT_PLAIN, - ClipDescription.MIMETYPE_TEXT_HTML - ) - - // 1. Avoid listening to the same clipboard data updated by `rustUpdateClipboard`. - // 2. Avoid sending the clipboard data before enabling client clipboard. - // 1) Disable clipboard - // 2) Copy text "a" - // 3) Enable clipboard - // 4) Switch to another app - // 5) Switch back to the app - // 6) "a" should not be sent to the client, because it's copied before enabling clipboard - // - // It's okay to that `rustEnableClientClipboard(false)` is called after `rustUpdateClipboard`, - // though the `lastUpdatedClipData` will be set to null once. - private var lastUpdatedClipData: ClipData? = null - private var isClientEnabled = true; - private var _isCaptureStarted = false; - - val isCaptureStarted: Boolean - get() = _isCaptureStarted - - fun checkPrimaryClip(isClient: Boolean) { - val clipData = clipboardManager.primaryClip - if (clipData != null && clipData.itemCount > 0) { - // Only handle the first item in the clipboard for now. - val clip = clipData.getItemAt(0) - // Ignore the `isClipboardDataEqual()` check if it's a host operation. - // Because it's an action manually triggered by the user. - if (isClient) { - if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) { - Log.d(logTag, "Clipboard data is the same as last update, ignore") - return - } - } - val mimeTypeCount = clipData.description.getMimeTypeCount() - val mimeTypes = mutableListOf() - for (i in 0 until mimeTypeCount) { - mimeTypes.add(clipData.description.getMimeType(i)) - } - var text: CharSequence? = null; - var html: String? = null; - if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { - text = clip?.text - } - if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) { - text = clip?.text - html = clip?.htmlText - } - var count = 0 - val clips = MultiClipboards.newBuilder() - if (text != null) { - val content = com.google.protobuf.ByteString.copyFromUtf8(text.toString()) - clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Text).setContent(content).build()) - count++ - } - if (html != null) { - val content = com.google.protobuf.ByteString.copyFromUtf8(html) - clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Html).setContent(content).build()) - count++ - } - if (count > 0) { - val clipsBytes = clips.build().toByteArray() - val isClientFlag = if (isClient) 1 else 0 - val clipsBuf = ByteBuffer.allocateDirect(clipsBytes.size + 1).apply { - put(isClientFlag.toByte()) - put(clipsBytes) - } - clipsBuf.flip() - lastUpdatedClipData = clipData - Log.d(logTag, "${if (isClient) "client" else "host"}, send clipboard data to the remote") - FFI.onClipboardUpdate(clipsBuf) - } - } - } - - private fun isSupportedMimeType(mimeType: String): Boolean { - return supportedMimeTypes.contains(mimeType) - } - - private fun isClipboardDataEqual(left: ClipData, right: ClipData): Boolean { - if (left.description.getMimeTypeCount() != right.description.getMimeTypeCount()) { - return false - } - val mimeTypeCount = left.description.getMimeTypeCount() - for (i in 0 until mimeTypeCount) { - if (left.description.getMimeType(i) != right.description.getMimeType(i)) { - return false - } - } - - if (left.itemCount != right.itemCount) { - return false - } - for (i in 0 until left.itemCount) { - val mimeType = left.description.getMimeType(i) - if (!isSupportedMimeType(mimeType)) { - continue - } - val leftItem = left.getItemAt(i) - val rightItem = right.getItemAt(i) - if (mimeType == ClipDescription.MIMETYPE_TEXT_PLAIN || mimeType == ClipDescription.MIMETYPE_TEXT_HTML) { - if (leftItem.text != rightItem.text || leftItem.htmlText != rightItem.htmlText) { - return false - } - } - } - return true - } - - fun setCaptureStarted(started: Boolean) { - _isCaptureStarted = started - } - - @Keep - fun rustEnableClientClipboard(enable: Boolean) { - Log.d(logTag, "rustEnableClientClipboard: enable: $enable") - isClientEnabled = enable - lastUpdatedClipData = null - } - - fun syncClipboard(isClient: Boolean) { - Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled") - if (isClient && !isClientEnabled) { - return - } - checkPrimaryClip(isClient) - } - - @Keep - fun rustUpdateClipboard(clips: ByteArray) { - val clips = MultiClipboards.parseFrom(clips) - var mimeTypes = mutableListOf() - var text: String? = null - var html: String? = null - for (clip in clips.getClipboardsList()) { - when (clip.format) { - ClipboardFormat.Text -> { - mimeTypes.add(ClipDescription.MIMETYPE_TEXT_PLAIN) - text = String(clip.content.toByteArray(), Charsets.UTF_8) - } - ClipboardFormat.Html -> { - mimeTypes.add(ClipDescription.MIMETYPE_TEXT_HTML) - html = String(clip.content.toByteArray(), Charsets.UTF_8) - } - ClipboardFormat.ImageRgba -> { - } - ClipboardFormat.ImagePng -> { - } - else -> { - Log.e(logTag, "Unsupported clipboard format: ${clip.format}") - } - } - } - - val clipDescription = ClipDescription("clipboard", mimeTypes.toTypedArray()) - var item: ClipData.Item? = null - if (text == null) { - Log.e(logTag, "No text content in clipboard") - return - } else { - if (html == null) { - item = ClipData.Item(text) - } else { - item = ClipData.Item(text, html) - } - } - if (item == null) { - Log.e(logTag, "No item in clipboard") - return - } - val clipData = ClipData(clipDescription, item) - lastUpdatedClipData = clipData - clipboardManager.setPrimaryClip(clipData) - } -} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt deleted file mode 100644 index be30b653e..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/VolumeController.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.carriez.flutter_hbb - -// Inspired by https://github.com/yosemiteyss/flutter_volume_controller/blob/main/android/src/main/kotlin/com/yosemiteyss/flutter_volume_controller/VolumeController.kt - -import android.media.AudioManager -import android.os.Build -import android.util.Log - -class VolumeController(private val audioManager: AudioManager) { - private val logTag = "volume controller" - - fun getVolume(streamType: Int): Double { - val current = audioManager.getStreamVolume(streamType) - val max = audioManager.getStreamMaxVolume(streamType) - return current.toDouble() / max - } - - fun setVolume(volume: Double, showSystemUI: Boolean, streamType: Int) { - val max = audioManager.getStreamMaxVolume(streamType) - audioManager.setStreamVolume( - streamType, - (max * volume).toInt(), - if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 - ) - } - - fun raiseVolume(step: Double?, showSystemUI: Boolean, streamType: Int) { - if (step == null) { - audioManager.adjustStreamVolume( - streamType, - AudioManager.ADJUST_RAISE, - if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 - ) - } else { - val target = getVolume(streamType) + step - setVolume(target, showSystemUI, streamType) - } - } - - fun lowerVolume(step: Double?, showSystemUI: Boolean, streamType: Int) { - if (step == null) { - audioManager.adjustStreamVolume( - streamType, - AudioManager.ADJUST_LOWER, - if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 - ) - } else { - val target = getVolume(streamType) - step - setVolume(target, showSystemUI, streamType) - } - } - - fun getMute(streamType: Int): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - audioManager.isStreamMute(streamType) - } else { - audioManager.getStreamVolume(streamType) == 0 - } - } - - private fun setMute(isMuted: Boolean, showSystemUI: Boolean, streamType: Int) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - audioManager.adjustStreamVolume( - streamType, - if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, - if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0 - ) - } else { - audioManager.setStreamMute(streamType, isMuted) - } - } - - fun toggleMute(showSystemUI: Boolean, streamType: Int) { - val isMuted = getMute(streamType) - setMute(!isMuted, showSystemUI, streamType) - } -} - 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 deleted file mode 100644 index 514d493b9..000000000 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt +++ /dev/null @@ -1,157 +0,0 @@ -package com.carriez.flutter_hbb - -import android.Manifest.permission.* -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.media.AudioRecord -import android.media.AudioRecord.READ_BLOCKING -import android.media.MediaCodecList -import android.media.MediaFormat -import android.net.Uri -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.os.PowerManager -import android.provider.Settings -import android.provider.Settings.* -import android.util.DisplayMetrics -import android.util.Log -import android.view.WindowManager -import androidx.annotation.RequiresApi -import androidx.core.content.ContextCompat.getSystemService -import com.hjq.permissions.Permission -import com.hjq.permissions.XXPermissions -import ffi.FFI -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 GET_VALUE = "get_value" - -const val KEY_IS_SUPPORT_VOICE_CALL = "KEY_IS_SUPPORT_VOICE_CALL" - -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) - -data class Info( - var width: Int, var height: Int, var scale: Int, var dpi: Int -) - -fun isSupportVoiceCall(): Boolean { - // https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_COMMUNICATION - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -} - -fun requestPermission(context: Context, type: String) { - XXPermissions.with(context) - .permission(type) - .request { _, all -> - if (all) { - Handler(Looper.getMainLooper()).post { - MainActivity.flutterMethodChannel?.invokeMethod( - "on_android_permission_result", - mapOf("type" to type, "result" to all) - ) - } - } - } -} - -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() - } -} - -class AudioReader(val bufSize: Int, private val maxFrames: Int) { - private var currentPos = 0 - private val bufferPool: Array - - init { - if (maxFrames < 0 || maxFrames > 32) { - throw Exception("Out of bounds") - } - if (bufSize <= 0) { - throw Exception("Wrong bufSize") - } - bufferPool = Array(maxFrames) { - ByteBuffer.allocateDirect(bufSize) - } - } - - private fun next() { - currentPos++ - if (currentPos >= maxFrames) { - currentPos = 0 - } - } - - @RequiresApi(Build.VERSION_CODES.M) - fun readSync(audioRecord: AudioRecord): ByteBuffer? { - val buffer = bufferPool[currentPos] - val res = audioRecord.read(buffer, bufSize, READ_BLOCKING) - return if (res > 0) { - next() - buffer - } else { - null - } - } -} - - -fun getScreenSize(windowManager: WindowManager) : Pair{ - var w = 0 - var h = 0 - @Suppress("DEPRECATION") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val m = windowManager.maximumWindowMetrics - w = m.bounds.width() - h = m.bounds.height() - } else { - val dm = DisplayMetrics() - windowManager.defaultDisplay.getRealMetrics(dm) - w = dm.widthPixels - h = dm.heightPixels - } - return Pair(w, h) -} - - fun translate(input: String): String { - Log.d("common", "translate:$LOCAL_NAME") - return FFI.translateLocale(LOCAL_NAME, input) -} \ No newline at end of file diff --git a/flutter/android/app/src/main/kotlin/ffi.kt b/flutter/android/app/src/main/kotlin/ffi.kt deleted file mode 100644 index e3c9d9830..000000000 --- a/flutter/android/app/src/main/kotlin/ffi.kt +++ /dev/null @@ -1,30 +0,0 @@ -// ffi.kt - -package ffi - -import android.content.Context -import java.nio.ByteBuffer - -import com.carriez.flutter_hbb.RdClipboardManager - -object FFI { - init { - System.loadLibrary("rustdesk") - } - - external fun init(ctx: Context) - external fun onAppStart(ctx: Context) - external fun setClipboardManager(clipboardManager: RdClipboardManager) - external fun startServer(app_dir: String, custom_client_config: String) - external fun startService() - external fun onVideoFrameUpdate(buf: ByteBuffer) - external fun onAudioFrameUpdate(buf: ByteBuffer) - external fun translateLocale(localeName: String, input: String): String - external fun refreshScreen() - external fun setFrameRawEnable(name: String, value: Boolean) - external fun setCodecInfo(info: String) - external fun getLocalOption(key: String): String - external fun getBuildinOption(key: String): String - external fun onClipboardUpdate(clips: ByteBuffer) - external fun isServiceClipboardEnabled(): Boolean -} diff --git a/flutter/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3f..000000000 --- a/flutter/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/flutter/android/app/src/main/res/drawable/check_blue.xml b/flutter/android/app/src/main/res/drawable/check_blue.xml deleted file mode 100644 index b06974b36..000000000 --- a/flutter/android/app/src/main/res/drawable/check_blue.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/flutter/android/app/src/main/res/drawable/close_red.xml b/flutter/android/app/src/main/res/drawable/close_red.xml deleted file mode 100644 index 02ff2c8b6..000000000 --- a/flutter/android/app/src/main/res/drawable/close_red.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/flutter/android/app/src/main/res/drawable/floating_window.xml b/flutter/android/app/src/main/res/drawable/floating_window.xml deleted file mode 100644 index d22152ddd..000000000 --- a/flutter/android/app/src/main/res/drawable/floating_window.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/flutter/android/app/src/main/res/drawable/launch_background.xml b/flutter/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f88..000000000 --- a/flutter/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 65291b96e..000000000 --- a/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 65291b96e..000000000 --- a/flutter/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 116904a84..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 7c8a2be8d..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index c8aef7fd5..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_stat_logo.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_stat_logo.png deleted file mode 100644 index 42e74fe0b..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-hdpi/ic_stat_logo.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 7dca207d8..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 0faadfca0..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 246d6ee7a..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/ic_stat_logo.png b/flutter/android/app/src/main/res/mipmap-mdpi/ic_stat_logo.png deleted file mode 100644 index d643a4fac..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-mdpi/ic_stat_logo.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 33a40ed83..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index e083cecf1..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index ab9c356dc..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_stat_logo.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_stat_logo.png deleted file mode 100644 index e02182dd1..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_stat_logo.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 4585230d3..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index b9975190c..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 79eab3d84..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_stat_logo.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_stat_logo.png deleted file mode 100644 index f7b57b329..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_stat_logo.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 463d20eeb..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index d781e2595..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 3cae0ce40..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_stat_logo.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_stat_logo.png deleted file mode 100644 index 9c2153e9e..000000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_stat_logo.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/values-night/styles.xml b/flutter/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 449a9f930..000000000 --- a/flutter/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/flutter/android/app/src/main/res/values/colors.xml b/flutter/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 273468987..000000000 --- a/flutter/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FF0071FF - \ No newline at end of file diff --git a/flutter/android/app/src/main/res/values/ic_launcher_background.xml b/flutter/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index ab9832824..000000000 --- a/flutter/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #ffffff - \ No newline at end of file diff --git a/flutter/android/app/src/main/res/values/strings.xml b/flutter/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 3e058a81b..000000000 --- a/flutter/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - RustDesk - Allow other devices to control your phone using virtual touch, when RustDesk screen sharing is established - diff --git a/flutter/android/app/src/main/res/values/styles.xml b/flutter/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 146267c91..000000000 --- a/flutter/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - diff --git a/flutter/android/app/src/main/res/xml/accessibility_service_config.xml b/flutter/android/app/src/main/res/xml/accessibility_service_config.xml deleted file mode 100644 index 90b57cd4e..000000000 --- a/flutter/android/app/src/main/res/xml/accessibility_service_config.xml +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/flutter/android/app/src/profile/AndroidManifest.xml b/flutter/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 64d68a588..000000000 --- a/flutter/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/flutter/android/build.gradle b/flutter/android/build.gradle deleted file mode 100644 index 401bea009..000000000 --- a/flutter/android/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -allprojects { - repositories { - google() - jcenter() - maven { url 'https://jitpack.io' } - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/flutter/android/flutter_hbb_android.iml b/flutter/android/flutter_hbb_android.iml deleted file mode 100644 index 18999696a..000000000 --- a/flutter/android/flutter_hbb_android.iml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter/android/gradle.properties b/flutter/android/gradle.properties deleted file mode 100644 index 804b29b30..000000000 --- a/flutter/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1024M -android.useAndroidX=true -android.enableJetifier=true -org.gradle.daemon=false diff --git a/flutter/android/gradle/wrapper/gradle-wrapper.properties b/flutter/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index cb576305f..000000000 --- a/flutter/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip diff --git a/flutter/android/settings.gradle b/flutter/android/settings.gradle deleted file mode 100644 index ae32fa00e..000000000 --- a/flutter/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.1" apply false - id "org.jetbrains.kotlin.android" version "2.1.21" apply false -} - -include ":app" diff --git a/flutter/assets/actions.svg b/flutter/assets/actions.svg deleted file mode 100644 index 0a3c4bc42..000000000 --- a/flutter/assets/actions.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/actions_mobile.svg b/flutter/assets/actions_mobile.svg deleted file mode 100644 index 32d8dc815..000000000 --- a/flutter/assets/actions_mobile.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/address_book.ttf b/flutter/assets/address_book.ttf deleted file mode 100644 index 509fb63c0..000000000 Binary files a/flutter/assets/address_book.ttf and /dev/null differ diff --git a/flutter/assets/android.svg b/flutter/assets/android.svg deleted file mode 100644 index 6fd89c9ab..000000000 --- a/flutter/assets/android.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/arrow.svg b/flutter/assets/arrow.svg deleted file mode 100644 index 50c484601..000000000 --- a/flutter/assets/arrow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-apple.svg b/flutter/assets/auth-apple.svg deleted file mode 100644 index 6933fbc3b..000000000 --- a/flutter/assets/auth-apple.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-auth0.svg b/flutter/assets/auth-auth0.svg deleted file mode 100644 index dbe3ed236..000000000 --- a/flutter/assets/auth-auth0.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-azure.svg b/flutter/assets/auth-azure.svg deleted file mode 100644 index b7435604d..000000000 --- a/flutter/assets/auth-azure.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-default.svg b/flutter/assets/auth-default.svg deleted file mode 100644 index bf5fa9073..000000000 --- a/flutter/assets/auth-default.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-facebook.svg b/flutter/assets/auth-facebook.svg deleted file mode 100644 index f58725000..000000000 --- a/flutter/assets/auth-facebook.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-github.svg b/flutter/assets/auth-github.svg deleted file mode 100644 index 778b7b341..000000000 --- a/flutter/assets/auth-github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-gitlab.svg b/flutter/assets/auth-gitlab.svg deleted file mode 100644 index 9402e1329..000000000 --- a/flutter/assets/auth-gitlab.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-google.svg b/flutter/assets/auth-google.svg deleted file mode 100644 index 18970f31a..000000000 --- a/flutter/assets/auth-google.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-microsoft.svg b/flutter/assets/auth-microsoft.svg deleted file mode 100644 index c9ce5f9cf..000000000 --- a/flutter/assets/auth-microsoft.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/auth-okta.svg b/flutter/assets/auth-okta.svg deleted file mode 100644 index 931e72844..000000000 --- a/flutter/assets/auth-okta.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/call_end.svg b/flutter/assets/call_end.svg deleted file mode 100644 index fb7c9d292..000000000 --- a/flutter/assets/call_end.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/call_wait.svg b/flutter/assets/call_wait.svg deleted file mode 100644 index 299e3d0cf..000000000 --- a/flutter/assets/call_wait.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/chat.svg b/flutter/assets/chat.svg deleted file mode 100644 index 3a8bae7e3..000000000 --- a/flutter/assets/chat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/chat2.svg b/flutter/assets/chat2.svg deleted file mode 100644 index 6510b0e1d..000000000 --- a/flutter/assets/chat2.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/flutter/assets/checkbox-outline.svg b/flutter/assets/checkbox-outline.svg deleted file mode 100644 index 77ca35529..000000000 --- a/flutter/assets/checkbox-outline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/chevron_up_chevron_down.svg b/flutter/assets/chevron_up_chevron_down.svg deleted file mode 100644 index b5ebf211a..000000000 --- a/flutter/assets/chevron_up_chevron_down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/close.svg b/flutter/assets/close.svg deleted file mode 100644 index 0dd66b666..000000000 --- a/flutter/assets/close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/device_group.ttf b/flutter/assets/device_group.ttf deleted file mode 100644 index a6e42704f..000000000 Binary files a/flutter/assets/device_group.ttf and /dev/null differ diff --git a/flutter/assets/display.svg b/flutter/assets/display.svg deleted file mode 100644 index eb8cd8cf6..000000000 --- a/flutter/assets/display.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/dots.svg b/flutter/assets/dots.svg deleted file mode 100644 index 628133f81..000000000 --- a/flutter/assets/dots.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/file.svg b/flutter/assets/file.svg deleted file mode 100644 index ded4d953c..000000000 --- a/flutter/assets/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/file_transfer.svg b/flutter/assets/file_transfer.svg deleted file mode 100644 index e1d8ccbec..000000000 --- a/flutter/assets/file_transfer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/folder.svg b/flutter/assets/folder.svg deleted file mode 100644 index ad8484bce..000000000 --- a/flutter/assets/folder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/folder_new.svg b/flutter/assets/folder_new.svg deleted file mode 100644 index ce498171b..000000000 --- a/flutter/assets/folder_new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/fullscreen.svg b/flutter/assets/fullscreen.svg deleted file mode 100644 index 992d21d42..000000000 --- a/flutter/assets/fullscreen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/fullscreen_exit.svg b/flutter/assets/fullscreen_exit.svg deleted file mode 100644 index ab93bba60..000000000 --- a/flutter/assets/fullscreen_exit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/gestures.ttf b/flutter/assets/gestures.ttf deleted file mode 100644 index aabec8ac9..000000000 Binary files a/flutter/assets/gestures.ttf and /dev/null differ diff --git a/flutter/assets/home.svg b/flutter/assets/home.svg deleted file mode 100644 index a779a2719..000000000 --- a/flutter/assets/home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/icon.svg b/flutter/assets/icon.svg deleted file mode 100644 index 4d43f8bcd..000000000 --- a/flutter/assets/icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/insecure.svg b/flutter/assets/insecure.svg deleted file mode 100644 index 5a344dd04..000000000 --- a/flutter/assets/insecure.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/insecure_relay.svg b/flutter/assets/insecure_relay.svg deleted file mode 100644 index 17b474e6e..000000000 --- a/flutter/assets/insecure_relay.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/kb_layout_iso.svg b/flutter/assets/kb_layout_iso.svg deleted file mode 100644 index 2d30feba2..000000000 --- a/flutter/assets/kb_layout_iso.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/kb_layout_not_iso.svg b/flutter/assets/kb_layout_not_iso.svg deleted file mode 100644 index 0c7f964d7..000000000 --- a/flutter/assets/kb_layout_not_iso.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/keyboard_mouse.svg b/flutter/assets/keyboard_mouse.svg deleted file mode 100644 index f6a5b4b2b..000000000 --- a/flutter/assets/keyboard_mouse.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/flutter/assets/linux.svg b/flutter/assets/linux.svg deleted file mode 100644 index 2c3697be9..000000000 --- a/flutter/assets/linux.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/mac.svg b/flutter/assets/mac.svg deleted file mode 100644 index ccf9c7aab..000000000 --- a/flutter/assets/mac.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/message_24dp_5F6368.svg b/flutter/assets/message_24dp_5F6368.svg deleted file mode 100644 index 5347a3d2d..000000000 --- a/flutter/assets/message_24dp_5F6368.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/more.ttf b/flutter/assets/more.ttf deleted file mode 100644 index 3b01435df..000000000 Binary files a/flutter/assets/more.ttf and /dev/null differ diff --git a/flutter/assets/peer_searchbar.ttf b/flutter/assets/peer_searchbar.ttf deleted file mode 100644 index 7f87e48ce..000000000 Binary files a/flutter/assets/peer_searchbar.ttf and /dev/null differ diff --git a/flutter/assets/pinned.svg b/flutter/assets/pinned.svg deleted file mode 100644 index 872f38a17..000000000 --- a/flutter/assets/pinned.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/rec.svg b/flutter/assets/rec.svg deleted file mode 100644 index 70f106139..000000000 --- a/flutter/assets/rec.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/record_screen.svg b/flutter/assets/record_screen.svg deleted file mode 100644 index bbd948c73..000000000 --- a/flutter/assets/record_screen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/refresh.svg b/flutter/assets/refresh.svg deleted file mode 100644 index 1a37816e5..000000000 --- a/flutter/assets/refresh.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/scam.png b/flutter/assets/scam.png deleted file mode 100644 index 9ccad3343..000000000 Binary files a/flutter/assets/scam.png and /dev/null differ diff --git a/flutter/assets/screen.svg b/flutter/assets/screen.svg deleted file mode 100644 index 992eecd03..000000000 --- a/flutter/assets/screen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/search.svg b/flutter/assets/search.svg deleted file mode 100644 index 682db43d3..000000000 --- a/flutter/assets/search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/secure.svg b/flutter/assets/secure.svg deleted file mode 100644 index fcd99f2f5..000000000 --- a/flutter/assets/secure.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/secure_relay.svg b/flutter/assets/secure_relay.svg deleted file mode 100644 index af54808a8..000000000 --- a/flutter/assets/secure_relay.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/tabbar.ttf b/flutter/assets/tabbar.ttf deleted file mode 100644 index a9220f348..000000000 Binary files a/flutter/assets/tabbar.ttf and /dev/null differ diff --git a/flutter/assets/transfer.svg b/flutter/assets/transfer.svg deleted file mode 100644 index d764210b3..000000000 --- a/flutter/assets/transfer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/trash.svg b/flutter/assets/trash.svg deleted file mode 100644 index 65aaaaf2b..000000000 --- a/flutter/assets/trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/unpinned.svg b/flutter/assets/unpinned.svg deleted file mode 100644 index e69da2ae4..000000000 --- a/flutter/assets/unpinned.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/voice_call.svg b/flutter/assets/voice_call.svg deleted file mode 100644 index 98ebd4bc8..000000000 --- a/flutter/assets/voice_call.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/voice_call_waiting.svg b/flutter/assets/voice_call_waiting.svg deleted file mode 100644 index f1771c3fd..000000000 --- a/flutter/assets/voice_call_waiting.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/assets/win.svg b/flutter/assets/win.svg deleted file mode 100644 index a0f7e3def..000000000 --- a/flutter/assets/win.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/flutter/build_android.sh b/flutter/build_android.sh deleted file mode 100755 index c6b639f87..000000000 --- a/flutter/build_android.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -MODE=${MODE:=release} -$ANDROID_NDK_HOME/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/* -flutter build apk --target-platform android-arm64,android-arm --${MODE} --obfuscate --split-debug-info ./split-debug-info -flutter build apk --split-per-abi --target-platform android-arm64,android-arm --${MODE} --obfuscate --split-debug-info ./split-debug-info -flutter build appbundle --target-platform android-arm64,android-arm --${MODE} --obfuscate --split-debug-info ./split-debug-info - -# build in linux -# $ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/* diff --git a/flutter/build_android_deps.sh b/flutter/build_android_deps.sh deleted file mode 100755 index 64fb9dad2..000000000 --- a/flutter/build_android_deps.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -set -e -o pipefail - -ANDROID_ABI=$1 - -# Build RustDesk dependencies for Android using vcpkg.json -# Required: -# 1. set VCPKG_ROOT / ANDROID_NDK path environment variables -# 2. vcpkg initialized -# 3. ndk, version: r25c or newer - -if [ -z "$ANDROID_NDK_HOME" ]; then - echo "Failed! Please set ANDROID_NDK_HOME" - exit 1 -fi - -if [ -z "$VCPKG_ROOT" ]; then - echo "Failed! Please set VCPKG_ROOT" - exit 1 -fi - -API_LEVEL="21" - -# Get directory of this script - -SCRIPTDIR="$(readlink -f "$0")" -SCRIPTDIR="$(dirname "$SCRIPTDIR")" - -# Check if vcpkg.json is one level up - in root directory of RD - -if [ ! -f "$SCRIPTDIR/../vcpkg.json" ]; then - echo "Failed! Please check where vcpkg.json is!" - exit 1 -fi - -# NDK llvm toolchain - -HOST_TAG="linux-x86_64" # current platform, set as `ls $ANDROID_NDK/toolchains/llvm/prebuilt/` -TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG - -function build { - ANDROID_ABI=$1 - - case "$ANDROID_ABI" in - arm64-v8a) - ABI=aarch64-linux-android$API_LEVEL - VCPKG_TARGET=arm64-android - ;; - armeabi-v7a) - ABI=armv7a-linux-androideabi$API_LEVEL - VCPKG_TARGET=arm-neon-android - ;; - x86_64) - ABI=x86_64-linux-android$API_LEVEL - VCPKG_TARGET=x64-android - ;; - x86) - ABI=i686-linux-android$API_LEVEL - VCPKG_TARGET=x86-android - ;; - *) - echo "ERROR: ANDROID_ABI must be one of: arm64-v8a, armeabi-v7a, x86_64, x86" >&2 - return 1 - esac - - echo "*** [$ANDROID_ABI][Start] Build and install vcpkg dependencies" - pushd "$SCRIPTDIR/.." - $VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed" - popd - head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-$VCPKG_TARGET-rel-out.log" || true - echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies" - -if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then - echo "*** [Start] Move arm-neon-android to arm-android" - - mv "$VCPKG_ROOT/installed/arm-neon-android" "$VCPKG_ROOT/installed/arm-android" - - echo "*** [Finished] Move arm-neon-android to arm-android" -fi -} - -if [ ! -z "$ANDROID_ABI" ]; then - build "$ANDROID_ABI" -else - echo "Usage: build-android-deps.sh " >&2 - exit 1 -fi diff --git a/flutter/build_fdroid.sh b/flutter/build_fdroid.sh deleted file mode 100755 index d50a6a6ce..000000000 --- a/flutter/build_fdroid.sh +++ /dev/null @@ -1,630 +0,0 @@ -#!/bin/bash - -# -# Script to build F-Droid release of RustDesk -# -# Copyright (C) 2024, The RustDesk Authors -# 2024, Vasyl Gello -# - -# The script is invoked by F-Droid builder system step-by-step. -# -# It accepts the following arguments: -# -# - versionName from https://github.com/rustdesk/rustdesk/releases/download/fdroid-version/rustdesk-version.txt -# - versionCode from https://github.com/rustdesk/rustdesk/releases/download/fdroid-version/rustdesk-version.txt -# - Android architecture to build APK for: armeabi-v7a arm64-v8av x86 x86_64 -# - The build step to execute: -# -# + prebuild: patch sources and do other stuff before the build -# + build: perform actual build of APK file -# - -# Start of functions - -# Install Flutter of version `VERSION` from Github repository -# into directory `FLUTTER_DIR` and apply patches if needed - -prepare_flutter() { - VERSION="${1}" - FLUTTER_DIR="${2}" - - if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then - git clone https://github.com/flutter/flutter "${FLUTTER_DIR}" - fi - - pushd "${FLUTTER_DIR}" - - git restore . - git checkout "${VERSION}" - - # Patch flutter - - if dpkg --compare-versions "${VERSION}" ge "3.24.4"; then - git apply "${ROOTDIR}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff" - fi - - flutter config --no-analytics - - popd # ${FLUTTER_DIR} -} - -# Start of script - -set -x - -# Note current working directory as root dir for patches - -ROOTDIR="${PWD}" - -# Parse command-line arguments - -VERNAME="${1}" -VERCODE="${2}" -ANDROID_ABI="${3}" -BUILDSTEP="${4}" - -if [ -z "${VERNAME}" ] || [ -z "${VERCODE}" ] || [ -z "${ANDROID_ABI}" ] || - [ -z "${BUILDSTEP}" ]; then - echo "ERROR: Command-line arguments are all required to be non-empty!" >&2 - exit 1 -fi - -# Set various architecture-specific identifiers - -case "${ANDROID_ABI}" in -arm64-v8a) - FLUTTER_TARGET=android-arm64 - NDK_TARGET=aarch64-linux-android - RUST_TARGET=aarch64-linux-android - RUSTDESK_FEATURES='flutter,hwcodec' - ;; -armeabi-v7a) - FLUTTER_TARGET=android-arm - NDK_TARGET=arm-linux-androideabi - RUST_TARGET=armv7-linux-androideabi - RUSTDESK_FEATURES='flutter,hwcodec' - ;; -x86_64) - FLUTTER_TARGET=android-x64 - NDK_TARGET=x86_64-linux-android - RUST_TARGET=x86_64-linux-android - RUSTDESK_FEATURES='flutter' - ;; -x86) - FLUTTER_TARGET=android-x86 - NDK_TARGET=i686-linux-android - RUST_TARGET=i686-linux-android - RUSTDESK_FEATURES='flutter' - ;; -*) - echo "ERROR: Unknown Android ABI '${ANDROID_ABI}'!" >&2 - exit 1 - ;; -esac - -# Check ANDROID_SDK_ROOT and sdkmanager present on PATH - -if [ ! -d "${ANDROID_SDK_ROOT}" ] || ! command -v sdkmanager 1>/dev/null; then - echo "ERROR: Can not find Android SDK!" >&2 - exit 1 -fi - -# Export necessary variables - -export PATH="${PATH}:${HOME}/flutter/bin:${HOME}/depot_tools" - -export VCPKG_ROOT="${HOME}/vcpkg" - -# Now act depending on build step - -# NOTE: F-Droid maintainers require explicit declaration of dependencies -# as root via `Builds.sudo` F-Droid metadata directive: -# https://gitlab.com/fdroid/fdroiddata/-/merge_requests/15343#note_1988918695 - -case "${BUILDSTEP}" in -prebuild) - # prebuild: patch sources and do other stuff before the build - - # - # Extract required versions for NDK, Rust, Flutter from - # '.github/workflows/flutter-build.yml' - # - - CARGO_NDK_VERSION="$(yq -r \ - .env.CARGO_NDK_VERSION \ - .github/workflows/flutter-build.yml)" - - # Flutter used to compile main Rustdesk library - - FLUTTER_VERSION="$(yq -r \ - .env.ANDROID_FLUTTER_VERSION \ - .github/workflows/flutter-build.yml)" - - if [ -z "${FLUTTER_VERSION}" ]; then - FLUTTER_VERSION="$(yq -r \ - .env.FLUTTER_VERSION \ - .github/workflows/flutter-build.yml)" - fi - - # Flutter used to compile Flutter<->Rust bridge files - - CARGO_EXPAND_VERSION="$(yq -r \ - .env.CARGO_EXPAND_VERSION \ - .github/workflows/bridge.yml)" - - FLUTTER_BRIDGE_VERSION="$(yq -r \ - .env.FLUTTER_VERSION \ - .github/workflows/bridge.yml)" - - FLUTTER_RUST_BRIDGE_VERSION="$(yq -r \ - .env.FLUTTER_RUST_BRIDGE_VERSION \ - .github/workflows/bridge.yml)" - - NDK_VERSION="$(yq -r \ - .env.NDK_VERSION \ - .github/workflows/flutter-build.yml)" - - RUST_VERSION="$(yq -r \ - .env.RUST_VERSION \ - .github/workflows/flutter-build.yml)" - - VCPKG_COMMIT_ID="$(yq -r \ - .env.VCPKG_COMMIT_ID \ - .github/workflows/flutter-build.yml)" - - if [ -z "${CARGO_NDK_VERSION}" ] || [ -z "${FLUTTER_VERSION}" ] || - [ -z "${FLUTTER_BRIDGE_VERSION}" ] || - [ -z "${FLUTTER_RUST_BRIDGE_VERSION}" ] || - [ -z "${NDK_VERSION}" ] || [ -z "${RUST_VERSION}" ] || - [ -z "${VCPKG_COMMIT_ID}" ]; then - echo "ERROR: Can not identify all required versions!" >&2 - exit 1 - fi - - # Map NDK version to revision - NDK_VERSION="$(curl https://gitlab.com/fdroid/android-sdk-transparency-log/-/raw/master/signed/checksums.json | - jq -r ".\"https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip\"[0].\"source.properties\"" | - sed -n -E 's/.*Pkg.Revision = ([0-9.]+).*/\1/p')" - - if [ -z "${NDK_VERSION}" ]; then - echo "ERROR: Can not map Android NDK codename to revision!" >&2 - exit 1 - fi - - export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}" - export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}" - - # - # Install the components - # - - set -e - - # Install Android NDK - - if [ ! -d "${ANDROID_NDK_ROOT}" ]; then - sdkmanager --install "ndk;${NDK_VERSION}" - fi - - # Install Rust - - if [ ! -f "${HOME}/rustup/rustup-init.sh" ]; then - pushd "${HOME}" - - git clone --depth 1 https://github.com/rust-lang/rustup - - popd # ${HOME} - fi - - pushd "${HOME}/rustup" - bash rustup-init.sh -y \ - --target "${RUST_TARGET}" \ - --default-toolchain "${RUST_VERSION}" - popd - - if ! command -v cargo 1>/dev/null 2>&1; then - . "${HOME}/.cargo/env" - fi - - # Install cargo-ndk - - cargo install \ - cargo-ndk \ - --version "${CARGO_NDK_VERSION}" \ - --locked - - # Install rust bridge generator - - cargo install \ - cargo-expand \ - --version "${CARGO_EXPAND_VERSION}" \ - --locked - cargo install flutter_rust_bridge_codegen \ - --version "${FLUTTER_RUST_BRIDGE_VERSION}" \ - --features "uuid" \ - --locked - - # Populate native vcpkg dependencies - - if [ ! -d "${VCPKG_ROOT}" ]; then - pushd "${HOME}" - - git clone \ - https://github.com/Microsoft/vcpkg.git - git clone \ - https://github.com/Microsoft/vcpkg-tool.git - - pushd vcpkg-tool - - mkdir build - - pushd build - - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -G 'Ninja' \ - -DVCPKG_DEVELOPMENT_WARNINGS=OFF \ - .. - - cmake --build . - - popd # build - - popd # vcpkg-tool - - pushd vcpkg - - git reset --hard "${VCPKG_COMMIT_ID}" - - cp -a ../vcpkg-tool/build/vcpkg vcpkg - - # disable telemetry - - touch "vcpkg.disable-metrics" - - popd # vcpkg - - popd # ${HOME} - fi - - # Install depot-tools for x86 - - if [ "${ANDROID_ABI}" = "x86" ]; then - if [ ! -d "${HOME}/depot_tools" ]; then - pushd "${HOME}" - - git clone \ - --depth 1 \ - https://chromium.googlesource.com/chromium/tools/depot_tools.git - - popd # ${HOME} - fi - fi - - # Patch the RustDesk sources - - git apply res/fdroid/patches/*.patch - - # If Flutter version used to generate bridge files differs from Flutter - # version used to compile Rustdesk library, generate bridge using the - # `FLUTTER_BRIDGE_VERSION` an restore the pubspec later - - if [ "${FLUTTER_VERSION}" != "${FLUTTER_BRIDGE_VERSION}" ]; then - # Find first libclang.so and set BRIDGE_LLVM_PATH - - BRIDGE_LLVM_PATH="$(find /usr/lib/ -name libclang.so | head -n1)" - - if [ -z "${BRIDGE_LLVM_PATH}" ]; then - echo 'ERROR: Can not find libclang.so for bridge generator!' >&2 - exit 1 - fi - - BRIDGE_LLVM_PATH="$(dirname "${BRIDGE_LLVM_PATH}")" - BRIDGE_LLVM_PATH="$(dirname "${BRIDGE_LLVM_PATH}")" - - # Install Flutter bridge version - - prepare_flutter "${FLUTTER_BRIDGE_VERSION}" "${HOME}/flutter" - - # Save changes - - git add . - - # Edit pubspec to make flutter bridge version work - - sed \ - -i \ - -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' \ - flutter/pubspec.yaml - - # Download Flutter dependencies - - pushd flutter - - flutter clean - flutter packages pub get - - popd # flutter - - # Generate FFI bindings - - flutter_rust_bridge_codegen \ - --rust-input ./src/flutter_ffi.rs \ - --dart-output ./flutter/lib/generated_bridge.dart \ - --llvm-path "${BRIDGE_LLVM_PATH}" - - # Add bridge files to save-list - - git add -f ./flutter/lib/generated_bridge.* ./src/bridge_generated.* - - # Restore everything - - git checkout '*' - git clean -dffx - git reset - - unset BRIDGE_LLVM_PATH - fi - - # Install Flutter version for RustDesk library build - - prepare_flutter "${FLUTTER_VERSION}" "${HOME}/flutter" - - # gms is not in these files now, but we still keep the following line for future reference(maybe). - - sed \ - -i \ - -e '/gms/d' \ - flutter/android/build.gradle \ - flutter/android/app/build.gradle - - # `firebase_analytics` is not in these files now, but we still keep the following lines. - - sed \ - -i \ - -e '/firebase_analytics/d' \ - flutter/pubspec.yaml - - sed \ - -i \ - -e '/ firebase/,/ version/d' \ - flutter/pubspec.lock - - sed \ - -i \ - -e '/firebase/Id' \ - flutter/lib/main.dart - - ;; -build) - # build: perform actual build of APK file - - set -e - - # - # Extract required versions for NDK, Rust, Flutter from - # '.github/workflows/flutter-build.yml' - # - - # Flutter used to compile main Rustdesk library - - FLUTTER_VERSION="$(yq -r \ - .env.ANDROID_FLUTTER_VERSION \ - .github/workflows/flutter-build.yml)" - - if [ -z "${FLUTTER_VERSION}" ]; then - FLUTTER_VERSION="$(yq -r \ - .env.FLUTTER_VERSION \ - .github/workflows/flutter-build.yml)" - fi - - NDK_VERSION="$(yq -r \ - .env.NDK_VERSION \ - .github/workflows/flutter-build.yml)" - - # Map NDK version to revision - NDK_VERSION="$(curl https://gitlab.com/fdroid/android-sdk-transparency-log/-/raw/master/signed/checksums.json | - jq -r ".\"https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip\"[0].\"source.properties\"" | - sed -n -E 's/.*Pkg.Revision = ([0-9.]+).*/\1/p')" - - if [ -z "${NDK_VERSION}" ]; then - echo "ERROR: Can not map Android NDK codename to revision!" >&2 - exit 1 - fi - - export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}" - export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}" - - if ! command -v cargo 1>/dev/null 2>&1; then - . "${HOME}/.cargo/env" - fi - - # Download Flutter dependencies - - pushd flutter - - flutter clean - flutter packages pub get - - popd # flutter - - # Build host android deps - - bash flutter/build_android_deps.sh "${ANDROID_ABI}" - - # Build rustdesk lib - - cargo ndk \ - --platform 21 \ - --target "${RUST_TARGET}" \ - --bindgen \ - build \ - --release \ - --features "${RUSTDESK_FEATURES}" - - mkdir -p "flutter/android/app/src/main/jniLibs/${ANDROID_ABI}" - - cp "target/${RUST_TARGET}/release/liblibrustdesk.so" \ - "flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so" - - cp "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/${NDK_TARGET}/libc++_shared.so" \ - "flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/" - - "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip" \ - "flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"/* - - # Build flutter-jit-release for x86 - - if [ "${ANDROID_ABI}" = "x86" ]; then - pushd flutter-sdk - - echo "## Sync flutter engine sources" - echo "### We need fakeroot because chromium base image is unpacked with weird uid/gid ownership" - - sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_VERSION}/" .gclient - - export FAKEROOTDONTTRYCHOWN=1 - - fakeroot gclient sync - - unset FAKEROOTDONTTRYCHOWN - - pushd src - - echo "## Patch away Google Play dependencies" - - rm \ - flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java \ - flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java flutter/shell/platform/android/io/flutter/embedding/android/FlutterPlayStoreSplitApplication.java - - sed \ - -i \ - -e '/PlayStore/d' \ - flutter/tools/android_lint/project.xml \ - flutter/shell/platform/android/BUILD.gn - - sed \ - -i \ - -e '/com.google.android.play/d' \ - flutter/tools/androidx/files.json - - echo "## Configure android engine build" - - flutter/tools/gn \ - --android --android-cpu x86 --runtime-mode=jit_release \ - --no-goma --no-enable-unittests - - echo "## Perform android engine build" - - ninja -C out/android_jit_release_x86 - - echo "## Configure host engine build" - - flutter/tools/gn \ - --android-cpu x86 --runtime-mode=jit_release \ - --no-goma --no-enable-unittests - - echo "## Perform android engine build" - - ninja -C out/host_jit_release_x86 - - echo "## Rename host engine" - - mv out/host_jit_release_x86 out/host_jit_release - - echo "## Mimic jit_release engine to debug to use with flutter build apk" - - pushd out/android_jit_release_x86 - - sed \ - -e 's/jit_release/debug/' \ - flutter_embedding_jit_release.maven-metadata.xml \ - 1>flutter_embedding_debug.maven-metadata.xml - - sed \ - -e 's/jit_release/debug/' \ - flutter_embedding_jit_release.pom \ - 1>flutter_embedding_debug.pom - - sed \ - -e 's/jit_release/debug/' \ - x86_jit_release.maven-metadata.xml \ - 1>x86_debug.maven-metadata.xml - - sed \ - -e 's/jit_release/debug/' \ - x86_jit_release.pom \ - 1>x86_debug.pom - - cp -a \ - flutter_embedding_jit_release-sources.jar \ - flutter_embedding_debug-sources.jar - - cp -a \ - flutter_embedding_jit_release.jar \ - flutter_embedding_debug.jar - - cp -a \ - x86_jit_release.jar \ - x86_debug.jar - - popd # out/android_jit_release_x86 - - popd # src - - popd # flutter-sdk - - echo "# Clean up intermediate engine files and show free space" - - rm -rf \ - flutter-sdk/src/out/android_jit_release_x86/obj \ - flutter-sdk/src/out/host_jit_release/obj - - mv flutter-sdk/src/out flutter-out - - rm -rf flutter-sdk - - mkdir -p flutter-sdk/src/ - - mv flutter-out flutter-sdk/src/out - fi - - # Build the apk - - pushd flutter - - if [ "${ANDROID_ABI}" = "x86" ]; then - flutter build apk \ - --local-engine-src-path="$(readlink -mf "../flutter-sdk/src")" \ - --local-engine=android_jit_release_x86 \ - --debug \ - --build-number="${VERCODE}" \ - --build-name="${VERNAME}" \ - --target-platform "${FLUTTER_TARGET}" - else - flutter build apk \ - --release \ - --build-number="${VERCODE}" \ - --build-name="${VERNAME}" \ - --target-platform "${FLUTTER_TARGET}" - fi - - popd # flutter - - rm -rf flutter-sdk - - # Special step for fdroiddata CI builds to remove .gitconfig - - rm -f /home/vagrant/.gitconfig - - ;; -*) - echo "ERROR: Unknown build step '${BUILDSTEP}'!" >&2 - exit 1 - ;; -esac - -# Report success - -echo "All done!" diff --git a/flutter/build_ios.sh b/flutter/build_ios.sh deleted file mode 100755 index cd1262626..000000000 --- a/flutter/build_ios.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# https://docs.flutter.dev/deployment/ios -# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info -# no obfuscate, because no easy to check errors -cd $(dirname $(dirname $(which flutter))) -git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff -cd - -flutter build ipa --release diff --git a/flutter/ios/.gitignore b/flutter/ios/.gitignore deleted file mode 100644 index 151026b91..000000000 --- a/flutter/ios/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/flutter/ios/Flutter/AppFrameworkInfo.plist b/flutter/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 1dc6cf765..000000000 --- a/flutter/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 13.0 - - diff --git a/flutter/ios/Flutter/Debug.xcconfig b/flutter/ios/Flutter/Debug.xcconfig deleted file mode 100644 index ec97fc6f3..000000000 --- a/flutter/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/flutter/ios/Flutter/Release.xcconfig b/flutter/ios/Flutter/Release.xcconfig deleted file mode 100644 index c4855bfe2..000000000 --- a/flutter/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/flutter/ios/Podfile b/flutter/ios/Podfile deleted file mode 100644 index b71c436f2..000000000 --- a/flutter/ios/Podfile +++ /dev/null @@ -1,45 +0,0 @@ -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -platform :ios, '13.0' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' - end - end -end - - diff --git a/flutter/ios/Podfile.lock b/flutter/ios/Podfile.lock deleted file mode 100644 index c9e9f9a2f..000000000 --- a/flutter/ios/Podfile.lock +++ /dev/null @@ -1,142 +0,0 @@ -PODS: - - device_info_plus (0.0.1): - - Flutter - - DKImagePickerController/Core (4.3.4): - - DKImagePickerController/ImageDataManager - - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.4) - - DKImagePickerController/PhotoGallery (4.3.4): - - DKImagePickerController/Core - - DKPhotoGallery - - DKImagePickerController/Resource (4.3.4) - - DKPhotoGallery (0.0.17): - - DKPhotoGallery/Core (= 0.0.17) - - DKPhotoGallery/Model (= 0.0.17) - - DKPhotoGallery/Preview (= 0.0.17) - - DKPhotoGallery/Resource (= 0.0.17) - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Core (0.0.17): - - DKPhotoGallery/Model - - DKPhotoGallery/Preview - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Model (0.0.17): - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Preview (0.0.17): - - DKPhotoGallery/Model - - DKPhotoGallery/Resource - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Resource (0.0.17): - - SDWebImage - - SwiftyGif - - file_picker (0.0.1): - - DKImagePickerController/PhotoGallery - - Flutter - - Flutter (1.0.0) - - flutter_keyboard_visibility (0.0.1): - - Flutter - - image_picker_ios (0.0.1): - - Flutter - - MTBBarcodeScanner (5.0.11) - - package_info_plus (0.4.5): - - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - qr_code_scanner (0.2.0): - - Flutter - - MTBBarcodeScanner - - SDWebImage (5.18.11): - - SDWebImage/Core (= 5.18.11) - - SDWebImage/Core (5.18.11) - - sqflite (0.0.3): - - Flutter - - FlutterMacOS - - SwiftyGif (5.4.4) - - uni_links (0.0.1): - - Flutter - - url_launcher_ios (0.0.1): - - Flutter - - video_player_avfoundation (0.0.1): - - Flutter - - FlutterMacOS - - wakelock_plus (0.0.1): - - Flutter - -DEPENDENCIES: - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - file_picker (from `.symlinks/plugins/file_picker/ios`) - - Flutter (from `Flutter`) - - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) - - uni_links (from `.symlinks/plugins/uni_links/ios`) - - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - -SPEC REPOS: - trunk: - - DKImagePickerController - - DKPhotoGallery - - MTBBarcodeScanner - - SDWebImage - - SwiftyGif - -EXTERNAL SOURCES: - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" - file_picker: - :path: ".symlinks/plugins/file_picker/ios" - Flutter: - :path: Flutter - flutter_keyboard_visibility: - :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" - image_picker_ios: - :path: ".symlinks/plugins/image_picker_ios/ios" - package_info_plus: - :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - qr_code_scanner: - :path: ".symlinks/plugins/qr_code_scanner/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" - uni_links: - :path: ".symlinks/plugins/uni_links/ios" - url_launcher_ios: - :path: ".symlinks/plugins/url_launcher_ios/ios" - video_player_avfoundation: - :path: ".symlinks/plugins/video_player_avfoundation/darwin" - wakelock_plus: - :path: ".symlinks/plugins/wakelock_plus/ios" - -SPEC CHECKSUMS: - device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 - DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac - DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: ce3938a0df3cc1ef404671531facef740d03f920 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 - image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 - MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb - package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c - qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 - wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 - -PODFILE CHECKSUM: 83d1b0fb6fc8613d8312a03b8e1540d37cfc5d2c - -COCOAPODS: 1.15.2 diff --git a/flutter/ios/Runner.xcodeproj/project.pbxproj b/flutter/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 36dc89ea8..000000000 --- a/flutter/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,756 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - C5678348E08E565F424B13A5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25E069BDBEF2890938537ABB /* Pods_Runner.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 0A3A301029F021DD0095DDA5 /* liblibrustdesk.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = liblibrustdesk.a; path = "../../target/aarch64-apple-ios/release/liblibrustdesk.a"; sourceTree = ""; }; - 0A3A301329F0AB660095DDA5 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 25E069BDBEF2890938537ABB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 40E78CC6B4293A3E6DA85154 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 4151ACC476A12FDC49BC7860 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C66E40D850585BD073A0EF7D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C5678348E08E565F424B13A5 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - FC03317C0FF27D4E19FBA24E /* Pods */, - F213315C743C5EC601AD8123 /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 0A3A301329F0AB660095DDA5 /* Runner.entitlements */, - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - F213315C743C5EC601AD8123 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 0A3A301029F021DD0095DDA5 /* liblibrustdesk.a */, - 25E069BDBEF2890938537ABB /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - FC03317C0FF27D4E19FBA24E /* Pods */ = { - isa = PBXGroup; - children = ( - 40E78CC6B4293A3E6DA85154 /* Pods-Runner.debug.xcconfig */, - C66E40D850585BD073A0EF7D /* Pods-Runner.release.xcconfig */, - 4151ACC476A12FDC49BC7860 /* Pods-Runner.profile.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 7EB62F3CF0939A56238651D9 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 649B29BA4C122652F1215682 /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 649B29BA4C122652F1215682 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 7EB62F3CF0939A56238651D9 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = HZF9JMC8YN; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - /usr/lib/swift, - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/", - "$(SDKROOT)/usr/lib/swift", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/liblibrustdesk.a", - "-ObjC", - "-l\"c++\"", - "-l\"sqlite3\"", - "-framework", - "\"AVFoundation\"", - "-framework", - "\"AVKit\"", - "-framework", - "\"DKImagePickerController\"", - "-framework", - "\"DKPhotoGallery\"", - "-framework", - "\"Foundation\"", - "-framework", - "\"ImageIO\"", - "-framework", - "\"MTBBarcodeScanner\"", - "-framework", - "\"Photos\"", - "-framework", - "\"QuartzCore\"", - "-framework", - "\"SDWebImage\"", - "-framework", - "\"SwiftyGif\"", - "-framework", - "\"UIKit\"", - "-framework", - "\"device_info_plus\"", - "-framework", - "\"file_picker\"", - "-framework", - "\"flutter_keyboard_visibility\"", - "-framework", - "\"image_picker_ios\"", - "-framework", - "\"package_info_plus\"", - "-framework", - "\"path_provider_foundation\"", - "-framework", - "\"qr_code_scanner\"", - "-framework", - "\"sqflite\"", - "-framework", - "\"uni_links\"", - "-framework", - "\"url_launcher_ios\"", - "-framework", - "\"video_player_avfoundation\"", - "-framework", - "\"wakelock_plus\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_STYLE = "non-global"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = HZF9JMC8YN; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - /usr/lib/swift, - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/", - "$(SDKROOT)/usr/lib/swift", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/liblibrustdesk.a", - "-ObjC", - "-l\"c++\"", - "-l\"sqlite3\"", - "-framework", - "\"AVFoundation\"", - "-framework", - "\"AVKit\"", - "-framework", - "\"DKImagePickerController\"", - "-framework", - "\"DKPhotoGallery\"", - "-framework", - "\"Foundation\"", - "-framework", - "\"ImageIO\"", - "-framework", - "\"MTBBarcodeScanner\"", - "-framework", - "\"Photos\"", - "-framework", - "\"QuartzCore\"", - "-framework", - "\"SDWebImage\"", - "-framework", - "\"SwiftyGif\"", - "-framework", - "\"UIKit\"", - "-framework", - "\"device_info_plus\"", - "-framework", - "\"file_picker\"", - "-framework", - "\"flutter_keyboard_visibility\"", - "-framework", - "\"image_picker_ios\"", - "-framework", - "\"package_info_plus\"", - "-framework", - "\"path_provider_foundation\"", - "-framework", - "\"qr_code_scanner\"", - "-framework", - "\"sqflite\"", - "-framework", - "\"uni_links\"", - "-framework", - "\"url_launcher_ios\"", - "-framework", - "\"video_player_avfoundation\"", - "-framework", - "\"wakelock_plus\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_STYLE = "non-global"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = HZF9JMC8YN; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", - /usr/lib/swift, - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/", - "$(SDKROOT)/usr/lib/swift", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/", - ); - OTHER_LDFLAGS = ( - "$(inherited)", - "$(PROJECT_DIR)/../../target/aarch64-apple-ios/release/liblibrustdesk.a", - "-ObjC", - "-l\"c++\"", - "-l\"sqlite3\"", - "-framework", - "\"AVFoundation\"", - "-framework", - "\"AVKit\"", - "-framework", - "\"DKImagePickerController\"", - "-framework", - "\"DKPhotoGallery\"", - "-framework", - "\"Foundation\"", - "-framework", - "\"ImageIO\"", - "-framework", - "\"MTBBarcodeScanner\"", - "-framework", - "\"Photos\"", - "-framework", - "\"QuartzCore\"", - "-framework", - "\"SDWebImage\"", - "-framework", - "\"SwiftyGif\"", - "-framework", - "\"UIKit\"", - "-framework", - "\"device_info_plus\"", - "-framework", - "\"file_picker\"", - "-framework", - "\"flutter_keyboard_visibility\"", - "-framework", - "\"image_picker_ios\"", - "-framework", - "\"package_info_plus\"", - "-framework", - "\"path_provider_foundation\"", - "-framework", - "\"qr_code_scanner\"", - "-framework", - "\"sqflite\"", - "-framework", - "\"uni_links\"", - "-framework", - "\"url_launcher_ios\"", - "-framework", - "\"video_player_avfoundation\"", - "-framework", - "\"wakelock_plus\"", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_STYLE = "non-global"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a62..000000000 --- a/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index 5e31d3d34..000000000 --- a/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata b/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c5e..000000000 --- a/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/flutter/ios/Runner/AppDelegate.swift b/flutter/ios/Runner/AppDelegate.swift deleted file mode 100644 index d9333b706..000000000 --- a/flutter/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,19 +0,0 @@ -import UIKit -import Flutter - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - dummyMethodToEnforceBundling(); - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } - - public func dummyMethodToEnforceBundling() { - dummy_method_to_enforce_bundling(); - session_get_rgba(nil, 0); - } -} diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 53611299a..000000000 --- a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-60x60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-60x60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-20x20@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-76x76@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "Icon-App-1024x1024@1x.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index fa4ecad0a..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 080f311fc..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 8daf5718b..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index fa5b04f7f..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index a4bc6dfd7..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 97831dc82..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index cfaff2b9b..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 8daf5718b..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 85365e185..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index bafaf7e07..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index bafaf7e07..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e5755e5fb..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index b60a754df..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 0ddd120e6..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 22628c536..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 00cabce83..000000000 --- a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "filename" : "LaunchImage.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "LaunchImage@2x.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "LaunchImage@3x.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter/ios/Runner/Base.lproj/Main.storyboard b/flutter/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index d68a3a7a5..000000000 --- a/flutter/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/flutter/ios/Runner/GoogleService-Info.plist b/flutter/ios/Runner/GoogleService-Info.plist deleted file mode 100644 index f39288230..000000000 --- a/flutter/ios/Runner/GoogleService-Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CLIENT_ID - 768133699366-k1rn3ls1u2n3nklmgd9t4cmpdob0c8bn.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.768133699366-k1rn3ls1u2n3nklmgd9t4cmpdob0c8bn - API_KEY - AIzaSyCf57HjCwSokt91CqFI0Mwf8D--ek0jvfc - GCM_SENDER_ID - 768133699366 - PLIST_VERSION - 1 - BUNDLE_ID - com.carriez.flutterHbb - PROJECT_ID - rustdesk - STORAGE_BUCKET - rustdesk.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:768133699366:ios:c33078a6181b9d507993e7 - DATABASE_URL - https://rustdesk.firebaseio.com - - \ No newline at end of file diff --git a/flutter/ios/Runner/Info.plist b/flutter/ios/Runner/Info.plist deleted file mode 100644 index 9351dac53..000000000 --- a/flutter/ios/Runner/Info.plist +++ /dev/null @@ -1,82 +0,0 @@ - - - - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - RustDesk - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - RustDesk - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLIconFile - - CFBundleURLName - com.carriez.rustdesk - CFBundleURLSchemes - - rustdesk - - - - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UIFileSharingEnabled - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportsDocumentBrowser - - UIViewControllerBasedStatusBarAppearance - - ITSAppUsesNonExemptEncryption - - io.flutter.embedded_views_preview - - NSCameraUsageDescription - This app needs camera access to scan QR codes - NSPhotoLibraryUsageDescription - This app needs photo library access to get QR codes from image - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - diff --git a/flutter/ios/Runner/Runner-Bridging-Header.h b/flutter/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index e930a3997..000000000 --- a/flutter/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1,3 +0,0 @@ -#import "GeneratedPluginRegistrant.h" - -#import "bridge_generated.h" diff --git a/flutter/ios/Runner/Runner.entitlements b/flutter/ios/Runner/Runner.entitlements deleted file mode 100644 index 75e36a143..000000000 --- a/flutter/ios/Runner/Runner.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - aps-environment - development - com.apple.developer.networking.wifi-info - - - diff --git a/flutter/ios/exportOptions.plist b/flutter/ios/exportOptions.plist deleted file mode 100644 index 6ceb2ac74..000000000 --- a/flutter/ios/exportOptions.plist +++ /dev/null @@ -1,15 +0,0 @@ - - - - - method - app-store - teamID - HZF9JMC8YN - provisioningProfiles - - com.carriez.flutterHbb - rustdesk-ios-prod-app-store - - - diff --git a/flutter/ios_arm64.sh b/flutter/ios_arm64.sh deleted file mode 100755 index 579baaa6d..000000000 --- a/flutter/ios_arm64.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib diff --git a/flutter/ios_x64.sh b/flutter/ios_x64.sh deleted file mode 100755 index 04b999313..000000000 --- a/flutter/ios_x64.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -cargo build --features flutter --release --target x86_64-apple-ios --lib diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart deleted file mode 100644 index 366a7b6ba..000000000 --- a/flutter/lib/common.dart +++ /dev/null @@ -1,4196 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; - -import 'package:back_button_interceptor/back_button_interceptor.dart'; -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common/formatter/id_formatter.dart'; -import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/main.dart'; -import 'package:flutter_hbb/models/peer_model.dart'; -import 'package:flutter_hbb/models/peer_tab_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_hbb/utils/platform_channel.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart'; -import 'package:provider/provider.dart'; -import 'package:uni_links/uni_links.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:uuid/uuid.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:window_size/window_size.dart' as window_size; - -import '../consts.dart'; -import 'common/widgets/overlay.dart'; -import 'mobile/pages/file_manager_page.dart'; -import 'mobile/pages/remote_page.dart'; -import 'mobile/pages/view_camera_page.dart'; -import 'mobile/pages/terminal_page.dart'; -import 'desktop/pages/remote_page.dart' as desktop_remote; -import 'desktop/pages/file_manager_page.dart' as desktop_file_manager; -import 'desktop/pages/view_camera_page.dart' as desktop_view_camera; -import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; -import 'models/model.dart'; -import 'models/platform_model.dart'; - -import 'package:flutter_hbb/native/win32.dart' - if (dart.library.html) 'package:flutter_hbb/web/win32.dart'; -import 'package:flutter_hbb/native/common.dart' - if (dart.library.html) 'package:flutter_hbb/web/common.dart'; -import 'package:flutter_hbb/utils/http_service.dart' as http; - -final globalKey = GlobalKey(); -final navigationBarKey = GlobalKey(); - -final isAndroid = isAndroid_; -final isIOS = isIOS_; -final isWindows = isWindows_; -final isMacOS = isMacOS_; -final isLinux = isLinux_; -final isDesktop = isDesktop_; -final isWeb = isWeb_; -final isWebDesktop = isWebDesktop_; -final isWebOnWindows = isWebOnWindows_; -final isWebOnLinux = isWebOnLinux_; -final isWebOnMacOs = isWebOnMacOS_; -var isMobile = isAndroid || isIOS; -var version = ''; -int androidVersion = 0; - -// Only used on Linux. -// `windowManager.setResizable(false)` will reset the window size to the default size on Linux. -// https://stackoverflow.com/questions/8193613/gtk-window-resize-disable-without-going-back-to-default -// So we need to use this flag to enable/disable resizable. -bool _linuxWindowResizable = true; - -// Only used on Windows(window manager). -bool _ignoreDevicePixelRatio = true; - -/// only available for Windows target -int windowsBuildNumber = 0; -DesktopType? desktopType; - -// Tolerance used for floating-point position comparisons to avoid precision errors. -const double _kPositionEpsilon = 1e-6; - -bool get isMainDesktopWindow => - desktopType == DesktopType.main || desktopType == DesktopType.cm; - -String get screenInfo => screenInfo_; - -/// Check if the app is running with single view mode. -bool isSingleViewApp() { - return desktopType == DesktopType.cm; -} - -/// * debug or test only, DO NOT enable in release build -bool isTest = false; - -typedef F = String Function(String); -typedef FMethod = String Function(String, dynamic); - -typedef StreamEventHandler = Future Function(Map); -typedef SessionID = UuidValue; -final iconHardDrive = MemoryImage(Uint8List.fromList(base64Decode( - 'iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAmVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjHWqVAAAAMnRSTlMAv0BmzLJNXlhiUu2fxXDgu7WuSUUe29LJvpqUjX53VTstD7ilNujCqTEk5IYH+vEoFjKvAagAAAPpSURBVHja7d0JbhpBEIXhB3jYzb5vBgzYgO04df/DJXGUKMwU9ECmZ6pQfSfw028LCXW3YYwxxhhjjDHGGGOM0eZ9VV1MckdKWLM1bRQ/35GW/WxHHu1me6ShuyHvNl34VhlTKsYVeDWj1EzgUZ1S1DrAk/UDparZgxd9Sl0BHnxSBhpI3jfKQG2FpLUpE69I2ILikv1nsvygjBwPSNKYMlNHggqUoSKS80AZCnwHqQ1zCRvW+CRegwRFeFAMKKrtM8gTPJlzSfwFgT9dJom3IDN4VGaSeAryAK8m0SSeghTg1ZYiql6CjBDhO8mzlyAVhKhIwgXxrh5NojGIhyRckEdwpCdhgpSQgiWTRGMQNonGIGySp0SDvMDBX5KWxiB8Eo1BgE00SYJBykhNnkmSWJAcLpGaJNMgfJKyxiDAK4WNEwryhMtkJsk8CJtEYxA+icYgQIfCcgkEqcJNXhIRQdgkGoPwSTQG+e8khdu/7JOVREwQIKCwF41B2CQljUH4JLcH6SI+OUlEBQHa0SQag/BJNAbhkjxqDMIn0RgEeI4muSlID9eSkERgEKAVTaIxCJ9EYxA2ydVB8hCASVLRGAQYR5NoDMIn0RgEyFHYSGMQPonGII4kziCNvBgNJonEk4u3GAk8Sprk6eYaqbMDY0oKvUm5jfC/viGiSypV7+M3i2iDsAGpNEDYjlTa3W8RdR/r544g50ilnA0RxoZIE2NIXqQbhkAkGyKNDZHGhkhjQ6SxIdLYEGlsiDQ2JGTVeD0264U9zipPh7XOooffpA6pfNCXjxl4/c3pUzlChwzor53zwYYVfpI5pOV6LWFF/2jiJ5FDSs5jdY/0rwUAkUMeXWdBqnSqD0DikBqdqCHsjTvELm9In0IOri/0pwAEDtlSyNaRjAIAAoesKWTtuusxByBwCJp0oomwBXcYUuCQgE50ENajE4OvZAKHLB1/68Br5NqiyCGYOY8YRd77kTkEb64n7lZN+mOIX4QOwb5FX0ZVx3uOxwW+SB0CbBubemWP8/rlaaeRX+M3uUOuZENsiA25zIbYkPsZElBIHwL13U/PTjJ/cyOOEoVM3I+hziDQlELm7pPxw3eI8/7gPh1fpLA6xGnEeDDgO0UcIAzzM35HxLPIq5SXe9BLzOsj9eUaQqyXzxS1QFSfWM2cCANiHcAISJ0AnCKpUwTuIkkA3EeSInAXSQKcs1V18e24wlllUmQp9v9zXKeHi+akRAMOPVKhAqdPBZeUmnnEsO6QcJ0+4qmOSbBxFfGVRiTUqITrdKcCbyYO3/K4wX4+aQ+FfNjXhu3JfAVjjDHGGGOMMcYYY4xIPwCgfqT6TbhCLAAAAABJRU5ErkJggg=='))); - -enum DesktopType { - main, - remote, - fileTransfer, - viewCamera, - terminal, - cm, - portForward, -} - -bool isDoubleEqual(double a, double b) { - return (a - b).abs() < _kPositionEpsilon; -} - -class IconFont { - static const _family1 = 'Tabbar'; - static const _family2 = 'PeerSearchbar'; - static const _family3 = 'AddressBook'; - static const _family4 = 'DeviceGroup'; - static const _family5 = 'More'; - - IconFont._(); - - static const IconData max = IconData(0xe606, fontFamily: _family1); - static const IconData restore = IconData(0xe607, fontFamily: _family1); - static const IconData close = IconData(0xe668, fontFamily: _family1); - static const IconData min = IconData(0xe609, fontFamily: _family1); - static const IconData add = IconData(0xe664, fontFamily: _family1); - static const IconData menu = IconData(0xe628, fontFamily: _family1); - static const IconData search = IconData(0xe6a4, fontFamily: _family2); - static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2); - static const IconData addressBook = IconData(0xe602, fontFamily: _family3); - static const IconData deviceGroupOutline = - IconData(0xe623, fontFamily: _family4); - static const IconData deviceGroupFill = - IconData(0xe748, fontFamily: _family4); - static const IconData more = IconData(0xe609, fontFamily: _family5); -} - -class ColorThemeExtension extends ThemeExtension { - const ColorThemeExtension({ - required this.border, - required this.border2, - required this.border3, - required this.highlight, - required this.drag_indicator, - required this.shadow, - required this.errorBannerBg, - required this.me, - required this.toastBg, - required this.toastText, - required this.divider, - }); - - final Color? border; - final Color? border2; - final Color? border3; - final Color? highlight; - final Color? drag_indicator; - final Color? shadow; - final Color? errorBannerBg; - final Color? me; - final Color? toastBg; - final Color? toastText; - final Color? divider; - - static final light = ColorThemeExtension( - border: Color(0xFFCCCCCC), - border2: Color(0xFFBBBBBB), - border3: Colors.black26, - highlight: Color(0xFFE5E5E5), - drag_indicator: Colors.grey[800], - shadow: Colors.black, - errorBannerBg: Color(0xFFFDEEEB), - me: Colors.green, - toastBg: Colors.black.withOpacity(0.6), - toastText: Colors.white, - divider: Colors.black38, - ); - - static final dark = ColorThemeExtension( - border: Color(0xFF555555), - border2: Color(0xFFE5E5E5), - border3: Colors.white24, - highlight: Color(0xFF3F3F3F), - drag_indicator: Colors.grey, - shadow: Colors.grey, - errorBannerBg: Color(0xFF470F2D), - me: Colors.greenAccent, - toastBg: Colors.white.withOpacity(0.6), - toastText: Colors.black, - divider: Colors.white38, - ); - - @override - ThemeExtension copyWith({ - Color? border, - Color? border2, - Color? border3, - Color? highlight, - Color? drag_indicator, - Color? shadow, - Color? errorBannerBg, - Color? me, - Color? toastBg, - Color? toastText, - Color? divider, - }) { - return ColorThemeExtension( - border: border ?? this.border, - border2: border2 ?? this.border2, - border3: border3 ?? this.border3, - highlight: highlight ?? this.highlight, - drag_indicator: drag_indicator ?? this.drag_indicator, - shadow: shadow ?? this.shadow, - errorBannerBg: errorBannerBg ?? this.errorBannerBg, - me: me ?? this.me, - toastBg: toastBg ?? this.toastBg, - toastText: toastText ?? this.toastText, - divider: divider ?? this.divider, - ); - } - - @override - ThemeExtension lerp( - ThemeExtension? other, double t) { - if (other is! ColorThemeExtension) { - return this; - } - return ColorThemeExtension( - border: Color.lerp(border, other.border, t), - border2: Color.lerp(border2, other.border2, t), - border3: Color.lerp(border3, other.border3, t), - highlight: Color.lerp(highlight, other.highlight, t), - drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t), - shadow: Color.lerp(shadow, other.shadow, t), - errorBannerBg: Color.lerp(shadow, other.errorBannerBg, t), - me: Color.lerp(shadow, other.me, t), - toastBg: Color.lerp(shadow, other.toastBg, t), - toastText: Color.lerp(shadow, other.toastText, t), - divider: Color.lerp(shadow, other.divider, t), - ); - } -} - -class MyTheme { - MyTheme._(); - - static const Color grayBg = Color(0xFFEFEFF2); - static const Color accent = Color(0xFF0071FF); - static const Color accent50 = Color(0x770071FF); - static const Color accent80 = Color(0xAA0071FF); - static const Color canvasColor = Color(0xFF212121); - static const Color border = Color(0xFFCCCCCC); - static const Color idColor = Color(0xFF00B6F0); - static const Color darkGray = Color.fromARGB(255, 148, 148, 148); - static const Color cmIdColor = Color(0xFF21790B); - static const Color dark = Colors.black87; - static const Color button = Color(0xFF2C8CFF); - static const Color hoverBorder = Color(0xFF999999); - - // ListTile - static const ListTileThemeData listTileTheme = ListTileThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(5), - ), - ), - ); - - static SwitchThemeData switchTheme() { - return SwitchThemeData( - splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius); - } - - static RadioThemeData radioTheme() { - return RadioThemeData( - splashRadius: (isDesktop || isWebDesktop) ? 0 : kRadialReactionRadius); - } - - // Checkbox - static const CheckboxThemeData checkboxTheme = CheckboxThemeData( - splashRadius: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(5), - ), - ), - ); - - // TextButton - // Value is used to calculate "dialog.actionsPadding" - static const double mobileTextButtonPaddingLR = 20; - - // TextButton on mobile needs a fixed padding, otherwise small buttons - // like "OK" has a larger left/right padding. - static TextButtonThemeData mobileTextButtonTheme = TextButtonThemeData( - style: TextButton.styleFrom( - padding: EdgeInsets.symmetric(horizontal: mobileTextButtonPaddingLR), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - ), - ); - - //tooltip - static TooltipThemeData tooltipTheme() { - return TooltipThemeData( - waitDuration: Duration(seconds: 1, milliseconds: 500), - ); - } - - // Dialogs - static const double dialogPadding = 24; - - // padding bottom depends on content (some dialogs has no content) - static EdgeInsets dialogTitlePadding({bool content = true}) { - final double p = dialogPadding; - - return EdgeInsets.fromLTRB(p, p, p, content ? 0 : p); - } - - // padding bottom depends on actions (mobile has dialogs without actions) - static EdgeInsets dialogContentPadding({bool actions = true}) { - final double p = dialogPadding; - - return (isDesktop || isWebDesktop) - ? EdgeInsets.fromLTRB(p, p, p, actions ? (p - 4) : p) - : EdgeInsets.fromLTRB(p, p, p, actions ? (p / 2) : p); - } - - static EdgeInsets dialogActionsPadding() { - final double p = dialogPadding; - - return (isDesktop || isWebDesktop) - ? EdgeInsets.fromLTRB(p, 0, p, (p - 4)) - : EdgeInsets.fromLTRB(p, 0, (p - mobileTextButtonPaddingLR), (p / 2)); - } - - static EdgeInsets dialogButtonPadding = (isDesktop || isWebDesktop) - ? EdgeInsets.only(left: dialogPadding) - : EdgeInsets.only(left: dialogPadding / 3); - - static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData( - thickness: MaterialStateProperty.all(6), - thumbColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.dragged)) { - return Colors.grey[900]; - } else if (states.contains(MaterialState.hovered)) { - return Colors.grey[700]; - } else { - return Colors.grey[500]; - } - }), - crossAxisMargin: 4, - ); - - static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme.copyWith( - thumbColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.dragged)) { - return Colors.grey[100]; - } else if (states.contains(MaterialState.hovered)) { - return Colors.grey[300]; - } else { - return Colors.grey[500]; - } - }), - ); - - static ThemeData lightTheme = ThemeData( - // https://stackoverflow.com/questions/77537315/after-upgrading-to-flutter-3-16-the-app-bar-background-color-button-size-and - useMaterial3: false, - brightness: Brightness.light, - hoverColor: Color.fromARGB(255, 224, 224, 224), - scaffoldBackgroundColor: Colors.white, - dialogBackgroundColor: Colors.white, - appBarTheme: AppBarTheme( - shadowColor: Colors.transparent, - ), - dialogTheme: DialogTheme( - elevation: 15, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0), - side: BorderSide( - width: 1, - color: grayBg, - ), - ), - ), - scrollbarTheme: scrollbarTheme, - inputDecorationTheme: isDesktop - ? InputDecorationTheme( - fillColor: grayBg, - filled: true, - isDense: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - ) - : null, - textTheme: const TextTheme( - titleLarge: TextStyle(fontSize: 19, color: Colors.black87), - titleSmall: TextStyle(fontSize: 14, color: Colors.black87), - bodySmall: TextStyle(fontSize: 12, color: Colors.black87, height: 1.25), - bodyMedium: - TextStyle(fontSize: 14, color: Colors.black87, height: 1.25), - labelLarge: TextStyle(fontSize: 16.0, color: MyTheme.accent80)), - cardColor: grayBg, - hintColor: Color(0xFFAAAAAA), - visualDensity: VisualDensity.adaptivePlatformDensity, - tabBarTheme: const TabBarTheme( - labelColor: Colors.black87, - ), - tooltipTheme: tooltipTheme(), - splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, - highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, - splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null, - textButtonTheme: (isDesktop || isWebDesktop) - ? TextButtonThemeData( - style: TextButton.styleFrom( - splashFactory: NoSplash.splashFactory, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0), - ), - ), - ) - : mobileTextButtonTheme, - 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), - ), - ), - ), - switchTheme: switchTheme(), - radioTheme: radioTheme(), - checkboxTheme: checkboxTheme, - listTileTheme: listTileTheme, - menuBarTheme: MenuBarThemeData( - style: - MenuStyle(backgroundColor: MaterialStatePropertyAll(Colors.white))), - colorScheme: ColorScheme.light( - primary: Colors.blue, secondary: accent, background: grayBg), - popupMenuTheme: PopupMenuThemeData( - color: Colors.white, - shape: RoundedRectangleBorder( - side: BorderSide( - color: (isDesktop || isWebDesktop) - ? Color(0xFFECECEC) - : Colors.transparent), - borderRadius: BorderRadius.all(Radius.circular(8.0)), - )), - ).copyWith( - extensions: >[ - ColorThemeExtension.light, - TabbarTheme.light, - ], - ); - static ThemeData darkTheme = ThemeData( - useMaterial3: false, - brightness: Brightness.dark, - hoverColor: Color.fromARGB(255, 45, 46, 53), - scaffoldBackgroundColor: Color(0xFF18191E), - dialogBackgroundColor: Color(0xFF18191E), - appBarTheme: AppBarTheme( - shadowColor: Colors.transparent, - ), - dialogTheme: DialogTheme( - elevation: 15, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0), - side: BorderSide( - width: 1, - color: Color(0xFF24252B), - ), - ), - ), - scrollbarTheme: scrollbarThemeDark, - inputDecorationTheme: (isDesktop || isWebDesktop) - ? InputDecorationTheme( - fillColor: Color(0xFF24252B), - filled: true, - isDense: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - ) - : null, - textTheme: const TextTheme( - titleLarge: TextStyle(fontSize: 19), - titleSmall: TextStyle(fontSize: 14), - bodySmall: TextStyle(fontSize: 12, height: 1.25), - bodyMedium: TextStyle(fontSize: 14, height: 1.25), - labelLarge: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, - color: accent80, - ), - ), - cardColor: Color(0xFF24252B), - visualDensity: VisualDensity.adaptivePlatformDensity, - tabBarTheme: const TabBarTheme( - labelColor: Colors.white70, - ), - tooltipTheme: tooltipTheme(), - splashColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, - highlightColor: (isDesktop || isWebDesktop) ? Colors.transparent : null, - splashFactory: (isDesktop || isWebDesktop) ? NoSplash.splashFactory : null, - textButtonTheme: (isDesktop || isWebDesktop) - ? TextButtonThemeData( - style: TextButton.styleFrom( - splashFactory: NoSplash.splashFactory, - disabledForegroundColor: Colors.white70, - foregroundColor: Colors.white70, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(18.0), - ), - ), - ) - : mobileTextButtonTheme, - 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), - ), - ), - ), - switchTheme: switchTheme(), - radioTheme: radioTheme(), - checkboxTheme: checkboxTheme, - listTileTheme: listTileTheme, - menuBarTheme: MenuBarThemeData( - style: MenuStyle( - backgroundColor: MaterialStatePropertyAll(Color(0xFF121212)))), - colorScheme: ColorScheme.dark( - primary: Colors.blue, - secondary: accent, - background: Color(0xFF24252B), - ), - popupMenuTheme: PopupMenuThemeData( - shape: RoundedRectangleBorder( - side: BorderSide(color: Colors.white24), - borderRadius: BorderRadius.all(Radius.circular(8.0)), - )), - ).copyWith( - extensions: >[ - ColorThemeExtension.dark, - TabbarTheme.dark, - ], - ); - - static ThemeMode getThemeModePreference() { - return themeModeFromString(bind.mainGetLocalOption(key: kCommConfKeyTheme)); - } - - static Future changeDarkMode(ThemeMode mode) async { - Get.changeThemeMode(mode); - if (desktopType == DesktopType.main || isAndroid || isIOS || isWeb) { - if (mode == ThemeMode.system) { - await bind.mainSetLocalOption( - key: kCommConfKeyTheme, value: defaultOptionTheme); - } else { - await bind.mainSetLocalOption( - key: kCommConfKeyTheme, value: mode.toShortString()); - } - if (!isWeb) await bind.mainChangeTheme(dark: mode.toShortString()); - // Synchronize the window theme of the system. - updateSystemWindowTheme(); - } - } - - static ThemeMode currentThemeMode() { - final preference = getThemeModePreference(); - if (preference == ThemeMode.system) { - if (WidgetsBinding.instance.platformDispatcher.platformBrightness == - Brightness.light) { - return ThemeMode.light; - } else { - return ThemeMode.dark; - } - } else { - return preference; - } - } - - static ColorThemeExtension color(BuildContext context) { - return Theme.of(context).extension()!; - } - - static TabbarTheme tabbar(BuildContext context) { - return Theme.of(context).extension()!; - } - - static ThemeMode themeModeFromString(String v) { - switch (v) { - case "light": - return ThemeMode.light; - case "dark": - return ThemeMode.dark; - default: - return ThemeMode.system; - } - } -} - -extension ParseToString on ThemeMode { - String toShortString() { - return toString().split('.').last; - } -} - -final ButtonStyle flatButtonStyle = TextButton.styleFrom( - minimumSize: Size(0, 36), - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(2.0)), - ), -); - -List supportedLocales = const [ - Locale('en', 'US'), - Locale('zh', 'CN'), - Locale('zh', 'TW'), - Locale('zh', 'SG'), - Locale('fr'), - Locale('de'), - Locale('it'), - Locale('ja'), - Locale('cs'), - Locale('pl'), - Locale('ko'), - Locale('hu'), - Locale('pt'), - Locale('ru'), - Locale('sk'), - Locale('id'), - Locale('da'), - Locale('eo'), - Locale('tr'), - Locale('kz'), - Locale('es'), - Locale('nl'), - Locale('nb'), - Locale('et'), - Locale('eu'), - Locale('bg'), - Locale('be'), - Locale('vn'), - Locale('uk'), - Locale('fa'), - Locale('ca'), - Locale('el'), - Locale('sv'), - Locale('sq'), - Locale('sr'), - Locale('th'), - Locale('sl'), - Locale('ro'), - Locale('lt'), - Locale('lv'), - Locale('ar'), - Locale('he'), - Locale('hr'), -]; - -String formatDurationToTime(Duration duration) { - var totalTime = duration.inSeconds; - final secs = totalTime % 60; - totalTime = (totalTime - secs) ~/ 60; - final mins = totalTime % 60; - totalTime = (totalTime - mins) ~/ 60; - return "${totalTime.toString().padLeft(2, "0")}:${mins.toString().padLeft(2, "0")}:${secs.toString().padLeft(2, "0")}"; -} - -closeConnection({String? id}) { - if (isAndroid || isIOS) { - () async { - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, - overlays: SystemUiOverlay.values); - gFFI.chatModel.hideChatOverlay(); - Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); - stateGlobal.isInMainPage = true; - }(); - } else { - if (isWeb) { - Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); - stateGlobal.isInMainPage = true; - } else { - final controller = Get.find(); - if (controller.tabType == DesktopTabType.terminal && - controller.onCloseWindow != null) { - // Terminal windows are scoped to one peer. The optional id passed to - // closeConnection() is that peer id, not a terminal tab key - // (${peerId}_${terminalId}). Closing from terminal dialogs should close - // the peer's whole terminal window, including all terminal tabs. - unawaited(controller.onCloseWindow!().catchError((e, _) { - debugPrint('[closeConnection] Failed to close terminal window: $e'); - })); - return; - } - controller.closeBy(id); - } - } -} - -Future windowOnTop(int? id) async { - if (!isDesktop) { - return; - } - print("Bring window '$id' on top"); - if (id == null) { - // main window - if (stateGlobal.isMinimized) { - await windowManager.restore(); - } - await windowManager.show(); - await windowManager.focus(); - await rustDeskWinManager.registerActiveWindow(kWindowMainId); - } else { - WindowController.fromWindowId(id) - ..focus() - ..show(); - rustDeskWinManager.call(WindowType.Main, kWindowEventShow, {"id": id}); - } -} - -typedef DialogBuilder = CustomAlertDialog Function( - StateSetter setState, void Function([dynamic]) close, BuildContext context); - -class Dialog { - OverlayEntry? entry; - Completer completer = Completer(); - - Dialog(); - - void complete(T? res) { - try { - if (!completer.isCompleted) { - completer.complete(res); - } - } catch (e) { - debugPrint("Dialog complete catch error: $e"); - } finally { - entry?.remove(); - } - } -} - -class OverlayKeyState { - final _overlayKey = GlobalKey(); - - /// use global overlay by default - OverlayState? get state => - _overlayKey.currentState ?? globalKey.currentState?.overlay; - - GlobalKey? get key => _overlayKey; -} - -class OverlayDialogManager { - final Map _dialogs = {}; - var _overlayKeyState = OverlayKeyState(); - int _tagCount = 0; - - OverlayEntry? _mobileActionsOverlayEntry; - RxBool mobileActionsOverlayVisible = true.obs; - - setMobileActionsOverlayVisible(bool v, {store = true}) { - if (store) { - bind.setLocalFlutterOption(k: kOptionShowMobileAction, v: v ? 'Y' : 'N'); - } - // No need to read the value from local storage after setting it. - // It better to toggle the value directly. - mobileActionsOverlayVisible.value = v; - } - - loadMobileActionsOverlayVisible() { - mobileActionsOverlayVisible.value = - bind.getLocalFlutterOption(k: kOptionShowMobileAction) != 'N'; - } - - void setOverlayState(OverlayKeyState overlayKeyState) { - _overlayKeyState = overlayKeyState; - } - - void dismissAll() { - _dialogs.forEach((key, value) { - value.complete(null); - BackButtonInterceptor.removeByName(key); - }); - _dialogs.clear(); - } - - void dismissByTag(String tag) { - _dialogs[tag]?.complete(null); - _dialogs.remove(tag); - BackButtonInterceptor.removeByName(tag); - } - - Future show(DialogBuilder builder, - {bool clickMaskDismiss = false, - bool backDismiss = false, - String? tag, - bool useAnimation = true, - bool forceGlobal = false}) { - final overlayState = - forceGlobal ? globalKey.currentState?.overlay : _overlayKeyState.state; - - if (overlayState == null) { - return Future.error( - "[OverlayDialogManager] Failed to show dialog, _overlayState is null, call [setOverlayState] first"); - } - - final String dialogTag; - if (tag != null) { - dialogTag = tag; - } else { - dialogTag = _tagCount.toString(); - _tagCount++; - } - - final dialog = Dialog(); - _dialogs[dialogTag] = dialog; - - close([res]) { - _dialogs.remove(dialogTag); - try { - dialog.complete(res); - } catch (e) { - debugPrint("Dialog complete catch error: $e"); - } - BackButtonInterceptor.removeByName(dialogTag); - } - - dialog.entry = OverlayEntry(builder: (context) { - bool innerClicked = false; - return Listener( - onPointerUp: (_) { - if (!innerClicked && clickMaskDismiss) { - close(); - } - innerClicked = false; - }, - child: Container( - color: Theme.of(context).brightness == Brightness.light - ? Colors.black12 - : Colors.black45, - child: StatefulBuilder(builder: (context, setState) { - return Listener( - onPointerUp: (_) => innerClicked = true, - child: builder(setState, close, overlayState.context), - ); - }))); - }); - overlayState.insert(dialog.entry!); - BackButtonInterceptor.add((stopDefaultButtonEvent, routeInfo) { - if (backDismiss) { - close(); - } - return true; - }, name: dialogTag); - return dialog.completer.future; - } - - String showLoading(String text, - {bool clickMaskDismiss = false, - bool showCancel = true, - VoidCallback? onCancel, - String? tag}) { - if (tag == null) { - tag = _tagCount.toString(); - _tagCount++; - } - show((setState, close, context) { - cancel() { - dismissAll(); - if (onCancel != null) { - onCancel(); - } - } - - return CustomAlertDialog( - content: Container( - constraints: const BoxConstraints(maxWidth: 240), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 30), - const Center(child: CircularProgressIndicator()), - const SizedBox(height: 20), - Center( - child: Text(translate(text), - style: const TextStyle(fontSize: 15))), - const SizedBox(height: 20), - Offstage( - offstage: !showCancel, - child: Center( - child: (isDesktop || isWebDesktop) - ? dialogButton('Cancel', onPressed: cancel) - : TextButton( - style: flatButtonStyle, - onPressed: cancel, - child: Text(translate('Cancel'), - style: const TextStyle( - color: MyTheme.accent))))) - ])), - onCancel: showCancel ? cancel : null, - ); - }, tag: tag); - return tag; - } - - void resetMobileActionsOverlay({FFI? ffi}) { - if (_mobileActionsOverlayEntry == null) return; - hideMobileActionsOverlay(); - showMobileActionsOverlay(ffi: ffi); - } - - void showMobileActionsOverlay({FFI? ffi}) { - if (_mobileActionsOverlayEntry != null) return; - final overlayState = _overlayKeyState.state; - if (overlayState == null) return; - - final overlay = makeMobileActionsOverlayEntry( - () => hideMobileActionsOverlay(), - ffi: ffi, - ); - overlayState.insert(overlay); - _mobileActionsOverlayEntry = overlay; - setMobileActionsOverlayVisible(true); - } - - void hideMobileActionsOverlay({store = true}) { - if (_mobileActionsOverlayEntry != null) { - _mobileActionsOverlayEntry!.remove(); - _mobileActionsOverlayEntry = null; - setMobileActionsOverlayVisible(false, store: store); - return; - } - } - - void toggleMobileActionsOverlay({FFI? ffi}) { - if (_mobileActionsOverlayEntry == null) { - showMobileActionsOverlay(ffi: ffi); - } else { - hideMobileActionsOverlay(); - } - } - - bool existing(String tag) { - return _dialogs.keys.contains(tag); - } -} - -makeMobileActionsOverlayEntry(VoidCallback? onHide, {FFI? ffi}) { - makeMobileActions(BuildContext context, double s) { - final scale = s < 0.85 ? 0.85 : s; - final session = ffi ?? gFFI; - const double overlayW = 200; - const double overlayH = 45; - computeOverlayPosition() { - final screenW = MediaQuery.of(context).size.width; - final screenH = MediaQuery.of(context).size.height; - final left = (screenW - overlayW * scale) / 2; - final top = screenH - (overlayH + 80) * scale; - return Offset(left, top); - } - - if (draggablePositions.mobileActions.isInvalid()) { - draggablePositions.mobileActions.update(computeOverlayPosition()); - } else { - draggablePositions.mobileActions.tryAdjust(overlayW, overlayH, scale); - } - return DraggableMobileActions( - scale: scale, - position: draggablePositions.mobileActions, - width: overlayW, - height: overlayH, - onBackPressed: session.inputModel.onMobileBack, - onHomePressed: session.inputModel.onMobileHome, - onRecentPressed: session.inputModel.onMobileApps, - onHidePressed: onHide, - ); - } - - return OverlayEntry(builder: (context) { - if (isDesktop) { - final c = Provider.of(context); - return makeMobileActions(context, c.scale * 2.0); - } else { - return makeMobileActions(globalKey.currentContext!, 1.0); - } - }); -} - -void showToast(String text, - {Duration timeout = const Duration(seconds: 3), - Alignment alignment = const Alignment(0.0, 0.8)}) { - final overlayState = globalKey.currentState?.overlay; - if (overlayState == null) return; - final entry = OverlayEntry(builder: (context) { - return IgnorePointer( - child: Align( - alignment: alignment, - child: Container( - decoration: BoxDecoration( - color: MyTheme.color(context).toastBg, - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - decoration: TextDecoration.none, - fontWeight: FontWeight.w300, - fontSize: 18, - color: MyTheme.color(context).toastText), - ), - ))); - }); - overlayState.insert(entry); - Future.delayed(timeout, () { - entry.remove(); - }); -} - -// TODO -// - Remove argument "contentPadding", no need for it, all should look the same. -// - Remove "required" for argument "content". See simple confirm dialog "delete peer", only title and actions are used. No need to "content: SizedBox.shrink()". -// - Make dead code alive, transform arguments "onSubmit" and "onCancel" into correspondenting buttons "ConfirmOkButton", "CancelButton". -class CustomAlertDialog extends StatelessWidget { - const CustomAlertDialog( - {Key? key, - this.title, - this.titlePadding, - required this.content, - this.actions, - this.contentPadding, - this.contentBoxConstraints = const BoxConstraints(maxWidth: 500), - this.onSubmit, - this.onCancel}) - : super(key: key); - - final Widget? title; - final EdgeInsetsGeometry? titlePadding; - final Widget content; - final List? actions; - final double? contentPadding; - final BoxConstraints contentBoxConstraints; - final Function()? onSubmit; - final Function()? onCancel; - - @override - Widget build(BuildContext context) { - // request focus - FocusScopeNode scopeNode = FocusScopeNode(); - Future.delayed(Duration.zero, () { - if (!scopeNode.hasFocus) scopeNode.requestFocus(); - }); - bool tabTapped = false; - if (isAndroid) gFFI.invokeMethod("enable_soft_keyboard", true); - - return FocusScope( - node: scopeNode, - autofocus: true, - onKey: (node, key) { - if (key.logicalKey == LogicalKeyboardKey.escape) { - if (key is RawKeyDownEvent) { - onCancel?.call(); - } - return KeyEventResult.handled; // avoid TextField exception on escape - } else if (!tabTapped && - onSubmit != null && - (key.logicalKey == LogicalKeyboardKey.enter || - key.logicalKey == LogicalKeyboardKey.numpadEnter)) { - if (key is RawKeyDownEvent) onSubmit?.call(); - return KeyEventResult.handled; - } else if (key.logicalKey == LogicalKeyboardKey.tab) { - if (key is RawKeyDownEvent) { - scopeNode.nextFocus(); - tabTapped = true; - } - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: AlertDialog( - scrollable: true, - title: title, - content: ConstrainedBox( - constraints: contentBoxConstraints, - child: content, - ), - actions: actions, - titlePadding: titlePadding ?? MyTheme.dialogTitlePadding(), - contentPadding: - MyTheme.dialogContentPadding(actions: actions is List), - actionsPadding: MyTheme.dialogActionsPadding(), - buttonPadding: MyTheme.dialogButtonPadding), - ); - } -} - -Widget createDialogContent(String text) { - final RegExp linkRegExp = RegExp(r'(https?://[^\s]+)'); - bool hasLink = linkRegExp.hasMatch(text); - - // Early return: no link, use default theme color - if (!hasLink) { - return SelectableText(text, style: const TextStyle(fontSize: 15)); - } - - final List spans = []; - int start = 0; - - linkRegExp.allMatches(text).forEach((match) { - if (match.start > start) { - spans.add(TextSpan(text: text.substring(start, match.start))); - } - spans.add(TextSpan( - text: match.group(0) ?? '', - style: const TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - String linkText = match.group(0) ?? ''; - linkText = linkText.replaceAll(RegExp(r'[.,;!?]+$'), ''); - launchUrl(Uri.parse(linkText)); - }, - )); - start = match.end; - }); - - if (start < text.length) { - spans.add(TextSpan(text: text.substring(start))); - } - - return SelectableText.rich( - TextSpan( - style: const TextStyle(fontSize: 15), - children: spans, - ), - ); -} - -void msgBox(SessionID sessionId, String type, String title, String text, - String link, OverlayDialogManager dialogManager, - {bool? hasCancel, - ReconnectHandle? reconnect, - int? reconnectTimeout, - VoidCallback? onSubmit, - int? submitTimeout}) { - dialogManager.dismissAll(); - List buttons = []; - bool hasOk = false; - submit() { - dialogManager.dismissAll(); - if (onSubmit != null) { - onSubmit.call(); - } else { - // https://github.com/rustdesk/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 - if (!type.contains("custom") && desktopType != DesktopType.portForward) { - closeConnection(); - } - } - } - - cancel() { - dialogManager.dismissAll(); - } - - jumplink() { - if (link.startsWith('http')) { - launchUrl(Uri.parse(link)); - } - } - - if (type != "connecting" && type != "success" && !type.contains("nook")) { - hasOk = true; - late final Widget btn; - if (submitTimeout != null) { - btn = _CountDownButton( - text: 'OK', - second: submitTimeout, - onPressed: submit, - submitOnTimeout: true, - ); - } else { - btn = dialogButton('OK', onPressed: submit); - } - buttons.insert(0, btn); - } - hasCancel ??= !type.contains("error") && - !type.contains("nocancel") && - type != "restarting"; - if (hasCancel) { - buttons.insert( - 0, dialogButton('Cancel', onPressed: cancel, isOutline: true)); - } - if (type.contains("hasclose")) { - buttons.insert( - 0, - dialogButton('Close', onPressed: () { - dialogManager.dismissAll(); - })); - } - if (reconnect != null && - title == "Connection Error" && - reconnectTimeout != null) { - // `enabled` is used to disable the dialog button once the button is clicked. - final enabled = true.obs; - final button = Obx(() => _CountDownButton( - text: 'Reconnect', - second: reconnectTimeout, - onPressed: enabled.isTrue - ? () { - // Disable the button - enabled.value = false; - reconnect(dialogManager, sessionId, false); - } - : null, - )); - buttons.insert(0, button); - } - if (link.isNotEmpty) { - buttons.insert(0, dialogButton('JumpLink', onPressed: jumplink)); - } - dialogManager.show( - (setState, close, context) => CustomAlertDialog( - title: null, - content: SelectionArea(child: msgboxContent(type, title, text)), - actions: buttons, - onSubmit: hasOk ? submit : null, - onCancel: hasCancel == true ? cancel : null, - ), - tag: '$sessionId-$type-$title-$text-$link', - ); -} - -Color? _msgboxColor(String type) { - if (type == "input-password" || type == "custom-os-password") { - return Color(0xFFAD448E); - } - if (type.contains("success")) { - return Color(0xFF32bea6); - } - if (type.contains("error") || type == "re-input-password") { - return Color(0xFFE04F5F); - } - return Color(0xFF2C8CFF); -} - -Widget msgboxIcon(String type) { - IconData? iconData; - if (type.contains("error") || type == "re-input-password") { - iconData = Icons.cancel; - } - if (type.contains("success")) { - iconData = Icons.check_circle; - } - if (type == "wait-uac" || type == "wait-remote-accept-nook") { - iconData = Icons.hourglass_top; - } - if (type == 'on-uac' || type == 'on-foreground-elevated') { - iconData = Icons.admin_panel_settings; - } - if (type.contains('info')) { - iconData = Icons.info; - } - if (iconData != null) { - return Icon(iconData, size: 50, color: _msgboxColor(type)) - .marginOnly(right: 16); - } - - return Offstage(); -} - -// title should be null -Widget msgboxContent(String type, String title, String text) { - String translateText(String text) { - if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) { - List words = text.split(': '); - for (var i = 0; i < words.length; ++i) { - words[i] = translate(words[i]); - } - text = words.join(': '); - } else { - List words = text.split(' '); - if (words.length > 1 && words[0].endsWith('_tip')) { - words[0] = translate(words[0]); - final rest = text.substring(words[0].length + 1); - text = '${words[0]} ${translate(rest)}'; - } else { - text = translate(text); - } - } - return text; - } - - return Row( - children: [ - msgboxIcon(type), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - translate(title), - style: TextStyle(fontSize: 21), - ).marginOnly(bottom: 10), - createDialogContent(translateText(text)), - ], - ), - ), - ], - ).marginOnly(bottom: 12); -} - -void msgBoxCommon(OverlayDialogManager dialogManager, String title, - Widget content, List buttons, - {bool hasCancel = true}) { - dialogManager.show((setState, close, context) => CustomAlertDialog( - title: Text( - translate(title), - style: TextStyle(fontSize: 21), - ), - content: content, - actions: buttons, - onCancel: hasCancel ? close : null, - )); -} - -Color str2color(String str, [alpha = 0xFF]) { - var hash = 160 << 16 + 114 << 8 + 91; - for (var i = 0; i < str.length; i += 1) { - hash = str.codeUnitAt(i) + ((hash << 5) - hash); - } - hash = hash % 16777216; - return Color((hash & 0xFF7FFF) | (alpha << 24)); -} - -Color str2color2(String str, {List existing = const []}) { - Map colorMap = { - "red": Colors.red, - "green": Colors.green, - "blue": Colors.blue, - "orange": Colors.orange, - "purple": Colors.purple, - "grey": Colors.grey, - "cyan": Colors.cyan, - "lime": Colors.lime, - "teal": Colors.teal, - "pink": Colors.pink[200]!, - "indigo": Colors.indigo, - "brown": Colors.brown, - }; - final color = colorMap[str.toLowerCase()]; - if (color != null) { - return color.withAlpha(0xFF); - } - if (str.toLowerCase() == 'yellow') { - return Colors.yellow.withAlpha(0xFF); - } - var hash = 0; - for (var i = 0; i < str.length; i++) { - hash += str.codeUnitAt(i); - } - List colorList = colorMap.values.toList(); - hash = hash % colorList.length; - var result = colorList[hash].withAlpha(0xFF); - if (existing.contains(result.value)) { - Color? notUsed = - colorList.firstWhereOrNull((e) => !existing.contains(e.value)); - if (notUsed != null) { - result = notUsed; - } - } - return result; -} - -const K = 1024; -const M = K * K; -const G = M * K; - -String readableFileSize(double size) { - if (size < K) { - return "${size.toStringAsFixed(2)} B"; - } else if (size < M) { - return "${(size / K).toStringAsFixed(2)} KB"; - } else if (size < G) { - return "${(size / M).toStringAsFixed(2)} MB"; - } else { - return "${(size / G).toStringAsFixed(2)} GB"; - } -} - -/// Flutter can't not catch PointerMoveEvent when size is 1 -/// This will happen in Android AccessibilityService Input -/// android can't init dispatching size yet ,see: https://stackoverflow.com/questions/59960451/android-accessibility-dispatchgesture-is-it-possible-to-specify-pressure-for-a -/// use this temporary solution until flutter or android fixes the bug -class AccessibilityListener extends StatelessWidget { - final Widget? child; - static final offset = 100; - - AccessibilityListener({this.child}); - - @override - Widget build(BuildContext context) { - return Listener( - onPointerDown: (evt) { - if (evt.size == 1) { - GestureBinding.instance.handlePointerEvent(PointerAddedEvent( - pointer: evt.pointer + offset, position: evt.position)); - GestureBinding.instance.handlePointerEvent(PointerDownEvent( - pointer: evt.pointer + offset, - size: 0.1, - position: evt.position)); - } - }, - onPointerUp: (evt) { - if (evt.size == 1) { - GestureBinding.instance.handlePointerEvent(PointerUpEvent( - pointer: evt.pointer + offset, - size: 0.1, - position: evt.position)); - GestureBinding.instance.handlePointerEvent(PointerRemovedEvent( - pointer: evt.pointer + offset, position: evt.position)); - } - }, - onPointerMove: (evt) { - if (evt.size == 1) { - GestureBinding.instance.handlePointerEvent(PointerMoveEvent( - pointer: evt.pointer + offset, - size: 0.1, - delta: evt.delta, - position: evt.position)); - } - }, - child: child); - } -} - -class AndroidPermissionManager { - static Completer? _completer; - static Timer? _timer; - static var _current = ""; - - static bool isWaitingFile() { - if (_completer != null) { - return !_completer!.isCompleted && _current == kManageExternalStorage; - } - return false; - } - - static Future check(String type) { - if (isDesktop || isWeb) { - return Future.value(true); - } - 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 || isWeb) { - return Future.value(true); - } - - gFFI.invokeMethod("request_permission", type); - - // clear last task - if (_completer?.isCompleted == false) { - _completer?.complete(false); - } - _timer?.cancel(); - - _current = type; - _completer = Completer(); - - _timer = Timer(Duration(seconds: 120), () { - if (_completer == null) return; - if (!_completer!.isCompleted) { - _completer!.complete(false); - } - _completer = null; - _current = ""; - }); - return _completer!.future; - } - - static complete(String type, bool res) { - if (type != _current) { - res = false; - } - _timer?.cancel(); - _completer?.complete(res); - _current = ""; - } -} - -RadioListTile getRadio( - Widget title, T toValue, T curValue, ValueChanged? onChange, - {bool? dense}) { - return RadioListTile( - visualDensity: VisualDensity.compact, - controlAffinity: ListTileControlAffinity.trailing, - title: title, - value: toValue, - groupValue: curValue, - onChanged: onChange, - dense: dense, - ); -} - -/// find ffi, tag is Remote ID -/// for session specific usage -FFI ffi(String? tag) { - return Get.find(tag: tag); -} - -/// Global FFI object -late FFI _globalFFI; - -FFI get gFFI => _globalFFI; - -Future initGlobalFFI() async { - debugPrint("_globalFFI init"); - _globalFFI = FFI(null); - debugPrint("_globalFFI init end"); - // after `put`, can also be globally found by Get.find(); - Get.put(_globalFFI, permanent: true); -} - -String translate(String name) { - if (name.startsWith('Failed to') && name.contains(': ')) { - return name.split(': ').map((x) => translate(x)).join(': '); - } - return platformFFI.translate(name, localeName); -} - -// This function must be kept the same as the one in rust and sciter code. -// rust: libs/hbb_common/src/config.rs -> option2bool() -// sciter: Does not have the function, but it should be kept the same. -bool option2bool(String option, String value) { - bool res; - if (option.startsWith("enable-")) { - res = value != "N"; - } else if (option.startsWith("allow-") || - option == kOptionStopService || - option == kOptionDirectServer || - option == kOptionForceAlwaysRelay) { - res = value == "Y"; - } else { - // "" is true - res = value != "N"; - } - return res; -} - -String bool2option(String option, bool b) { - String res; - if (option.startsWith('enable-') && - option != kOptionEnableUdpPunch && - option != kOptionEnableIpv6Punch) { - res = b ? defaultOptionYes : 'N'; - } else if (option.startsWith('allow-') || - option == kOptionStopService || - option == kOptionDirectServer || - option == kOptionForceAlwaysRelay) { - res = b ? 'Y' : defaultOptionNo; - } else { - res = b ? 'Y' : 'N'; - } - return res; -} - -mainSetBoolOption(String key, bool value) async { - String v = bool2option(key, value); - await bind.mainSetOption(key: key, value: v); -} - -Future mainGetBoolOption(String key) async { - return option2bool(key, await bind.mainGetOption(key: key)); -} - -bool mainGetBoolOptionSync(String key) { - return option2bool(key, bind.mainGetOptionSync(key: key)); -} - -mainSetLocalBoolOption(String key, bool value) async { - String v = bool2option(key, value); - await bind.mainSetLocalOption(key: key, value: v); -} - -bool mainGetLocalBoolOptionSync(String key) { - return option2bool(key, bind.mainGetLocalOption(key: key)); -} - -bool mainGetPeerBoolOptionSync(String id, String key) { - return option2bool(key, bind.mainGetPeerOptionSync(id: id, key: key)); -} - -// Don't use `option2bool()` and `bool2option()` to convert the session option. -// Use `sessionGetToggleOption()` and `sessionToggleOption()` instead. -// Because all session options use `Y` and `` as values. - -Future matchPeer( - String searchText, Peer peer, PeerTabIndex peerTabIndex) async { - if (searchText.isEmpty) { - return true; - } - if (peer.id.toLowerCase().contains(searchText)) { - return true; - } - if (peer.hostname.toLowerCase().contains(searchText) || - peer.username.toLowerCase().contains(searchText)) { - return true; - } - if (peer.alias.toLowerCase().contains(searchText)) { - return true; - } - if (peerTabShowNote(peerTabIndex) && - peer.note.toLowerCase().contains(searchText)) { - return true; - } - return false; -} - -/// Get the image for the current [platform]. -Widget getPlatformImage(String platform, {double size = 50}) { - if (platform.isEmpty) { - return Container(width: size, height: size); - } - if (platform == kPeerPlatformMacOS) { - platform = 'mac'; - } else if (platform != kPeerPlatformLinux && - platform != kPeerPlatformAndroid) { - platform = 'win'; - } else { - platform = platform.toLowerCase(); - } - return SvgPicture.asset('assets/$platform.svg', height: size, width: size); -} - -class LastWindowPosition { - double? width; - double? height; - double? offsetWidth; - double? offsetHeight; - bool? isMaximized; - bool? isFullscreen; - - LastWindowPosition(this.width, this.height, this.offsetWidth, - this.offsetHeight, this.isMaximized, this.isFullscreen); - - bool equals(LastWindowPosition other) { - return ((width == other.width) && - (height == other.height) && - (offsetWidth == other.offsetWidth) && - (offsetHeight == other.offsetHeight) && - (isMaximized == other.isMaximized) && - (isFullscreen == other.isFullscreen)); - } - - Map toJson() { - return { - "width": width, - "height": height, - "offsetWidth": offsetWidth, - "offsetHeight": offsetHeight, - "isMaximized": isMaximized, - "isFullscreen": isFullscreen, - }; - } - - @override - String toString() { - return jsonEncode(toJson()); - } - - static LastWindowPosition? loadFromString(String content) { - if (content.isEmpty) { - return null; - } - try { - final m = jsonDecode(content); - return LastWindowPosition(m["width"], m["height"], m["offsetWidth"], - m["offsetHeight"], m["isMaximized"], m["isFullscreen"]); - } catch (e) { - debugPrintStack( - label: - 'Failed to load LastWindowPosition "$content" ${e.toString()}'); - return null; - } - } -} - -String get windowFramePrefix => - kWindowPrefix + - (bind.isIncomingOnly() - ? "incoming_" - : (bind.isOutgoingOnly() ? "outgoing_" : "")); - -typedef WindowKey = ({WindowType type, int? windowId}); - -LastWindowPosition? _lastWindowPosition = null; -final Debouncer _saveWindowDebounce = Debouncer(delay: Duration(seconds: 1)); - -/// Save window position and size on exit -/// Note that windowId must be provided if it's subwindow -Future saveWindowPosition(WindowType type, - {int? windowId, bool? flush}) async { - if (type != WindowType.Main && windowId == null) { - debugPrint( - "Error: windowId cannot be null when saving positions for sub window"); - } - - Offset? position; - Size? sz; - late bool isMaximized; - bool isFullscreen = stateGlobal.fullscreen.isTrue; - - setPreFrame() { - final pos = bind.getLocalFlutterOption(k: windowFramePrefix + type.name); - var lpos = LastWindowPosition.loadFromString(pos); - if (lpos != null) { - if (lpos.offsetWidth != null && lpos.offsetHeight != null) { - position = Offset(lpos.offsetWidth!, lpos.offsetHeight!); - } - if (lpos.width != null && lpos.height != null) { - sz = Size(lpos.width!, lpos.height!); - } - } - } - - switch (type) { - case WindowType.Main: - // Checking `bind.isIncomingOnly()` is a simple workaround for MacOS. - // `await windowManager.isMaximized()` will always return true - // if is not resizable. The reason is unknown. - // - // `setResizable(!bind.isIncomingOnly());` in main.dart - isMaximized = - bind.isIncomingOnly() ? false : await windowManager.isMaximized(); - if (isFullscreen || isMaximized) { - setPreFrame(); - } else { - position = await windowManager.getPosition( - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - sz = await windowManager.getSize( - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - } - break; - default: - final wc = WindowController.fromWindowId(windowId!); - isMaximized = await wc.isMaximized(); - if (isFullscreen || isMaximized) { - setPreFrame(); - } else { - final Rect frame; - try { - frame = await wc.getFrame(); - } catch (e) { - debugPrint( - "Failed to get frame of window $windowId, it may be hidden"); - return; - } - position = frame.topLeft; - sz = frame.size; - } - break; - } - if (isWindows && position != null) { - const kMinOffset = -10000; - const kMaxOffset = 10000; - if (position!.dx < kMinOffset || - position!.dy < kMinOffset || - position!.dx > kMaxOffset || - position!.dy > kMaxOffset) { - debugPrint("Invalid position: $position, ignore saving position"); - return; - } - } - - final pos = LastWindowPosition(sz?.width, sz?.height, position?.dx, - position?.dy, isMaximized, isFullscreen); - - final WindowKey key = (type: type, windowId: windowId); - - final bool haveNewWindowPosition = - (_lastWindowPosition == null) || !pos.equals(_lastWindowPosition!); - final bool isPreviousNewWindowPositionPending = _saveWindowDebounce.isRunning; - - if (haveNewWindowPosition || isPreviousNewWindowPositionPending) { - _lastWindowPosition = pos; - - if (flush ?? false) { - // If a previous update is pending, replace it. - _saveWindowDebounce.cancel(); - await _saveWindowPositionActual(key); - } else if (haveNewWindowPosition) { - _saveWindowDebounce.call(() => _saveWindowPositionActual(key)); - } - } -} - -Future _saveWindowPositionActual(WindowKey key) async { - LastWindowPosition? pos = _lastWindowPosition; - - if (pos != null) { - debugPrint( - "Saving frame: ${key.windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}, isMaximized:${pos.isMaximized}, isFullscreen:${pos.isFullscreen}"); - - await bind.setLocalFlutterOption( - k: windowFramePrefix + key.type.name, v: pos.toString()); - - if ((key.type == WindowType.RemoteDesktop || - key.type == WindowType.ViewCamera) && - key.windowId != null) { - await _saveSessionWindowPosition(key.type, key.windowId!, - pos.isMaximized ?? false, pos.isFullscreen ?? false, pos); - } - } -} - -Future _saveSessionWindowPosition(WindowType windowType, int windowId, - bool isMaximized, bool isFullscreen, LastWindowPosition pos) async { - final remoteList = await DesktopMultiWindow.invokeMethod( - windowId, kWindowEventGetRemoteList, null); - getPeerPos(String peerId) { - if (isMaximized || isFullscreen) { - final peerPos = bind.mainGetPeerFlutterOptionSync( - id: peerId, k: windowFramePrefix + windowType.name); - var lpos = LastWindowPosition.loadFromString(peerPos); - return LastWindowPosition( - lpos?.width ?? pos.offsetWidth, - lpos?.height ?? pos.offsetHeight, - lpos?.offsetWidth ?? pos.offsetWidth, - lpos?.offsetHeight ?? pos.offsetHeight, - isMaximized, - isFullscreen) - .toString(); - } else { - return pos.toString(); - } - } - - if (remoteList != null) { - for (final peerId in remoteList.split(',')) { - bind.mainSetPeerFlutterOptionSync( - id: peerId, - k: windowFramePrefix + windowType.name, - v: getPeerPos(peerId)); - } - } -} - -Future _adjustRestoreMainWindowSize(double? width, double? height) async { - const double minWidth = 1; - const double minHeight = 1; - const double maxWidth = 6480; - const double maxHeight = 6480; - - final defaultWidth = - ((isDesktop || isWebDesktop) ? 1280 : kMobileDefaultDisplayWidth) - .toDouble(); - final defaultHeight = - ((isDesktop || isWebDesktop) ? 720 : kMobileDefaultDisplayHeight) - .toDouble(); - double restoreWidth = width ?? defaultWidth; - double restoreHeight = height ?? defaultHeight; - - if (restoreWidth < minWidth) { - restoreWidth = defaultWidth; - } - if (restoreHeight < minHeight) { - restoreHeight = defaultHeight; - } - if (restoreWidth > maxWidth) { - restoreWidth = defaultWidth; - } - if (restoreHeight > maxHeight) { - restoreHeight = defaultHeight; - } - return Size(restoreWidth, restoreHeight); -} - -// Consider using Rect.contains() instead, -// though the implementation is not exactly the same. -bool isPointInRect(Offset point, Rect rect) { - return point.dx >= rect.left && - point.dx <= rect.right && - point.dy >= rect.top && - point.dy <= rect.bottom; -} - -/// return null means center -Future _adjustRestoreMainWindowOffset( - double? left, - double? top, - double? width, - double? height, -) async { - if (left == null || top == null || width == null || height == null) { - return null; - } - - if (isDesktop || isWebDesktop) { - final screens = await window_size.getScreenList(); - if (screens.isNotEmpty) { - final windowRect = Rect.fromLTWH(left, top, width, height); - bool isVisible = false; - for (final screen in screens) { - final intersection = windowRect.intersect(screen.visibleFrame); - if (intersection.width >= 10.0 && intersection.height >= 10.0) { - isVisible = true; - break; - } - } - if (!isVisible) { - return null; - } - return Offset(left, top); - } - } - - double frameLeft = 0.0; - double frameTop = 0.0; - double frameRight = ((isDesktop || isWebDesktop) - ? kDesktopMaxDisplaySize - : kMobileMaxDisplaySize) - .toDouble(); - double frameBottom = ((isDesktop || isWebDesktop) - ? kDesktopMaxDisplaySize - : kMobileMaxDisplaySize) - .toDouble(); - - final minWidth = 10.0; - if ((left + minWidth) > frameRight || - (top + minWidth) > frameBottom || - (left + width - minWidth) < frameLeft || - top < frameTop) { - return null; - } else { - return Offset(left, top); - } -} - -/// Restore window position and size on start -/// Note that windowId must be provided if it's subwindow -// -// display is used to set the offset of the window in individual display mode. -Future restoreWindowPosition(WindowType type, - {int? windowId, String? peerId, int? display}) async { - if (bind - .mainGetEnv(key: "DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION") - .isNotEmpty) { - return false; - } - if (type != WindowType.Main && windowId == null) { - debugPrint( - "Error: windowId cannot be null when saving positions for sub window"); - return false; - } - - bool isRemotePeerPos = false; - String? pos; - // No need to check mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs) - // Though "open in tabs" is true and the new window restore peer position, it's ok. - if ((type == WindowType.RemoteDesktop || type == WindowType.ViewCamera) && - windowId != null && - peerId != null) { - final peerPos = bind.mainGetPeerFlutterOptionSync( - id: peerId, k: windowFramePrefix + type.name); - if (peerPos.isNotEmpty) { - pos = peerPos; - } - isRemotePeerPos = pos != null; - } - pos ??= bind.getLocalFlutterOption(k: windowFramePrefix + type.name); - - var lpos = LastWindowPosition.loadFromString(pos); - if (lpos == null) { - debugPrint("No window position saved, trying to center the window."); - switch (type) { - case WindowType.Main: - // Center the main window only if no position is saved (on first run). - if (isWindows || isLinux) { - await windowManager.center(); - } - // For MacOS, the window is already centered by default. - // See https://github.com/rustdesk/rustdesk/blob/9b9276e7524523d7f667fefcd0694d981443df0e/flutter/macos/Runner/Base.lproj/MainMenu.xib#L333 - // If `` in `` is not set, the window will be centered. - break; - default: - // No need to change the position of a sub window if no position is saved, - // since the default position is already centered. - // https://github.com/rustdesk/rustdesk/blob/317639169359936f7f9f85ef445ec9774218772d/flutter/lib/utils/multi_window_manager.dart#L163 - break; - } - return true; - } - if (type == WindowType.RemoteDesktop || type == WindowType.ViewCamera) { - if (!isRemotePeerPos && windowId != null) { - if (lpos.offsetWidth != null) { - lpos.offsetWidth = lpos.offsetWidth! + windowId * kNewWindowOffset; - } - if (lpos.offsetHeight != null) { - lpos.offsetHeight = lpos.offsetHeight! + windowId * kNewWindowOffset; - } - } - if (display != null) { - if (lpos.offsetWidth != null) { - lpos.offsetWidth = lpos.offsetWidth! + display * kNewWindowOffset; - } - if (lpos.offsetHeight != null) { - lpos.offsetHeight = lpos.offsetHeight! + display * kNewWindowOffset; - } - } - } - - final size = await _adjustRestoreMainWindowSize(lpos.width, lpos.height); - final offsetLeftTop = await _adjustRestoreMainWindowOffset( - lpos.offsetWidth, - lpos.offsetHeight, - size.width, - size.height, - ); - debugPrint( - "restore lpos: ${size.width}/${size.height}, offset:${offsetLeftTop?.dx}/${offsetLeftTop?.dy}, isMaximized: ${lpos.isMaximized}, isFullscreen: ${lpos.isFullscreen}"); - - switch (type) { - case WindowType.Main: - restorePos() async { - if (offsetLeftTop == null) { - await windowManager.center(); - } else { - await windowManager.setPosition(offsetLeftTop, - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - } - } - if (lpos.isMaximized == true) { - await restorePos(); - if (!(bind.isIncomingOnly() || bind.isOutgoingOnly())) { - await windowManager.maximize(); - } - } else { - final storeSize = !bind.isIncomingOnly() || bind.isOutgoingOnly(); - if (isWindows) { - if (storeSize) { - // We need to set the window size first to avoid the incorrect size in some special cases. - // E.g. There are two monitors, the left one is 100% DPI and the right one is 175% DPI. - // The window belongs to the left monitor, but if it is moved a little to the right, it will belong to the right monitor. - // After restoring, the size will be incorrect. - // See known issue in https://github.com/rustdesk/rustdesk/pull/9840 - await windowManager.setSize(size, - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - } - await restorePos(); - if (storeSize) { - await windowManager.setSize(size, - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - } - } else { - if (storeSize) { - await windowManager.setSize(size, - ignoreDevicePixelRatio: _ignoreDevicePixelRatio); - } - await restorePos(); - } - } - return true; - default: - final wc = WindowController.fromWindowId(windowId!); - restoreFrame() async { - if (offsetLeftTop == null) { - await wc.center(); - } else { - final frame = Rect.fromLTWH( - offsetLeftTop.dx, offsetLeftTop.dy, size.width, size.height); - await wc.setFrame(frame); - } - } - if (lpos.isFullscreen == true) { - if (!isMacOS) { - await restoreFrame(); - } - // An duration is needed to avoid the window being restored after fullscreen. - Future.delayed(Duration(milliseconds: 300), () async { - if (kWindowId == windowId) { - stateGlobal.setFullscreen(true); - } else { - // If is not current window, we need to send a fullscreen message to `windowId` - DesktopMultiWindow.invokeMethod( - windowId, kWindowEventSetFullscreen, 'true'); - } - }); - } else if (lpos.isMaximized == true) { - await restoreFrame(); - // An duration is needed to avoid the window being restored after maximized. - Future.delayed(Duration(milliseconds: 300), () async { - await wc.maximize(); - }); - } else { - await restoreFrame(); - } - break; - } - return false; -} - -var webInitialLink = ""; - -/// Initialize uni links for macos/windows -/// -/// [Availability] -/// initUniLinks should only be used on macos/windows. -/// we use dbus for linux currently. -Future initUniLinks() async { - if (isLinux) { - return false; - } - // check cold boot - try { - final initialLink = await getInitialLink(); - print("initialLink: $initialLink"); - if (initialLink == null || initialLink.isEmpty) { - return false; - } - if (isWeb) { - webInitialLink = initialLink; - return false; - } else { - return handleUriLink(uriString: initialLink); - } - } catch (err) { - debugPrintStack(label: "$err"); - return false; - } -} - -/// Listen for uni links. -/// -/// * handleByFlutter: Should uni links be handled by Flutter. -/// -/// Returns a [StreamSubscription] which can listen the uni links. -StreamSubscription? listenUniLinks({handleByFlutter = true}) { - if (isLinux || isWeb) { - return null; - } - - final sub = uriLinkStream.listen((Uri? uri) { - debugPrint("A uri was received: $uri. handleByFlutter $handleByFlutter"); - if (uri != null) { - if (handleByFlutter) { - handleUriLink(uri: uri); - } else { - bind.sendUrlScheme(url: uri.toString()); - } - } else { - print("uni listen error: uri is empty."); - } - }, onError: (err) { - print("uni links error: $err"); - }); - return sub; -} - -enum UriLinkType { - remoteDesktop, - fileTransfer, - viewCamera, - portForward, - rdp, - terminal, -} - -setEnvTerminalAdmin() { - bind.mainSetEnv(key: 'IS_TERMINAL_ADMIN', value: 'Y'); -} - -// uri link handler -bool handleUriLink({List? cmdArgs, Uri? uri, String? uriString}) { - List? args; - if (cmdArgs != null && cmdArgs.isNotEmpty) { - args = cmdArgs; - // rustdesk - if (args[0].startsWith(bind.mainUriPrefixSync())) { - final uri = Uri.tryParse(args[0]); - if (uri != null) { - args = urlLinkToCmdArgs(uri); - } - } - } else if (uri != null) { - args = urlLinkToCmdArgs(uri); - } else if (uriString != null) { - final uri = Uri.tryParse(uriString); - if (uri != null) { - args = urlLinkToCmdArgs(uri); - } - } - if (args == null) { - return false; - } - - if (args.isEmpty) { - windowOnTop(null); - return true; - } - - UriLinkType? type; - String? id; - String? password; - String? switchUuid; - bool? forceRelay; - for (int i = 0; i < args.length; i++) { - switch (args[i]) { - case '--connect': - case '--play': - type = UriLinkType.remoteDesktop; - id = args[i + 1]; - i++; - break; - case '--file-transfer': - type = UriLinkType.fileTransfer; - id = args[i + 1]; - i++; - break; - case '--view-camera': - type = UriLinkType.viewCamera; - id = args[i + 1]; - i++; - break; - case '--port-forward': - type = UriLinkType.portForward; - id = args[i + 1]; - i++; - break; - case '--rdp': - type = UriLinkType.rdp; - id = args[i + 1]; - i++; - break; - case '--terminal': - type = UriLinkType.terminal; - id = args[i + 1]; - i++; - break; - case '--terminal-admin': - setEnvTerminalAdmin(); - type = UriLinkType.terminal; - id = args[i + 1]; - i++; - break; - case '--password': - password = args[i + 1]; - i++; - break; - case '--switch_uuid': - switchUuid = args[i + 1]; - i++; - break; - case '--relay': - forceRelay = true; - break; - default: - break; - } - } - if (type != null && id != null) { - switch (type) { - case UriLinkType.remoteDesktop: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newRemoteDesktop(id!, - password: password, - switchUuid: switchUuid, - forceRelay: forceRelay); - }); - break; - case UriLinkType.fileTransfer: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newFileTransfer(id!, - password: password, forceRelay: forceRelay); - }); - break; - case UriLinkType.viewCamera: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newViewCamera(id!, - password: password, forceRelay: forceRelay); - }); - break; - case UriLinkType.portForward: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newPortForward(id!, false, - password: password, forceRelay: forceRelay); - }); - break; - case UriLinkType.rdp: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newPortForward(id!, true, - password: password, forceRelay: forceRelay); - }); - break; - case UriLinkType.terminal: - Future.delayed(Duration.zero, () { - rustDeskWinManager.newTerminal(id!, - password: password, forceRelay: forceRelay); - }); - break; - } - - return true; - } - - return false; -} - -List? urlLinkToCmdArgs(Uri uri) { - String? command; - String? id; - final options = [ - "connect", - "play", - "file-transfer", - "view-camera", - "port-forward", - "rdp", - "terminal", - "terminal-admin", - ]; - if (uri.authority.isEmpty && - uri.path.split('').every((char) => char == '/')) { - return []; - } else if (uri.authority == "connection" && uri.path.startsWith("/new/")) { - // For compatibility - command = '--connect'; - id = uri.path.substring("/new/".length); - } else if (uri.authority == "config") { - if (isAndroid || isIOS) { - final allowDeepLinkServerSettings = - bind.mainGetBuildinOption(key: kOptionAllowDeepLinkServerSettings) == - 'Y'; - if (!allowDeepLinkServerSettings) { - debugPrint( - "Ignore rustdesk://config because $kOptionAllowDeepLinkServerSettings is not enabled."); - // Keep the user-facing error generic; detailed rejection reason is in debug logs. - // Delay toast to avoid missing overlay during cold-start deeplink handling. - Timer(Duration(seconds: 1), () { - showToast(translate('Failed')); - }); - return null; - } - final config = uri.path.substring("/".length); - // add a timer to make showToast work - Timer(Duration(seconds: 1), () { - importConfig(null, null, config); - }); - } - return null; - } else if (uri.authority == "password") { - if (isAndroid || isIOS) { - final allowDeepLinkPassword = - bind.mainGetBuildinOption(key: kOptionAllowDeepLinkPassword) == 'Y'; - if (!allowDeepLinkPassword) { - debugPrint( - "Ignore rustdesk://password because $kOptionAllowDeepLinkPassword is not enabled."); - // Keep the user-facing error generic; detailed rejection reason is in debug logs. - // Delay toast to avoid missing overlay during cold-start deeplink handling. - Timer(Duration(seconds: 1), () { - showToast(translate('Failed')); - }); - return null; - } - final password = uri.path.substring("/".length); - if (password.isNotEmpty) { - Timer(Duration(seconds: 1), () async { - final ok = - await bind.mainSetPermanentPasswordWithResult(password: password); - showToast(translate(ok ? 'Successful' : 'Failed')); - }); - } - } - } else if (options.contains(uri.authority)) { - command = '--${uri.authority}'; - if (uri.path.length > 1) { - id = uri.path.substring(1); - } - } else if (uri.authority.length > 2 && - (uri.path.length <= 1 || - (uri.path == '/r' || uri.path.startsWith('/r@')))) { - // rustdesk:// - // rustdesk:///r - // rustdesk:///r@ - command = '--connect'; - id = uri.authority; - if (uri.path.length > 1) { - id = id + uri.path; - } - } - - var queryParameters = - uri.queryParameters.map((k, v) => MapEntry(k.toLowerCase(), v)); - - var key = queryParameters["key"]; - if (id != null) { - if (key != null) { - id = "$id?key=$key"; - } - } - - if (isMobile && id != null) { - final forceRelay = queryParameters["relay"] != null; - final password = queryParameters["password"]; - - // Determine connection type based on command - if (command == '--file-transfer') { - connect(Get.context!, id, - isFileTransfer: true, forceRelay: forceRelay, password: password); - } else if (command == '--view-camera') { - connect(Get.context!, id, - isViewCamera: true, forceRelay: forceRelay, password: password); - } else if (command == '--terminal') { - connect(Get.context!, id, - isTerminal: true, forceRelay: forceRelay, password: password); - } else if (command == 'terminal-admin') { - setEnvTerminalAdmin(); - connect(Get.context!, id, - isTerminal: true, forceRelay: forceRelay, password: password); - } else { - // Default to remote desktop for '--connect', '--play', or direct connection - connect(Get.context!, id, forceRelay: forceRelay, password: password); - } - return null; - } - - List args = List.empty(growable: true); - if (command != null && id != null) { - args.add(command); - args.add(id); - var param = queryParameters; - String? password = param["password"]; - if (password != null) args.addAll(['--password', password]); - String? switch_uuid = param["switch_uuid"]; - if (switch_uuid != null) args.addAll(['--switch_uuid', switch_uuid]); - if (param["relay"] != null) args.add("--relay"); - return args; - } - - return null; -} - -connectMainDesktop(String id, - {required bool isFileTransfer, - required bool isViewCamera, - required bool isTerminal, - required bool isTcpTunneling, - required bool isRDP, - bool? forceRelay, - String? password, - String? connToken, - bool? isSharedPassword}) async { - if (isFileTransfer) { - await rustDeskWinManager.newFileTransfer(id, - password: password, - isSharedPassword: isSharedPassword, - connToken: connToken, - forceRelay: forceRelay); - } else if (isViewCamera) { - await rustDeskWinManager.newViewCamera(id, - password: password, - isSharedPassword: isSharedPassword, - connToken: connToken, - forceRelay: forceRelay); - } else if (isTcpTunneling || isRDP) { - await rustDeskWinManager.newPortForward(id, isRDP, - password: password, - isSharedPassword: isSharedPassword, - connToken: connToken, - forceRelay: forceRelay); - } else if (isTerminal) { - await rustDeskWinManager.newTerminal(id, - password: password, - isSharedPassword: isSharedPassword, - connToken: connToken, - forceRelay: forceRelay); - } else { - await rustDeskWinManager.newRemoteDesktop(id, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay); - } -} - -/// Connect to a peer with [id]. -/// If [isFileTransfer], starts a session only for file transfer. -/// If [isViewCamera], starts a session only for view camera. -/// If [isTcpTunneling], starts a session only for tcp tunneling. -/// If [isRDP], starts a session only for rdp. -connect(BuildContext context, String id, - {bool isFileTransfer = false, - bool isViewCamera = false, - bool isTerminal = false, - bool isTcpTunneling = false, - bool isRDP = false, - bool forceRelay = false, - String? password, - String? connToken, - bool? isSharedPassword}) async { - if (id == '') return; - if (!isDesktop || desktopType == DesktopType.main) { - try { - if (Get.isRegistered()) { - final idController = Get.find(); - idController.text = formatID(id); - } - if (Get.isRegistered()) { - final fieldTextEditingController = Get.find(); - fieldTextEditingController.text = formatID(id); - } - } catch (_) {} - } - id = id.replaceAll(' ', ''); - final oldId = id; - id = await bind.mainHandleRelayId(id: id); - forceRelay = id != oldId || forceRelay; - assert(!(isFileTransfer && isTcpTunneling && isRDP), - "more than one connect type"); - - if (isDesktop) { - if (desktopType == DesktopType.main) { - await connectMainDesktop( - id, - isFileTransfer: isFileTransfer, - isViewCamera: isViewCamera, - isTerminal: isTerminal, - isTcpTunneling: isTcpTunneling, - isRDP: isRDP, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay, - ); - } else { - await rustDeskWinManager.call(WindowType.Main, kWindowConnect, { - 'id': id, - 'isFileTransfer': isFileTransfer, - 'isViewCamera': isViewCamera, - 'isTerminal': isTerminal, - 'isTcpTunneling': isTcpTunneling, - 'isRDP': isRDP, - 'password': password, - 'isSharedPassword': isSharedPassword, - 'forceRelay': forceRelay, - 'connToken': connToken, - }); - } - } else { - if (isFileTransfer) { - if (isAndroid) { - if (!await AndroidPermissionManager.check(kManageExternalStorage)) { - if (!await AndroidPermissionManager.request(kManageExternalStorage)) { - return; - } - } - } - if (isWeb) { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - desktop_file_manager.FileManagerPage( - id: id, - password: password, - isSharedPassword: isSharedPassword), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => FileManagerPage( - id: id, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay), - ), - ); - } - } else if (isViewCamera) { - if (isWeb) { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - desktop_view_camera.ViewCameraPage( - key: ValueKey(id), - id: id, - toolbarState: ToolbarState(), - password: password, - isSharedPassword: isSharedPassword, - ), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => ViewCameraPage( - id: id, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay), - ), - ); - } - } else if (isTerminal) { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => TerminalPage( - id: id, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay, - ), - ), - ); - } else { - if (isWeb) { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => desktop_remote.RemotePage( - key: ValueKey(id), - id: id, - toolbarState: ToolbarState(), - password: password, - isSharedPassword: isSharedPassword, - ), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => RemotePage( - id: id, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay), - ), - ); - } - } - stateGlobal.isInMainPage = false; - } - - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.unfocus(); - } -} - -Map getHttpHeaders() { - return { - 'Authorization': 'Bearer ${bind.mainGetLocalOption(key: 'access_token')}' - }; -} - -// Simple wrapper of built-in types for reference use. -class SimpleWrapper { - T value; - SimpleWrapper(this.value); -} - -/// Wakelock manager with reference counting for desktop. -/// Ensures wakelock is only disabled when all sessions are closed/minimized. -/// -/// Note: Each isolate has its own WakelockPlus instance with independent assertion. -/// As long as one isolate has wakelock enabled, the screen stays awake. -/// This manager handles multiple tabs within the same isolate. -class WakelockManager { - static final Set _enabledKeys = {}; - // Don't use WakelockPlus.enabled, it causes error on Android: - // Unhandled Exception: FormatException: Message corrupted - // - // On Linux, multiple enable() calls create only one inhibit, but each disable() - // only releases if _cookie != null. So we need our own _enabled state to avoid - // calling disable() when not enabled. - // See: https://github.com/fluttercommunity/wakelock_plus/blob/0c74e5bbc6aefac57b6c96bb7ef987705ed559ec/wakelock_plus/lib/src/wakelock_plus_linux_plugin.dart#L48 - static bool _enabled = false; - - static void enable(UniqueKey key, {bool isServer = false}) { - // Check if we should keep awake during outgoing sessions - if (!isServer) { - final keepAwake = - mainGetLocalBoolOptionSync(kOptionKeepAwakeDuringOutgoingSessions); - if (!keepAwake) { - return; // Don't enable wakelock if user disabled keep awake - } - } - if (isDesktop) { - _enabledKeys.add(key); - } - if (!_enabled) { - _enabled = true; - WakelockPlus.enable(); - } - } - - static void disable(UniqueKey key) { - if (isDesktop) { - _enabledKeys.remove(key); - if (_enabledKeys.isNotEmpty) { - return; - } - } - if (_enabled) { - WakelockPlus.disable(); - _enabled = false; - } - } -} - -/// call this to reload current window. -/// -/// [Note] -/// Must have [RefreshWrapper] on the top of widget tree. -void reloadCurrentWindow() { - if (Get.context != null) { - // reload self window - RefreshWrapper.of(Get.context!)?.rebuild(); - } else { - debugPrint( - "reload current window failed, global BuildContext does not exist"); - } -} - -/// call this to reload all windows, including main + all sub windows. -Future reloadAllWindows() async { - reloadCurrentWindow(); - try { - final ids = await DesktopMultiWindow.getAllSubWindowIds(); - for (final id in ids) { - DesktopMultiWindow.invokeMethod(id, kWindowActionRebuild); - } - } on AssertionError { - // ignore - } -} - -/// Indicate the flutter app is running in portable mode. -/// -/// [Note] -/// Portable build is only available on Windows. -bool isRunningInPortableMode() { - if (!isWindows) { - return false; - } - return bool.hasEnvironment(kEnvPortableExecutable); -} - -/// Window status callback -Future onActiveWindowChanged() async { - print( - "[MultiWindowHandler] active window changed: ${rustDeskWinManager.getActiveWindows()}"); - if (rustDeskWinManager.getActiveWindows().isEmpty) { - // close all sub windows - try { - if (isLinux) { - await Future.wait([ - saveWindowPosition(WindowType.Main), - rustDeskWinManager.closeAllSubWindows() - ]); - } else { - await rustDeskWinManager.closeAllSubWindows(); - } - } catch (err) { - debugPrintStack(label: "$err"); - } finally { - debugPrint("Start closing RustDesk..."); - await windowManager.setPreventClose(false); - await windowManager.close(); - if (isMacOS) { - // If we call without delay, `flutter/macos/Runner/MainFlutterWindow.swift` can handle the "terminate" event. - // But the app will not close. - // - // No idea why we need to delay here, `terminate()` itself is also an async function. - // - // A quick workaround, use `Timer.periodic` to avoid the app not closing. - // Because `await windowManager.close()` and `RdPlatformChannel.instance.terminate()` - // may not work since `Flutter 3.24.4`, see the following logs. - // A delay will allow the app to close. - // - //``` - // embedder.cc (2725): 'FlutterPlatformMessageCreateResponseHandle' returned 'kInvalidArguments'. Engine handle was invalid. - // 2024-11-11 11:41:11.546 RustDesk[90272:2567686] Failed to create a FlutterPlatformMessageResponseHandle (2) - // embedder.cc (2672): 'FlutterEngineSendPlatformMessage' returned 'kInvalidArguments'. Invalid engine handle. - // 2024-11-11 11:41:11.565 RustDesk[90272:2567686] Failed to send message to Flutter engine on channel 'flutter/lifecycle' (2). - // ``` - periodic_immediate( - Duration(milliseconds: 30), RdPlatformChannel.instance.terminate); - } - } - } -} - -Timer periodic_immediate(Duration duration, Future Function() callback) { - Future.delayed(Duration.zero, callback); - return Timer.periodic(duration, (timer) async { - await callback(); - }); -} - -/// return a human readable windows version -WindowsTarget getWindowsTarget(int buildNumber) { - if (!isWindows) { - return WindowsTarget.naw; - } - if (buildNumber >= 22000) { - return WindowsTarget.w11; - } else if (buildNumber >= 10240) { - return WindowsTarget.w10; - } else if (buildNumber >= 9600) { - return WindowsTarget.w8_1; - } else if (buildNumber >= 9200) { - return WindowsTarget.w8; - } else if (buildNumber >= 7601) { - return WindowsTarget.w7; - } else if (buildNumber >= 6002) { - return WindowsTarget.vista; - } else { - // minimum support - return WindowsTarget.xp; - } -} - -/// Get windows target build number. -/// -/// [Note] -/// Please use this function wrapped with `Platform.isWindows`. -int getWindowsTargetBuildNumber() { - return getWindowsTargetBuildNumber_(); -} - -/// Indicating we need to use compatible ui mode. -/// -/// [Conditions] -/// - Windows 7, window will overflow when we use frameless ui. -bool get kUseCompatibleUiMode => - isWindows && - const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion); - -bool get isWin10 => windowsBuildNumber.windowsVersion == WindowsTarget.w10; - -class ServerConfig { - late String idServer; - late String relayServer; - late String apiServer; - late String key; - - ServerConfig( - {String? idServer, String? relayServer, String? apiServer, String? key}) { - this.idServer = idServer?.trim() ?? ''; - this.relayServer = relayServer?.trim() ?? ''; - this.apiServer = apiServer?.trim() ?? ''; - this.key = key?.trim() ?? ''; - } - - /// decode from shared string (from user shared or rustdesk-server generated) - /// also see [encode] - /// throw when decoding failure - ServerConfig.decode(String msg) { - var json = {}; - try { - // back compatible - json = jsonDecode(msg); - } catch (err) { - final input = msg.split('').reversed.join(''); - final bytes = base64Decode(base64.normalize(input)); - json = jsonDecode(utf8.decode(bytes, allowMalformed: true)); - } - idServer = json['host'] ?? ''; - relayServer = json['relay'] ?? ''; - apiServer = json['api'] ?? ''; - key = json['key'] ?? ''; - } - - /// encode to shared string - /// also see [ServerConfig.decode] - String encode() { - Map config = {}; - config['host'] = idServer.trim(); - config['relay'] = relayServer.trim(); - config['api'] = apiServer.trim(); - config['key'] = key.trim(); - return base64UrlEncode(Uint8List.fromList(jsonEncode(config).codeUnits)) - .split('') - .reversed - .join(); - } - - /// from local options - ServerConfig.fromOptions(Map options) - : idServer = options['custom-rendezvous-server'] ?? "", - relayServer = options['relay-server'] ?? "", - apiServer = options['api-server'] ?? "", - key = options['key'] ?? ""; -} - -Widget dialogButton(String text, - {required VoidCallback? onPressed, - bool isOutline = false, - Widget? icon, - TextStyle? style, - ButtonStyle? buttonStyle}) { - if (isDesktop || isWebDesktop) { - if (isOutline) { - 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 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, - ), - ); - } -} - -int versionCmp(String v1, String v2) { - return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2); -} - -String getWindowName({WindowType? overrideType}) { - final name = bind.mainGetAppNameSync(); - switch (overrideType ?? kWindowType) { - case WindowType.Main: - return name; - case WindowType.FileTransfer: - return "File Transfer - $name"; - case WindowType.ViewCamera: - return "View Camera - $name"; - case WindowType.PortForward: - return "Port Forward - $name"; - case WindowType.RemoteDesktop: - return "Remote Desktop - $name"; - default: - break; - } - return name; -} - -String getWindowNameWithId(String id, {WindowType? overrideType}) { - return "${DesktopTab.tablabelGetter(id).value} - ${getWindowName(overrideType: overrideType)}"; -} - -Future updateSystemWindowTheme() async { - // Set system window theme for macOS. - final userPreference = MyTheme.getThemeModePreference(); - if (userPreference != ThemeMode.system) { - if (isMacOS) { - await RdPlatformChannel.instance.changeSystemWindowTheme( - userPreference == ThemeMode.light - ? SystemWindowTheme.light - : SystemWindowTheme.dark); - } - } -} - -/// macOS only -/// -/// Note: not found a general solution for rust based AVFoundation bingding. -/// [AVFoundation] crate has compile error. -const kMacOSPermChannel = MethodChannel("org.rustdesk.rustdesk/host"); - -enum PermissionAuthorizeType { - undetermined, - authorized, - denied, // and restricted -} - -Future osxCanRecordAudio() async { - int res = await kMacOSPermChannel.invokeMethod("canRecordAudio"); - print(res); - if (res > 0) { - return PermissionAuthorizeType.authorized; - } else if (res == 0) { - return PermissionAuthorizeType.undetermined; - } else { - return PermissionAuthorizeType.denied; - } -} - -Future osxRequestAudio() async { - return await kMacOSPermChannel.invokeMethod("requestRecordAudio"); -} - -Widget futureBuilder( - {required Future? future, required Widget Function(dynamic data) hasData}) { - return FutureBuilder( - future: future, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return hasData(snapshot.data!); - } else { - if (snapshot.hasError) { - debugPrint(snapshot.error.toString()); - } - return Container(); - } - }); -} - -void onCopyFingerprint(String value) { - if (value.isNotEmpty) { - Clipboard.setData(ClipboardData(text: value)); - showToast('$value\n${translate("Copied")}'); - } else { - showToast(translate("no fingerprints")); - } -} - -Future callMainCheckSuperUserPermission() async { - bool checked = await bind.mainCheckSuperUserPermission(); - if (isMacOS) { - await windowManager.show(); - } - return checked; -} - -Future start_service(bool is_start) async { - bool checked = !bind.mainIsInstalled() || - !isMacOS || - await callMainCheckSuperUserPermission(); - if (checked) { - mainSetBoolOption(kOptionStopService, !is_start); - } -} - -Future canBeBlocked() async { - if (isWeb) { - // Web can only act as a controller, never as a controlled side, - // so it should never be blocked by a remote session. - return false; - } - // First check control permission - final controlPermission = await bind.mainGetCommon( - key: "is-remote-modify-enabled-by-control-permissions"); - if (controlPermission == "true") { - return false; - } else if (controlPermission == "false") { - return true; - } - - // Check local settings - var accessMode = await bind.mainGetOption(key: kOptionAccessMode); - var isCustomAccessMode = accessMode != 'full' && accessMode != 'view'; - var option = option2bool(kOptionAllowRemoteConfigModification, - await bind.mainGetOption(key: kOptionAllowRemoteConfigModification)); - return accessMode == 'view' || (isCustomAccessMode && !option); -} - -// to-do: web not implemented -Future shouldBeBlocked(RxBool block, WhetherUseRemoteBlock? use) async { - if (use != null && !await use()) { - block.value = false; - return; - } - var time0 = DateTime.now().millisecondsSinceEpoch; - await bind.mainCheckMouseTime(); - Timer(const Duration(milliseconds: 120), () async { - var d = time0 - await bind.mainGetMouseTime(); - if (d < 120) { - block.value = true; - } else { - block.value = false; - } - }); -} - -typedef WhetherUseRemoteBlock = Future Function(); -Widget buildRemoteBlock( - {required Widget child, - required RxBool block, - required bool mask, - WhetherUseRemoteBlock? use}) { - return Obx(() => MouseRegion( - onEnter: (_) async { - await shouldBeBlocked(block, use); - }, - onExit: (event) => block.value = false, - child: Stack(children: [ - // scope block tab - preventMouseKeyBuilder(child: child, block: block.value), - // mask block click, cm not block click and still use check_click_time to avoid block local click - if (mask) - Offstage( - offstage: !block.value, - child: Container( - color: Colors.black.withOpacity(0.5), - )), - ]), - )); -} - -Widget preventMouseKeyBuilder({required Widget child, required bool block}) { - return ExcludeFocus( - excluding: block, child: AbsorbPointer(child: child, absorbing: block)); -} - -Widget unreadMessageCountBuilder(RxInt? count, - {double? size, double? fontSize}) { - return Obx(() => Offstage( - offstage: !((count?.value ?? 0) > 0), - child: Container( - width: size ?? 16, - height: size ?? 16, - decoration: BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - child: Center( - child: Text("${count?.value ?? 0}", - maxLines: 1, - style: TextStyle(color: Colors.white, fontSize: fontSize ?? 10)), - ), - ))); -} - -Widget unreadTopRightBuilder(RxInt? count, {Widget? icon}) { - return Stack( - children: [ - icon ?? Icon(Icons.chat), - Positioned( - top: 0, - right: 0, - child: unreadMessageCountBuilder(count, size: 12, fontSize: 8)) - ], - ); -} - -String toCapitalized(String s) { - if (s.isEmpty) { - return s; - } - return s.substring(0, 1).toUpperCase() + s.substring(1); -} - -Widget buildErrorBanner(BuildContext context, - {required RxBool loading, - required RxString err, - required Function? retry, - required Function close}) { - return Obx(() => Offstage( - offstage: !(!loading.value && err.value.isNotEmpty), - child: Center( - child: Container( - color: MyTheme.color(context).errorBannerBg, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - FittedBox( - child: Icon( - Icons.info, - color: Color.fromARGB(255, 249, 81, 81), - ), - ).marginAll(4), - Flexible( - child: Align( - alignment: Alignment.centerLeft, - child: Tooltip( - message: translate(err.value), - child: SelectableText( - translate(err.value), - ), - )).marginSymmetric(vertical: 2), - ), - if (retry != null) - InkWell( - onTap: () { - retry.call(); - }, - child: Text( - translate("Retry"), - style: TextStyle(color: MyTheme.accent), - )).marginSymmetric(horizontal: 5), - FittedBox( - child: InkWell( - onTap: () { - close.call(); - }, - child: Icon(Icons.close).marginSymmetric(horizontal: 5), - ), - ).marginAll(4) - ], - ), - )).marginOnly(bottom: 14), - )); -} - -String getDesktopTabLabel(String peerId, String alias) { - String label = alias.isEmpty ? peerId : alias; - try { - String peer = bind.mainGetPeerSync(id: peerId); - Map config = jsonDecode(peer); - if (config['info']['hostname'] is String) { - String hostname = config['info']['hostname']; - if (hostname.isNotEmpty && - !label.toLowerCase().contains(hostname.toLowerCase())) { - label += "@$hostname"; - } - } - } catch (e) { - debugPrint("Failed to get hostname:$e"); - } - return label; -} - -sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async { - if (pi.currentDisplay == kAllDisplayValue) { - for (int i = 0; i < pi.displays.length; i++) { - await bind.sessionRefresh(sessionId: sessionId, display: i); - } - } else { - await bind.sessionRefresh(sessionId: sessionId, display: pi.currentDisplay); - } -} - -Future> getScreenListWayland() async { - final screenRectList = []; - if (isMainDesktopWindow) { - for (var screen in await window_size.getScreenList()) { - final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor; - double l = screen.frame.left; - double t = screen.frame.top; - double r = screen.frame.right; - double b = screen.frame.bottom; - final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale); - screenRectList.add(rect); - } - } else { - final screenList = await rustDeskWinManager.call( - WindowType.Main, kWindowGetScreenList, ''); - try { - for (var screen in jsonDecode(screenList.result) as List) { - final scale = kIgnoreDpi ? 1.0 : screen['scaleFactor']; - double l = screen['frame']['l']; - double t = screen['frame']['t']; - double r = screen['frame']['r']; - double b = screen['frame']['b']; - final rect = Rect.fromLTRB(l / scale, t / scale, r / scale, b / scale); - screenRectList.add(rect); - } - } catch (e) { - debugPrint('Failed to parse screenList: $e'); - } - } - return screenRectList; -} - -Future> getScreenListNotWayland() async { - final screenRectList = []; - final displays = bind.mainGetDisplays(); - if (displays.isEmpty) { - return screenRectList; - } - try { - for (var display in jsonDecode(displays) as List) { - // to-do: scale factor ? - // final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor; - double l = display['x'].toDouble(); - double t = display['y'].toDouble(); - double r = (display['x'] + display['w']).toDouble(); - double b = (display['y'] + display['h']).toDouble(); - screenRectList.add(Rect.fromLTRB(l, t, r, b)); - } - } catch (e) { - debugPrint('Failed to parse displays: $e'); - } - return screenRectList; -} - -Future> getScreenRectList() async { - return bind.mainCurrentIsWayland() - ? await getScreenListWayland() - : await getScreenListNotWayland(); -} - -openMonitorInTheSameTab(int i, FFI ffi, PeerInfo pi, - {bool updateCursorPos = true}) { - final displays = i == kAllDisplayValue - ? List.generate(pi.displays.length, (index) => index) - : [i]; - // Try clear image model before switching from all displays - // 1. The remote side has multiple displays. - // 2. Do not use texture render. - // 3. Connect to Display 1. - // 4. Switch to multi-displays `kAllDisplayValue` - // 5. Switch to Display 2. - // Then the remote page will display last picture of Display 1 at the beginning. - if (pi.forceTextureRender && i != kAllDisplayValue) { - ffi.imageModel.clearImage(); - } - bind.sessionSwitchDisplay( - isDesktop: isDesktop, - sessionId: ffi.sessionId, - value: Int32List.fromList(displays), - ); - ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, ffi.id, - updateCursorPos: updateCursorPos); -} - -// Open new tab or window to show this monitor. -// For now just open new window. -// -// screenRect is used to move the new window to the specified screen and set fullscreen. -openMonitorInNewTabOrWindow(int i, String peerId, PeerInfo pi, - {Rect? screenRect}) { - final args = { - 'window_id': stateGlobal.windowId, - 'peer_id': peerId, - 'display': i, - 'display_count': pi.displays.length, - 'window_type': (kWindowType ?? WindowType.RemoteDesktop).index, - }; - if (screenRect != null) { - args['screen_rect'] = { - 'l': screenRect.left, - 't': screenRect.top, - 'r': screenRect.right, - 'b': screenRect.bottom, - }; - } - DesktopMultiWindow.invokeMethod( - kMainWindowId, kWindowEventOpenMonitorSession, jsonEncode(args)); -} - -setNewConnectWindowFrame(int windowId, String peerId, int preSessionCount, - WindowType windowType, int? display, Rect? screenRect) async { - if (screenRect == null) { - // Do not restore window position to new connection if there's a pre-session. - // https://github.com/rustdesk/rustdesk/discussions/8825 - if (preSessionCount == 0) { - await restoreWindowPosition(windowType, - windowId: windowId, display: display, peerId: peerId); - } - } else { - await tryMoveToScreenAndSetFullscreen(screenRect); - } -} - -tryMoveToScreenAndSetFullscreen(Rect? screenRect) async { - if (screenRect == null) { - return; - } - final wc = WindowController.fromWindowId(stateGlobal.windowId); - final curFrame = await wc.getFrame(); - final frame = - Rect.fromLTWH(screenRect.left + 30, screenRect.top + 30, 600, 400); - if (stateGlobal.fullscreen.isTrue && - curFrame.left <= frame.left && - curFrame.top <= frame.top && - curFrame.width >= frame.width && - curFrame.height >= frame.height) { - return; - } - await wc.setFrame(frame); - // An duration is needed to avoid the window being restored after fullscreen. - Future.delayed(Duration(milliseconds: 300), () async { - stateGlobal.setFullscreen(true); - }); -} - -parseParamScreenRect(Map params) { - Rect? screenRect; - if (params['screen_rect'] != null) { - double l = params['screen_rect']['l']; - double t = params['screen_rect']['t']; - double r = params['screen_rect']['r']; - double b = params['screen_rect']['b']; - screenRect = Rect.fromLTRB(l, t, r, b); - } - return screenRect; -} - -get isInputSourceFlutter => stateGlobal.getInputSource() == "Input source 2"; - -class _CountDownButton extends StatefulWidget { - _CountDownButton({ - Key? key, - required this.text, - required this.second, - required this.onPressed, - this.submitOnTimeout = false, - }) : super(key: key); - final String text; - final VoidCallback? onPressed; - final int second; - final bool submitOnTimeout; - - @override - State<_CountDownButton> createState() => _CountDownButtonState(); -} - -class _CountDownButtonState extends State<_CountDownButton> { - late int _countdownSeconds = widget.second; - - Timer? _timer; - - @override - void initState() { - super.initState(); - _startCountdownTimer(); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _startCountdownTimer() { - _timer = Timer.periodic(Duration(seconds: 1), (timer) { - if (_countdownSeconds <= 0) { - timer.cancel(); - if (widget.submitOnTimeout) { - widget.onPressed?.call(); - } - } else { - setState(() { - _countdownSeconds--; - }); - } - }); - } - - @override - Widget build(BuildContext context) { - return dialogButton( - '${translate(widget.text)} (${_countdownSeconds}s)', - onPressed: widget.onPressed, - isOutline: true, - ); - } -} - -importConfig(List? controllers, List? errMsgs, - String? text) { - text = text?.trim(); - if (text != null && text.isNotEmpty) { - try { - final sc = ServerConfig.decode(text); - if (isWeb || isIOS) { - sc.relayServer = ''; - } - if (sc.idServer.isNotEmpty) { - Future success = setServerConfig(controllers, errMsgs, sc); - success.then((value) { - if (value) { - showToast(translate('Import server configuration successfully')); - } else { - showToast(translate('Invalid server configuration')); - } - }); - } else { - showToast(translate('Invalid server configuration')); - } - return sc; - } catch (e) { - showToast(translate('Invalid server configuration')); - } - } else { - showToast(translate('Clipboard is empty')); - } -} - -Future setServerConfig( - List? controllers, - List? errMsgs, - ServerConfig config, -) async { - String removeEndSlash(String input) { - if (input.endsWith('/')) { - return input.substring(0, input.length - 1); - } - return input; - } - - config.idServer = removeEndSlash(config.idServer.trim()); - config.relayServer = removeEndSlash(config.relayServer.trim()); - config.apiServer = removeEndSlash(config.apiServer.trim()); - config.key = config.key.trim(); - if (controllers != null) { - controllers[0].text = config.idServer; - controllers[1].text = config.relayServer; - controllers[2].text = config.apiServer; - controllers[3].text = config.key; - } - // id - if (config.idServer.isNotEmpty && errMsgs != null) { - errMsgs[0].value = translate(await bind.mainTestIfValidServer( - server: config.idServer, testWithProxy: true)); - if (errMsgs[0].isNotEmpty) { - return false; - } - } - // relay - if (config.relayServer.isNotEmpty && errMsgs != null) { - errMsgs[1].value = translate(await bind.mainTestIfValidServer( - server: config.relayServer, testWithProxy: true)); - if (errMsgs[1].isNotEmpty) { - return false; - } - } - // api - if (config.apiServer.isNotEmpty && errMsgs != null) { - if (!config.apiServer.startsWith('http://') && - !config.apiServer.startsWith('https://')) { - errMsgs[2].value = - '${translate("API Server")}: ${translate("invalid_http")}'; - return false; - } - } - final oldApiServer = await bind.mainGetApiServer(); - - // should set one by one - await bind.mainSetOption( - key: 'custom-rendezvous-server', value: config.idServer); - await bind.mainSetOption(key: 'relay-server', value: config.relayServer); - await bind.mainSetOption(key: 'api-server', value: config.apiServer); - await bind.mainSetOption(key: 'key', value: config.key); - final newApiServer = await bind.mainGetApiServer(); - if (oldApiServer.isNotEmpty && - oldApiServer != newApiServer && - gFFI.userModel.isLogin) { - gFFI.userModel.logOut(apiServer: oldApiServer); - } - return true; -} - -ColorFilter? svgColor(Color? color) { - if (color == null) { - return null; - } else { - return ColorFilter.mode(color, BlendMode.srcIn); - } -} - -// ignore: must_be_immutable -class ComboBox extends StatelessWidget { - late final List keys; - late final List values; - late final String initialKey; - late final Function(String key) onChanged; - late final bool enabled; - late String current; - - ComboBox({ - Key? key, - required this.keys, - required this.values, - required this.initialKey, - required this.onChanged, - this.enabled = true, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var index = keys.indexOf(initialKey); - if (index < 0) { - index = 0; - } - var ref = values[index].obs; - current = keys[index]; - return Container( - decoration: BoxDecoration( - border: Border.all( - color: enabled - ? MyTheme.color(context).border2 ?? MyTheme.border - : MyTheme.border, - ), - borderRadius: - BorderRadius.circular(8), //border raiuds of dropdown button - ), - height: 42, // should be the height of a TextField - child: Obx(() => DropdownButton( - isExpanded: true, - value: ref.value, - elevation: 16, - underline: Container(), - style: TextStyle( - color: enabled - ? Theme.of(context).textTheme.titleMedium?.color - : disabledTextColor(context, enabled)), - icon: const Icon( - Icons.expand_more_sharp, - size: 20, - ).marginOnly(right: 15), - onChanged: enabled - ? (String? newValue) { - if (newValue != null && newValue != ref.value) { - ref.value = newValue; - current = newValue; - onChanged(keys[values.indexOf(newValue)]); - } - } - : null, - items: values.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(fontSize: 15), - overflow: TextOverflow.ellipsis, - ).marginOnly(left: 15), - ); - }).toList(), - )), - ).marginOnly(bottom: 5); - } -} - -Color? disabledTextColor(BuildContext context, bool enabled) { - return enabled - ? null - : Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6); -} - -Widget loadPowered(BuildContext context) { - if (bind.mainGetBuildinOption(key: "hide-powered-by-me") == 'Y') { - return SizedBox.shrink(); - } - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - launchUrl(Uri.parse('https://rustdesk.com')); - }, - child: Opacity( - opacity: 0.5, - child: Text( - translate("powered_by_me"), - overflow: TextOverflow.clip, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(fontSize: 9, decoration: TextDecoration.underline), - )), - ), - ).marginOnly(top: 6); -} - -// max 300 x 60 -Widget loadLogo() { - return FutureBuilder( - future: rootBundle.load('assets/logo.png'), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - final image = Image.asset( - 'assets/logo.png', - fit: BoxFit.contain, - errorBuilder: (ctx, error, stackTrace) { - return Container(); - }, - ); - return Container( - constraints: BoxConstraints(maxWidth: 300, maxHeight: 60), - child: image, - ).marginOnly(left: 12, right: 12, top: 12); - } - return const Offstage(); - }); -} - -Widget loadIcon(double size) { - return Image.asset('assets/icon.png', - width: size, - height: size, - errorBuilder: (ctx, error, stackTrace) => SvgPicture.asset( - 'assets/icon.svg', - width: size, - height: size, - )); -} - -var imcomingOnlyHomeSize = Size(280, 300); -Size getIncomingOnlyHomeSize() { - final magicWidth = isWindows ? 11.0 : 2.0; - final magicHeight = 10.0; - return imcomingOnlyHomeSize + - Offset(magicWidth, kDesktopRemoteTabBarHeight + magicHeight); -} - -Size getIncomingOnlySettingsSize() { - return Size(768, 600); -} - -bool isInHomePage() { - final controller = Get.find(); - return controller.state.value.selected == 0; -} - -Widget _buildPresetPasswordWarning() { - if (bind.mainGetBuildinOption(key: kOptionRemovePresetPasswordWarning) != - 'N') { - return SizedBox.shrink(); - } - return Container( - color: Colors.yellow, - child: Column( - children: [ - Align( - child: Text( - translate("Security Alert"), - style: TextStyle( - color: Colors.red, - fontSize: - 18, // https://github.com/rustdesk/rustdesk-server-pro/issues/261 - fontWeight: FontWeight.bold, - ), - )).paddingOnly(bottom: 8), - Text( - translate("preset_password_warning"), - style: TextStyle(color: Colors.red), - ) - ], - ).paddingAll(8), - ); // Show a warning message if the Future completed with true -} - -Widget buildPresetPasswordWarningMobile() { - if (bind.isPresetPasswordMobileOnly()) { - return _buildPresetPasswordWarning(); - } else { - return SizedBox.shrink(); - } -} - -Widget buildPresetPasswordWarning() { - return FutureBuilder( - future: bind.isPresetPassword(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return CircularProgressIndicator(); // Show a loading spinner while waiting for the Future to complete - } else if (snapshot.hasError) { - return Text( - 'Error: ${snapshot.error}'); // Show an error message if the Future completed with an error - } else if (snapshot.hasData && snapshot.data == true) { - return _buildPresetPasswordWarning(); - } else { - return SizedBox - .shrink(); // Show nothing if the Future completed with false or null - } - }, - ); -} - -// https://github.com/leanflutter/window_manager/blob/87dd7a50b4cb47a375b9fc697f05e56eea0a2ab3/lib/src/widgets/virtual_window_frame.dart#L44 -Widget buildVirtualWindowFrame(BuildContext context, Widget child) { - boxShadow() => isMainDesktopWindow - ? [ - if (stateGlobal.fullscreen.isFalse || stateGlobal.isMaximized.isFalse) - BoxShadow( - color: Colors.black.withOpacity(0.1), - offset: Offset( - 0.0, - stateGlobal.isFocused.isTrue - ? kFrameBoxShadowOffsetFocused - : kFrameBoxShadowOffsetUnfocused), - blurRadius: kFrameBoxShadowBlurRadius, - ), - ] - : null; - return Obx( - () => Container( - decoration: BoxDecoration( - color: isMainDesktopWindow - ? Colors.transparent - : Theme.of(context).colorScheme.background, - border: Border.all( - color: Theme.of(context).dividerColor, - width: stateGlobal.windowBorderWidth.value, - ), - borderRadius: BorderRadius.circular( - (stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.isTrue) - ? 0 - : kFrameBorderRadius, - ), - boxShadow: boxShadow(), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular( - (stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.isTrue) - ? 0 - : kFrameClipRRectBorderRadius, - ), - child: child, - ), - ), - ); -} - -get windowResizeEdgeSize => - isLinux && !_linuxWindowResizable ? 0.0 : kWindowResizeEdgeSize; - -// `windowManager.setResizable(false)` will reset the window size to the default size on Linux and then set unresizable. -// See _linuxWindowResizable for more details. -// So we use `setResizable()` instead of `windowManager.setResizable()`. -// -// We can only call `windowManager.setResizable(false)` if we need the default size on Linux. -setResizable(bool resizable) { - if (isLinux) { - _linuxWindowResizable = resizable; - stateGlobal.refreshResizeEdgeSize(); - } else { - windowManager.setResizable(resizable); - } -} - -isOptionFixed(String key) => bind.mainIsOptionFixed(key: key); - -bool isChangePermanentPasswordDisabled() => - bind.mainGetBuildinOption(key: kOptionDisableChangePermanentPassword) == - 'Y'; - -bool isChangeIdDisabled() => - bind.mainGetBuildinOption(key: kOptionDisableChangeId) == 'Y'; - -bool isUnlockPinDisabled() => - bind.mainGetBuildinOption(key: kOptionDisableUnlockPin) == 'Y'; - -bool? _isCustomClient; -bool get isCustomClient { - _isCustomClient ??= bind.isCustomClient(); - return _isCustomClient!; -} - -get defaultOptionLang => isCustomClient ? 'default' : ''; -get defaultOptionTheme => isCustomClient ? 'system' : ''; -get defaultOptionYes => isCustomClient ? 'Y' : ''; -get defaultOptionNo => isCustomClient ? 'N' : ''; -get defaultOptionWhitelist => isCustomClient ? ',' : ''; -get defaultOptionAccessMode => isCustomClient ? 'custom' : ''; -get defaultOptionApproveMode => isCustomClient ? 'password-click' : ''; - -bool whitelistNotEmpty() { - // https://rustdesk.com/docs/en/self-host/client-configuration/advanced-settings/#whitelist - final v = bind.mainGetOptionSync(key: kOptionWhitelist); - return v != '' && v != ','; -} - -// `setMovable()` is only supported on macOS. -// -// On macOS, the window can be dragged by the tab bar by default. -// We need to disable the movable feature to prevent the window from being dragged by the tabs in the tab bar. -// -// When we drag the blank tab bar (not the tab), the window will be dragged normally by adding the `onPanStart` handle. -// -// See the following code for more details: -// https://github.com/rustdesk/rustdesk/blob/ce1dac3b8613596b4d8ae981275f9335489eb935/flutter/lib/desktop/widgets/tabbar_widget.dart#L385 -// https://github.com/rustdesk/rustdesk/blob/ce1dac3b8613596b4d8ae981275f9335489eb935/flutter/lib/desktop/widgets/tabbar_widget.dart#L399 -// -// @platforms macos -disableWindowMovable(int? windowId) { - if (!isMacOS) { - return; - } - - if (windowId == null) { - windowManager.setMovable(false); - } else { - WindowController.fromWindowId(windowId).setMovable(false); - } -} - -Widget netWorkErrorWidget() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(translate("network_error_tip")), - ElevatedButton( - onPressed: gFFI.userModel.refreshCurrentUser, - child: Text(translate("Retry"))) - .marginSymmetric(vertical: 16), - SelectableText(gFFI.userModel.networkError.value, - style: TextStyle(fontSize: 11, color: Colors.red)), - ], - )); -} - -List? get windowManagerEnableResizeEdges => isWindows - ? [ - ResizeEdge.topLeft, - ResizeEdge.top, - ResizeEdge.topRight, - ] - : null; - -List? get subWindowManagerEnableResizeEdges => isWindows - ? [ - SubWindowResizeEdge.topLeft, - SubWindowResizeEdge.top, - SubWindowResizeEdge.topRight, - ] - : null; - -void earlyAssert() { - assert('\1' == '1'); -} - -void checkUpdate() { - if (!isWeb) { - if (!bind.isCustomClient()) { - platformFFI.registerEventHandler( - kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, - (Map evt) async { - if (evt['url'] is String) { - stateGlobal.updateUrl.value = evt['url']; - } - }); - Timer(const Duration(seconds: 1), () async { - bind.mainGetSoftwareUpdateUrl(); - }); - } - } -} - -// https://github.com/flutter/flutter/issues/153560#issuecomment-2497160535 -// For TextField, TextFormField -extension WorkaroundFreezeLinuxMint on Widget { - Widget workaroundFreezeLinuxMint() { - // No need to check if is Linux Mint, because this workaround is harmless on other platforms. - if (isLinux) { - return ExcludeSemantics(child: this); - } else { - return this; - } - } -} - -// Don't use `extension` here, the border looks weird if using `extension` in my test. -Widget workaroundWindowBorder(BuildContext context, Widget child) { - if (!isWin10) { - return child; - } - - final isLight = Theme.of(context).brightness == Brightness.light; - final borderColor = isLight ? Colors.black87 : Colors.grey; - final width = isLight ? 0.5 : 0.1; - - getBorderWidget(Widget child) { - return Obx(() => - (stateGlobal.isMaximized.isTrue || stateGlobal.fullscreen.isTrue) - ? Offstage() - : child); - } - - final List borders = [ - getBorderWidget(Container( - color: borderColor, - height: width + 0.1, - )) - ]; - if (kWindowType == WindowType.Main && !isLight) { - borders.addAll([ - getBorderWidget(Align( - alignment: Alignment.topLeft, - child: Container( - color: borderColor, - width: width, - ), - )), - getBorderWidget(Align( - alignment: Alignment.topRight, - child: Container( - color: borderColor, - width: width, - ), - )), - getBorderWidget(Align( - alignment: Alignment.bottomCenter, - child: Container( - color: borderColor, - height: width, - ), - )), - ]); - } - return Stack( - children: [ - child, - ...borders, - ], - ); -} - -void updateTextAndPreserveSelection( - TextEditingController controller, String text) { - // Only care about select all for now. - final isSelected = controller.selection.isValid && - controller.selection.end > controller.selection.start; - - // Set text will make the selection invalid. - controller.text = text; - - if (isSelected) { - controller.selection = TextSelection( - baseOffset: 0, extentOffset: controller.value.text.length); - } -} - -List getPrinterNames() { - final printerNamesJson = bind.mainGetPrinterNames(); - if (printerNamesJson.isEmpty) { - return []; - } - try { - final List printerNamesList = jsonDecode(printerNamesJson); - final appPrinterName = '$appName Printer'; - return printerNamesList - .map((e) => e.toString()) - .where((name) => name != appPrinterName) - .toList(); - } catch (e) { - debugPrint('failed to parse printer names, err: $e'); - return []; - } -} - -String _appName = ''; -String get appName { - if (_appName.isEmpty) { - _appName = bind.mainGetAppNameSync(); - } - return _appName; -} - -String getConnectionText(bool secure, bool direct, String streamType) { - String connectionText; - if (secure && direct) { - connectionText = translate("Direct and encrypted connection"); - } else if (secure && !direct) { - connectionText = translate("Relayed and encrypted connection"); - } else if (!secure && direct) { - connectionText = translate("Direct and unencrypted connection"); - } else { - connectionText = translate("Relayed and unencrypted connection"); - } - if (streamType == 'Relay') { - streamType = 'TCP'; - } - if (streamType.isEmpty) { - return connectionText; - } else { - return '$connectionText ($streamType)'; - } -} - -String decode_http_response(http.Response resp) { - try { - // https://github.com/rustdesk/rustdesk-server-pro/discussions/758 - return utf8.decode(resp.bodyBytes, allowMalformed: true); - } catch (e) { - debugPrint('Failed to decode response as UTF-8: $e'); - // Fallback to bodyString which handles encoding automatically - return resp.body; - } -} - -bool peerTabShowNote(PeerTabIndex peerTabIndex) { - return peerTabIndex == PeerTabIndex.ab || peerTabIndex == PeerTabIndex.group; -} - -// TODO: We should support individual bits combinations in the future. -// But for now, just keep it simple, because the old code only supports single button. -// No users have requested multi-button support yet. -String mouseButtonsToPeer(int buttons) { - switch (buttons) { - case kPrimaryMouseButton: - return 'left'; - case kSecondaryMouseButton: - return 'right'; - case kMiddleMouseButton: - return 'wheel'; - case kBackMouseButton: - return 'back'; - case kForwardMouseButton: - return 'forward'; - default: - return ''; - } -} - -/// Build an avatar widget from an avatar URL or data URI string. -/// Returns [fallback] if avatar is empty or cannot be decoded. -/// [borderRadius] defaults to [size]/2 (circle). -Widget? buildAvatarWidget({ - required String avatar, - required double size, - double? borderRadius, - Widget? fallback, -}) { - final trimmed = avatar.trim(); - if (trimmed.isEmpty) return fallback; - - ImageProvider? imageProvider; - if (trimmed.startsWith('data:image/')) { - final comma = trimmed.indexOf(','); - if (comma > 0) { - try { - imageProvider = MemoryImage(base64Decode(trimmed.substring(comma + 1))); - } catch (_) {} - } - } else if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { - imageProvider = NetworkImage(trimmed); - } - - if (imageProvider == null) return fallback; - - final radius = borderRadius ?? size / 2; - return ClipRRect( - borderRadius: BorderRadius.circular(radius), - child: Image( - image: imageProvider, - width: size, - height: size, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => fallback ?? SizedBox.shrink(), - ), - ); -} diff --git a/flutter/lib/common/formatter/id_formatter.dart b/flutter/lib/common/formatter/id_formatter.dart deleted file mode 100644 index c2329d53f..000000000 --- a/flutter/lib/common/formatter/id_formatter.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -class IDTextEditingController extends TextEditingController { - IDTextEditingController({String? text}) : super(text: text); - - String get id => trimID(value.text); - - set id(String newID) => text = formatID(newID); -} - -class IDTextInputFormatter extends TextInputFormatter { - @override - TextEditingValue formatEditUpdate( - TextEditingValue oldValue, TextEditingValue newValue) { - if (newValue.text.isEmpty) { - return newValue.copyWith(text: ''); - } else if (newValue.text.compareTo(oldValue.text) == 0) { - return newValue; - } else { - int selectionIndexFromTheRight = - newValue.text.length - newValue.selection.extentOffset; - String newID = formatID(newValue.text); - return TextEditingValue( - text: newID, - selection: TextSelection.collapsed( - offset: newID.length - selectionIndexFromTheRight, - ), - // https://github.com/flutter/flutter/issues/78066#issuecomment-797869906 - composing: newValue.composing, - ); - } - } -} - -String formatID(String id) { - String id2 = id.replaceAll(' ', ''); - String suffix = ''; - if (id2.endsWith(r'\r') || id2.endsWith(r'/r')) { - suffix = id2.substring(id2.length - 2, id2.length); - id2 = id2.substring(0, id2.length - 2); - } - if (int.tryParse(id2) == null) return id; - String newID = ''; - if (id2.length <= 3) { - newID = id2; - } else { - var n = id2.length; - var a = n % 3 != 0 ? n % 3 : 3; - newID = id2.substring(0, a); - for (var i = a; i < n; i += 3) { - newID += " ${id2.substring(i, i + 3)}"; - } - } - return newID + suffix; -} - -String trimID(String id) { - return id.replaceAll(' ', ''); -} diff --git a/flutter/lib/common/hbbs/hbbs.dart b/flutter/lib/common/hbbs/hbbs.dart deleted file mode 100644 index 0c729e4df..000000000 --- a/flutter/lib/common/hbbs/hbbs.dart +++ /dev/null @@ -1,302 +0,0 @@ -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; - -import 'package:flutter_hbb/models/peer_model.dart'; - -import '../../models/platform_model.dart'; - -class HttpType { - static const kAuthReqTypeAccount = "account"; - static const kAuthReqTypeMobile = "mobile"; - static const kAuthReqTypeSMSCode = "sms_code"; - static const kAuthReqTypeEmailCode = "email_code"; - static const kAuthReqTypeTfaCode = "tfa_code"; - - static const kAuthResTypeToken = "access_token"; - static const kAuthResTypeEmailCheck = "email_check"; - static const kAuthResTypeTfaCheck = "tfa_check"; -} - -enum UserStatus { kDisabled, kNormal, kUnverified } - -// to-do: The UserPayload does not contain all the fields of the user. -// Is all the fields of the user needed? -class UserPayload { - String name = ''; - String displayName = ''; - String avatar = ''; - String email = ''; - String note = ''; - String? verifier; - UserStatus status; - bool isAdmin = false; - - UserPayload.fromJson(Map json) - : name = json['name'] ?? '', - displayName = json['display_name'] ?? '', - avatar = json['avatar'] ?? '', - email = json['email'] ?? '', - note = json['note'] ?? '', - verifier = json['verifier'], - status = json['status'] == 0 - ? UserStatus.kDisabled - : json['status'] == -1 - ? UserStatus.kUnverified - : UserStatus.kNormal, - isAdmin = json['is_admin'] == true; - - Map toJson() { - final Map map = { - 'name': name, - 'display_name': displayName, - 'avatar': avatar, - 'status': status == UserStatus.kDisabled - ? 0 - : status == UserStatus.kUnverified - ? -1 - : 1, - }; - return map; - } - - Map toGroupCacheJson() { - final Map map = { - 'name': name, - 'display_name': displayName, - }; - return map; - } - - String get displayNameOrName { - return displayName.trim().isEmpty ? name : displayName; - } -} - -class PeerPayload { - String id = ''; - Map info = {}; - int? status; - String user = ''; - String user_name = ''; - String? device_group_name; - String note = ''; - - PeerPayload.fromJson(Map json) - : id = json['id'] ?? '', - info = (json['info'] is Map) ? json['info'] : {}, - status = json['status'], - user = json['user'] ?? '', - user_name = json['user_name'] ?? '', - device_group_name = json['device_group_name'] ?? '', - note = json['note'] ?? ''; - - static Peer toPeer(PeerPayload p) { - return Peer.fromJson({ - "id": p.id, - 'loginName': p.user_name, - "username": p.info['username'] ?? '', - "platform": _platform(p.info['os']), - "hostname": p.info['device_name'], - "device_group_name": p.device_group_name, - "note": p.note, - }); - } - - static String? _platform(dynamic field) { - if (field == null) { - return null; - } - final fieldStr = field.toString(); - List list = fieldStr.split(' / '); - if (list.isEmpty) return null; - final os = list[0]; - switch (os.toLowerCase()) { - case 'windows': - return kPeerPlatformWindows; - case 'linux': - return kPeerPlatformLinux; - case 'macos': - return kPeerPlatformMacOS; - case 'android': - return kPeerPlatformAndroid; - default: - if (fieldStr.toLowerCase().contains('linux')) { - return kPeerPlatformLinux; - } - return null; - } - } -} - -class LoginRequest { - String? username; - String? password; - String? id; - String? uuid; - bool? autoLogin; - String? type; - String? verificationCode; - String? tfaCode; - String? secret; - - LoginRequest( - {this.username, - this.password, - this.id, - this.uuid, - this.autoLogin, - this.type, - this.verificationCode, - this.tfaCode, - this.secret}); - - Map toJson() { - final Map data = {}; - if (username != null) data['username'] = username; - if (password != null) data['password'] = password; - if (id != null) data['id'] = id; - if (uuid != null) data['uuid'] = uuid; - if (autoLogin != null) data['autoLogin'] = autoLogin; - if (type != null) data['type'] = type; - if (verificationCode != null) { - data['verificationCode'] = verificationCode; - } - if (tfaCode != null) data['tfaCode'] = tfaCode; - if (secret != null) data['secret'] = secret; - - Map deviceInfo = {}; - try { - deviceInfo = jsonDecode(bind.mainGetLoginDeviceInfo()); - } catch (e) { - debugPrint('Failed to decode get device info: $e'); - } - data['deviceInfo'] = deviceInfo; - return data; - } -} - -class LoginResponse { - String? access_token; - String? type; - String? tfa_type; - String? secret; - UserPayload? user; - - LoginResponse( - {this.access_token, this.type, this.tfa_type, this.secret, this.user}); - - LoginResponse.fromJson(Map json) { - access_token = json['access_token']; - type = json['type']; - tfa_type = json['tfa_type']; - secret = json['secret']; - user = json['user'] != null ? UserPayload.fromJson(json['user']) : null; - } -} - -class RequestException implements Exception { - int statusCode; - String cause; - RequestException(this.statusCode, this.cause); - - @override - String toString() { - return "RequestException, statusCode: $statusCode, error: $cause"; - } -} - -enum ShareRule { - read(1), - readWrite(2), - fullControl(3); - - const ShareRule(this.value); - final int value; - - static String desc(int v) { - if (v == ShareRule.read.value) { - return translate('Read-only'); - } - if (v == ShareRule.readWrite.value) { - return translate('Read/Write'); - } - if (v == ShareRule.fullControl.value) { - return translate('Full Control'); - } - return v.toString(); - } - - static String shortDesc(int v) { - if (v == ShareRule.read.value) { - return 'R'; - } - if (v == ShareRule.readWrite.value) { - return 'RW'; - } - if (v == ShareRule.fullControl.value) { - return 'F'; - } - return v.toString(); - } - - static ShareRule? fromValue(int v) { - if (v == ShareRule.read.value) { - return ShareRule.read; - } - if (v == ShareRule.readWrite.value) { - return ShareRule.readWrite; - } - if (v == ShareRule.fullControl.value) { - return ShareRule.fullControl; - } - return null; - } -} - -class AbProfile { - String guid; - String name; - String owner; - String? note; - dynamic info; - int rule; - - AbProfile(this.guid, this.name, this.owner, this.note, this.rule, this.info); - - AbProfile.fromJson(Map json) - : guid = json['guid'] ?? '', - name = json['name'] ?? '', - owner = json['owner'] ?? '', - note = json['note'] ?? '', - info = json['info'], - rule = json['rule'] ?? 0; -} - -class AbTag { - String name; - int color; - - AbTag(this.name, this.color); - - AbTag.fromJson(Map json) - : name = json['name'] ?? '', - color = json['color'] ?? ''; -} - -class DeviceGroupPayload { - String name; - - DeviceGroupPayload(this.name); - - DeviceGroupPayload.fromJson(Map json) - : name = json['name'] ?? ''; - - Map toGroupCacheJson() { - final Map map = { - 'name': name, - }; - return map; - } -} diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart deleted file mode 100644 index 4f9373ccd..000000000 --- a/flutter/lib/common/shared_state.dart +++ /dev/null @@ -1,368 +0,0 @@ -import 'package:flutter_hbb/common.dart'; -import 'package:get/get.dart'; - -import '../consts.dart'; - -// TODO: A lot of dup code. - -class PrivacyModeState { - static String tag(String id) => 'privacy_mode_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxString state = ''.obs; - Get.put(state, tag: key); - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } else { - Get.find(tag: key).value = ''; - } - } - - static RxString find(String id) => Get.find(tag: tag(id)); -} - -class BlockInputState { - static String tag(String id) => 'block_input_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxBool state = false.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = false; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id) => Get.find(tag: tag(id)); -} - -class CurrentDisplayState { - static String tag(String id) => 'current_display_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxInt state = RxInt(0); - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = 0; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxInt find(String id) => Get.find(tag: tag(id)); -} - -class ConnectionType { - final Rx _secure = kInvalidValueStr.obs; - final Rx _direct = kInvalidValueStr.obs; - final Rx _stream_type = kInvalidValueStr.obs; - - Rx get secure => _secure; - Rx get direct => _direct; - Rx get stream_type => _stream_type; - - static String get strSecure => 'secure'; - static String get strInsecure => 'insecure'; - static String get strDirect => ''; - static String get strIndirect => '_relay'; - - void setSecure(bool v) { - _secure.value = v ? strSecure : strInsecure; - } - - void setDirect(bool v) { - _direct.value = v ? strDirect : strIndirect; - } - - void setStreamType(String v) { - _stream_type.value = v; - } - - bool isValid() { - return _secure.value != kInvalidValueStr && - _direct.value != kInvalidValueStr && - _stream_type.value != kInvalidValueStr; - } -} - -class ConnectionTypeState { - static String tag(String id) => 'connection_type_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final ConnectionType collectionType = ConnectionType(); - Get.put(collectionType, tag: key); - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static ConnectionType find(String id) => - Get.find(tag: tag(id)); -} - -class FingerprintState { - static String tag(String id) => 'fingerprint_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxString state = ''.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = ''; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxString find(String id) => Get.find(tag: tag(id)); -} - -class ShowRemoteCursorState { - static String tag(String id) => 'show_remote_cursor_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxBool state = false.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = false; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id) => Get.find(tag: tag(id)); -} - -class ShowRemoteCursorLockState { - static String tag(String id) => 'show_remote_cursor_lock_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxBool state = false.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = false; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id) => Get.find(tag: tag(id)); -} - -class KeyboardEnabledState { - static String tag(String id) => 'keyboard_enabled_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - // Server side, default true - final RxBool state = true.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = true; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id) => Get.find(tag: tag(id)); -} - -class RemoteCursorMovedState { - static String tag(String id) => 'remote_cursor_moved_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxBool state = false.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = false; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id) => Get.find(tag: tag(id)); -} - -class RemoteCountState { - static String tag() => 'remote_count_'; - - static void init() { - final key = tag(); - if (!Get.isRegistered(tag: key)) { - final RxInt state = 1.obs; - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = 1; - } - } - - static void delete() { - final key = tag(); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxInt find() => Get.find(tag: tag()); -} - -class PeerBoolOption { - static String tag(String id, String opt) => 'peer_{$opt}_$id'; - - static void init(String id, String opt, bool Function() init_getter) { - final key = tag(id, opt); - if (!Get.isRegistered(tag: key)) { - final RxBool value = RxBool(init_getter()); - Get.put(value, tag: key); - } else { - Get.find(tag: key).value = init_getter(); - } - } - - static void delete(String id, String opt) { - final key = tag(id, opt); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxBool find(String id, String opt) => - Get.find(tag: tag(id, opt)); -} - -class PeerStringOption { - static String tag(String id, String opt) => 'peer_{$opt}_$id'; - - static void init(String id, String opt, String Function() init_getter) { - final key = tag(id, opt); - if (!Get.isRegistered(tag: key)) { - final RxString value = RxString(init_getter()); - Get.put(value, tag: key); - } else { - Get.find(tag: key).value = init_getter(); - } - } - - static void delete(String id, String opt) { - final key = tag(id, opt); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxString find(String id, String opt) => - Get.find(tag: tag(id, opt)); -} - -class UnreadChatCountState { - static String tag(id) => 'unread_chat_count_$id'; - - static void init(String id) { - final key = tag(id); - if (!Get.isRegistered(tag: key)) { - final RxInt state = RxInt(0); - Get.put(state, tag: key); - } else { - Get.find(tag: key).value = 0; - } - } - - static void delete(String id) { - final key = tag(id); - if (Get.isRegistered(tag: key)) { - Get.delete(tag: key); - } - } - - static RxInt find(String id) => Get.find(tag: tag(id)); -} - -initSharedStates(String id) { - PrivacyModeState.init(id); - BlockInputState.init(id); - CurrentDisplayState.init(id); - KeyboardEnabledState.init(id); - ShowRemoteCursorState.init(id); - ShowRemoteCursorLockState.init(id); - RemoteCursorMovedState.init(id); - FingerprintState.init(id); - PeerBoolOption.init(id, kOptionZoomCursor, () => false); - UnreadChatCountState.init(id); - if (isMobile) ConnectionTypeState.init(id); // desktop in other places -} - -removeSharedStates(String id) { - PrivacyModeState.delete(id); - BlockInputState.delete(id); - CurrentDisplayState.delete(id); - ShowRemoteCursorState.delete(id); - ShowRemoteCursorLockState.delete(id); - KeyboardEnabledState.delete(id); - RemoteCursorMovedState.delete(id); - FingerprintState.delete(id); - PeerBoolOption.delete(id, kOptionZoomCursor); - UnreadChatCountState.delete(id); - if (isMobile) ConnectionTypeState.delete(id); -} diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart deleted file mode 100644 index 054a1666c..000000000 --- a/flutter/lib/common/widgets/address_book.dart +++ /dev/null @@ -1,899 +0,0 @@ -import 'dart:math'; - -import 'package:bot_toast/bot_toast.dart'; -import 'package:dropdown_button2/dropdown_button2.dart'; -import 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/formatter/id_formatter.dart'; -import 'package:flutter_hbb/common/hbbs/hbbs.dart'; -import 'package:flutter_hbb/common/widgets/peer_card.dart'; -import 'package:flutter_hbb/common/widgets/peers_view.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/models/ab_model.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:url_launcher/url_launcher_string.dart'; -import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; -import 'package:get/get.dart'; -import 'package:flex_color_picker/flex_color_picker.dart'; - -import '../../common.dart'; -import 'dialog.dart'; -import 'login.dart'; - -final hideAbTagsPanel = false.obs; - -class AddressBook extends StatefulWidget { - final EdgeInsets? menuPadding; - const AddressBook({Key? key, this.menuPadding}) : super(key: key); - - @override - State createState() { - return _AddressBookState(); - } -} - -class _AddressBookState extends State { - var menuPos = RelativeRect.fill; - - @override - Widget build(BuildContext context) => Obx(() { - if (!gFFI.userModel.isLogin) { - return Center( - child: ElevatedButton( - onPressed: loginDialog, child: Text(translate("Login")))); - } else if (gFFI.userModel.networkError.isNotEmpty) { - return netWorkErrorWidget(); - } else { - return Column( - children: [ - // NOT use Offstage to wrap LinearProgressIndicator - if (gFFI.abModel.currentAbLoading.value && - gFFI.abModel.currentAbEmpty) - const LinearProgressIndicator(), - buildErrorBanner(context, - loading: gFFI.abModel.currentAbLoading, - err: gFFI.abModel.abPullError, - retry: null, - close: gFFI.abModel.clearPullErrors), - buildErrorBanner(context, - loading: gFFI.abModel.currentAbLoading, - err: gFFI.abModel.currentAbPushError, - retry: null, // remove retry - close: () => gFFI.abModel.currentAbPushError.value = ''), - Expanded( - child: Obx(() => stateGlobal.isPortrait.isTrue - ? _buildAddressBookPortrait() - : _buildAddressBookLandscape()), - ), - ], - ); - } - }); - - Widget _buildAddressBookLandscape() { - return Row( - children: [ - Offstage( - offstage: hideAbTagsPanel.value, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Theme.of(context).colorScheme.background)), - child: Container( - width: 200, - height: double.infinity, - child: Column( - children: [ - _buildAbDropdown(), - _buildTagHeader().marginOnly( - left: 8.0, - right: gFFI.abModel.legacyMode.value ? 8.0 : 0, - top: gFFI.abModel.legacyMode.value ? 8.0 : 0), - Expanded( - child: Container( - width: double.infinity, - height: double.infinity, - child: _buildTags(), - ), - ), - _buildAbPermission(), - ], - ), - ), - ).marginOnly(right: 12.0)), - _buildPeersViews() - ], - ); - } - - Widget _buildAddressBookPortrait() { - const padding = 8.0; - return Column( - children: [ - Offstage( - offstage: hideAbTagsPanel.value, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - border: Border.all( - color: Theme.of(context).colorScheme.background)), - child: Container( - padding: - const EdgeInsets.fromLTRB(padding, 0, padding, padding), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildAbDropdown(), - _buildTagHeader().marginOnly(left: 8.0, right: 0), - Container( - width: double.infinity, - child: _buildTags(), - ), - ], - ), - ), - ).marginOnly(bottom: 12.0)), - _buildPeersViews() - ], - ); - } - - Widget _buildAbPermission() { - icon(IconData data, String tooltip) { - return Tooltip( - message: translate(tooltip), - waitDuration: Duration.zero, - child: Icon(data, size: 12.0).marginSymmetric(horizontal: 2.0)); - } - - return Obx(() { - if (gFFI.abModel.legacyMode.value) return Offstage(); - if (gFFI.abModel.current.isPersonal()) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - icon(Icons.cloud_off, "Personal"), - ], - ); - } else { - List children = []; - final rule = gFFI.abModel.current.sharedProfile()?.rule; - if (rule == ShareRule.read.value) { - children.add( - icon(Icons.visibility, ShareRule.desc(ShareRule.read.value))); - } else if (rule == ShareRule.readWrite.value) { - children - .add(icon(Icons.edit, ShareRule.desc(ShareRule.readWrite.value))); - } else if (rule == ShareRule.fullControl.value) { - children.add(icon( - Icons.security, ShareRule.desc(ShareRule.fullControl.value))); - } - final owner = gFFI.abModel.current.sharedProfile()?.owner; - if (owner != null) { - children.add(icon(Icons.person, "${translate("Owner")}: $owner")); - } - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: children, - ); - } - }); - } - - Widget _buildAbDropdown() { - if (gFFI.abModel.legacyMode.value) { - return Offstage(); - } - final names = gFFI.abModel.addressBookNames(); - if (!names.contains(gFFI.abModel.currentName.value)) { - return Offstage(); - } - // order: personal, divider, character order - // https://pub.dev/packages/dropdown_button2#3-dropdownbutton2-with-items-of-different-heights-like-dividers - final personalAddressBookName = gFFI.abModel.personalAddressBookName(); - bool contains = names.remove(personalAddressBookName); - names.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase())); - if (contains) { - names.insert(0, personalAddressBookName); - } - - Row buildItem(String e, {bool button = false}) { - return Row( - children: [ - Expanded( - child: Tooltip( - waitDuration: Duration(milliseconds: 500), - message: gFFI.abModel.translatedName(e), - child: Text( - gFFI.abModel.translatedName(e), - style: button ? null : TextStyle(fontSize: 14.0), - maxLines: 1, - overflow: TextOverflow.ellipsis, - textAlign: button ? TextAlign.center : null, - )), - ), - ], - ); - } - - final items = names - .map((e) => DropdownMenuItem(value: e, child: buildItem(e))) - .toList(); - var menuItemStyleData = MenuItemStyleData(height: 36); - if (contains && items.length > 1) { - items.insert(1, DropdownMenuItem(enabled: false, child: Divider())); - List customHeights = List.filled(items.length, 36); - customHeights[1] = 4; - menuItemStyleData = MenuItemStyleData(customHeights: customHeights); - } - final TextEditingController textEditingController = TextEditingController(); - - final isOptFixed = isOptionFixed(kOptionCurrentAbName); - return DropdownButton2( - value: gFFI.abModel.currentName.value, - onChanged: isOptFixed - ? null - : (value) { - if (value != null) { - gFFI.abModel.setCurrentName(value); - bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value); - } - }, - customButton: Obx(() => Container( - height: stateGlobal.isPortrait.isFalse ? 48 : 40, - child: Row(children: [ - Expanded( - child: - buildItem(gFFI.abModel.currentName.value, button: true)), - Icon(Icons.arrow_drop_down), - ]), - )), - underline: Container( - height: 0.7, - color: Theme.of(context).dividerColor.withOpacity(0.1), - ), - menuItemStyleData: menuItemStyleData, - items: items, - isExpanded: true, - isDense: true, - dropdownSearchData: DropdownSearchData( - searchController: textEditingController, - searchInnerWidgetHeight: 50, - searchInnerWidget: Container( - height: 50, - padding: const EdgeInsets.only( - top: 8, - bottom: 4, - right: 8, - left: 8, - ), - child: TextFormField( - expands: true, - maxLines: null, - controller: textEditingController, - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 8, - ), - hintText: translate('Search'), - hintStyle: const TextStyle(fontSize: 12), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ).workaroundFreezeLinuxMint(), - ), - searchMatchFn: (item, searchValue) { - return item.value - .toString() - .toLowerCase() - .contains(searchValue.toLowerCase()); - }, - ), - ); - } - - Widget _buildTagHeader() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(translate('Tags')), - Listener( - onPointerDown: (e) { - final x = e.position.dx; - final y = e.position.dy; - menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onPointerUp: (_) => _showMenu(menuPos), - child: build_more(context, invert: true)), - ], - ); - } - - Widget _buildTags() { - return Obx(() { - List tags; - if (gFFI.abModel.sortTags.value) { - tags = gFFI.abModel.currentAbTags.toList(); - tags.sort(); - } else { - tags = gFFI.abModel.currentAbTags.toList(); - } - tags = [kUntagged, ...tags].toList(); - final editPermission = gFFI.abModel.current.canWrite(); - tagBuilder(String e) { - return AddressBookTag( - name: e, - tags: gFFI.abModel.selectedTags, - onTap: () { - if (gFFI.abModel.selectedTags.contains(e)) { - gFFI.abModel.selectedTags.remove(e); - } else { - gFFI.abModel.selectedTags.add(e); - } - }, - showActionMenu: editPermission); - } - - gridView(bool isPortrait) => DynamicGridView.builder( - shrinkWrap: isPortrait, - gridDelegate: SliverGridDelegateWithWrapping(), - itemCount: tags.length, - itemBuilder: (BuildContext context, int index) { - final e = tags[index]; - return tagBuilder(e); - }); - final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return Obx(() => stateGlobal.isPortrait.isFalse - ? gridView(false) - : LimitedBox(maxHeight: maxHeight, child: gridView(true))); - }); - } - - Widget _buildPeersViews() { - return Expanded( - child: Align( - alignment: Alignment.topLeft, - child: AddressBookPeersView( - menuPadding: widget.menuPadding, - )), - ); - } - - @protected - MenuEntryBase syncMenuItem() { - final isOptFixed = isOptionFixed(syncAbOption); - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Sync with recent sessions'), - getter: () async { - return shouldSyncAb(); - }, - setter: (bool v) async { - gFFI.abModel.setShouldAsync(v); - }, - dismissOnClicked: true, - enabled: (!isOptFixed).obs, - ); - } - - @protected - MenuEntryBase sortMenuItem() { - final isOptFixed = isOptionFixed(sortAbTagsOption); - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Sort tags'), - getter: () async { - return shouldSortTags(); - }, - setter: (bool v) async { - bind.mainSetLocalOption( - key: sortAbTagsOption, value: v ? 'Y' : defaultOptionNo); - gFFI.abModel.sortTags.value = v; - }, - dismissOnClicked: true, - enabled: (!isOptFixed).obs, - ); - } - - @protected - MenuEntryBase filterMenuItem() { - final isOptFixed = isOptionFixed(filterAbTagOption); - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Filter by intersection'), - getter: () async { - return filterAbTagByIntersection(); - }, - setter: (bool v) async { - bind.mainSetLocalOption( - key: filterAbTagOption, value: v ? 'Y' : defaultOptionNo); - gFFI.abModel.filterByIntersection.value = v; - }, - dismissOnClicked: true, - enabled: (!isOptFixed).obs, - ); - } - - void _showMenu(RelativeRect pos) { - final canWrite = gFFI.abModel.current.canWrite(); - final items = [ - if (canWrite) getEntry(translate("Add ID"), addIdToCurrentAb), - if (canWrite) getEntry(translate("Add Tag"), abAddTag), - getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags), - if (gFFI.abModel.legacyMode.value) - sortMenuItem(), // It's already sorted after pulling down - if (canWrite) syncMenuItem(), - filterMenuItem(), - if (!gFFI.abModel.legacyMode.value && canWrite) - MenuEntryDivider(), - if (!gFFI.abModel.legacyMode.value && canWrite) - getEntry(translate("ab_web_console_tip"), () async { - final url = await bind.mainGetApiServer(); - if (await canLaunchUrlString(url)) { - launchUrlString(url); - } - }), - ]; - - mod_menu.showMenu( - context: context, - position: pos, - items: items - .map((e) => e.build( - context, - MenuConfig( - commonColor: CustomPopupMenuTheme.commonColor, - height: CustomPopupMenuTheme.height, - dividerHeight: CustomPopupMenuTheme.dividerHeight))) - .expand((i) => i) - .toList(), - elevation: 8, - ); - } - - void addIdToCurrentAb() async { - if (gFFI.abModel.isCurrentAbFull(true)) { - return; - } - var isInProgress = false; - var passwordVisible = false; - IDTextEditingController idController = IDTextEditingController(text: ''); - TextEditingController aliasController = TextEditingController(text: ''); - TextEditingController passwordController = TextEditingController(text: ''); - TextEditingController noteController = TextEditingController(text: ''); - final tags = List.of(gFFI.abModel.currentAbTags); - var selectedTag = List.empty(growable: true).obs; - final style = TextStyle(fontSize: 14.0); - String? errorMsg; - final isCurrentAbShared = !gFFI.abModel.current.isPersonal(); - - gFFI.dialogManager.show((setState, close, context) { - submit() async { - setState(() { - isInProgress = true; - errorMsg = null; - }); - String id = idController.id; - if (id.isEmpty) { - // pass - } else { - if (gFFI.abModel.idContainByCurrent(id)) { - setState(() { - isInProgress = false; - errorMsg = translate('ID already exists'); - }); - return; - } - var password = ''; - if (isCurrentAbShared) { - password = passwordController.text; - } - String? errMsg2 = await gFFI.abModel.addIdToCurrent( - id, - aliasController.text.trim(), - password, - selectedTag, - noteController.text); - if (errMsg2 != null) { - setState(() { - isInProgress = false; - errorMsg = errMsg2; - }); - return; - } - // final currentPeers - } - close(); - } - - double marginBottom = 4; - - row({required Widget label, required Widget input}) { - makeChild(bool isPortrait) => Row( - children: [ - !isPortrait - ? ConstrainedBox( - constraints: const BoxConstraints(minWidth: 100), - child: label.marginOnly(right: 10)) - : SizedBox.shrink(), - Expanded( - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 200), - child: input), - ), - ], - ).marginOnly(bottom: !isPortrait ? 8 : 0); - return Obx(() => makeChild(stateGlobal.isPortrait.isTrue)); - } - - return CustomAlertDialog( - title: Text(translate("Add ID")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - row( - label: Row( - children: [ - Text( - '*', - style: TextStyle(color: Colors.red, fontSize: 14), - ), - Text( - 'ID', - style: style, - ), - ], - ), - input: Obx(() => TextField( - controller: idController, - inputFormatters: [IDTextInputFormatter()], - decoration: InputDecoration( - labelText: stateGlobal.isPortrait.isFalse - ? null - : translate('ID'), - errorText: errorMsg, - errorMaxLines: 5), - ).workaroundFreezeLinuxMint())), - row( - label: Text( - translate('Alias'), - style: style, - ), - input: Obx(() => TextField( - controller: aliasController, - decoration: InputDecoration( - labelText: stateGlobal.isPortrait.isFalse - ? null - : translate('Alias'), - ), - ).workaroundFreezeLinuxMint()), - ), - if (isCurrentAbShared) - row( - label: Text( - translate('Password'), - style: style, - ), - input: Obx( - () => TextField( - controller: passwordController, - obscureText: !passwordVisible, - decoration: InputDecoration( - labelText: stateGlobal.isPortrait.isFalse - ? null - : translate('Password'), - suffixIcon: IconButton( - icon: Icon( - passwordVisible - ? Icons.visibility - : Icons.visibility_off, - color: MyTheme.lightTheme.primaryColor), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ), - ).workaroundFreezeLinuxMint(), - )), - row( - label: Text( - translate('Note'), - style: style, - ), - input: Obx( - () => TextField( - controller: noteController, - maxLines: 3, - minLines: 1, - maxLength: 300, - decoration: InputDecoration( - labelText: stateGlobal.isPortrait.isFalse - ? null - : translate('Note'), - ), - ).workaroundFreezeLinuxMint(), - )), - if (gFFI.abModel.currentAbTags.isNotEmpty) - Align( - alignment: Alignment.centerLeft, - child: Text( - translate('Tags'), - style: style, - ), - ).marginOnly(top: 8, bottom: marginBottom), - if (gFFI.abModel.currentAbTags.isNotEmpty) - Align( - alignment: Alignment.centerLeft, - child: Wrap( - children: tags - .map((e) => AddressBookTag( - name: e, - tags: selectedTag, - onTap: () { - if (selectedTag.contains(e)) { - selectedTag.remove(e); - } else { - selectedTag.add(e); - } - }, - showActionMenu: false)) - .toList(growable: false), - ), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - if (!gFFI.abModel.current.isPersonal()) - Row(children: [ - Icon(Icons.info, color: Colors.amber).marginOnly(right: 4), - Text( - translate('share_warning_tip'), - style: TextStyle(fontSize: 12), - ) - ]).marginSymmetric(vertical: 10), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); - } - - void abAddTag() async { - var field = ""; - var msg = ""; - var isInProgress = false; - TextEditingController controller = TextEditingController(text: field); - gFFI.dialogManager.show((setState, close, context) { - submit() async { - setState(() { - msg = ""; - isInProgress = true; - }); - field = controller.text.trim(); - if (field.isEmpty) { - // pass - } else { - final tags = field.trim().split(RegExp(r"[\s,;\n]+")); - field = tags.join(','); - for (var t in [kUntagged, translate(kUntagged)]) { - if (tags.contains(t)) { - BotToast.showText( - contentColor: Colors.red, text: 'Tag name cannot be "$t"'); - isInProgress = false; - return; - } - } - gFFI.abModel.addTags(tags); - // final currentPeers - } - close(); - } - - return CustomAlertDialog( - title: Text(translate("Add Tag")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("whitelist_sep")), - const SizedBox( - height: 8.0, - ), - Row( - children: [ - Expanded( - child: TextField( - maxLines: null, - decoration: InputDecoration( - errorText: msg.isEmpty ? null : translate(msg), - ), - controller: controller, - autofocus: true, - ).workaroundFreezeLinuxMint(), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); - } -} - -class AddressBookTag extends StatelessWidget { - final String name; - final RxList tags; - final Function()? onTap; - final bool showActionMenu; - - const AddressBookTag( - {Key? key, - required this.name, - required this.tags, - this.onTap, - this.showActionMenu = true}) - : super(key: key); - - @override - Widget build(BuildContext context) { - var pos = RelativeRect.fill; - - void setPosition(TapDownDetails e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - pos = RelativeRect.fromLTRB(x, y, x, y); - } - - const double radius = 8; - final isUnTagged = name == kUntagged; - final showAction = showActionMenu && !isUnTagged; - return GestureDetector( - onTap: onTap, - onTapDown: showAction ? setPosition : null, - onSecondaryTapDown: showAction ? setPosition : null, - onSecondaryTap: showAction ? () => _showMenu(context, pos) : null, - onLongPress: showAction ? () => _showMenu(context, pos) : null, - child: Obx(() => Container( - decoration: BoxDecoration( - color: tags.contains(name) - ? gFFI.abModel.getCurrentAbTagColor(name) - : Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(4)), - margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), - padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 6.0), - child: IntrinsicWidth( - child: Row( - children: [ - if (!isUnTagged) - Container( - width: radius, - height: radius, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: tags.contains(name) - ? Colors.white - : gFFI.abModel.getCurrentAbTagColor(name)), - ).marginOnly(right: radius / 2), - Expanded( - child: Text(isUnTagged ? translate(name) : name, - style: TextStyle( - overflow: TextOverflow.ellipsis, - color: tags.contains(name) ? Colors.white : null)), - ), - ], - ), - ), - )), - ); - } - - void _showMenu(BuildContext context, RelativeRect pos) { - final items = [ - getEntry(translate("Rename"), () { - renameDialog( - oldName: name, - validator: (String? newName) { - if (newName == null || newName.isEmpty) { - return translate('Can not be empty'); - } - if (newName != name && - gFFI.abModel.currentAbTags.contains(newName)) { - return translate('Already exists'); - } - return null; - }, - onSubmit: (String newName) { - if (name != newName) { - gFFI.abModel.renameTag(name, newName); - } - Future.delayed(Duration.zero, () => Get.back()); - }, - onCancel: () { - Future.delayed(Duration.zero, () => Get.back()); - }); - }), - getEntry(translate(translate('Change Color')), () async { - final model = gFFI.abModel; - Color oldColor = model.getCurrentAbTagColor(name); - Color newColor = await showColorPickerDialog( - context, - oldColor, - pickersEnabled: { - ColorPickerType.accent: false, - ColorPickerType.wheel: true, - }, - pickerTypeLabels: { - ColorPickerType.primary: translate("Primary Color"), - ColorPickerType.wheel: translate("HSV Color"), - }, - actionButtons: ColorPickerActionButtons( - dialogOkButtonLabel: translate("OK"), - dialogCancelButtonLabel: translate("Cancel")), - showColorCode: true, - ); - if (oldColor != newColor) { - model.setTagColor(name, newColor); - } - }), - getEntry(translate("Delete"), () { - gFFI.abModel.deleteTag(name); - Future.delayed(Duration.zero, () => Get.back()); - }), - ]; - - mod_menu.showMenu( - context: context, - position: pos, - items: items - .map((e) => e.build( - context, - MenuConfig( - commonColor: CustomPopupMenuTheme.commonColor, - height: CustomPopupMenuTheme.height, - dividerHeight: CustomPopupMenuTheme.dividerHeight))) - .expand((i) => i) - .toList(), - elevation: 8, - ); - } -} - -MenuEntryButton getEntry(String title, VoidCallback proc) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - title, - style: style, - ), - proc: proc, - dismissOnClicked: true, - ); -} diff --git a/flutter/lib/common/widgets/animated_rotation_widget.dart b/flutter/lib/common/widgets/animated_rotation_widget.dart deleted file mode 100644 index 0efc71552..000000000 --- a/flutter/lib/common/widgets/animated_rotation_widget.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class AnimatedRotationWidget extends StatefulWidget { - final VoidCallback onPressed; - final ValueChanged? onHover; - final Widget child; - final RxBool? spinning; - const AnimatedRotationWidget( - {super.key, - required this.onPressed, - required this.child, - this.spinning, - this.onHover}); - - @override - State createState() => AnimatedRotationWidgetState(); -} - -class AnimatedRotationWidgetState extends State { - double turns = 0.0; - - @override - void initState() { - super.initState(); - widget.spinning?.listen((v) { - if (v && mounted) { - setState(() { - turns += 1; - }); - } - }); - } - - @override - Widget build(BuildContext context) { - return AnimatedRotation( - turns: turns, - duration: const Duration(milliseconds: 200), - onEnd: () { - if (widget.spinning?.value == true && mounted) { - setState(() => turns += 1.0); - } - }, - child: InkWell( - onTap: () { - if (mounted) setState(() => turns += 1.0); - widget.onPressed(); - }, - onHover: widget.onHover, - child: widget.child)); - } -} diff --git a/flutter/lib/common/widgets/audio_input.dart b/flutter/lib/common/widgets/audio_input.dart deleted file mode 100644 index 1f8f1a8b9..000000000 --- a/flutter/lib/common/widgets/audio_input.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; - -const _kSystemSound = 'System Sound'; - -typedef AudioINputSetDevice = void Function(String device); -typedef AudioInputBuilder = Widget Function( - List devices, String currentDevice, AudioINputSetDevice setDevice); - -class AudioInput extends StatelessWidget { - final AudioInputBuilder builder; - final bool isCm; - final bool isVoiceCall; - - const AudioInput( - {Key? key, - required this.builder, - required this.isCm, - required this.isVoiceCall}) - : super(key: key); - - static String getDefault() { - if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound); - return ''; - } - - static Future getAudioInput(bool isCm, bool isVoiceCall) { - if (isVoiceCall) { - return bind.getVoiceCallInputDevice(isCm: isCm); - } else { - return bind.mainGetOption(key: 'audio-input'); - } - } - - static Future getValue(bool isCm, bool isVoiceCall) async { - String device = await getAudioInput(isCm, isVoiceCall); - if (device.isNotEmpty) { - return device; - } else { - return getDefault(); - } - } - - static Future setDevice( - String device, bool isCm, bool isVoiceCall) async { - if (device == getDefault()) device = ''; - if (isVoiceCall) { - await bind.setVoiceCallInputDevice(isCm: isCm, device: device); - } else { - await bind.mainSetOption(key: 'audio-input', value: device); - } - } - - static Future> getDevicesInfo( - bool isCm, bool isVoiceCall) async { - List devices = (await bind.mainGetSoundInputs()).toList(); - if (bind.mainAudioSupportLoopback()) { - devices.insert(0, translate(_kSystemSound)); - } - String current = await getValue(isCm, isVoiceCall); - return {'devices': devices, 'current': current}; - } - - @override - Widget build(BuildContext context) { - return futureBuilder( - future: getDevicesInfo(isCm, isVoiceCall), - hasData: (data) { - String currentDevice = data['current']; - List devices = data['devices'] as List; - if (devices.isEmpty) { - return const Offstage(); - } - return builder(devices, currentDevice, (devices) { - setDevice(devices, isCm, isVoiceCall); - }); - }, - ); - } -} diff --git a/flutter/lib/common/widgets/autocomplete.dart b/flutter/lib/common/widgets/autocomplete.dart deleted file mode 100644 index ec64cca18..000000000 --- a/flutter/lib/common/widgets/autocomplete.dart +++ /dev/null @@ -1,257 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/formatter/id_formatter.dart'; -import '../../../models/platform_model.dart'; -import 'package:flutter_hbb/models/peer_model.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/widgets/peer_card.dart'; - -class AllPeersLoader { - List peers = []; - - bool _isPeersLoading = false; - bool _isPeersLoaded = false; - - final String _listenerKey = 'AllPeersLoader'; - - late void Function(VoidCallback) setState; - - bool get needLoad => !_isPeersLoaded && !_isPeersLoading; - bool get isPeersLoaded => _isPeersLoaded; - - AllPeersLoader(); - - void init(void Function(VoidCallback) setState) { - this.setState = setState; - gFFI.recentPeersModel.addListener(_mergeAllPeers); - gFFI.lanPeersModel.addListener(_mergeAllPeers); - gFFI.abModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers); - gFFI.groupModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers); - } - - void clear() { - gFFI.recentPeersModel.removeListener(_mergeAllPeers); - gFFI.lanPeersModel.removeListener(_mergeAllPeers); - gFFI.abModel.removePeerUpdateListener(_listenerKey); - gFFI.groupModel.removePeerUpdateListener(_listenerKey); - } - - Future getAllPeers() async { - if (!needLoad) { - return; - } - _isPeersLoading = true; - - if (gFFI.recentPeersModel.peers.isEmpty) { - bind.mainLoadRecentPeers(); - } - if (gFFI.lanPeersModel.peers.isEmpty) { - bind.mainLoadLanPeers(); - } - // No need to care about peers from abModel, and group model. - // Because they will pull data in `refreshCurrentUser()` on startup. - - final startTime = DateTime.now(); - _mergeAllPeers(); - final diffTime = DateTime.now().difference(startTime).inMilliseconds; - if (diffTime < 100) { - await Future.delayed(Duration(milliseconds: diffTime)); - } - } - - void _mergeAllPeers() { - Map combinedPeers = {}; - for (var p in gFFI.abModel.allPeers()) { - if (!combinedPeers.containsKey(p.id)) { - combinedPeers[p.id] = p.toJson(); - } - } - for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) { - if (!combinedPeers.containsKey(p.id)) { - combinedPeers[p.id] = p.toJson(); - } - } - - List parsedPeers = []; - for (var peer in combinedPeers.values) { - parsedPeers.add(Peer.fromJson(peer)); - } - - Set peerIds = combinedPeers.keys.toSet(); - for (final peer in gFFI.lanPeersModel.peers) { - if (!peerIds.contains(peer.id)) { - parsedPeers.add(peer); - peerIds.add(peer.id); - } - } - - for (final peer in gFFI.recentPeersModel.peers) { - if (!peerIds.contains(peer.id)) { - parsedPeers.add(peer); - peerIds.add(peer.id); - } - } - for (final id in gFFI.recentPeersModel.restPeerIds) { - if (!peerIds.contains(id)) { - parsedPeers.add(Peer.fromJson({'id': id})); - peerIds.add(id); - } - } - - peers = parsedPeers; - setState(() { - _isPeersLoading = false; - _isPeersLoaded = true; - }); - } -} - -class AutocompletePeerTile extends StatefulWidget { - final VoidCallback onSelect; - final Peer peer; - - const AutocompletePeerTile({ - Key? key, - required this.onSelect, - required this.peer, - }) : super(key: key); - - @override - AutocompletePeerTileState createState() => AutocompletePeerTileState(); -} - -class AutocompletePeerTileState extends State { - List _frontN(List list, int n) { - if (list.length <= n) { - return list; - } else { - return list.sublist(0, n); - } - } - - @override - Widget build(BuildContext context) { - final double tileRadius = 5; - final name = - '${widget.peer.username}${widget.peer.username.isNotEmpty && widget.peer.hostname.isNotEmpty ? '@' : ''}${widget.peer.hostname}'; - final greyStyle = TextStyle( - fontSize: 11, - color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - final child = GestureDetector( - onTap: () => widget.onSelect(), - child: Padding( - padding: EdgeInsets.only(left: 5, right: 5), - child: Container( - height: 42, - margin: EdgeInsets.only(bottom: 5), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - decoration: BoxDecoration( - color: str2color( - '${widget.peer.id}${widget.peer.platform}', 0x7f), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(tileRadius), - bottomLeft: Radius.circular(tileRadius), - ), - ), - alignment: Alignment.center, - width: 42, - height: null, - child: Padding( - padding: EdgeInsets.all(6), - child: getPlatformImage(widget.peer.platform, - size: 30))), - Expanded( - child: Container( - padding: EdgeInsets.only(left: 10), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.only( - topRight: Radius.circular(tileRadius), - bottomRight: Radius.circular(tileRadius), - ), - ), - child: Row( - children: [ - Expanded( - child: Container( - margin: EdgeInsets.only(top: 2), - child: Container( - margin: EdgeInsets.only(top: 2), - child: Column( - children: [ - Container( - margin: - EdgeInsets.only(top: 2), - child: Row(children: [ - getOnline( - 8, widget.peer.online), - Expanded( - child: Text( - widget.peer.alias.isEmpty - ? formatID( - widget.peer.id) - : widget.peer.alias, - overflow: - TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .titleSmall, - )), - widget.peer.alias.isNotEmpty - ? Padding( - padding: - const EdgeInsets - .only( - left: 5, - right: 5), - child: Text( - "(${widget.peer.id})", - style: greyStyle, - overflow: - TextOverflow - .ellipsis, - )) - : Container(), - ])), - Align( - alignment: Alignment.centerLeft, - child: Text( - name, - style: greyStyle, - textAlign: TextAlign.start, - overflow: - TextOverflow.ellipsis, - ), - ), - ], - )))), - ], - )), - ) - ], - )))); - final colors = _frontN(widget.peer.tags, 25) - .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) - .toList(); - return Tooltip( - message: !(isDesktop || isWebDesktop) - ? '' - : widget.peer.tags.isNotEmpty - ? '${translate('Tags')}: ${widget.peer.tags.join(', ')}' - : '', - child: Stack(children: [ - child, - if (colors.isNotEmpty) - Positioned( - top: 5, - right: 10, - child: CustomPaint( - painter: TagPainter(radius: 3, colors: colors), - ), - ) - ]), - ); - } -} diff --git a/flutter/lib/common/widgets/chat_page.dart b/flutter/lib/common/widgets/chat_page.dart deleted file mode 100644 index 4b0954d40..000000000 --- a/flutter/lib/common/widgets/chat_page.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:dash_chat_2/dash_chat_2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/models/chat_model.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; - -import '../../mobile/pages/home_page.dart'; - -enum ChatPageType { - mobileMain, - desktopCM, -} - -class ChatPage extends StatelessWidget implements PageShape { - late final ChatModel chatModel; - final ChatPageType? type; - - ChatPage({ChatModel? chatModel, this.type}) { - this.chatModel = chatModel ?? gFFI.chatModel; - } - - @override - final title = translate("Chat"); - - @override - final icon = unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum); - - @override - final appBarActions = [ - PopupMenuButton( - tooltip: "", - icon: unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum, - icon: Icon(Icons.group)), - itemBuilder: (context) { - // only mobile need [appBarActions], just bind gFFI.chatModel - final chatModel = gFFI.chatModel; - return chatModel.messages.entries.map((entry) { - final key = entry.key; - final user = entry.value.chatUser; - final client = gFFI.serverModel.clients - .firstWhereOrNull((e) => e.id == key.connId); - final connected = - gFFI.serverModel.clients.any((e) => e.id == key.connId); - return PopupMenuItem( - child: Row( - children: [ - Icon( - key.isOut - ? Icons.call_made_rounded - : Icons.call_received_rounded, - color: MyTheme.accent) - .marginOnly(right: 6), - Text("${user.firstName} ${user.id}"), - if (connected) - Container( - width: 10, - height: 10, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Color.fromARGB(255, 46, 205, 139)), - ).marginSymmetric(horizontal: 2), - if (client != null) - unreadMessageCountBuilder(client.unreadChatMessageCount) - .marginOnly(left: 4) - ], - ), - value: key, - ); - }).toList(); - }, - onSelected: (key) { - gFFI.chatModel.changeCurrentKey(key); - }) - ]; - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider.value( - value: chatModel, - child: Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: Consumer( - builder: (context, chatModel, child) { - final readOnly = type == ChatPageType.mobileMain && - (chatModel.currentKey.connId == ChatModel.clientModeID || - gFFI.serverModel.clients.every((e) => - e.id != chatModel.currentKey.connId || - chatModel.currentUser == null)) || - type == ChatPageType.desktopCM && - gFFI.serverModel.clients - .firstWhereOrNull( - (e) => e.id == chatModel.currentKey.connId) - ?.disconnected == - true; - return Stack( - children: [ - LayoutBuilder(builder: (context, constraints) { - final chat = DashChat( - onSend: chatModel.send, - currentUser: chatModel.me, - messages: chatModel - .messages[chatModel.currentKey]?.chatMessages ?? - [], - readOnly: readOnly, - inputOptions: InputOptions( - focusNode: chatModel.inputNode, - textController: chatModel.textController, - inputTextStyle: TextStyle( - fontSize: 14, - color: Theme.of(context).textTheme.titleLarge?.color), - inputDecoration: InputDecoration( - isDense: true, - hintText: translate('Write a message'), - filled: true, - fillColor: Theme.of(context).colorScheme.background, - contentPadding: EdgeInsets.all(10), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - width: 1, - style: BorderStyle.solid, - ), - ), - ), - sendButtonBuilder: defaultSendButton( - padding: - EdgeInsets.symmetric(horizontal: 6, vertical: 0), - color: MyTheme.accent, - icon: Icons.send_rounded, - ), - ), - messageOptions: MessageOptions( - showOtherUsersAvatar: false, - showOtherUsersName: false, - textColor: Colors.white, - maxWidth: constraints.maxWidth * 0.7, - messageTextBuilder: (message, _, __) { - final isOwnMessage = message.user.id.isBlank!; - return Column( - crossAxisAlignment: isOwnMessage - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - Text(message.text, - style: TextStyle(color: Colors.white)), - Text( - "${message.createdAt.hour}:${message.createdAt.minute.toString().padLeft(2, '0')}", - style: TextStyle( - color: Colors.white, - fontSize: 8, - ), - ).marginOnly(top: 3), - ], - ); - }, - messageDecorationBuilder: - (message, previousMessage, nextMessage) { - final isOwnMessage = message.user.id.isBlank!; - return defaultMessageDecoration( - color: - isOwnMessage ? MyTheme.accent : Colors.blueGrey, - borderTopLeft: 8, - borderTopRight: 8, - borderBottomRight: isOwnMessage ? 2 : 8, - borderBottomLeft: isOwnMessage ? 8 : 2, - ); - }, - ), - ).workaroundFreezeLinuxMint(); - return SelectionArea(child: chat); - }), - ], - ).paddingOnly(bottom: 8); - }, - ), - ), - ); - } -} diff --git a/flutter/lib/common/widgets/connection_page_title.dart b/flutter/lib/common/widgets/connection_page_title.dart deleted file mode 100644 index ba03c2656..000000000 --- a/flutter/lib/common/widgets/connection_page_title.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../../common.dart'; - -Widget getConnectionPageTitle(BuildContext context, bool isWeb) { - return Row( - children: [ - Expanded( - child: Row( - children: [ - AutoSizeText( - translate('Control Remote Desktop'), - maxLines: 1, - style: Theme.of(context) - .textTheme - .titleLarge - ?.merge(TextStyle(height: 1)), - ).marginOnly(right: 4), - Tooltip( - waitDuration: Duration(milliseconds: 300), - message: translate(isWeb ? "web_id_input_tip" : "id_input_tip"), - child: Icon( - Icons.help_outline_outlined, - size: 16, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5), - ), - ), - ], - )), - ], - ); -} diff --git a/flutter/lib/common/widgets/custom_password.dart b/flutter/lib/common/widgets/custom_password.dart deleted file mode 100644 index dafc23b44..000000000 --- a/flutter/lib/common/widgets/custom_password.dart +++ /dev/null @@ -1,129 +0,0 @@ -// https://github.com/rodrigobastosv/fancy_password_field -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:get/get.dart'; -import 'package:password_strength/password_strength.dart'; - -abstract class ValidationRule { - String get name; - bool validate(String value); -} - -class UppercaseValidationRule extends ValidationRule { - @override - String get name => translate('uppercase'); - @override - bool validate(String value) { - return value.runes.any((int rune) { - var character = String.fromCharCode(rune); - return character.toUpperCase() == character && - character.toLowerCase() != character; - }); - } -} - -class LowercaseValidationRule extends ValidationRule { - @override - String get name => translate('lowercase'); - - @override - bool validate(String value) { - return value.runes.any((int rune) { - var character = String.fromCharCode(rune); - return character.toLowerCase() == character && - character.toUpperCase() != character; - }); - } -} - -class DigitValidationRule extends ValidationRule { - @override - String get name => translate('digit'); - - @override - bool validate(String value) { - return value.contains(RegExp(r'[0-9]')); - } -} - -class SpecialCharacterValidationRule extends ValidationRule { - @override - String get name => translate('special character'); - - @override - bool validate(String value) { - return value.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]')); - } -} - -class MinCharactersValidationRule extends ValidationRule { - final int _numberOfCharacters; - MinCharactersValidationRule(this._numberOfCharacters); - - @override - String get name => translate('length>=$_numberOfCharacters'); - - @override - bool validate(String value) { - return value.length >= _numberOfCharacters; - } -} - -class PasswordStrengthIndicator extends StatelessWidget { - final RxString password; - final double weakMedium = 0.33; - final double mediumStrong = 0.67; - const PasswordStrengthIndicator({Key? key, required this.password}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Obx(() { - var strength = estimatePasswordStrength(password.value); - return Row( - children: [ - Expanded( - child: _indicator( - password.isEmpty ? Colors.grey : _getColor(strength))), - Expanded( - child: _indicator(password.isEmpty || strength < weakMedium - ? Colors.grey - : _getColor(strength))), - Expanded( - child: _indicator(password.isEmpty || strength < mediumStrong - ? Colors.grey - : _getColor(strength))), - Text(password.isEmpty ? '' : translate(_getLabel(strength))) - .marginOnly(left: password.isEmpty ? 0 : 8), - ], - ); - }); - } - - Widget _indicator(Color color) { - return Container( - height: 8, - color: color, - ); - } - - String _getLabel(double strength) { - if (strength < weakMedium) { - return 'Weak'; - } else if (strength < mediumStrong) { - return 'Medium'; - } else { - return 'Strong'; - } - } - - Color _getColor(double strength) { - if (strength < weakMedium) { - return Colors.yellow; - } else if (strength < mediumStrong) { - return Colors.blue; - } else { - return Colors.green; - } - } -} diff --git a/flutter/lib/common/widgets/custom_scale_base.dart b/flutter/lib/common/widgets/custom_scale_base.dart deleted file mode 100644 index 6eceef13f..000000000 --- a/flutter/lib/common/widgets/custom_scale_base.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:debounce_throttle/debounce_throttle.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/utils/scale.dart'; -import 'package:flutter_hbb/common.dart'; - -/// Base class providing shared custom scale control logic for both mobile and desktop widgets. -/// Implementations must provide [ffi] and [onScaleChanged] getters. -abstract class CustomScaleControls extends State { - /// FFI instance for session interaction - FFI get ffi; - - /// Callback invoked when scale value changes - ValueChanged? get onScaleChanged; - - late int _scaleValue; - late final Debouncer _debouncerScale; - // Normalized slider position in [0, 1]. We map it nonlinearly to percent. - double _scalePos = 0.0; - - int get scaleValue => _scaleValue; - double get scalePos => _scalePos; - - int mapPosToPercent(double p) => _mapPosToPercent(p); - - static const int minPercent = kScaleCustomMinPercent; - static const int pivotPercent = kScaleCustomPivotPercent; // 100% should be at 1/3 of track - static const int maxPercent = kScaleCustomMaxPercent; - static const double pivotPos = kScaleCustomPivotPos; // first 1/3 → up to 100% - static const double detentEpsilon = kScaleCustomDetentEpsilon; // snap range around pivot (~0.6%) - - // Clamp helper for local use - int _clampScale(int v) => clampCustomScalePercent(v); - - // Map normalized position [0,1] → percent [5,1000] with 100 at 1/3 width. - int _mapPosToPercent(double p) { - if (p <= 0.0) return minPercent; - if (p >= 1.0) return maxPercent; - if (p <= pivotPos) { - final q = p / pivotPos; // 0..1 - final v = minPercent + q * (pivotPercent - minPercent); - return _clampScale(v.round()); - } else { - final q = (p - pivotPos) / (1.0 - pivotPos); // 0..1 - final v = pivotPercent + q * (maxPercent - pivotPercent); - return _clampScale(v.round()); - } - } - - // Map percent [5,1000] → normalized position [0,1] - double _mapPercentToPos(int percent) { - final p = _clampScale(percent); - if (p <= pivotPercent) { - final q = (p - minPercent) / (pivotPercent - minPercent); - return q * pivotPos; - } else { - final q = (p - pivotPercent) / (maxPercent - pivotPercent); - return pivotPos + q * (1.0 - pivotPos); - } - } - - // Snap normalized position to the pivot when close to it - double _snapNormalizedPos(double p) { - if ((p - pivotPos).abs() <= detentEpsilon) return pivotPos; - if (p < 0.0) return 0.0; - if (p > 1.0) return 1.0; - return p; - } - - @override - void initState() { - super.initState(); - _scaleValue = 100; - _debouncerScale = Debouncer( - kDebounceCustomScaleDuration, - onChanged: (v) async { - await _applyScale(v); - }, - initialValue: _scaleValue, - ); - WidgetsBinding.instance.addPostFrameCallback((_) async { - try { - final v = await getSessionCustomScalePercent(ffi.sessionId); - if (mounted) { - setState(() { - _scaleValue = v; - _scalePos = _mapPercentToPos(v); - }); - } - } catch (e, st) { - debugPrint('[CustomScale] Failed to get initial value: $e'); - debugPrintStack(stackTrace: st); - } - }); - } - - Future _applyScale(int v) async { - v = clampCustomScalePercent(v); - setState(() { - _scaleValue = v; - }); - try { - await bind.sessionSetFlutterOption( - sessionId: ffi.sessionId, - k: kCustomScalePercentKey, - v: v.toString()); - final curStyle = await bind.sessionGetViewStyle(sessionId: ffi.sessionId); - if (curStyle != kRemoteViewStyleCustom) { - await bind.sessionSetViewStyle( - sessionId: ffi.sessionId, value: kRemoteViewStyleCustom); - } - await ffi.canvasModel.updateViewStyle(); - if (isMobile) { - HapticFeedback.selectionClick(); - } - onScaleChanged?.call(v); - } catch (e, st) { - debugPrint('[CustomScale] Apply failed: $e'); - debugPrintStack(stackTrace: st); - } - } - - void nudgeScale(int delta) { - final next = _clampScale(_scaleValue + delta); - setState(() { - _scaleValue = next; - _scalePos = _mapPercentToPos(next); - }); - onScaleChanged?.call(next); - _debouncerScale.value = next; - } - - @override - void dispose() { - _debouncerScale.cancel(); - super.dispose(); - } - - void onSliderChanged(double v) { - final snapped = _snapNormalizedPos(v); - final next = _mapPosToPercent(snapped); - if (next != _scaleValue || snapped != _scalePos) { - setState(() { - _scalePos = snapped; - _scaleValue = next; - }); - onScaleChanged?.call(next); - _debouncerScale.value = next; - } - } -} diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart deleted file mode 100644 index 7534fb2a1..000000000 --- a/flutter/lib/common/widgets/dialog.dart +++ /dev/null @@ -1,2867 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:bot_toast/bot_toast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common/shared_state.dart'; -import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/models/peer_model.dart'; -import 'package:flutter_hbb/models/peer_tab_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:get/get.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:flutter_hbb/utils/http_service.dart' as http; - -import '../../common.dart'; -import '../../models/model.dart'; -import '../../models/platform_model.dart'; -import 'address_book.dart'; - -void clientClose(SessionID sessionId, FFI ffi) async { - if (allowAskForNoteAtEndOfConnection(ffi, true)) { - if (await showConnEndAuditDialogCloseCanceled(ffi: ffi)) { - return; - } - closeConnection(); - } else { - msgBox(sessionId, 'info', 'Close', 'Are you sure to close the connection?', - '', ffi.dialogManager); - } -} - -abstract class ValidationRule { - String get name; - bool validate(String value); -} - -class LengthRangeValidationRule extends ValidationRule { - final int _min; - final int _max; - - LengthRangeValidationRule(this._min, this._max); - - @override - String get name => translate('length %min% to %max%') - .replaceAll('%min%', _min.toString()) - .replaceAll('%max%', _max.toString()); - - @override - bool validate(String value) { - return value.length >= _min && value.length <= _max; - } -} - -class RegexValidationRule extends ValidationRule { - final String _name; - final RegExp _regex; - - RegexValidationRule(this._name, this._regex); - - @override - String get name => translate(_name); - - @override - bool validate(String value) { - return value.isNotEmpty ? value.contains(_regex) : false; - } -} - -void changeIdDialog() { - var newId = ""; - var msg = ""; - var isInProgress = false; - TextEditingController controller = TextEditingController(); - final RxString rxId = controller.text.trim().obs; - - final rules = [ - RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')), - LengthRangeValidationRule(6, 16), - RegexValidationRule('allowed characters', RegExp(r'^[\w-]*$')) - ]; - - gFFI.dialogManager.show((setState, close, context) { - submit() async { - debugPrint("onSubmit"); - newId = controller.text.trim(); - - final Iterable violations = rules.where((r) => !r.validate(newId)); - if (violations.isNotEmpty) { - setState(() { - msg = (isDesktop || isWebDesktop) - ? '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}' - : violations.map((r) => r.name).join(', '); - }); - return; - } - - setState(() { - msg = ""; - isInProgress = true; - bind.mainChangeId(newId: newId); - }); - - var status = await bind.mainGetAsyncStatus(); - while (status == " ") { - await Future.delayed(const Duration(milliseconds: 100)); - status = await bind.mainGetAsyncStatus(); - } - if (status.isEmpty) { - // ok - close(); - return; - } - setState(() { - isInProgress = false; - msg = (isDesktop || isWebDesktop) - ? '${translate('Prompt')}: ${translate(status)}' - : translate(status); - }); - } - - return CustomAlertDialog( - title: Text(translate("Change ID")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("id_change_tip")), - const SizedBox( - height: 12.0, - ), - TextField( - decoration: InputDecoration( - labelText: translate('Your new ID'), - errorText: msg.isEmpty ? null : translate(msg), - suffixText: '${rxId.value.length}/16', - suffixStyle: const TextStyle(fontSize: 12, color: Colors.grey)), - inputFormatters: [ - LengthLimitingTextInputFormatter(16), - // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) - ], - controller: controller, - autofocus: true, - onChanged: (value) { - setState(() { - rxId.value = value.trim(); - msg = ''; - }); - }, - ).workaroundFreezeLinuxMint(), - const SizedBox( - height: 8.0, - ), - (isDesktop || isWebDesktop) - ? 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(), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void changeWhiteList({Function()? callback}) async { - final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist); - var newWhiteListField = curWhiteList == defaultOptionWhitelist - ? '' - : curWhiteList.split(',').join('\n'); - var controller = TextEditingController(text: newWhiteListField); - var msg = ""; - var isInProgress = false; - final isOptFixed = isOptionFixed(kOptionWhitelist); - gFFI.dialogManager.show((setState, close, context) { - return CustomAlertDialog( - title: Text(translate("IP Whitelisting")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("whitelist_sep")), - const SizedBox( - height: 8.0, - ), - Row( - children: [ - Expanded( - child: TextField( - maxLines: null, - decoration: InputDecoration( - errorText: msg.isEmpty ? null : translate(msg), - ), - controller: controller, - enabled: !isOptFixed, - autofocus: true) - .workaroundFreezeLinuxMint(), - ), - ], - ), - const SizedBox( - height: 4.0, - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - if (!isOptFixed) - dialogButton("Clear", onPressed: () async { - await bind.mainSetOption( - key: kOptionWhitelist, value: defaultOptionWhitelist); - callback?.call(); - close(); - }, isOutline: true), - if (!isOptFixed) - dialogButton( - "OK", - onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - }); - newWhiteListField = controller.text.trim(); - var newWhiteList = ""; - if (newWhiteListField.isEmpty) { - // pass - } else { - final ips = - newWhiteListField.trim().split(RegExp(r"[\s,;\n]+")); - // test ip - final ipMatch = RegExp( - r"^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$"); - final ipv6Match = RegExp( - r"^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$"); - for (final ip in ips) { - if (!ipMatch.hasMatch(ip) && !ipv6Match.hasMatch(ip)) { - msg = "${translate("Invalid IP")} $ip"; - setState(() { - isInProgress = false; - }); - return; - } - } - newWhiteList = ips.join(','); - } - if (newWhiteList.trim().isEmpty) { - newWhiteList = defaultOptionWhitelist; - } - await bind.mainSetOption( - key: kOptionWhitelist, value: newWhiteList); - callback?.call(); - close(); - }, - ), - ], - onCancel: close, - ); - }); -} - -Future changeDirectAccessPort( - String currentIP, String currentPort) async { - final controller = TextEditingController(text: currentPort); - await gFFI.dialogManager.show((setState, close, context) { - return CustomAlertDialog( - title: Text(translate("Change Local Port")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8.0), - Row( - children: [ - Expanded( - child: TextField( - maxLines: null, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: '21118', - isCollapsed: true, - prefix: Text('$currentIP : '), - suffix: IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.clear, size: 16), - onPressed: () => controller.clear())), - 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])$')), - ], - controller: controller, - autofocus: true) - .workaroundFreezeLinuxMint(), - ), - ], - ), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: () async { - await bind.mainSetOption( - key: kOptionDirectAccessPort, value: controller.text); - close(); - }), - ], - onCancel: close, - ); - }); - return controller.text; -} - -Future changeAutoDisconnectTimeout(String old) async { - final controller = TextEditingController(text: old); - await gFFI.dialogManager.show((setState, close, context) { - return CustomAlertDialog( - title: Text(translate("Timeout in minutes")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8.0), - Row( - children: [ - Expanded( - child: TextField( - maxLines: null, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: '10', - isCollapsed: true, - suffix: IconButton( - padding: EdgeInsets.zero, - icon: const Icon(Icons.clear, size: 16), - onPressed: () => controller.clear())), - 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])$')), - ], - controller: controller, - autofocus: true) - .workaroundFreezeLinuxMint(), - ), - ], - ), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: () async { - await bind.mainSetOption( - key: kOptionAutoDisconnectTimeout, value: controller.text); - close(); - }), - ], - onCancel: close, - ); - }); - return controller.text; -} - -class DialogTextField extends StatelessWidget { - final String title; - final String? hintText; - final bool obscureText; - final String? errorText; - final String? helperText; - final Widget? prefixIcon; - final Widget? suffixIcon; - final TextEditingController controller; - final FocusNode? focusNode; - final TextInputType? keyboardType; - final List? inputFormatters; - final int? maxLength; - - static const kUsernameTitle = 'Username'; - static const kUsernameIcon = Icon(Icons.account_circle_outlined); - static const kPasswordTitle = 'Password'; - static const kPasswordIcon = Icon(Icons.lock_outline); - - DialogTextField( - {Key? key, - this.focusNode, - this.obscureText = false, - this.errorText, - this.helperText, - this.prefixIcon, - this.suffixIcon, - this.hintText, - this.keyboardType, - this.inputFormatters, - this.maxLength, - required this.title, - required this.controller}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: Column( - children: [ - TextField( - decoration: InputDecoration( - labelText: title, - hintText: hintText, - prefixIcon: prefixIcon, - suffixIcon: suffixIcon, - helperText: helperText, - helperMaxLines: 8, - ), - controller: controller, - focusNode: focusNode, - autofocus: true, - obscureText: obscureText, - keyboardType: keyboardType, - inputFormatters: inputFormatters, - maxLength: maxLength, - ), - if (errorText != null) - Align( - alignment: Alignment.centerLeft, - child: SelectableText( - errorText!, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 12, - ), - textAlign: TextAlign.left, - ).paddingOnly(top: 8, left: 12), - ), - ], - ).workaroundFreezeLinuxMint(), - ), - ], - ).paddingSymmetric(vertical: 4.0); - } -} - -abstract class ValidationField extends StatelessWidget { - ValidationField({Key? key}) : super(key: key); - - String? validate(); - bool get isReady; -} - -class Dialog2FaField extends ValidationField { - Dialog2FaField({ - Key? key, - required this.controller, - this.autoFocus = true, - this.reRequestFocus = false, - this.title, - this.hintText, - this.errorText, - this.readyCallback, - this.onChanged, - }) : super(key: key); - - final TextEditingController controller; - final bool autoFocus; - final bool reRequestFocus; - final String? title; - final String? hintText; - final String? errorText; - final VoidCallback? readyCallback; - final VoidCallback? onChanged; - final errMsg = translate('2FA code must be 6 digits.'); - - @override - Widget build(BuildContext context) { - return DialogVerificationCodeField( - title: title ?? translate('2FA code'), - controller: controller, - errorText: errorText, - autoFocus: autoFocus, - reRequestFocus: reRequestFocus, - hintText: hintText, - readyCallback: readyCallback, - onChanged: _onChanged, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), - ], - ); - } - - String get text => controller.text; - bool get isAllDigits => text.codeUnits.every((e) => e >= 48 && e <= 57); - - @override - bool get isReady => text.length == 6 && isAllDigits; - - @override - String? validate() => isReady ? null : errMsg; - - _onChanged(StateSetter setState, SimpleWrapper errText) { - onChanged?.call(); - - if (text.length > 6) { - setState(() => errText.value = errMsg); - return; - } - - if (!isAllDigits) { - setState(() => errText.value = errMsg); - return; - } - - if (isReady) { - readyCallback?.call(); - return; - } - - if (errText.value != null) { - setState(() => errText.value = null); - } - } -} - -class DialogEmailCodeField extends ValidationField { - DialogEmailCodeField({ - Key? key, - required this.controller, - this.autoFocus = true, - this.reRequestFocus = false, - this.hintText, - this.errorText, - this.readyCallback, - this.onChanged, - }) : super(key: key); - - final TextEditingController controller; - final bool autoFocus; - final bool reRequestFocus; - final String? hintText; - final String? errorText; - final VoidCallback? readyCallback; - final VoidCallback? onChanged; - final errMsg = translate('Email verification code must be 6 characters.'); - - @override - Widget build(BuildContext context) { - return DialogVerificationCodeField( - title: translate('Verification code'), - controller: controller, - errorText: errorText, - autoFocus: autoFocus, - reRequestFocus: reRequestFocus, - hintText: hintText, - readyCallback: readyCallback, - helperText: translate('verification_tip'), - onChanged: _onChanged, - keyboardType: TextInputType.visiblePassword, - ); - } - - String get text => controller.text; - - @override - bool get isReady => text.length == 6; - - @override - String? validate() => isReady ? null : errMsg; - - _onChanged(StateSetter setState, SimpleWrapper errText) { - onChanged?.call(); - - if (text.length > 6) { - setState(() => errText.value = errMsg); - return; - } - - if (isReady) { - readyCallback?.call(); - return; - } - - if (errText.value != null) { - setState(() => errText.value = null); - } - } -} - -class DialogVerificationCodeField extends StatefulWidget { - DialogVerificationCodeField({ - Key? key, - required this.controller, - required this.title, - this.autoFocus = true, - this.reRequestFocus = false, - this.helperText, - this.hintText, - this.errorText, - this.textLength, - this.readyCallback, - this.onChanged, - this.keyboardType, - this.inputFormatters, - }) : super(key: key); - - final TextEditingController controller; - final bool autoFocus; - final bool reRequestFocus; - final String title; - final String? helperText; - final String? hintText; - final String? errorText; - final int? textLength; - final VoidCallback? readyCallback; - final Function(StateSetter setState, SimpleWrapper errText)? - onChanged; - final TextInputType? keyboardType; - final List? inputFormatters; - - @override - State createState() => - _DialogVerificationCodeField(); -} - -class _DialogVerificationCodeField extends State { - final _focusNode = FocusNode(); - Timer? _timer; - Timer? _timerReRequestFocus; - SimpleWrapper errorText = SimpleWrapper(null); - String _preText = ''; - - @override - void initState() { - super.initState(); - if (widget.autoFocus) { - _timer = - Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus()); - - if (widget.onChanged != null) { - widget.controller.addListener(() { - final text = widget.controller.text.trim(); - if (text == _preText) return; - widget.onChanged!(setState, errorText); - _preText = text; - }); - } - } - - // software secure keyboard will take the focus since flutter 3.13 - // request focus again when android account password obtain focus - if (isAndroid && widget.reRequestFocus) { - _focusNode.addListener(() { - if (_focusNode.hasFocus) { - _timerReRequestFocus?.cancel(); - _timerReRequestFocus = Timer( - Duration(milliseconds: 100), () => _focusNode.requestFocus()); - } - }); - } - } - - @override - void dispose() { - _timer?.cancel(); - _timerReRequestFocus?.cancel(); - _focusNode.unfocus(); - _focusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DialogTextField( - title: widget.title, - controller: widget.controller, - errorText: widget.errorText ?? errorText.value, - focusNode: _focusNode, - helperText: widget.helperText, - keyboardType: widget.keyboardType, - inputFormatters: widget.inputFormatters, - ); - } -} - -class PasswordWidget extends StatefulWidget { - PasswordWidget({ - Key? key, - required this.controller, - this.autoFocus = true, - this.reRequestFocus = false, - this.hintText, - this.errorText, - this.title, - this.maxLength, - }) : super(key: key); - - final TextEditingController controller; - final bool autoFocus; - final bool reRequestFocus; - final String? hintText; - final String? errorText; - final String? title; - final int? maxLength; - - @override - State createState() => _PasswordWidgetState(); -} - -class _PasswordWidgetState extends State { - bool _passwordVisible = false; - final _focusNode = FocusNode(); - Timer? _timer; - Timer? _timerReRequestFocus; - - @override - void initState() { - super.initState(); - if (widget.autoFocus) { - _timer = - Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus()); - } - // software secure keyboard will take the focus since flutter 3.13 - // request focus again when android account password obtain focus - if (isAndroid && widget.reRequestFocus) { - _focusNode.addListener(() { - if (_focusNode.hasFocus) { - _timerReRequestFocus?.cancel(); - _timerReRequestFocus = Timer( - Duration(milliseconds: 100), () => _focusNode.requestFocus()); - } - }); - } - } - - @override - void dispose() { - _timer?.cancel(); - _timerReRequestFocus?.cancel(); - _focusNode.unfocus(); - _focusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return DialogTextField( - title: translate(widget.title ?? DialogTextField.kPasswordTitle), - hintText: translate(widget.hintText ?? 'Enter your password'), - controller: widget.controller, - prefixIcon: DialogTextField.kPasswordIcon, - suffixIcon: IconButton( - icon: Icon( - // Based on passwordVisible state choose the icon - _passwordVisible ? Icons.visibility : Icons.visibility_off, - color: MyTheme.lightTheme.primaryColor), - onPressed: () { - // Update the state i.e. toggle the state of passwordVisible variable - setState(() { - _passwordVisible = !_passwordVisible; - }); - }, - ), - obscureText: !_passwordVisible, - errorText: widget.errorText, - focusNode: _focusNode, - maxLength: widget.maxLength, - ); - } -} - -void wrongPasswordDialog(SessionID sessionId, - OverlayDialogManager dialogManager, type, title, text) { - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - cancel() { - close(); - closeConnection(); - } - - submit() { - enterPasswordDialog(sessionId, dialogManager); - } - - return CustomAlertDialog( - title: null, - content: msgboxContent(type, title, text), - onSubmit: submit, - onCancel: cancel, - actions: [ - dialogButton( - 'Cancel', - onPressed: cancel, - isOutline: true, - ), - dialogButton( - 'Retry', - onPressed: submit, - ), - ]); - }); -} - -void enterPasswordDialog( - SessionID sessionId, OverlayDialogManager dialogManager) async { - await _connectDialog( - sessionId, - dialogManager, - passwordController: TextEditingController(), - ); -} - -void enterUserLoginDialog( - SessionID sessionId, - OverlayDialogManager dialogManager, - String osAccountDescTip, - bool canRememberAccount) async { - await _connectDialog( - sessionId, - dialogManager, - osUsernameController: TextEditingController(), - osPasswordController: TextEditingController(), - osAccountDescTip: osAccountDescTip, - canRememberAccount: canRememberAccount, - ); -} - -void enterUserLoginAndPasswordDialog( - SessionID sessionId, - OverlayDialogManager dialogManager, - String osAccountDescTip, - bool canRememberAccount) async { - await _connectDialog( - sessionId, - dialogManager, - osUsernameController: TextEditingController(), - osPasswordController: TextEditingController(), - passwordController: TextEditingController(), - osAccountDescTip: osAccountDescTip, - canRememberAccount: canRememberAccount, - ); -} - -_connectDialog( - SessionID sessionId, - OverlayDialogManager dialogManager, { - TextEditingController? osUsernameController, - TextEditingController? osPasswordController, - TextEditingController? passwordController, - String? osAccountDescTip, - bool canRememberAccount = true, -}) async { - final errUsername = ''.obs; - var rememberPassword = false; - if (passwordController != null) { - rememberPassword = - await bind.sessionGetRemember(sessionId: sessionId) ?? false; - } - var rememberAccount = false; - if (canRememberAccount && osUsernameController != null) { - rememberAccount = - await bind.sessionGetRemember(sessionId: sessionId) ?? false; - } - if (osUsernameController != null) { - osUsernameController.addListener(() { - if (errUsername.value.isNotEmpty) { - errUsername.value = ''; - } - }); - } - - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - cancel() { - close(); - closeConnection(); - } - - submit() { - if (osUsernameController != null) { - if (osUsernameController.text.trim().isEmpty) { - errUsername.value = translate('Empty Username'); - setState(() {}); - return; - } - } - final osUsername = osUsernameController?.text.trim() ?? ''; - final osPassword = osPasswordController?.text.trim() ?? ''; - final password = passwordController?.text.trim() ?? ''; - if (passwordController != null && password.isEmpty) return; - if (rememberAccount) { - bind.sessionPeerOption( - sessionId: sessionId, name: 'os-username', value: osUsername); - bind.sessionPeerOption( - sessionId: sessionId, name: 'os-password', value: osPassword); - } - gFFI.login( - osUsername, - osPassword, - sessionId, - password, - rememberPassword, - ); - close(); - dialogManager.showLoading(translate('Logging in...'), - onCancel: closeConnection); - } - - descWidget(String text) { - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - text, - maxLines: 3, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 16), - ), - ), - Container( - height: 8, - ), - ], - ); - } - - rememberWidget( - String desc, - bool remember, - ValueChanged? onChanged, - ) { - return CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text(desc), - value: remember, - onChanged: onChanged, - ); - } - - osAccountWidget() { - if (osUsernameController == null || osPasswordController == null) { - return Offstage(); - } - return Column( - children: [ - if (osAccountDescTip != null) descWidget(translate(osAccountDescTip)), - DialogTextField( - title: translate(DialogTextField.kUsernameTitle), - controller: osUsernameController, - prefixIcon: DialogTextField.kUsernameIcon, - errorText: null, - ), - if (errUsername.value.isNotEmpty) - Align( - alignment: Alignment.centerLeft, - child: SelectableText( - errUsername.value, - style: TextStyle( - color: Theme.of(context).colorScheme.error, - fontSize: 12, - ), - textAlign: TextAlign.left, - ).paddingOnly(left: 12, bottom: 2), - ), - PasswordWidget( - controller: osPasswordController, - autoFocus: false, - ), - if (canRememberAccount) - rememberWidget( - translate('remember_account_tip'), - rememberAccount, - (v) { - if (v != null) { - setState(() => rememberAccount = v); - } - }, - ), - ], - ); - } - - passwdWidget() { - if (passwordController == null) { - return Offstage(); - } - return Column( - children: [ - descWidget(translate('verify_rustdesk_password_tip')), - PasswordWidget( - controller: passwordController, - autoFocus: osUsernameController == null, - ), - rememberWidget( - translate('Remember password'), - rememberPassword, - (v) { - if (v != null) { - setState(() => rememberPassword = v); - } - }, - ), - ], - ); - } - - return CustomAlertDialog( - 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: [ - osAccountWidget(), - osUsernameController == null || passwordController == null - ? Offstage() - : Container(height: 12), - passwdWidget(), - ]), - 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, - ); - }); -} - -void showWaitUacDialog( - SessionID sessionId, OverlayDialogManager dialogManager, String type) { - dialogManager.dismissAll(); - dialogManager.show( - tag: '$sessionId-wait-uac', - (setState, close, context) => CustomAlertDialog( - title: null, - content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'), - actions: [ - dialogButton( - 'OK', - icon: Icon(Icons.done_rounded), - onPressed: close, - ), - ], - )); -} - -// Another username && password dialog? -void showRequestElevationDialog( - SessionID sessionId, OverlayDialogManager dialogManager) { - RxString groupValue = ''.obs; - RxString errUser = ''.obs; - RxString errPwd = ''.obs; - TextEditingController userController = TextEditingController(); - TextEditingController pwdController = TextEditingController(); - - void onRadioChanged(String? value) { - if (value != null) { - groupValue.value = value; - } - } - - // TODO get from theme - final double fontSizeNote = 13.00; - - Widget OptionRequestPermissions = Obx( - () => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Radio( - visualDensity: VisualDensity(horizontal: -4, vertical: -4), - value: '', - groupValue: groupValue.value, - onChanged: onRadioChanged, - ).marginOnly(right: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InkWell( - hoverColor: Colors.transparent, - onTap: () => groupValue.value = '', - child: Text( - translate('Ask the remote user for authentication'), - ), - ).marginOnly(bottom: 10), - Text( - translate('Choose this if the remote account is administrator'), - style: TextStyle(fontSize: fontSizeNote), - ), - ], - ).marginOnly(top: 3), - ), - ], - ), - ); - - Widget OptionCredentials = Obx( - () => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Radio( - visualDensity: VisualDensity(horizontal: -4, vertical: -4), - value: 'logon', - groupValue: groupValue.value, - onChanged: onRadioChanged, - ).marginOnly(right: 10), - Expanded( - child: InkWell( - hoverColor: Colors.transparent, - onTap: () => onRadioChanged('logon'), - child: Text( - translate('Transmit the username and password of administrator'), - ), - ).marginOnly(top: 4), - ), - ], - ), - ); - - Widget UacNote = Container( - padding: EdgeInsets.fromLTRB(10, 8, 8, 8), - decoration: BoxDecoration( - color: MyTheme.currentThemeMode() == ThemeMode.dark - ? Color.fromARGB(135, 87, 87, 90) - : Colors.grey[100], - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey), - ), - child: Row( - children: [ - Icon(Icons.info_outline_rounded, size: 20).marginOnly(right: 10), - Expanded( - child: Text( - translate('still_click_uac_tip'), - style: TextStyle( - fontSize: fontSizeNote, fontWeight: FontWeight.normal), - ), - ) - ], - ), - ); - - var content = Obx( - () => Column( - children: [ - OptionRequestPermissions.marginOnly(bottom: 15), - OptionCredentials, - Offstage( - offstage: 'logon' != groupValue.value, - child: Column( - children: [ - UacNote.marginOnly(bottom: 10), - DialogTextField( - controller: userController, - title: translate('Username'), - hintText: translate('elevation_username_tip'), - prefixIcon: DialogTextField.kUsernameIcon, - errorText: errUser.isEmpty ? null : errUser.value, - ), - PasswordWidget( - controller: pwdController, - autoFocus: false, - errorText: errPwd.isEmpty ? null : errPwd.value, - ), - ], - ).marginOnly(left: stateGlobal.isPortrait.isFalse ? 35 : 0), - ).marginOnly(top: 10), - ], - ), - ); - - dialogManager.dismissAll(); - dialogManager.show(tag: '$sessionId-request-elevation', - (setState, close, context) { - void submit() { - if (groupValue.value == 'logon') { - if (userController.text.isEmpty) { - errUser.value = translate('Empty Username'); - return; - } - if (pwdController.text.isEmpty) { - errPwd.value = translate('Empty Password'); - return; - } - bind.sessionElevateWithLogon( - sessionId: sessionId, - username: userController.text, - password: pwdController.text); - } else { - bind.sessionElevateDirect(sessionId: sessionId); - } - close(); - showWaitUacDialog(sessionId, dialogManager, "wait-uac"); - } - - return CustomAlertDialog( - title: Text(translate('Request Elevation')), - content: content, - 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, - ); - }); -} - -void showOnBlockDialog( - SessionID sessionId, - String type, - String title, - String text, - OverlayDialogManager dialogManager, -) { - if (dialogManager.existing('$sessionId-wait-uac') || - dialogManager.existing('$sessionId-request-elevation')) { - return; - } - dialogManager.show(tag: '$sessionId-$type', (setState, close, context) { - void submit() { - close(); - showRequestElevationDialog(sessionId, dialogManager); - } - - return CustomAlertDialog( - title: null, - content: msgboxContent(type, title, - "${translate(text)}${type.contains('uac') ? '\n' : '\n\n'}${translate('request_elevation_tip')}"), - actions: [ - dialogButton('Wait', onPressed: close, isOutline: true), - dialogButton('Request Elevation', onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void showElevationError(SessionID sessionId, String type, String title, - String text, OverlayDialogManager dialogManager) { - dialogManager.show(tag: '$sessionId-$type', (setState, close, context) { - void submit() { - close(); - showRequestElevationDialog(sessionId, dialogManager); - } - - return CustomAlertDialog( - title: null, - content: msgboxContent(type, title, text), - actions: [ - dialogButton('Cancel', onPressed: () { - close(); - }, isOutline: true), - if (text != 'No permission') dialogButton('Retry', onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void showWaitAcceptDialog(SessionID sessionId, String type, String title, - String text, OverlayDialogManager dialogManager) { - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - onCancel() { - closeConnection(); - } - - return CustomAlertDialog( - title: null, - content: msgboxContent(type, title, text), - actions: [ - dialogButton('Cancel', onPressed: onCancel, isOutline: true), - ], - onCancel: onCancel, - ); - }); -} - -void showRestartRemoteDevice(PeerInfo pi, String id, SessionID sessionId, - OverlayDialogManager dialogManager) async { - final res = await dialogManager - .show((setState, close, context) => CustomAlertDialog( - title: Row(children: [ - Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28), - Flexible( - child: 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), - )); - if (res == true) bind.sessionRestartRemoteDevice(sessionId: sessionId); -} - -showSetOSPassword( - SessionID sessionId, - bool login, - OverlayDialogManager dialogManager, - String? osPassword, - Function()? closeCallback, -) async { - final controller = TextEditingController(); - osPassword ??= - await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ?? - ''; - var autoLogin = - await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') != - ''; - controller.text = osPassword; - dialogManager.show((setState, close, context) { - closeWithCallback([dynamic]) { - close(); - if (closeCallback != null) closeCallback(); - } - - submit() { - var text = controller.text.trim(); - bind.sessionPeerOption( - sessionId: sessionId, name: 'os-password', value: text); - bind.sessionPeerOption( - sessionId: sessionId, - name: 'auto-login', - value: autoLogin ? 'Y' : ''); - if (text != '' && login) { - bind.sessionInputOsPassword(sessionId: sessionId, value: text); - } - closeWithCallback(); - } - - return CustomAlertDialog( - 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); - }, - ), - ], - ), - actions: [ - dialogButton( - "Cancel", - icon: Icon(Icons.close_rounded), - onPressed: closeWithCallback, - isOutline: true, - ), - dialogButton( - "OK", - icon: Icon(Icons.done_rounded), - onPressed: submit, - ), - ], - onSubmit: submit, - onCancel: closeWithCallback, - ); - }); -} - -showSetOSAccount( - SessionID sessionId, - OverlayDialogManager dialogManager, -) async { - final usernameController = TextEditingController(); - final passwdController = TextEditingController(); - var username = - await bind.sessionGetOption(sessionId: sessionId, arg: 'os-username') ?? - ''; - var password = - await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ?? - ''; - usernameController.text = username; - passwdController.text = password; - dialogManager.show((setState, close, context) { - submit() { - final username = usernameController.text.trim(); - final password = usernameController.text.trim(); - bind.sessionPeerOption( - sessionId: sessionId, name: 'os-username', value: username); - bind.sessionPeerOption( - sessionId: sessionId, name: 'os-password', value: password); - close(); - } - - descWidget(String text) { - return Column( - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text( - text, - maxLines: 3, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 16), - ), - ), - Container( - height: 8, - ), - ], - ); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.password_rounded, color: MyTheme.accent), - Text(translate('OS Account')).paddingOnly(left: 10), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - descWidget(translate("os_account_desk_tip")), - DialogTextField( - title: translate(DialogTextField.kUsernameTitle), - controller: usernameController, - prefixIcon: DialogTextField.kUsernameIcon, - errorText: null, - ), - PasswordWidget(controller: passwdController), - ], - ), - 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, - ); - }); -} - -Widget buildNoteTextField({ - required TextEditingController controller, - required VoidCallback onEscape, -}) { - final focusNode = FocusNode( - onKey: (FocusNode node, RawKeyEvent evt) { - if (evt.logicalKey.keyLabel == 'Enter') { - if (evt is RawKeyDownEvent) { - int pos = controller.selection.base.offset; - controller.text = - '${controller.text.substring(0, pos)}\n${controller.text.substring(pos)}'; - controller.selection = - TextSelection.fromPosition(TextPosition(offset: pos + 1)); - } - return KeyEventResult.handled; - } - if (evt.logicalKey.keyLabel == 'Esc') { - if (evt is RawKeyDownEvent) { - onEscape(); - } - return KeyEventResult.handled; - } else { - return KeyEventResult.ignored; - } - }, - ); - - return TextField( - autofocus: true, - keyboardType: TextInputType.multiline, - textInputAction: TextInputAction.newline, - decoration: InputDecoration( - hintText: translate('input note here'), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - contentPadding: EdgeInsets.all(12), - ), - minLines: 5, - maxLines: null, - maxLength: 256, - controller: controller, - focusNode: focusNode, - ).workaroundFreezeLinuxMint(); -} - -showAuditDialog(FFI ffi) async { - final controller = TextEditingController( - text: bind.sessionGetLastAuditNote(sessionId: ffi.sessionId)); - ffi.dialogManager.show((setState, close, context) { - submit() { - var text = controller.text; - bind.sessionSendNote(sessionId: ffi.sessionId, note: text); - close(); - } - - return CustomAlertDialog( - title: Text(translate('Note')), - content: SizedBox( - width: 250, - height: 120, - child: buildNoteTextField( - controller: controller, - onEscape: close, - )), - actions: [ - dialogButton('Cancel', onPressed: close, isOutline: true), - dialogButton('OK', onPressed: submit) - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -bool allowAskForNoteAtEndOfConnection(FFI? ffi, bool closedByControlling) { - if (ffi == null) { - return false; - } - return mainGetLocalBoolOptionSync(kOptionAllowAskForNoteAtEndOfConnection) && - bind - .sessionGetAuditServerSync(sessionId: ffi.sessionId, typ: "conn") - .isNotEmpty && - bind.sessionGetAuditGuid(sessionId: ffi.sessionId).isNotEmpty && - bind.sessionGetLastAuditNote(sessionId: ffi.sessionId).isEmpty && - (!closedByControlling || - bind.willSessionCloseCloseSession(sessionId: ffi.sessionId)); -} - -// return value: close canceled -// true: return -// false: go on -Future desktopTryShowTabAuditDialogCloseCancelled( - {required String id, required DesktopTabController tabController}) async { - try { - final page = - tabController.state.value.tabs.firstWhere((tab) => tab.key == id).page; - final ffi = (page as dynamic).ffi; - final res = await showConnEndAuditDialogCloseCanceled(ffi: ffi); - return res; - } catch (e) { - debugPrint('Failed to show audit dialog: $e'); - return false; - } -} - -// return value: -// true: return -// false: go on -Future showConnEndAuditDialogCloseCanceled( - {required FFI ffi, String? type, String? title, String? text}) async { - final res = await _showConnEndAuditDialogCloseCanceled( - ffi: ffi, type: type, title: title, text: text); - if (res == true) { - return true; - } - return false; -} - -// return value: -// true: return -// false / null: go on -Future _showConnEndAuditDialogCloseCanceled({ - required FFI ffi, - String? type, - String? title, - String? text, -}) async { - final closedByControlling = type == null; - final showDialog = allowAskForNoteAtEndOfConnection(ffi, closedByControlling); - if (!showDialog) { - return false; - } - ffi.dialogManager.dismissAll(); - - Future updateAuditNoteByGuid(String auditGuid, String note) async { - debugPrint('Updating audit note for GUID: $auditGuid, note: $note'); - try { - final apiServer = await bind.mainGetApiServer(); - if (apiServer.isEmpty) { - debugPrint('API server is empty, cannot update audit note'); - return; - } - final url = '$apiServer/api/audit'; - var headers = getHttpHeaders(); - headers['Content-Type'] = "application/json"; - final body = jsonEncode({ - 'guid': auditGuid, - 'note': note, - }); - - final response = await http.put( - Uri.parse(url), - headers: headers, - body: body, - ); - - if (response.statusCode == 200) { - debugPrint('Successfully updated audit note for GUID: $auditGuid'); - } else { - debugPrint( - 'Failed to update audit note. Status: ${response.statusCode}, Body: ${response.body}'); - } - } catch (e) { - debugPrint('Error updating audit note: $e'); - } - } - - final controller = TextEditingController(); - bool askForNote = - mainGetLocalBoolOptionSync(kOptionAllowAskForNoteAtEndOfConnection); - final isOptFixed = isOptionFixed(kOptionAllowAskForNoteAtEndOfConnection); - bool isInProgress = false; - - return await ffi.dialogManager.show((setState, close, context) { - cancel() { - close(true); - } - - set() async { - if (isInProgress) return; - setState(() { - isInProgress = true; - }); - var text = controller.text; - if (text.isNotEmpty) { - await updateAuditNoteByGuid( - bind.sessionGetAuditGuid(sessionId: ffi.sessionId), text) - .timeout(const Duration(seconds: 6), onTimeout: () { - debugPrint('updateAuditNoteByGuid timeout after 6s'); - }); - } - // Save the "ask for note" preference - if (!isOptFixed) { - await mainSetLocalBoolOption( - kOptionAllowAskForNoteAtEndOfConnection, askForNote); - } - } - - submit() async { - await set(); - close(false); - } - - final buttons = [ - dialogButton('OK', onPressed: isInProgress ? null : submit) - ]; - if (type == 'relay-hint' || type == 'relay-hint2') { - buttons.add(dialogButton('Retry', onPressed: () async { - await set(); - close(true); - ffi.ffiModel.reconnect(ffi.dialogManager, ffi.sessionId, false); - })); - if (type == 'relay-hint2') { - buttons.add(dialogButton('Connect via relay', onPressed: () async { - await set(); - close(true); - ffi.ffiModel.reconnect(ffi.dialogManager, ffi.sessionId, true); - })); - } - } - if (closedByControlling) { - buttons.add(dialogButton('Cancel', - onPressed: isInProgress ? null : cancel, isOutline: true)); - } - - Widget content; - if (closedByControlling) { - content = SelectionArea( - child: msgboxContent( - 'info', 'Close', 'Are you sure to close the connection?')); - } else { - content = - SelectionArea(child: msgboxContent(type, title ?? '', text ?? '')); - } - - return CustomAlertDialog( - title: null, - content: SizedBox( - width: 350, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - content, - const SizedBox(height: 16), - SizedBox( - height: 120, - child: buildNoteTextField( - controller: controller, - onEscape: cancel, - ), - ), - if (!isOptFixed) ...[ - const SizedBox(height: 8), - InkWell( - onTap: () { - setState(() { - askForNote = !askForNote; - }); - }, - child: Row( - children: [ - Checkbox( - value: askForNote, - onChanged: (value) { - setState(() { - askForNote = value ?? false; - }); - }, - ), - Expanded( - child: Text( - translate('note-at-conn-end-tip'), - style: const TextStyle(fontSize: 13), - ), - ), - ], - ), - ), - ], - if (isInProgress) - const LinearProgressIndicator().marginOnly(top: 4), - ], - )), - actions: buttons, - onSubmit: submit, - onCancel: cancel, - ); - }); -} - -void showConfirmSwitchSidesDialog( - SessionID sessionId, String id, OverlayDialogManager dialogManager) async { - dialogManager.show((setState, close, context) { - submit() async { - await bind.sessionSwitchSides(sessionId: sessionId); - closeConnection(id: id); - } - - return CustomAlertDialog( - content: msgboxContent('info', 'Switch Sides', - 'Please confirm if you want to share your desktop?'), - actions: [ - dialogButton('Cancel', onPressed: close, isOutline: true), - dialogButton('OK', onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async { - double initQuality = kDefaultQuality; - double initFps = kDefaultFps; - bool qualitySet = false; - bool fpsSet = false; - - bool? direct; - try { - direct = - ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect; - } catch (_) {} - bool hideFps = (await bind.mainIsUsingPublicServer() && direct != true) || - versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0; - bool hideMoreQuality = - (await bind.mainIsUsingPublicServer() && direct != true) || - versionCmp(ffi.ffiModel.pi.version, '1.2.2') < 0; - - setCustomValues({double? quality, double? fps}) async { - debugPrint("setCustomValues quality:$quality, fps:$fps"); - if (quality != null) { - qualitySet = true; - await bind.sessionSetCustomImageQuality( - sessionId: sessionId, value: quality.toInt()); - } - if (fps != null) { - fpsSet = true; - await bind.sessionSetCustomFps(sessionId: sessionId, fps: fps.toInt()); - } - if (!qualitySet) { - qualitySet = true; - await bind.sessionSetCustomImageQuality( - sessionId: sessionId, value: initQuality.toInt()); - } - if (!hideFps && !fpsSet) { - fpsSet = true; - await bind.sessionSetCustomFps( - sessionId: sessionId, fps: initFps.toInt()); - } - } - - final btnClose = dialogButton('Close', onPressed: () async { - await setCustomValues(); - ffi.dialogManager.dismissAll(); - }); - - // quality - final quality = await bind.sessionGetCustomImageQuality(sessionId: sessionId); - initQuality = quality != null && quality.isNotEmpty - ? quality[0].toDouble() - : kDefaultQuality; - if (initQuality < kMinQuality || - initQuality > (!hideMoreQuality ? kMaxMoreQuality : kMaxQuality)) { - initQuality = kDefaultQuality; - } - // fps - final fpsOption = - await bind.sessionGetOption(sessionId: sessionId, arg: 'custom-fps'); - initFps = fpsOption == null - ? kDefaultFps - : double.tryParse(fpsOption) ?? kDefaultFps; - if (initFps < kMinFps || initFps > kMaxFps) { - initFps = kDefaultFps; - } - - final content = customImageQualityWidget( - initQuality: initQuality, - initFps: initFps, - setQuality: (v) => setCustomValues(quality: v), - setFps: (v) => setCustomValues(fps: v), - showFps: !hideFps, - showMoreQuality: !hideMoreQuality); - msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]); -} - -trackpadSpeedDialog(SessionID sessionId, FFI ffi) async { - int initSpeed = ffi.inputModel.trackpadSpeed; - final curSpeed = SimpleWrapper(initSpeed); - final btnClose = dialogButton('Close', onPressed: () async { - if (curSpeed.value <= kMaxTrackpadSpeed && - curSpeed.value >= kMinTrackpadSpeed && - curSpeed.value != initSpeed) { - await bind.sessionSetTrackpadSpeed( - sessionId: sessionId, value: curSpeed.value); - await ffi.inputModel.updateTrackpadSpeed(); - } - ffi.dialogManager.dismissAll(); - }); - msgBoxCommon( - ffi.dialogManager, - 'Trackpad speed', - TrackpadSpeedWidget( - value: curSpeed, - ), - [btnClose]); -} - -void deleteConfirmDialog(Function onSubmit, String title) async { - gFFI.dialogManager.show( - (setState, close, context) { - submit() async { - await onSubmit(); - close(); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.delete_rounded, - color: Colors.red, - ), - Expanded( - child: Text(title, overflow: TextOverflow.ellipsis).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, - ); - }, - ); -} - -void editAbTagDialog( - List currentTags, Function(List) onSubmit) { - var isInProgress = false; - - final tags = List.of(gFFI.abModel.currentAbTags); - var selectedTag = currentTags.obs; - - gFFI.dialogManager.show((setState, close, context) { - submit() async { - setState(() { - isInProgress = true; - }); - await onSubmit(selectedTag); - close(); - } - - return CustomAlertDialog( - title: Text(translate("Edit Tag")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Wrap( - children: tags - .map((e) => AddressBookTag( - name: e, - tags: selectedTag, - onTap: () { - if (selectedTag.contains(e)) { - selectedTag.remove(e); - } else { - selectedTag.add(e); - } - }, - showActionMenu: false)) - .toList(growable: false), - ), - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void editAbPeerNoteDialog(String id) { - var isInProgress = false; - final currentNote = gFFI.abModel.getPeerNote(id); - var controller = TextEditingController(text: currentNote); - - gFFI.dialogManager.show((setState, close, context) { - submit() async { - setState(() { - isInProgress = true; - }); - await gFFI.abModel.changeNote(id: id, note: controller.text); - close(); - } - - return CustomAlertDialog( - title: Text(translate("Edit note")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextField( - controller: controller, - autofocus: true, - maxLines: 3, - minLines: 1, - maxLength: 300, - decoration: InputDecoration( - labelText: translate('Note'), - ), - ).workaroundFreezeLinuxMint(), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void renameDialog( - {required String oldName, - FormFieldValidator? validator, - required ValueChanged onSubmit, - Function? onCancel}) async { - RxBool isInProgress = false.obs; - var controller = TextEditingController(text: oldName); - final formKey = GlobalKey(); - gFFI.dialogManager.show((setState, close, context) { - submit() async { - String text = controller.text.trim(); - if (validator != null && formKey.currentState?.validate() == false) { - return; - } - isInProgress.value = true; - onSubmit(text); - close(); - isInProgress.value = false; - } - - cancel() { - onCancel?.call(); - close(); - } - - return CustomAlertDialog( - 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: [ - Container( - child: Form( - key: formKey, - child: TextFormField( - controller: controller, - autofocus: true, - decoration: InputDecoration(labelText: translate('Name')), - validator: validator, - ).workaroundFreezeLinuxMint(), - ), - ), - // NOT use Offstage to wrap LinearProgressIndicator - Obx(() => - isInProgress.value ? const LinearProgressIndicator() : Offstage()) - ], - ), - 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, - ); - }); -} - -void changeBot({Function()? callback}) async { - if (bind.mainHasValidBotSync()) { - await bind.mainSetOption(key: "bot", value: ""); - callback?.call(); - return; - } - String errorText = ''; - bool loading = false; - final controller = TextEditingController(); - gFFI.dialogManager.show((setState, close, context) { - onVerify() async { - final token = controller.text.trim(); - if (token == "") return; - loading = true; - errorText = ''; - setState(() {}); - final error = await bind.mainVerifyBot(token: token); - if (error == "") { - callback?.call(); - close(); - } else { - errorText = translate(error); - loading = false; - setState(() {}); - } - } - - final codeField = TextField( - autofocus: true, - controller: controller, - decoration: InputDecoration( - hintText: translate('Token'), - ), - ).workaroundFreezeLinuxMint(); - - return CustomAlertDialog( - title: Text(translate("Telegram bot")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText(translate("enable-bot-desc"), - style: TextStyle(fontSize: 12)) - .marginOnly(bottom: 12), - Row(children: [Expanded(child: codeField)]), - if (errorText != '') - Text(errorText, style: TextStyle(color: Colors.red)) - .marginOnly(top: 12), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - loading - ? CircularProgressIndicator() - : dialogButton("OK", onPressed: onVerify), - ], - onCancel: close, - ); - }); -} - -void change2fa({Function()? callback}) async { - if (bind.mainHasValid2FaSync()) { - await bind.mainSetOption(key: "2fa", value: ""); - await bind.mainClearTrustedDevices(); - callback?.call(); - return; - } - var new2fa = (await bind.mainGenerate2Fa()); - final secretRegex = RegExp(r'secret=([^&]+)'); - final secret = secretRegex.firstMatch(new2fa)?.group(1); - String? errorText; - final controller = TextEditingController(); - gFFI.dialogManager.show((setState, close, context) { - onVerify() async { - if (await bind.mainVerify2Fa(code: controller.text.trim())) { - callback?.call(); - close(); - } else { - errorText = translate('wrong-2fa-code'); - } - } - - final codeField = Dialog2FaField( - controller: controller, - errorText: errorText, - onChanged: () => setState(() => errorText = null), - title: translate('Verification code'), - readyCallback: () { - onVerify(); - setState(() {}); - }, - ); - - getOnSubmit() => codeField.isReady ? onVerify : null; - - return CustomAlertDialog( - title: Text(translate("enable-2fa-title")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText(translate("enable-2fa-desc"), - style: TextStyle(fontSize: 12)) - .marginOnly(bottom: 12), - SizedBox( - width: 160, - height: 160, - child: QrImageView( - backgroundColor: Colors.white, - data: new2fa, - version: QrVersions.auto, - size: 160, - gapless: false, - )).marginOnly(bottom: 6), - SelectableText(secret ?? '', style: TextStyle(fontSize: 12)) - .marginOnly(bottom: 12), - Row(children: [Expanded(child: codeField)]), - ], - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: getOnSubmit()), - ], - onCancel: close, - ); - }); -} - -void enter2FaDialog( - SessionID sessionId, OverlayDialogManager dialogManager) async { - final controller = TextEditingController(); - final RxBool submitReady = false.obs; - final RxBool trustThisDevice = false.obs; - - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - cancel() { - close(); - closeConnection(); - } - - submit() { - gFFI.send2FA(sessionId, controller.text.trim(), trustThisDevice.value); - close(); - dialogManager.showLoading(translate('Logging in...'), - onCancel: closeConnection); - } - - late Dialog2FaField codeField; - - codeField = Dialog2FaField( - controller: controller, - title: translate('Verification code'), - onChanged: () => submitReady.value = codeField.isReady, - ); - - final trustField = Obx(() => CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text(translate("Trust this device")), - value: trustThisDevice.value, - onChanged: (value) { - if (value == null) return; - trustThisDevice.value = value; - }, - )); - - return CustomAlertDialog( - title: Text(translate('enter-2fa-title')), - content: Column( - children: [ - codeField, - if (bind.sessionGetEnableTrustedDevices(sessionId: sessionId)) - trustField, - ], - ), - actions: [ - dialogButton('Cancel', - onPressed: cancel, - isOutline: true, - style: TextStyle( - color: Theme.of(context).textTheme.bodyMedium?.color)), - Obx(() => dialogButton( - 'OK', - onPressed: submitReady.isTrue ? submit : null, - )), - ], - onSubmit: submit, - onCancel: cancel); - }); -} - -// This dialog should not be dismissed, otherwise it will be black screen, have not reproduced this. -void showWindowsSessionsDialog( - String type, - String title, - String text, - OverlayDialogManager dialogManager, - SessionID sessionId, - String peerId, - String sessions) { - List sessionsList = []; - try { - sessionsList = json.decode(sessions); - } catch (e) { - print(e); - } - List sids = []; - List names = []; - for (var session in sessionsList) { - sids.add(session['sid']); - names.add(session['name']); - } - String selectedUserValue = sids.first; - dialogManager.dismissAll(); - dialogManager.show((setState, close, context) { - submit() { - bind.sessionSendSelectedSessionId( - sessionId: sessionId, sid: selectedUserValue); - close(); - } - - return CustomAlertDialog( - title: null, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - msgboxContent(type, title, text).marginOnly(bottom: 12), - ComboBox( - keys: sids, - values: names, - initialKey: selectedUserValue, - onChanged: (value) { - selectedUserValue = value; - }), - ], - ), - actions: [ - dialogButton('Connect', onPressed: submit, isOutline: false), - ], - ); - }); -} - -void addPeersToAbDialog( - List peers, -) async { - Future addTo(String abname) async { - final mapList = peers.map((e) { - var json = e.toJson(); - // remove password when add to another address book to avoid re-share - json.remove('password'); - json.remove('hash'); - return json; - }).toList(); - final errMsg = await gFFI.abModel.addPeersTo(mapList, abname); - if (errMsg == null) { - showToast(translate('Successful')); - return true; - } else { - BotToast.showText(text: errMsg, contentColor: Colors.red); - return false; - } - } - - // if only one address book and it is personal, add to it directly - if (gFFI.abModel.addressbooks.length == 1 && - gFFI.abModel.current.isPersonal()) { - await addTo(gFFI.abModel.currentName.value); - return; - } - - RxBool isInProgress = false.obs; - final names = gFFI.abModel.addressBooksCanWrite(); - RxString currentName = gFFI.abModel.currentName.value.obs; - TextEditingController controller = TextEditingController(); - if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) { - names.remove(currentName.value); - } - if (names.isEmpty) { - debugPrint('no address book to add peers to, should not happen'); - return; - } - if (!names.contains(currentName.value)) { - currentName.value = names[0]; - } - gFFI.dialogManager.show((setState, close, context) { - submit() async { - if (controller.text != gFFI.abModel.translatedName(currentName.value)) { - BotToast.showText( - text: 'illegal address book name: ${controller.text}', - contentColor: Colors.red); - return; - } - isInProgress.value = true; - if (await addTo(currentName.value)) { - close(); - } - isInProgress.value = false; - } - - cancel() { - close(); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(IconFont.addressBook, color: MyTheme.accent), - Text(translate('Add to address book')).paddingOnly(left: 10), - ], - ), - content: Obx(() => Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // https://github.com/flutter/flutter/issues/145081 - DropdownMenu( - initialSelection: currentName.value, - onSelected: (value) { - if (value != null) { - currentName.value = value; - } - }, - dropdownMenuEntries: names - .map((e) => DropdownMenuEntry( - value: e, label: gFFI.abModel.translatedName(e))) - .toList(), - inputDecorationTheme: InputDecorationTheme( - isDense: true, border: UnderlineInputBorder()), - enableFilter: true, - controller: controller, - ), - // NOT use Offstage to wrap LinearProgressIndicator - isInProgress.value ? const LinearProgressIndicator() : Offstage() - ], - )), - 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, - ); - }); -} - -void setSharedAbPasswordDialog(String abName, Peer peer) { - TextEditingController controller = TextEditingController(text: ''); - RxBool isInProgress = false.obs; - RxBool isInputEmpty = true.obs; - bool passwordVisible = false; - controller.addListener(() { - isInputEmpty.value = controller.text.isEmpty; - }); - gFFI.dialogManager.show((setState, close, context) { - change(String password) async { - isInProgress.value = true; - bool res = - await gFFI.abModel.changeSharedPassword(abName, peer.id, password); - isInProgress.value = false; - if (res) { - showToast(translate('Successful')); - } - close(); - } - - cancel() { - close(); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.key, color: MyTheme.accent), - Text(translate(peer.password.isEmpty - ? 'Set shared password' - : 'Change Password')) - .paddingOnly(left: 10), - ], - ), - content: Obx(() => Column(children: [ - TextField( - controller: controller, - autofocus: true, - obscureText: !passwordVisible, - decoration: InputDecoration( - suffixIcon: IconButton( - icon: Icon( - passwordVisible ? Icons.visibility : Icons.visibility_off, - color: MyTheme.lightTheme.primaryColor), - onPressed: () { - setState(() { - passwordVisible = !passwordVisible; - }); - }, - ), - ), - ).workaroundFreezeLinuxMint(), - if (!gFFI.abModel.current.isPersonal()) - Row(children: [ - Icon(Icons.info, color: Colors.amber).marginOnly(right: 4), - Text( - translate('share_warning_tip'), - style: TextStyle(fontSize: 12), - ) - ]).marginSymmetric(vertical: 10), - // NOT use Offstage to wrap LinearProgressIndicator - isInProgress.value ? const LinearProgressIndicator() : Offstage() - ])), - actions: [ - dialogButton( - "Cancel", - icon: Icon(Icons.close_rounded), - onPressed: cancel, - isOutline: true, - ), - if (peer.password.isNotEmpty) - dialogButton( - "Remove", - icon: Icon(Icons.delete_outline_rounded), - onPressed: () => change(''), - buttonStyle: ButtonStyle( - backgroundColor: MaterialStatePropertyAll(Colors.red)), - ), - Obx(() => dialogButton( - "OK", - icon: Icon(Icons.done_rounded), - onPressed: - isInputEmpty.value ? null : () => change(controller.text), - )), - ], - onSubmit: isInputEmpty.value ? null : () => change(controller.text), - onCancel: cancel, - ); - }); -} - -void CommonConfirmDialog(OverlayDialogManager dialogManager, String content, - VoidCallback onConfirm) { - dialogManager.show((setState, close, context) { - submit() { - close(); - onConfirm.call(); - } - - return CustomAlertDialog( - content: Row( - children: [ - Expanded( - child: Text(content, - style: const TextStyle(fontSize: 15), - textAlign: TextAlign.start), - ), - ], - ).marginOnly(bottom: 12), - actions: [ - dialogButton(translate("Cancel"), onPressed: close, isOutline: true), - dialogButton(translate("OK"), onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void changeUnlockPinDialog(String oldPin, Function() callback) { - final pinController = TextEditingController(text: oldPin); - final confirmController = TextEditingController(text: oldPin); - String? pinErrorText; - String? confirmationErrorText; - final maxLength = bind.mainMaxEncryptLen(); - gFFI.dialogManager.show((setState, close, context) { - submit() async { - pinErrorText = null; - confirmationErrorText = null; - final pin = pinController.text.trim(); - final confirm = confirmController.text.trim(); - if (pin != confirm) { - setState(() { - confirmationErrorText = - translate('The confirmation is not identical.'); - }); - return; - } - final errorMsg = bind.mainSetUnlockPin(pin: pin); - if (errorMsg != '') { - setState(() { - pinErrorText = translate(errorMsg); - }); - return; - } - callback.call(); - close(); - } - - return CustomAlertDialog( - title: Text(translate("Set PIN")), - content: Column( - children: [ - DialogTextField( - title: 'PIN', - controller: pinController, - obscureText: true, - errorText: pinErrorText, - maxLength: maxLength, - ), - DialogTextField( - title: translate('Confirmation'), - controller: confirmController, - obscureText: true, - errorText: confirmationErrorText, - maxLength: maxLength, - ) - ], - ).marginOnly(bottom: 12), - actions: [ - dialogButton(translate("Cancel"), onPressed: close, isOutline: true), - dialogButton(translate("OK"), onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void checkUnlockPinDialog(String correctPin, Function() passCallback) { - final controller = TextEditingController(); - String? errorText; - gFFI.dialogManager.show((setState, close, context) { - submit() async { - final pin = controller.text.trim(); - if (correctPin != pin) { - setState(() { - errorText = translate('Wrong PIN'); - }); - return; - } - passCallback.call(); - close(); - } - - return CustomAlertDialog( - content: Row( - children: [ - Expanded( - child: PasswordWidget( - title: 'PIN', - controller: controller, - errorText: errorText, - hintText: '', - )) - ], - ).marginOnly(bottom: 12), - actions: [ - dialogButton(translate("Cancel"), onPressed: close, isOutline: true), - dialogButton(translate("OK"), onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -void confrimDeleteTrustedDevicesDialog( - RxList trustedDevices, RxList selectedDevices) { - CommonConfirmDialog(gFFI.dialogManager, '${translate('Confirm Delete')}?', - () async { - if (selectedDevices.isEmpty) return; - if (selectedDevices.length == trustedDevices.length) { - await bind.mainClearTrustedDevices(); - trustedDevices.clear(); - selectedDevices.clear(); - } else { - final json = jsonEncode(selectedDevices.map((e) => e.toList()).toList()); - await bind.mainRemoveTrustedDevices(json: json); - trustedDevices.removeWhere((element) { - return selectedDevices.contains(element.hwid); - }); - selectedDevices.clear(); - } - }); -} - -void manageTrustedDeviceDialog() async { - RxList trustedDevices = (await TrustedDevice.get()).obs; - RxList selectedDevices = RxList.empty(); - gFFI.dialogManager.show((setState, close, context) { - return CustomAlertDialog( - title: Text(translate("Manage trusted devices")), - content: trustedDevicesTable(trustedDevices, selectedDevices), - actions: [ - Obx(() => dialogButton(translate("Delete"), - onPressed: selectedDevices.isEmpty - ? null - : () { - confrimDeleteTrustedDevicesDialog( - trustedDevices, - selectedDevices, - ); - }, - isOutline: false) - .marginOnly(top: 12)), - dialogButton(translate("Close"), onPressed: close, isOutline: true) - .marginOnly(top: 12), - ], - onCancel: close, - ); - }); -} - -class TrustedDevice { - late final Uint8List hwid; - late final int time; - late final String id; - late final String name; - late final String platform; - - TrustedDevice.fromJson(Map json) { - final hwidList = json['hwid'] as List; - hwid = Uint8List.fromList(hwidList.cast()); - time = json['time']; - id = json['id']; - name = json['name']; - platform = json['platform']; - } - - String daysRemaining() { - final expiry = time + 90 * 24 * 60 * 60 * 1000; - final remaining = expiry - DateTime.now().millisecondsSinceEpoch; - if (remaining < 0) { - return '0'; - } - return (remaining / (24 * 60 * 60 * 1000)).toStringAsFixed(0); - } - - static Future> get() async { - final List devices = List.empty(growable: true); - try { - final devicesJson = await bind.mainGetTrustedDevices(); - if (devicesJson.isNotEmpty) { - final devicesList = json.decode(devicesJson); - if (devicesList is List) { - for (var device in devicesList) { - devices.add(TrustedDevice.fromJson(device)); - } - } - } - } catch (e) { - print(e.toString()); - } - devices.sort((a, b) => b.time.compareTo(a.time)); - return devices; - } -} - -Widget trustedDevicesTable( - RxList devices, RxList selectedDevices) { - RxBool selectAll = false.obs; - setSelectAll() { - if (selectedDevices.isNotEmpty && - selectedDevices.length == devices.length) { - selectAll.value = true; - } else { - selectAll.value = false; - } - } - - devices.listen((_) { - setSelectAll(); - }); - selectedDevices.listen((_) { - setSelectAll(); - }); - return FittedBox( - child: Obx(() => DataTable( - columns: [ - DataColumn( - label: Checkbox( - value: selectAll.value, - onChanged: (value) { - if (value == true) { - selectedDevices.clear(); - selectedDevices.addAll(devices.map((e) => e.hwid)); - } else { - selectedDevices.clear(); - } - }, - )), - DataColumn(label: Text(translate('Platform'))), - DataColumn(label: Text(translate('ID'))), - DataColumn(label: Text(translate('Username'))), - DataColumn(label: Text(translate('Days remaining'))), - ], - rows: devices.map((device) { - return DataRow(cells: [ - DataCell(Checkbox( - value: selectedDevices.contains(device.hwid), - onChanged: (value) { - if (value == null) return; - if (value) { - selectedDevices.remove(device.hwid); - selectedDevices.add(device.hwid); - } else { - selectedDevices.remove(device.hwid); - } - }, - )), - DataCell(Text(device.platform)), - DataCell(Text(device.id)), - DataCell(Text(device.name)), - DataCell(Text(device.daysRemaining())), - ]); - }).toList(), - )), - ); -} diff --git a/flutter/lib/common/widgets/gestures.dart b/flutter/lib/common/widgets/gestures.dart deleted file mode 100644 index 0501ca453..000000000 --- a/flutter/lib/common/widgets/gestures.dart +++ /dev/null @@ -1,797 +0,0 @@ -import 'dart:async'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_hbb/common/widgets/remote_input.dart'; - -enum GestureState { - none, - oneFingerPan, - twoFingerScale, - threeFingerVerticalDrag -} - -class CustomTouchGestureRecognizer extends ScaleGestureRecognizer { - CustomTouchGestureRecognizer({ - Object? debugOwner, - Set? supportedDevices, - }) : super( - debugOwner: debugOwner, - supportedDevices: supportedDevices, - ) { - _init(); - } - - // oneFingerPan - GestureDragStartCallback? onOneFingerPanStart; - GestureDragUpdateCallback? onOneFingerPanUpdate; - GestureDragEndCallback? onOneFingerPanEnd; - GestureDragCancelCallback? onOneFingerPanCancel; - - // twoFingerScale : scale + pan event - GestureScaleStartCallback? onTwoFingerScaleStart; - GestureScaleUpdateCallback? onTwoFingerScaleUpdate; - GestureScaleEndCallback? onTwoFingerScaleEnd; - - // threeFingerVerticalDrag - GestureDragStartCallback? onThreeFingerVerticalDragStart; - GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate; - GestureDragEndCallback? onThreeFingerVerticalDragEnd; - - var _currentState = GestureState.none; - Timer? _debounceTimer; - - void _init() { - debugPrint("CustomTouchGestureRecognizer init"); - // onStart = (d) {}; - onUpdate = (d) { - _debounceTimer?.cancel(); - if (d.pointerCount == 1 && _currentState != GestureState.oneFingerPan) { - onOneFingerStartDebounce(d); - } else if (d.pointerCount == 2 && - _currentState != GestureState.twoFingerScale) { - onTwoFingerStartDebounce(d); - } else if (d.pointerCount == 3 && - _currentState != GestureState.threeFingerVerticalDrag) { - _currentState = GestureState.threeFingerVerticalDrag; - if (onThreeFingerVerticalDragStart != null) { - onThreeFingerVerticalDragStart!( - DragStartDetails(globalPosition: d.localFocalPoint)); - } - debugPrint("start threeFingerScale"); - } - if (_currentState != GestureState.none) { - switch (_currentState) { - case GestureState.oneFingerPan: - if (onOneFingerPanUpdate != null) { - onOneFingerPanUpdate!(_getDragUpdateDetails(d)); - } - break; - case GestureState.twoFingerScale: - if (onTwoFingerScaleUpdate != null) { - onTwoFingerScaleUpdate!(d); - } - break; - case GestureState.threeFingerVerticalDrag: - if (onThreeFingerVerticalDragUpdate != null) { - onThreeFingerVerticalDragUpdate!(_getDragUpdateDetails(d)); - } - break; - default: - break; - } - return; - } - }; - onEnd = (d) { - debugPrint("ScaleGestureRecognizer onEnd"); - _debounceTimer?.cancel(); - // end - switch (_currentState) { - case GestureState.oneFingerPan: - debugPrint("OneFingerState.pan onEnd"); - if (onOneFingerPanEnd != null) { - onOneFingerPanEnd!(_getDragEndDetails(d)); - } - break; - case GestureState.twoFingerScale: - debugPrint("TwoFingerState.scale onEnd"); - if (onTwoFingerScaleEnd != null) { - onTwoFingerScaleEnd!(d); - } - if (isSpecialHoldDragActive) { - // If we are in special drag mode, we need to reset the state. - // Otherwise, the next `onTwoFingerScaleUpdate()` will handle a wrong `focalPoint`. - _currentState = GestureState.none; - return; - } - break; - case GestureState.threeFingerVerticalDrag: - debugPrint("ThreeFingerState.vertical onEnd"); - if (onThreeFingerVerticalDragEnd != null) { - onThreeFingerVerticalDragEnd!(_getDragEndDetails(d)); - } - break; - default: - break; - } - _debounceTimer = Timer(Duration(milliseconds: 200), () { - _currentState = GestureState.none; - }); - }; - } - - // FIXME: This debounce logic is not working properly. - // If we move our finger very fast, we won't be able to detect the "oneFingerPan" event sometimes. - void onOneFingerStartDebounce(ScaleUpdateDetails d) { - start(ScaleUpdateDetails d) { - _currentState = GestureState.oneFingerPan; - if (onOneFingerPanStart != null) { - onOneFingerPanStart!(DragStartDetails( - localPosition: d.localFocalPoint, globalPosition: d.focalPoint)); - } - } - - if (_currentState != GestureState.none) { - _debounceTimer = Timer(Duration(milliseconds: 200), () { - start(d); - debugPrint("debounce start oneFingerPan"); - }); - } else { - start(d); - debugPrint("start oneFingerPan"); - } - } - - void onTwoFingerStartDebounce(ScaleUpdateDetails d) { - start(ScaleUpdateDetails d) { - _currentState = GestureState.twoFingerScale; - if (onTwoFingerScaleStart != null) { - onTwoFingerScaleStart!(ScaleStartDetails( - localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint)); - } - } - - if (_currentState == GestureState.threeFingerVerticalDrag) { - _debounceTimer = Timer(Duration(milliseconds: 200), () { - start(d); - debugPrint("debounce start twoFingerScale"); - }); - } else { - start(d); - debugPrint("start twoFingerScale"); - } - } - - DragUpdateDetails _getDragUpdateDetails(ScaleUpdateDetails d) => - DragUpdateDetails( - globalPosition: d.focalPoint, - localPosition: d.localFocalPoint, - delta: d.focalPointDelta); - - DragEndDetails _getDragEndDetails(ScaleEndDetails d) => - DragEndDetails(velocity: d.velocity); - - @override - void rejectGesture(int pointer) { - super.rejectGesture(pointer); - switch (_currentState) { - case GestureState.oneFingerPan: - if (onOneFingerPanCancel != null) { - onOneFingerPanCancel!(); - } - break; - case GestureState.twoFingerScale: - // Reset scale state if needed, currently self-contained - break; - case GestureState.threeFingerVerticalDrag: - // Reset drag state if needed, currently self-contained - break; - default: - break; - } - _currentState = GestureState.none; - } -} - -class HoldTapMoveGestureRecognizer extends GestureRecognizer { - HoldTapMoveGestureRecognizer({ - Object? debugOwner, - Set? supportedDevices, - }) : super( - debugOwner: debugOwner, - supportedDevices: supportedDevices, - ); - - GestureDragStartCallback? onHoldDragStart; - GestureDragUpdateCallback? onHoldDragUpdate; - GestureDragDownCallback? onHoldDragDown; - GestureDragCancelCallback? onHoldDragCancel; - GestureDragEndCallback? onHoldDragEnd; - - bool _isStart = false; - - Timer? _firstTapUpTimer; - Timer? _secondTapDownTimer; - _TapTracker? _firstTap; - _TapTracker? _secondTap; - - PointerDownEvent? _lastPointerDownEvent; - - final Map _trackers = {}; - - @override - bool isPointerAllowed(PointerDownEvent event) { - if (_firstTap == null) { - switch (event.buttons) { - case kPrimaryButton: - if (onHoldDragStart == null && - onHoldDragUpdate == null && - onHoldDragCancel == null && - onHoldDragEnd == null) { - return false; - } - break; - default: - return false; - } - } - return super.isPointerAllowed(event); - } - - @override - void addAllowedPointer(PointerDownEvent event) { - if (_firstTap != null) { - if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) { - // Ignore out-of-bounds second taps. - return; - } else if (!_firstTap!.hasElapsedMinTime() || - !_firstTap!.hasSameButton(event)) { - // Restart when the second tap is too close to the first (touch screens - // often detect touches intermittently), or when buttons mismatch. - _reset(); - return _trackTap(event); - } else if (onHoldDragDown != null) { - invokeCallback( - 'onHoldDragDown', - () => onHoldDragDown!(DragDownDetails( - globalPosition: event.position, - localPosition: event.localPosition))); - } - } - _trackTap(event); - } - - void _trackTap(PointerDownEvent event) { - _stopFirstTapUpTimer(); - _stopSecondTapDownTimer(); - final _TapTracker tracker = _TapTracker( - event: event, - entry: GestureBinding.instance.gestureArena.add(event.pointer, this), - doubleTapMinTime: kDoubleTapMinTime, - gestureSettings: gestureSettings, - ); - _trackers[event.pointer] = tracker; - _lastPointerDownEvent = event; - tracker.startTrackingPointer(_handleEvent, event.transform); - } - - void _handleEvent(PointerEvent event) { - final _TapTracker tracker = _trackers[event.pointer]!; - if (event is PointerUpEvent) { - if (_firstTap == null && _secondTap == null) { - _registerFirstTap(tracker); - } else if (_secondTap != null) { - if (event.pointer == _secondTap!.pointer) { - if (onHoldDragEnd != null) { - onHoldDragEnd!(DragEndDetails()); - _secondTap = null; - _isStart = false; - } - } - } else { - _reject(tracker); - } - } else if (event is PointerDownEvent) { - if (_firstTap != null && _secondTap == null) { - _registerSecondTap(tracker); - } - } else if (event is PointerMoveEvent) { - if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) { - if (_firstTap != null && _firstTap!.pointer == event.pointer) { - // first tap move - _reject(tracker); - } else if (_secondTap != null && _secondTap!.pointer == event.pointer) { - // debugPrint("_secondTap move"); - // second tap move - if (!_isStart) { - _resolve(); - } - if (onHoldDragUpdate != null) { - onHoldDragUpdate!(DragUpdateDetails( - globalPosition: event.position, - localPosition: event.localPosition, - delta: event.delta)); - } - } - } - } else if (event is PointerCancelEvent) { - _reject(tracker); - } - } - - @override - void acceptGesture(int pointer) {} - - @override - void rejectGesture(int pointer) { - _TapTracker? tracker = _trackers[pointer]; - // If tracker isn't in the list, check if this is the first tap tracker - if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) { - tracker = _firstTap; - } - // If tracker is still null, we rejected ourselves already - if (tracker != null) { - _reject(tracker); - } - } - - void _resolve() { - _stopSecondTapDownTimer(); - _firstTap?.entry.resolve(GestureDisposition.accepted); - _secondTap?.entry.resolve(GestureDisposition.accepted); - _isStart = true; - // TODO start details - if (onHoldDragStart != null) { - onHoldDragStart!(DragStartDetails( - kind: _lastPointerDownEvent?.kind, - )); - } - } - - void _reject(_TapTracker tracker) { - try { - _checkCancel(); - _isStart = false; - _trackers.remove(tracker.pointer); - tracker.entry.resolve(GestureDisposition.rejected); - _freezeTracker(tracker); - _reset(); - } catch (e) { - debugPrint("Failed to _reject:$e"); - } - } - - @override - void dispose() { - _reset(); - super.dispose(); - } - - void _reset() { - _isStart = false; - // debugPrint("reset"); - _stopFirstTapUpTimer(); - _stopSecondTapDownTimer(); - if (_firstTap != null) { - if (_trackers.isNotEmpty) { - _checkCancel(); - } - // Note, order is important below in order for the resolve -> reject logic - // to work properly. - final _TapTracker tracker = _firstTap!; - _firstTap = null; - _reject(tracker); - GestureBinding.instance.gestureArena.release(tracker.pointer); - - if (_secondTap != null) { - final _TapTracker tracker = _secondTap!; - _secondTap = null; - _reject(tracker); - GestureBinding.instance.gestureArena.release(tracker.pointer); - } - } - _firstTap = null; - _secondTap = null; - _clearTrackers(); - } - - void _registerFirstTap(_TapTracker tracker) { - _startFirstTapUpTimer(); - GestureBinding.instance.gestureArena.hold(tracker.pointer); - // Note, order is important below in order for the clear -> reject logic to - // work properly. - _freezeTracker(tracker); - _trackers.remove(tracker.pointer); - _firstTap = tracker; - } - - void _registerSecondTap(_TapTracker tracker) { - if (_firstTap != null) { - _stopFirstTapUpTimer(); - _freezeTracker(_firstTap!); - _firstTap = null; - } - - _startSecondTapDownTimer(); - GestureBinding.instance.gestureArena.hold(tracker.pointer); - - _secondTap = tracker; - - // TODO - } - - void _clearTrackers() { - _trackers.values.toList().forEach(_reject); - assert(_trackers.isEmpty); - } - - void _freezeTracker(_TapTracker tracker) { - tracker.stopTrackingPointer(_handleEvent); - } - - void _startFirstTapUpTimer() { - _firstTapUpTimer ??= Timer(kDoubleTapTimeout, _reset); - } - - void _startSecondTapDownTimer() { - _secondTapDownTimer ??= Timer(kDoubleTapTimeout, _resolve); - } - - void _stopFirstTapUpTimer() { - if (_firstTapUpTimer != null) { - _firstTapUpTimer!.cancel(); - _firstTapUpTimer = null; - } - } - - void _stopSecondTapDownTimer() { - if (_secondTapDownTimer != null) { - _secondTapDownTimer!.cancel(); - _secondTapDownTimer = null; - } - } - - void _checkCancel() { - if (onHoldDragCancel != null) { - invokeCallback('onHoldDragCancel', onHoldDragCancel!); - } - } - - @override - String get debugDescription => 'double tap'; -} - -class DoubleFinerTapGestureRecognizer extends GestureRecognizer { - DoubleFinerTapGestureRecognizer({ - Object? debugOwner, - Set? supportedDevices, - }) : super( - debugOwner: debugOwner, - supportedDevices: supportedDevices, - ); - - GestureTapDownCallback? onDoubleFinerTapDown; - GestureTapDownCallback? onDoubleFinerTap; - GestureTapCancelCallback? onDoubleFinerTapCancel; - - Timer? _firstTapTimer; - _TapTracker? _firstTap; - - PointerDownEvent? _lastPointerDownEvent; - - var _isStart = false; - - final Set _upTap = {}; - - final Map _trackers = {}; - - @override - bool isPointerAllowed(PointerDownEvent event) { - if (_firstTap == null) { - switch (event.buttons) { - case kPrimaryButton: - if (onDoubleFinerTapDown == null && - onDoubleFinerTap == null && - onDoubleFinerTapCancel == null) { - return false; - } - break; - default: - return false; - } - } - return super.isPointerAllowed(event); - } - - @override - void addAllowedPointer(PointerDownEvent event) { - debugPrint("addAllowedPointer"); - if (_isStart) { - // second - if (onDoubleFinerTapDown != null) { - final TapDownDetails details = TapDownDetails( - globalPosition: event.position, - localPosition: event.localPosition, - kind: getKindForPointer(event.pointer), - ); - invokeCallback( - 'onDoubleFinerTapDown', () => onDoubleFinerTapDown!(details)); - } - } else { - // first tap - _isStart = true; - _lastPointerDownEvent = event; - _startFirstTapDownTimer(); - } - _trackTap(event); - } - - void _trackTap(PointerDownEvent event) { - final _TapTracker tracker = _TapTracker( - event: event, - entry: GestureBinding.instance.gestureArena.add(event.pointer, this), - doubleTapMinTime: kDoubleTapMinTime, - gestureSettings: gestureSettings, - ); - _trackers[event.pointer] = tracker; - // debugPrint("_trackers:$_trackers"); - tracker.startTrackingPointer(_handleEvent, event.transform); - - _registerTap(tracker); - } - - void _handleEvent(PointerEvent event) { - final _TapTracker tracker = _trackers[event.pointer]!; - if (event is PointerUpEvent) { - debugPrint("PointerUpEvent"); - _upTap.add(tracker.pointer); - } else if (event is PointerMoveEvent) { - if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) { - _reject(tracker); - } - } else if (event is PointerCancelEvent) { - _reject(tracker); - } - } - - @override - void acceptGesture(int pointer) {} - - @override - void rejectGesture(int pointer) { - _TapTracker? tracker = _trackers[pointer]; - // If tracker isn't in the list, check if this is the first tap tracker - if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) { - tracker = _firstTap; - } - // If tracker is still null, we rejected ourselves already - if (tracker != null) { - _reject(tracker); - } - } - - void _reject(_TapTracker tracker) { - _trackers.remove(tracker.pointer); - tracker.entry.resolve(GestureDisposition.rejected); - _freezeTracker(tracker); - if (_firstTap != null) { - if (tracker == _firstTap) { - _reset(); - } else { - _checkCancel(); - if (_trackers.isEmpty) { - _reset(); - } - } - } - } - - @override - void dispose() { - _reset(); - super.dispose(); - } - - void _reset() { - _stopFirstTapUpTimer(); - _firstTap = null; - _clearTrackers(); - } - - void _registerTap(_TapTracker tracker) { - GestureBinding.instance.gestureArena.hold(tracker.pointer); - // Note, order is important below in order for the clear -> reject logic to - // work properly. - } - - void _clearTrackers() { - _trackers.values.toList().forEach(_reject); - assert(_trackers.isEmpty); - } - - void _freezeTracker(_TapTracker tracker) { - tracker.stopTrackingPointer(_handleEvent); - } - - void _startFirstTapDownTimer() { - _firstTapTimer ??= Timer(kDoubleTapTimeout, _timeoutCheck); - } - - void _stopFirstTapUpTimer() { - if (_firstTapTimer != null) { - _firstTapTimer!.cancel(); - _firstTapTimer = null; - } - } - - void _timeoutCheck() { - _isStart = false; - if (_upTap.length == 2) { - _resolve(); - } else { - _reset(); - } - _upTap.clear(); - } - - void _resolve() { - // TODO tap down details - if (onDoubleFinerTap != null) { - onDoubleFinerTap!(TapDownDetails( - kind: _lastPointerDownEvent?.kind, - )); - } - _trackers.forEach((key, value) { - value.entry.resolve(GestureDisposition.accepted); - }); - _reset(); - } - - void _checkCancel() { - if (onDoubleFinerTapCancel != null) { - invokeCallback('onHoldDragCancel', onDoubleFinerTapCancel!); - } - } - - @override - String get debugDescription => 'double tap'; -} - -/// TapTracker helps track individual tap sequences as part of a -/// larger gesture. -class _TapTracker { - _TapTracker({ - required PointerDownEvent event, - required this.entry, - required Duration doubleTapMinTime, - required this.gestureSettings, - }) : pointer = event.pointer, - _initialGlobalPosition = event.position, - initialButtons = event.buttons, - _doubleTapMinTimeCountdown = - _CountdownZoned(duration: doubleTapMinTime); - - final DeviceGestureSettings? gestureSettings; - final int pointer; - final GestureArenaEntry entry; - final Offset _initialGlobalPosition; - final int initialButtons; - final _CountdownZoned _doubleTapMinTimeCountdown; - - bool _isTrackingPointer = false; - - void startTrackingPointer(PointerRoute route, Matrix4? transform) { - if (!_isTrackingPointer) { - _isTrackingPointer = true; - GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform); - } - } - - void stopTrackingPointer(PointerRoute route) { - if (_isTrackingPointer) { - _isTrackingPointer = false; - GestureBinding.instance.pointerRouter.removeRoute(pointer, route); - } - } - - bool isWithinGlobalTolerance(PointerEvent event, double tolerance) { - final Offset offset = event.position - _initialGlobalPosition; - return offset.distance <= tolerance; - } - - bool hasElapsedMinTime() { - return _doubleTapMinTimeCountdown.timeout; - } - - bool hasSameButton(PointerDownEvent event) { - return event.buttons == initialButtons; - } -} - -/// CountdownZoned tracks whether the specified duration has elapsed since -/// creation, honoring [Zone]. -class _CountdownZoned { - _CountdownZoned({required Duration duration}) { - Timer(duration, _onTimeout); - } - - bool _timeout = false; - - bool get timeout => _timeout; - - void _onTimeout() { - _timeout = true; - } -} - -RawGestureDetector getMixinGestureDetector({ - Widget? child, - GestureTapUpCallback? onTapUp, - GestureTapDownCallback? onDoubleTapDown, - GestureDoubleTapCallback? onDoubleTap, - GestureLongPressDownCallback? onLongPressDown, - GestureLongPressCallback? onLongPress, - GestureDragStartCallback? onHoldDragStart, - GestureDragUpdateCallback? onHoldDragUpdate, - GestureDragCancelCallback? onHoldDragCancel, - GestureDragEndCallback? onHoldDragEnd, - GestureTapDownCallback? onDoubleFinerTap, - GestureDragStartCallback? onOneFingerPanStart, - GestureDragUpdateCallback? onOneFingerPanUpdate, - GestureDragEndCallback? onOneFingerPanEnd, - GestureDragCancelCallback? onOneFingerPanCancel, - GestureScaleUpdateCallback? onTwoFingerScaleUpdate, - GestureScaleEndCallback? onTwoFingerScaleEnd, - GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate, -}) { - return RawGestureDetector( - child: child, - gestures: { - // Official - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(), (instance) { - instance.onTapUp = onTapUp; - }), - DoubleTapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => DoubleTapGestureRecognizer(), (instance) { - instance - ..onDoubleTapDown = onDoubleTapDown - ..onDoubleTap = onDoubleTap; - }), - LongPressGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer(), (instance) { - instance - ..onLongPressDown = onLongPressDown - ..onLongPress = onLongPress; - }), - // Customized - HoldTapMoveGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => HoldTapMoveGestureRecognizer(), - (instance) => instance - ..onHoldDragStart = onHoldDragStart - ..onHoldDragUpdate = onHoldDragUpdate - ..onHoldDragCancel = onHoldDragCancel - ..onHoldDragEnd = onHoldDragEnd), - DoubleFinerTapGestureRecognizer: GestureRecognizerFactoryWithHandlers< - DoubleFinerTapGestureRecognizer>( - () => DoubleFinerTapGestureRecognizer(), (instance) { - instance.onDoubleFinerTap = onDoubleFinerTap; - }), - CustomTouchGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => CustomTouchGestureRecognizer(), (instance) { - instance - ..onOneFingerPanStart = onOneFingerPanStart - ..onOneFingerPanUpdate = onOneFingerPanUpdate - ..onOneFingerPanEnd = onOneFingerPanEnd - ..onOneFingerPanCancel = onOneFingerPanCancel - ..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate - ..onTwoFingerScaleEnd = onTwoFingerScaleEnd - ..onThreeFingerVerticalDragUpdate = onThreeFingerVerticalDragUpdate; - }), - }); -} diff --git a/flutter/lib/common/widgets/login.dart b/flutter/lib/common/widgets/login.dart deleted file mode 100644 index ee376de68..000000000 --- a/flutter/lib/common/widgets/login.dart +++ /dev/null @@ -1,790 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/hbbs/hbbs.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/user_model.dart'; -import 'package:get/get.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import '../../common.dart'; -import './dialog.dart'; - -const kOpSvgList = [ - 'github', - 'gitlab', - 'google', - 'apple', - 'okta', - 'facebook', - 'azure', - 'auth0', - 'microsoft' -]; - -class _IconOP extends StatelessWidget { - final String op; - final String? icon; - final EdgeInsets margin; - const _IconOP( - {Key? key, - required this.op, - required this.icon, - this.margin = const EdgeInsets.symmetric(horizontal: 4.0)}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final svgFile = - kOpSvgList.contains(op.toLowerCase()) ? op.toLowerCase() : 'default'; - return Container( - margin: margin, - child: icon == null - ? SvgPicture.asset( - 'assets/auth-$svgFile.svg', - width: 20, - ) - : SvgPicture.string( - icon!, - width: 20, - ), - ); - } -} - -class ButtonOP extends StatelessWidget { - final String op; - final RxString curOP; - final String? icon; - final Color primaryColor; - final double height; - final Function() onTap; - - const ButtonOP({ - Key? key, - required this.op, - required this.curOP, - required this.icon, - required this.primaryColor, - required this.height, - required this.onTap, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final opLabel = { - 'github': 'GitHub', - 'gitlab': 'GitLab' - }[op.toLowerCase()] ?? - toCapitalized(op); - return Row(children: [ - Container( - height: height, - width: 200, - child: Obx(() => ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: curOP.value.isEmpty || curOP.value == op - ? primaryColor - : Colors.grey, - ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)), - onPressed: curOP.value.isEmpty || curOP.value == op ? onTap : null, - child: Row( - children: [ - SizedBox( - width: 30, - child: _IconOP( - op: op, - icon: icon, - margin: EdgeInsets.only(right: 5), - ), - ), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Center( - child: Text(translate("Continue with {$opLabel}"))), - ), - ), - ], - ))), - ), - ]); - } -} - -class ConfigOP { - final String op; - final String? icon; - ConfigOP({required this.op, required this.icon}); -} - -class WidgetOP extends StatefulWidget { - final ConfigOP config; - final RxString curOP; - final Function(Map) cbLogin; - const WidgetOP({ - Key? key, - required this.config, - required this.curOP, - required this.cbLogin, - }) : super(key: key); - - @override - State createState() { - return _WidgetOPState(); - } -} - -class _WidgetOPState extends State { - Timer? _updateTimer; - String _stateMsg = ''; - String _failedMsg = ''; - String _url = ''; - - @override - void dispose() { - super.dispose(); - _updateTimer?.cancel(); - } - - _beginQueryState() { - _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { - _updateState(); - }); - } - - _updateState() { - bind.mainAccountAuthResult().then((result) { - if (result.isEmpty) { - return; - } - final resultMap = jsonDecode(result); - if (resultMap == null) { - return; - } - final String stateMsg = resultMap['state_msg']; - String failedMsg = resultMap['failed_msg']; - final String? url = resultMap['url']; - final bool urlLaunched = (resultMap['url_launched'] as bool?) ?? false; - final authBody = resultMap['auth_body']; - if (_stateMsg != stateMsg || _failedMsg != failedMsg) { - if (_url.isEmpty && url != null && url.isNotEmpty) { - if (!urlLaunched) { - launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); - } - _url = url; - } - if (authBody != null) { - _updateTimer?.cancel(); - widget.curOP.value = ''; - widget.cbLogin(authBody as Map); - } - - setState(() { - _stateMsg = stateMsg; - _failedMsg = failedMsg; - if (failedMsg.isNotEmpty) { - widget.curOP.value = ''; - _updateTimer?.cancel(); - } - }); - } - }); - } - - _resetState() { - _stateMsg = ''; - _failedMsg = ''; - _url = ''; - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - ButtonOP( - op: widget.config.op, - curOP: widget.curOP, - icon: widget.config.icon, - primaryColor: str2color(widget.config.op, 0x7f), - height: 36, - onTap: () async { - _resetState(); - widget.curOP.value = widget.config.op; - await bind.mainAccountAuth(op: widget.config.op, rememberMe: true); - _beginQueryState(); - }, - ), - Obx(() { - if (widget.curOP.isNotEmpty && - widget.curOP.value != widget.config.op) { - _failedMsg = ''; - } - return Offstage( - offstage: - _failedMsg.isEmpty && widget.curOP.value != widget.config.op, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (_stateMsg.isNotEmpty && _failedMsg.isEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: SelectableText( - translate(_stateMsg), - style: DefaultTextStyle.of(context) - .style - .copyWith(fontSize: 12), - ), - ), - if (_failedMsg.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Builder(builder: (context) { - final errorColor = - Theme.of(context).colorScheme.error; - final bgColor = Theme.of(context) - .colorScheme - .errorContainer - .withOpacity(0.3); - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - decoration: BoxDecoration( - color: bgColor, - borderRadius: BorderRadius.circular(4.0), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.error_outline, - color: errorColor, size: 16), - const SizedBox(width: 6), - Flexible( - child: SelectableText( - translate(_failedMsg), - style: DefaultTextStyle.of(context) - .style - .copyWith( - fontSize: 13, - color: errorColor, - ), - ), - ), - ], - ), - ); - }), - ), - ], - ), - ); - }), - Obx( - () => Offstage( - offstage: widget.curOP.value != widget.config.op, - child: const SizedBox( - height: 5.0, - ), - ), - ), - Obx( - () => Offstage( - offstage: widget.curOP.value != widget.config.op, - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: 20), - child: ElevatedButton( - onPressed: () { - widget.curOP.value = ''; - _updateTimer?.cancel(); - _resetState(); - bind.mainAccountAuthCancel(); - }, - child: Text( - translate('Cancel'), - style: TextStyle(fontSize: 15), - ), - ), - ), - ), - ), - ], - ); - } -} - -class LoginWidgetOP extends StatelessWidget { - final List ops; - final RxString curOP; - final Function(Map) cbLogin; - - LoginWidgetOP({ - Key? key, - required this.ops, - required this.curOP, - required this.cbLogin, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var children = ops - .map((op) => [ - WidgetOP( - config: op, - curOP: curOP, - cbLogin: cbLogin, - ), - const Divider( - indent: 5, - endIndent: 5, - ) - ]) - .expand((i) => i) - .toList(); - if (children.isNotEmpty) { - children.removeLast(); - } - return SingleChildScrollView( - child: Container( - width: 200, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: children, - ))); - } -} - -class LoginWidgetUserPass extends StatelessWidget { - final TextEditingController username; - final TextEditingController pass; - final String? usernameMsg; - final String? passMsg; - final bool isInProgress; - final RxString curOP; - final Function() onLogin; - final FocusNode? userFocusNode; - const LoginWidgetUserPass({ - Key? key, - this.userFocusNode, - required this.username, - required this.pass, - required this.usernameMsg, - required this.passMsg, - required this.isInProgress, - required this.curOP, - required this.onLogin, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 8.0), - DialogTextField( - title: translate(DialogTextField.kUsernameTitle), - controller: username, - focusNode: userFocusNode, - prefixIcon: DialogTextField.kUsernameIcon, - errorText: usernameMsg), - PasswordWidget( - controller: pass, - autoFocus: false, - reRequestFocus: true, - errorText: passMsg, - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - const SizedBox(height: 12.0), - FittedBox( - child: - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - height: 38, - width: 200, - child: Obx(() => ElevatedButton( - child: Text( - translate('Login'), - style: TextStyle(fontSize: 16), - ), - onPressed: - curOP.value.isEmpty || curOP.value == 'rustdesk' - ? () { - onLogin(); - } - : null, - )), - ), - ])), - ], - )); - } -} - -const kAuthReqTypeOidc = 'oidc/'; - -// call this directly -Future loginDialog() async { - var username = - TextEditingController(text: UserModel.getLocalUserInfo()?['name'] ?? ''); - var password = TextEditingController(); - final userFocusNode = FocusNode()..requestFocus(); - Timer(Duration(milliseconds: 100), () => userFocusNode..requestFocus()); - - String? usernameMsg; - String? passwordMsg; - var isInProgress = false; - final RxString curOP = ''.obs; - // Track hover state for the close icon - bool isCloseHovered = false; - - final loginOptions = [].obs; - Future.delayed(Duration.zero, () async { - loginOptions.value = await UserModel.queryOidcLoginOptions(); - }); - - final res = await gFFI.dialogManager.show((setState, close, context) { - username.addListener(() { - if (usernameMsg != null) { - setState(() => usernameMsg = null); - } - }); - - password.addListener(() { - if (passwordMsg != null) { - setState(() => passwordMsg = null); - } - }); - - onDialogCancel() { - isInProgress = false; - close(false); - } - - handleLoginResponse(LoginResponse resp, bool storeIfAccessToken, - void Function([dynamic])? close) async { - switch (resp.type) { - case HttpType.kAuthResTypeToken: - if (resp.access_token != null) { - if (storeIfAccessToken) { - await bind.mainSetLocalOption( - key: 'access_token', value: resp.access_token!); - await bind.mainSetLocalOption( - key: 'user_info', value: jsonEncode(resp.user ?? {})); - } - if (close != null) { - close(true); - } - return; - } - break; - case HttpType.kAuthResTypeEmailCheck: - bool? isEmailVerification; - if (resp.tfa_type == null || - resp.tfa_type == HttpType.kAuthResTypeEmailCheck) { - isEmailVerification = true; - } else if (resp.tfa_type == HttpType.kAuthResTypeTfaCheck) { - isEmailVerification = false; - } else { - passwordMsg = "Failed, bad tfa type from server"; - } - if (isEmailVerification != null) { - if (isMobile) { - if (close != null) close(null); - verificationCodeDialog( - resp.user, resp.secret, isEmailVerification); - } else { - setState(() => isInProgress = false); - // Workaround for web, close the dialog first, then show the verification code dialog. - // Otherwise, the text field will keep selecting the text and we can't input the code. - // Not sure why this happens. - if (isWeb && close != null) close(null); - final res = await verificationCodeDialog( - resp.user, resp.secret, isEmailVerification); - if (res == true) { - if (!isWeb && close != null) close(false); - return; - } - } - } - break; - default: - passwordMsg = "Failed, bad response from server"; - break; - } - } - - onLogin() async { - // validate - if (username.text.isEmpty) { - setState(() => usernameMsg = translate('Username missed')); - return; - } - if (password.text.isEmpty) { - setState(() => passwordMsg = translate('Password missed')); - return; - } - curOP.value = 'rustdesk'; - setState(() => isInProgress = true); - try { - final resp = await gFFI.userModel.login(LoginRequest( - username: username.text, - password: password.text, - id: await bind.mainGetMyId(), - uuid: await bind.mainGetUuid(), - autoLogin: true, - type: HttpType.kAuthReqTypeAccount)); - await handleLoginResponse(resp, true, close); - } on RequestException catch (err) { - passwordMsg = translate(err.cause); - } catch (err) { - passwordMsg = "Unknown Error: $err"; - } - curOP.value = ''; - setState(() => isInProgress = false); - } - - thirdAuthWidget() => Obx(() { - return Offstage( - offstage: loginOptions.isEmpty, - child: Column( - children: [ - const SizedBox( - height: 8.0, - ), - Center( - child: Text( - translate('or'), - style: TextStyle(fontSize: 16), - )), - const SizedBox( - height: 8.0, - ), - LoginWidgetOP( - ops: loginOptions - .map((e) => ConfigOP(op: e['name'], icon: e['icon'])) - .toList(), - curOP: curOP, - cbLogin: (Map authBody) async { - LoginResponse? resp; - try { - // access_token is already stored in the rust side. - resp = - gFFI.userModel.getLoginResponseFromAuthBody(authBody); - } catch (e) { - debugPrint( - 'Failed to parse oidc login body: "$authBody"'); - } - close(true); - - if (resp != null) { - handleLoginResponse(resp, false, null); - } - }, - ), - ], - ), - ); - }); - - final title = Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - translate('Login'), - ).marginOnly(top: MyTheme.dialogPadding), - MouseRegion( - onEnter: (_) => setState(() => isCloseHovered = true), - onExit: (_) => setState(() => isCloseHovered = false), - child: InkWell( - child: Icon( - Icons.close, - size: 25, - // No need to handle the branch of null. - // Because we can ensure the color is not null when debug. - color: isCloseHovered - ? Colors.white - : Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.55), - ), - onTap: onDialogCancel, - hoverColor: Colors.red, - borderRadius: BorderRadius.circular(5), - ), - ).marginOnly(top: 10, right: 15), - ], - ); - final titlePadding = EdgeInsets.fromLTRB(MyTheme.dialogPadding, 0, 0, 0); - - return CustomAlertDialog( - title: title, - titlePadding: titlePadding, - contentBoxConstraints: BoxConstraints(minWidth: 400), - content: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox( - height: 8.0, - ), - LoginWidgetUserPass( - username: username, - pass: password, - usernameMsg: usernameMsg, - passMsg: passwordMsg, - isInProgress: isInProgress, - curOP: curOP, - onLogin: onLogin, - userFocusNode: userFocusNode, - ), - thirdAuthWidget(), - ], - ), - onCancel: onDialogCancel, - onSubmit: onLogin, - ); - }); - - if (res != null) { - await UserModel.updateOtherModels(); - } - - return res; -} - -Future verificationCodeDialog( - UserPayload? user, String? secret, bool isEmailVerification) async { - var autoLogin = true; - var isInProgress = false; - String? errorText; - - final code = TextEditingController(); - - final res = await gFFI.dialogManager.show((setState, close, context) { - void onVerify() async { - setState(() => isInProgress = true); - - try { - final resp = await gFFI.userModel.login(LoginRequest( - verificationCode: code.text, - tfaCode: isEmailVerification ? null : code.text, - secret: secret, - username: user?.name, - id: await bind.mainGetMyId(), - uuid: await bind.mainGetUuid(), - autoLogin: autoLogin, - type: HttpType.kAuthReqTypeEmailCode)); - - switch (resp.type) { - case HttpType.kAuthResTypeToken: - if (resp.access_token != null) { - await bind.mainSetLocalOption( - key: 'access_token', value: resp.access_token!); - close(true); - return; - } - break; - default: - errorText = "Failed, bad response from server"; - break; - } - } on RequestException catch (err) { - errorText = translate(err.cause); - } catch (err) { - errorText = "Unknown Error: $err"; - } - - setState(() => isInProgress = false); - } - - final codeField = isEmailVerification - ? DialogEmailCodeField( - controller: code, - errorText: errorText, - readyCallback: onVerify, - onChanged: () => errorText = null, - ) - : Dialog2FaField( - controller: code, - errorText: errorText, - readyCallback: onVerify, - onChanged: () => errorText = null, - ); - - getOnSubmit() => codeField.isReady ? onVerify : null; - - return CustomAlertDialog( - title: Text(translate("Verification code")), - contentBoxConstraints: BoxConstraints(maxWidth: 300), - content: Column( - children: [ - Offstage( - offstage: !isEmailVerification || user?.email == null, - child: TextField( - decoration: InputDecoration( - labelText: "Email", prefixIcon: Icon(Icons.email)), - readOnly: true, - controller: TextEditingController(text: user?.email), - ).workaroundFreezeLinuxMint()), - isEmailVerification ? const SizedBox(height: 8) : const Offstage(), - codeField, - /* - CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Row(children: [ - Expanded(child: Text(translate("Trust this device"))) - ]), - value: trustThisDevice, - onChanged: (v) { - if (v == null) return; - setState(() => trustThisDevice = !trustThisDevice); - }, - ), - */ - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) const LinearProgressIndicator(), - ], - ), - onCancel: close, - onSubmit: getOnSubmit(), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("Verify", onPressed: getOnSubmit()), - ]); - }); - // For verification code, desktop update other models in login dialog, mobile need to close login dialog first, - // otherwise the soft keyboard will jump out on each key press, so mobile update in verification code dialog. - if (isMobile && res == true) { - await UserModel.updateOtherModels(); - } - - return res; -} - -void logOutConfirmDialog() { - gFFI.dialogManager.show((setState, close, context) { - submit() { - close(); - gFFI.userModel.logOut(); - } - - return CustomAlertDialog( - content: Text(translate("logout_tip")), - actions: [ - dialogButton(translate("Cancel"), onPressed: close, isOutline: true), - dialogButton(translate("OK"), onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} diff --git a/flutter/lib/common/widgets/my_group.dart b/flutter/lib/common/widgets/my_group.dart deleted file mode 100644 index 74ce34e71..000000000 --- a/flutter/lib/common/widgets/my_group.dart +++ /dev/null @@ -1,309 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/hbbs/hbbs.dart'; -import 'package:flutter_hbb/common/widgets/login.dart'; -import 'package:flutter_hbb/common/widgets/peers_view.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:get/get.dart'; - -import '../../common.dart'; - -class MyGroup extends StatefulWidget { - final EdgeInsets? menuPadding; - const MyGroup({Key? key, this.menuPadding}) : super(key: key); - - @override - State createState() { - return _MyGroupState(); - } -} - -class _MyGroupState extends State { - RxBool get isSelectedDeviceGroup => gFFI.groupModel.isSelectedDeviceGroup; - RxString get selectedAccessibleItemName => - gFFI.groupModel.selectedAccessibleItemName; - RxString get searchAccessibleItemNameText => - gFFI.groupModel.searchAccessibleItemNameText; - static TextEditingController searchUserController = TextEditingController(); - - @override - Widget build(BuildContext context) { - return Obx(() { - if (!gFFI.userModel.isLogin) { - return Center( - child: ElevatedButton( - onPressed: loginDialog, child: Text(translate("Login")))); - } else if (gFFI.userModel.networkError.isNotEmpty) { - return netWorkErrorWidget(); - } else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return Column( - children: [ - buildErrorBanner(context, - loading: gFFI.groupModel.groupLoading, - err: gFFI.groupModel.groupLoadError, - retry: null, - close: () => gFFI.groupModel.groupLoadError.value = ''), - Expanded( - child: Obx(() => stateGlobal.isPortrait.isTrue - ? _buildPortrait() - : _buildLandscape())), - ], - ); - }); - } - - Widget _buildLandscape() { - return Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: - Border.all(color: Theme.of(context).colorScheme.background)), - child: Container( - width: 150, - height: double.infinity, - child: Column( - children: [ - _buildLeftHeader(), - Expanded( - child: Container( - width: double.infinity, - height: double.infinity, - child: _buildLeftList(), - ), - ) - ], - ), - ), - ).marginOnly(right: 12.0), - Expanded( - child: Align( - alignment: Alignment.topLeft, - child: MyGroupPeerView( - menuPadding: widget.menuPadding, - )), - ) - ], - ); - } - - Widget _buildPortrait() { - return Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - border: - Border.all(color: Theme.of(context).colorScheme.background)), - child: Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildLeftHeader(), - Container( - width: double.infinity, - child: _buildLeftList(), - ) - ], - ), - ), - ).marginOnly(bottom: 12.0), - Expanded( - child: Align( - alignment: Alignment.topLeft, - child: MyGroupPeerView( - menuPadding: widget.menuPadding, - )), - ) - ], - ); - } - - Widget _buildLeftHeader() { - final fontSize = 14.0; - return Row( - children: [ - Expanded( - child: TextField( - controller: searchUserController, - onChanged: (value) { - searchAccessibleItemNameText.value = value; - selectedAccessibleItemName.value = ''; - }, - textAlignVertical: TextAlignVertical.center, - style: TextStyle(fontSize: fontSize), - decoration: InputDecoration( - filled: false, - prefixIcon: Icon( - Icons.search_rounded, - color: Theme.of(context).hintColor, - ).paddingOnly(top: 2), - hintText: translate("Search"), - hintStyle: TextStyle(fontSize: fontSize), - border: InputBorder.none, - isDense: true, - ), - ).workaroundFreezeLinuxMint()), - ], - ); - } - - Widget _buildLeftList() { - return Obx(() { - final userItems = gFFI.groupModel.users.where((p0) { - if (searchAccessibleItemNameText.isNotEmpty) { - final search = searchAccessibleItemNameText.value.toLowerCase(); - return p0.name.toLowerCase().contains(search) || - p0.displayNameOrName.toLowerCase().contains(search); - } - return true; - }).toList(); - // Count occurrences of each displayNameOrName to detect duplicates - final displayNameCount = {}; - for (final u in userItems) { - final dn = u.displayNameOrName; - displayNameCount[dn] = (displayNameCount[dn] ?? 0) + 1; - } - final deviceGroupItems = gFFI.groupModel.deviceGroups.where((p0) { - if (searchAccessibleItemNameText.isNotEmpty) { - return p0.name - .toLowerCase() - .contains(searchAccessibleItemNameText.value.toLowerCase()); - } - return true; - }).toList(); - listView(bool isPortrait) => ListView.builder( - shrinkWrap: isPortrait, - itemCount: deviceGroupItems.length + userItems.length, - itemBuilder: (context, index) => index < deviceGroupItems.length - ? _buildDeviceGroupItem(deviceGroupItems[index]) - : _buildUserItem(userItems[index - deviceGroupItems.length], - displayNameCount)); - var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); - return Obx(() => stateGlobal.isPortrait.isFalse - ? listView(false) - : LimitedBox(maxHeight: maxHeight, child: listView(true))); - }); - } - - Widget _buildUserItem(UserPayload user, Map displayNameCount) { - final username = user.name; - final dn = user.displayNameOrName; - final isDuplicate = (displayNameCount[dn] ?? 0) > 1; - final displayName = - isDuplicate && user.displayName.trim().isNotEmpty - ? '${user.displayName} (@$username)' - : dn; - return InkWell(onTap: () { - isSelectedDeviceGroup.value = false; - if (selectedAccessibleItemName.value != username) { - selectedAccessibleItemName.value = username; - } else { - selectedAccessibleItemName.value = ''; - } - }, child: Obx( - () { - bool selected = !isSelectedDeviceGroup.value && - selectedAccessibleItemName.value == username; - final isMe = username == gFFI.userModel.userName.value; - final colorMe = MyTheme.color(context).me!; - return Container( - decoration: BoxDecoration( - color: selected ? MyTheme.color(context).highlight : null, - border: Border( - bottom: BorderSide( - width: 0.7, - color: Theme.of(context).dividerColor.withOpacity(0.1))), - ), - child: Container( - child: Row( - children: [ - Container( - width: 20, - height: 20, - decoration: BoxDecoration( - color: str2color(username, 0xAF), - shape: BoxShape.circle, - ), - child: Align( - alignment: Alignment.center, - child: Center( - child: Text( - displayName.characters.first.toUpperCase(), - style: TextStyle(color: Colors.white), - textAlign: TextAlign.center, - ), - ), - ), - ).marginOnly(right: 4), - if (isMe) Flexible(child: Text(displayName)), - if (isMe) - Flexible( - child: Container( - margin: EdgeInsets.only(left: 5), - padding: EdgeInsets.symmetric(horizontal: 3, vertical: 1), - decoration: BoxDecoration( - color: colorMe.withAlpha(20), - borderRadius: BorderRadius.all(Radius.circular(2)), - border: Border.all(color: colorMe.withAlpha(100))), - child: Text( - translate('Me'), - style: TextStyle( - color: colorMe.withAlpha(200), fontSize: 12), - ), - ), - ), - if (!isMe) Expanded(child: Text(displayName)), - ], - ).paddingSymmetric(vertical: 4), - ), - ); - }, - )).marginSymmetric(horizontal: 12).marginOnly(bottom: 6); - } - - Widget _buildDeviceGroupItem(DeviceGroupPayload deviceGroup) { - final name = deviceGroup.name; - return InkWell(onTap: () { - isSelectedDeviceGroup.value = true; - if (selectedAccessibleItemName.value != name) { - selectedAccessibleItemName.value = name; - } else { - selectedAccessibleItemName.value = ''; - } - }, child: Obx( - () { - bool selected = isSelectedDeviceGroup.value && - selectedAccessibleItemName.value == name; - return Container( - decoration: BoxDecoration( - color: selected ? MyTheme.color(context).highlight : null, - border: Border( - bottom: BorderSide( - width: 0.7, - color: Theme.of(context).dividerColor.withOpacity(0.1))), - ), - child: Container( - child: Row( - children: [ - Container( - width: 20, - height: 20, - child: Icon(IconFont.deviceGroupOutline, - color: MyTheme.accent, size: 19), - ).marginOnly(right: 4), - Expanded(child: Text(name)), - ], - ).paddingSymmetric(vertical: 4), - ), - ); - }, - )).marginSymmetric(horizontal: 12).marginOnly(bottom: 6); - } -} diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart deleted file mode 100644 index 3fb63616d..000000000 --- a/flutter/lib/common/widgets/overlay.dart +++ /dev/null @@ -1,674 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:debounce_throttle/debounce_throttle.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; - -import '../../consts.dart'; -import '../../desktop/widgets/tabbar_widget.dart'; -import '../../models/chat_model.dart'; -import '../../models/model.dart'; -import 'chat_page.dart'; - -class DraggableChatWindow extends StatelessWidget { - const DraggableChatWindow( - {Key? key, - this.position = Offset.zero, - required this.width, - required this.height, - required this.chatModel}) - : super(key: key); - - final Offset position; - final double width; - final double height; - final ChatModel chatModel; - - @override - Widget build(BuildContext context) { - if (draggablePositions.chatWindow.isInvalid()) { - draggablePositions.chatWindow.update(position); - } - return isIOS - ? IOSDraggable( - position: draggablePositions.chatWindow, - chatModel: chatModel, - width: width, - height: height, - builder: (context) { - return Column( - children: [ - _buildMobileAppBar(context), - Expanded( - child: ChatPage(chatModel: chatModel), - ), - ], - ); - }, - ) - : Draggable( - checkKeyboard: true, - checkScreenSize: true, - position: draggablePositions.chatWindow, - width: width, - height: height, - chatModel: chatModel, - builder: (context, onPanUpdate) { - final child = Scaffold( - resizeToAvoidBottomInset: false, - appBar: CustomAppBar( - onPanUpdate: onPanUpdate, - appBar: (isDesktop || isWebDesktop) - ? _buildDesktopAppBar(context) - : _buildMobileAppBar(context), - ), - body: ChatPage(chatModel: chatModel), - ); - return Container( - decoration: - BoxDecoration(border: Border.all(color: MyTheme.border)), - child: child); - }); - } - - Widget _buildMobileAppBar(BuildContext context) { - return Container( - color: Theme.of(context).colorScheme.primary, - height: 50, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Text( - translate("Chat"), - style: const TextStyle( - color: Colors.white, - fontFamily: 'WorkSans', - fontWeight: FontWeight.bold, - fontSize: 20), - )), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconButton( - onPressed: () { - chatModel.hideChatWindowOverlay(); - }, - icon: const Icon( - Icons.keyboard_arrow_down, - color: Colors.white, - )), - IconButton( - onPressed: () { - chatModel.hideChatWindowOverlay(); - chatModel.hideChatIconOverlay(); - }, - icon: const Icon( - Icons.close, - color: Colors.white, - )) - ], - ) - ], - ), - ); - } - - Widget _buildDesktopAppBar(BuildContext context) { - return Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Theme.of(context).hintColor.withOpacity(0.4)))), - height: 38, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), - child: Obx(() => Opacity( - opacity: chatModel.isWindowFocus.value ? 1.0 : 0.4, - child: Row(children: [ - Icon(Icons.chat_bubble_outline, - size: 20, color: Theme.of(context).colorScheme.primary), - SizedBox(width: 6), - Text(translate("Chat")) - ])))), - Padding( - padding: EdgeInsets.all(2), - child: ActionIcon( - message: 'Close', - icon: IconFont.close, - onTap: chatModel.hideChatWindowOverlay, - isClose: true, - boxSize: 32, - )) - ], - ), - ); - } -} - -class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { - final GestureDragUpdateCallback onPanUpdate; - final Widget appBar; - - const CustomAppBar( - {Key? key, required this.onPanUpdate, required this.appBar}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return GestureDetector(onPanUpdate: onPanUpdate, child: appBar); - } - - @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); -} - -/// floating buttons of back/home/recent actions for android -class DraggableMobileActions extends StatelessWidget { - DraggableMobileActions( - {this.onBackPressed, - this.onRecentPressed, - this.onHomePressed, - this.onHidePressed, - required this.position, - required this.width, - required this.height, - required this.scale}); - - final double scale; - final DraggableKeyPosition position; - final double width; - final double height; - final VoidCallback? onBackPressed; - final VoidCallback? onHomePressed; - final VoidCallback? onRecentPressed; - final VoidCallback? onHidePressed; - - @override - Widget build(BuildContext context) { - return Draggable( - position: position, - width: scale * width, - height: scale * height, - builder: (_, onPanUpdate) { - return GestureDetector( - onPanUpdate: onPanUpdate, - child: Card( - color: Colors.transparent, - shadowColor: Colors.transparent, - child: Container( - decoration: BoxDecoration( - color: MyTheme.accent.withOpacity(0.4), - borderRadius: - BorderRadius.all(Radius.circular(15 * scale))), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - IconButton( - color: Colors.white, - onPressed: onBackPressed, - splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.arrow_back), - iconSize: 24 * scale), - IconButton( - color: Colors.white, - onPressed: onHomePressed, - splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.home), - iconSize: 24 * scale), - IconButton( - color: Colors.white, - onPressed: onRecentPressed, - splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.more_horiz), - iconSize: 24 * scale), - const VerticalDivider( - width: 0, - thickness: 2, - indent: 10, - endIndent: 10, - ), - IconButton( - color: Colors.white, - onPressed: onHidePressed, - splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.keyboard_arrow_down), - iconSize: 24 * scale), - ], - ), - ))); - }); - } -} - -class DraggableKeyPosition { - final String key; - Offset _pos; - late Debouncer _debouncerStore; - DraggableKeyPosition(this.key) - : _pos = DraggablePositions.kInvalidDraggablePosition; - - get pos => _pos; - - _loadPosition(String k) { - final value = bind.getLocalFlutterOption(k: k); - if (value.isNotEmpty) { - final parts = value.split(','); - if (parts.length == 2) { - return Offset(double.parse(parts[0]), double.parse(parts[1])); - } - } - return DraggablePositions.kInvalidDraggablePosition; - } - - load() { - _pos = _loadPosition(key); - _debouncerStore = Debouncer(const Duration(milliseconds: 500), - onChanged: (v) => _store(), initialValue: 0); - } - - update(Offset pos) { - _pos = pos; - _triggerStore(); - } - - // Adjust position to keep it in the screen - // Only used for desktop and web desktop - tryAdjust(double w, double h, double scale) { - final size = MediaQuery.of(Get.context!).size; - w = w * scale; - h = h * scale; - double x = _pos.dx; - double y = _pos.dy; - if (x + w > size.width) { - x = size.width - w; - } - final tabBarHeight = isDesktop ? kDesktopRemoteTabBarHeight : 0; - if (y + h > (size.height - tabBarHeight)) { - y = size.height - tabBarHeight - h; - } - if (x < 0) { - x = 0; - } - if (y < 0) { - y = 0; - } - if (x != _pos.dx || y != _pos.dy) { - update(Offset(x, y)); - } - } - - isInvalid() { - return _pos == DraggablePositions.kInvalidDraggablePosition; - } - - _triggerStore() => _debouncerStore.value = _debouncerStore.value + 1; - _store() { - bind.setLocalFlutterOption(k: key, v: '${_pos.dx},${_pos.dy}'); - } -} - -class DraggablePositions { - static const kChatWindow = 'draggablePositionChat'; - static const kMobileActions = 'draggablePositionMobile'; - static const kIOSDraggable = 'draggablePositionIOS'; - - static const kInvalidDraggablePosition = Offset(-999999, -999999); - final chatWindow = DraggableKeyPosition(kChatWindow); - final mobileActions = DraggableKeyPosition(kMobileActions); - final iOSDraggable = DraggableKeyPosition(kIOSDraggable); - - load() { - chatWindow.load(); - mobileActions.load(); - iOSDraggable.load(); - } -} - -DraggablePositions draggablePositions = DraggablePositions(); - -class Draggable extends StatefulWidget { - Draggable( - {Key? key, - this.checkKeyboard = false, - this.checkScreenSize = false, - required this.position, - required this.width, - required this.height, - this.chatModel, - required this.builder}) - : super(key: key); - - final bool checkKeyboard; - final bool checkScreenSize; - final DraggableKeyPosition position; - final double width; - final double height; - final ChatModel? chatModel; - final Widget Function(BuildContext, GestureDragUpdateCallback) builder; - - @override - State createState() => _DraggableState(chatModel); -} - -class _DraggableState extends State { - late ChatModel? _chatModel; - bool _keyboardVisible = false; - double _saveHeight = 0; - double _lastBottomHeight = 0; - - _DraggableState(ChatModel? chatModel) { - _chatModel = chatModel; - } - - get position => widget.position.pos; - - void onPanUpdate(DragUpdateDetails d) { - final offset = d.delta; - final size = MediaQuery.of(context).size; - double x = 0; - double y = 0; - - if (position.dx + offset.dx + widget.width > size.width) { - x = size.width - widget.width; - } else if (position.dx + offset.dx < 0) { - x = 0; - } else { - x = position.dx + offset.dx; - } - - if (position.dy + offset.dy + widget.height > size.height) { - y = size.height - widget.height; - } else if (position.dy + offset.dy < 0) { - y = 0; - } else { - y = position.dy + offset.dy; - } - setState(() { - widget.position.update(Offset(x, y)); - }); - _chatModel?.setChatWindowPosition(position); - } - - checkScreenSize() { - // Ensure the draggable always stays within current screen bounds - widget.position.tryAdjust(widget.width, widget.height, 1); - } - - checkKeyboard() { - final bottomHeight = MediaQuery.of(context).viewInsets.bottom; - final currentVisible = bottomHeight != 0; - - // save - if (!_keyboardVisible && currentVisible) { - _saveHeight = position.dy; - } - - // reset - if (_lastBottomHeight > 0 && bottomHeight == 0) { - setState(() { - widget.position.update(Offset(position.dx, _saveHeight)); - }); - } - - // onKeyboardVisible - if (_keyboardVisible && currentVisible) { - final sumHeight = bottomHeight + widget.height; - final contextHeight = MediaQuery.of(context).size.height; - if (sumHeight + position.dy > contextHeight) { - final y = contextHeight - sumHeight; - setState(() { - widget.position.update(Offset(position.dx, y)); - }); - } - } - - _keyboardVisible = currentVisible; - _lastBottomHeight = bottomHeight; - } - - @override - Widget build(BuildContext context) { - if (widget.checkKeyboard) { - checkKeyboard(); - } - if (widget.checkScreenSize) { - checkScreenSize(); - } - return Stack(children: [ - Positioned( - top: position.dy, - left: position.dx, - width: widget.width, - height: widget.height, - child: widget.builder(context, onPanUpdate)) - ]); - } -} - -class IOSDraggable extends StatefulWidget { - const IOSDraggable( - {Key? key, - this.chatModel, - required this.position, - required this.width, - required this.height, - required this.builder}) - : super(key: key); - - final DraggableKeyPosition position; - final ChatModel? chatModel; - final double width; - final double height; - final Widget Function(BuildContext) builder; - - @override - IOSDraggableState createState() => - IOSDraggableState(chatModel, width, height); -} - -class IOSDraggableState extends State { - late ChatModel? _chatModel; - late double _width; - late double _height; - bool _keyboardVisible = false; - double _saveHeight = 0; - double _lastBottomHeight = 0; - - IOSDraggableState(ChatModel? chatModel, double w, double h) { - _chatModel = chatModel; - _width = w; - _height = h; - } - - DraggableKeyPosition get position => widget.position; - - checkKeyboard() { - final bottomHeight = MediaQuery.of(context).viewInsets.bottom; - final currentVisible = bottomHeight != 0; - - // save - if (!_keyboardVisible && currentVisible) { - _saveHeight = position.pos.dy; - } - - // reset - if (_lastBottomHeight > 0 && bottomHeight == 0) { - setState(() { - position.update(Offset(position.pos.dx, _saveHeight)); - }); - } - - // onKeyboardVisible - if (_keyboardVisible && currentVisible) { - final sumHeight = bottomHeight + _height; - final contextHeight = MediaQuery.of(context).size.height; - if (sumHeight + position.pos.dy > contextHeight) { - final y = contextHeight - sumHeight; - setState(() { - position.update(Offset(position.pos.dx, y)); - }); - } - } - - _keyboardVisible = currentVisible; - _lastBottomHeight = bottomHeight; - } - - @override - void initState() { - super.initState(); - position.tryAdjust(_width, _height, 1); - } - - @override - Widget build(BuildContext context) { - checkKeyboard(); - return Stack( - children: [ - Positioned( - left: position.pos.dx, - top: position.pos.dy, - child: GestureDetector( - onPanUpdate: (details) { - setState(() { - position.update(position.pos + details.delta); - }); - _chatModel?.setChatWindowPosition(position.pos); - }, - child: Material( - child: Container( - width: _width, - height: _height, - decoration: - BoxDecoration(border: Border.all(color: MyTheme.border)), - child: widget.builder(context), - ), - ), - ), - ), - ], - ); - } -} - -class QualityMonitor extends StatelessWidget { - final QualityMonitorModel qualityMonitorModel; - QualityMonitor(this.qualityMonitorModel); - - Widget _row(String info, String? value, {Color? rightColor}) { - return Row( - children: [ - Expanded( - flex: 8, - child: AutoSizeText(info, - style: TextStyle(color: Color.fromARGB(255, 210, 210, 210)), - textAlign: TextAlign.right, - maxLines: 1)), - Spacer(flex: 1), - Expanded( - flex: 8, - child: AutoSizeText(value ?? '', - style: TextStyle(color: rightColor ?? Colors.white), - maxLines: 1)), - ], - ); - } - - @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: qualityMonitorModel, - child: Consumer( - builder: (context, qualityMonitorModel, child) => qualityMonitorModel - .show - ? Container( - constraints: BoxConstraints(maxWidth: 200), - padding: const EdgeInsets.all(8), - color: MyTheme.canvasColor.withAlpha(150), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _row("Speed", qualityMonitorModel.data.speed ?? '-'), - _row("FPS", qualityMonitorModel.data.fps ?? '-'), - // let delay be 0 if fps is 0 - _row( - "Delay", - "${qualityMonitorModel.data.delay == null ? '-' : (qualityMonitorModel.data.fps ?? "").replaceAll(' ', '').replaceAll('0', '').isEmpty ? 0 : qualityMonitorModel.data.delay}ms", - rightColor: Colors.green), - _row("Target Bitrate", - "${qualityMonitorModel.data.targetBitrate ?? '-'}kb"), - _row( - "Codec", qualityMonitorModel.data.codecFormat ?? '-'), - _row("Chroma", qualityMonitorModel.data.chroma ?? '-'), - ], - ), - ) - : const SizedBox.shrink())); -} - -class BlockableOverlayState extends OverlayKeyState { - final _middleBlocked = false.obs; - - VoidCallback? onMiddleBlockedClick; // to-do use listener - - RxBool get middleBlocked => _middleBlocked; - - void addMiddleBlockedListener(void Function(bool) cb) { - _middleBlocked.listen(cb); - } - - void setMiddleBlocked(bool blocked) { - if (blocked != _middleBlocked.value) { - _middleBlocked.value = blocked; - } - } - - void applyFfi(FFI ffi) { - ffi.dialogManager.setOverlayState(this); - ffi.chatModel.setOverlayState(this); - // make remote page penetrable automatically, effective for chat over remote - onMiddleBlockedClick = () { - setMiddleBlocked(false); - }; - } -} - -class BlockableOverlay extends StatelessWidget { - final Widget underlying; - final List? upperLayer; - - final BlockableOverlayState state; - - BlockableOverlay( - {required this.underlying, required this.state, this.upperLayer}); - - @override - Widget build(BuildContext context) { - final initialEntries = [ - OverlayEntry(builder: (_) => underlying), - - /// middle layer - OverlayEntry( - builder: (context) => Obx(() => Listener( - onPointerDown: (_) { - state.onMiddleBlockedClick?.call(); - }, - child: Container( - color: - state.middleBlocked.value ? Colors.transparent : null)))), - ]; - - if (upperLayer != null) { - initialEntries.addAll(upperLayer!); - } - - /// set key - return Overlay(key: state.key, initialEntries: initialEntries); - } -} diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart deleted file mode 100644 index 1f9f3ed7f..000000000 --- a/flutter/lib/common/widgets/peer_card.dart +++ /dev/null @@ -1,1581 +0,0 @@ -import 'package:bot_toast/bot_toast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/peer_tab_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; - -import '../../common.dart'; -import '../../common/formatter/id_formatter.dart'; -import '../../models/peer_model.dart'; -import '../../models/platform_model.dart'; -import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; -import '../../desktop/widgets/popup_menu.dart'; -import 'dart:math' as math; - -typedef PopupMenuEntryBuilder = Future>> - Function(BuildContext); - -enum PeerUiType { grid, tile, list } - -final peerCardUiType = PeerUiType.grid.obs; - -bool? hideUsernameOnCard; - -class _PeerCard extends StatefulWidget { - final Peer peer; - final PeerTabIndex tab; - final Function(BuildContext, String) connect; - final PopupMenuEntryBuilder popupMenuEntryBuilder; - - const _PeerCard( - {required this.peer, - required this.tab, - required this.connect, - required this.popupMenuEntryBuilder, - Key? key}) - : super(key: key); - - @override - _PeerCardState createState() => _PeerCardState(); -} - -/// State for the connection page. -class _PeerCardState extends State<_PeerCard> - with AutomaticKeepAliveClientMixin { - var _menuPos = RelativeRect.fill; - final double _cardRadius = 16; - final double _tileRadius = 5; - final double _borderWidth = 2; - - @override - Widget build(BuildContext context) { - super.build(context); - return Obx(() => - stateGlobal.isPortrait.isTrue ? _buildPortrait() : _buildLandscape()); - } - - Widget gestureDetector({required Widget child}) { - final PeerTabModel peerTabModel = Provider.of(context); - final peer = super.widget.peer; - return GestureDetector( - onDoubleTap: peerTabModel.multiSelectionMode - ? null - : () => widget.connect(context, peer.id), - onTap: () { - if (peerTabModel.multiSelectionMode) { - peerTabModel.select(peer); - } else { - if (isMobile) { - widget.connect(context, peer.id); - } else { - peerTabModel.select(peer); - } - } - }, - onLongPress: () => peerTabModel.select(peer), - child: child); - } - - Widget _buildPortrait() { - final peer = super.widget.peer; - return Card( - margin: EdgeInsets.symmetric(horizontal: 2), - child: gestureDetector( - child: Container( - padding: EdgeInsets.only(left: 12, top: 8, bottom: 8), - child: _buildPeerTile(context, peer, null)), - )); - } - - Widget _buildLandscape() { - final peer = super.widget.peer; - var deco = Rx( - BoxDecoration( - border: Border.all(color: Colors.transparent, width: _borderWidth), - 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: BorderRadius.circular( - peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius, - ), - ); - }, - onExit: (evt) { - deco.value = BoxDecoration( - border: Border.all(color: Colors.transparent, width: _borderWidth), - borderRadius: BorderRadius.circular( - peerCardUiType.value == PeerUiType.grid ? _cardRadius : _tileRadius, - ), - ); - }, - child: gestureDetector( - child: Obx(() => peerCardUiType.value == PeerUiType.grid - ? _buildPeerCard(context, peer, deco) - : _buildPeerTile(context, peer, deco))), - ); - } - - bool _showNote(Peer peer) { - return peerTabShowNote(widget.tab) && peer.note.isNotEmpty; - } - - makeChild(bool isPortrait, Peer peer) { - final name = hideUsernameOnCard == true - ? peer.hostname - : '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; - final greyStyle = TextStyle( - fontSize: 11, - color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); - final showNote = _showNote(peer); - - return Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - decoration: BoxDecoration( - color: str2color('${peer.id}${peer.platform}', 0x7f), - borderRadius: isPortrait - ? BorderRadius.circular(_tileRadius) - : BorderRadius.only( - topLeft: Radius.circular(_tileRadius), - bottomLeft: Radius.circular(_tileRadius), - ), - ), - alignment: Alignment.center, - width: isPortrait ? 50 : 42, - height: isPortrait ? 50 : null, - child: Stack( - children: [ - getPlatformImage(peer.platform, size: isPortrait ? 38 : 30) - .paddingAll(6), - if (_shouldBuildPasswordIcon(peer)) - Positioned( - top: 1, - left: 1, - child: Icon(Icons.key, size: 6, color: Colors.white), - ), - ], - )), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.only( - topRight: Radius.circular(_tileRadius), - bottomRight: Radius.circular(_tileRadius), - ), - ), - child: Row( - children: [ - Expanded( - child: Column( - children: [ - Row(children: [ - getOnline(isPortrait ? 4 : 8, peer.online), - Expanded( - child: Text( - peer.alias.isEmpty ? formatID(peer.id) : peer.alias, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleSmall, - )), - ]).marginOnly(top: isPortrait ? 0 : 2), - Row( - children: [ - Flexible( - child: Tooltip( - message: name, - waitDuration: const Duration(seconds: 1), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - name, - style: isPortrait ? null : greyStyle, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - if (showNote) - Expanded( - child: Tooltip( - message: peer.note, - waitDuration: const Duration(seconds: 1), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - peer.note, - style: isPortrait ? null : greyStyle, - textAlign: TextAlign.start, - overflow: TextOverflow.ellipsis, - ).marginOnly( - left: peerCardUiType.value == - PeerUiType.list - ? 32 - : 4), - ), - ), - ) - ], - ), - ], - ).marginOnly(top: 2), - ), - isPortrait - ? checkBoxOrActionMorePortrait(peer) - : checkBoxOrActionMoreLandscape(peer, isTile: true), - ], - ).paddingOnly(left: 10.0, top: 3.0), - ), - ) - ], - ); - } - - Widget _buildPeerTile( - BuildContext context, Peer peer, Rx? deco) { - hideUsernameOnCard ??= - bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y'; - final colors = _frontN(peer.tags, 25) - .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) - .toList(); - return Tooltip( - message: !(isDesktop || isWebDesktop) - ? '' - : peer.tags.isNotEmpty - ? '${translate('Tags')}: ${peer.tags.join(', ')}' - : '', - child: Stack(children: [ - Obx( - () => deco == null - ? makeChild(stateGlobal.isPortrait.isTrue, peer) - : Container( - foregroundDecoration: deco.value, - child: makeChild(stateGlobal.isPortrait.isTrue, peer), - ), - ), - if (colors.isNotEmpty) - Obx(() => Positioned( - top: 2, - right: stateGlobal.isPortrait.isTrue ? 20 : 10, - child: CustomPaint( - painter: TagPainter(radius: 3, colors: colors), - ), - )) - ]), - ); - } - - Widget _buildPeerCard( - BuildContext context, Peer peer, Rx deco) { - hideUsernameOnCard ??= - bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y'; - final name = hideUsernameOnCard == true - ? peer.hostname - : '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; - final child = Card( - color: Colors.transparent, - elevation: 0, - margin: EdgeInsets.zero, - // to-do: memory leak here, more investigation needed. - // Continious rebuilds of `Obx()` will cause memory leak here. - // The simple demo does not have this issue. - child: Obx( - () => Container( - foregroundDecoration: deco.value, - child: ClipRRect( - borderRadius: BorderRadius.circular(_cardRadius - _borderWidth), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Container( - color: str2color('${peer.id}${peer.platform}', 0x7f), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(6), - child: - getPlatformImage(peer.platform, size: 60), - ), - Row( - children: [ - Expanded( - child: Tooltip( - message: name, - waitDuration: const Duration(seconds: 1), - child: Text( - name, - style: const TextStyle( - color: Colors.white70, - fontSize: 12), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ), - if (_showNote(peer)) - Row( - children: [ - Expanded( - child: Tooltip( - message: peer.note, - waitDuration: const Duration(seconds: 1), - child: Text( - peer.note, - style: const TextStyle( - color: Colors.white38, - fontSize: 10), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - )) - ], - ), - ], - ).paddingOnly(top: 4.0, left: 4.0, right: 4.0), - ), - ], - ), - ), - ), - Container( - color: Theme.of(context).colorScheme.background, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row(children: [ - getOnline(8, peer.online), - Expanded( - child: Text( - peer.alias.isEmpty ? formatID(peer.id) : peer.alias, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleSmall, - )), - ]).paddingSymmetric(vertical: 8)), - checkBoxOrActionMoreLandscape(peer, isTile: false), - ], - ).paddingSymmetric(horizontal: 12.0), - ) - ], - ), - ), - ), - ), - ); - - final colors = _frontN(peer.tags, 25) - .map((e) => gFFI.abModel.getCurrentAbTagColor(e)) - .toList(); - return Tooltip( - message: peer.tags.isNotEmpty - ? '${translate('Tags')}: ${peer.tags.join(', ')}' - : '', - child: Stack(children: [ - child, - if (_shouldBuildPasswordIcon(peer)) - Positioned( - top: 4, - left: 12, - child: Icon(Icons.key, size: 12, color: Colors.white), - ), - if (colors.isNotEmpty) - Positioned( - top: 4, - right: 12, - child: CustomPaint( - painter: TagPainter(radius: 4, colors: colors), - ), - ) - ]), - ); - } - - List _frontN(List list, int n) { - if (list.length <= n) { - return list; - } else { - return list.sublist(0, n); - } - } - - Widget checkBoxOrActionMorePortrait(Peer peer) { - final PeerTabModel peerTabModel = Provider.of(context); - final selected = peerTabModel.isPeerSelected(peer.id); - if (peerTabModel.multiSelectionMode) { - return Padding( - padding: const EdgeInsets.all(12), - child: selected - ? Icon( - Icons.check_box, - color: MyTheme.accent, - ) - : Icon(Icons.check_box_outline_blank), - ); - } else { - return InkWell( - child: const Padding( - padding: EdgeInsets.all(12), child: Icon(Icons.more_vert)), - onTapDown: (e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () { - _showPeerMenu(peer.id); - }); - } - } - - Widget checkBoxOrActionMoreLandscape(Peer peer, {required bool isTile}) { - final PeerTabModel peerTabModel = Provider.of(context); - final selected = peerTabModel.isPeerSelected(peer.id); - if (peerTabModel.multiSelectionMode) { - final icon = selected - ? Icon( - Icons.check_box, - color: MyTheme.accent, - ) - : Icon(Icons.check_box_outline_blank); - bool last = peerTabModel.isShiftDown && peer.id == peerTabModel.lastId; - double right = isTile ? 4 : 0; - if (last) { - return Container( - decoration: BoxDecoration( - border: Border.all(color: MyTheme.accent, width: 1)), - child: icon, - ).marginOnly(right: right); - } else { - return icon.marginOnly(right: right); - } - } else { - return _actionMore(peer); - } - } - - Widget _actionMore(Peer peer) => Listener( - onPointerDown: (e) { - final x = e.position.dx; - final y = e.position.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onPointerUp: (_) => _showPeerMenu(peer.id), - child: build_more(context)); - - bool _shouldBuildPasswordIcon(Peer peer) { - if (gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index) return false; - if (gFFI.abModel.current.isPersonal()) return false; - return peer.password.isNotEmpty; - } - - /// Show the peer menu and handle user's choice. - /// User might remove the peer or send a file to the peer. - void _showPeerMenu(String id) async { - await mod_menu.showMenu( - context: context, - position: _menuPos, - items: await super.widget.popupMenuEntryBuilder(context), - elevation: 8, - ); - } - - @override - bool get wantKeepAlive => true; -} - -abstract class BasePeerCard extends StatelessWidget { - final Peer peer; - final PeerTabIndex tab; - final EdgeInsets? menuPadding; - - BasePeerCard( - {required this.peer, required this.tab, this.menuPadding, Key? key}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return _PeerCard( - peer: peer, - tab: tab, - connect: (BuildContext context, String id) => - connectInPeerTab(context, peer, tab), - popupMenuEntryBuilder: _buildPopupMenuEntry, - ); - } - - Future>> _buildPopupMenuEntry( - BuildContext context) async => - (await _buildMenuItems(context)) - .map((e) => e.build( - context, - const MenuConfig( - commonColor: CustomPopupMenuTheme.commonColor, - height: CustomPopupMenuTheme.height, - dividerHeight: CustomPopupMenuTheme.dividerHeight))) - .expand((i) => i) - .toList(); - - @protected - Future>> _buildMenuItems(BuildContext context); - - MenuEntryBase _connectCommonAction( - BuildContext context, - String title, { - bool isFileTransfer = false, - bool isViewCamera = false, - bool isTcpTunneling = false, - bool isRDP = false, - bool isTerminal = false, - bool isTerminalRunAsAdmin = false, - }) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - title, - style: style, - ), - proc: () { - if (isTerminalRunAsAdmin) { - setEnvTerminalAdmin(); - } - connectInPeerTab( - context, - peer, - tab, - isFileTransfer: isFileTransfer, - isViewCamera: isViewCamera, - isTcpTunneling: isTcpTunneling, - isRDP: isRDP, - isTerminal: isTerminal || isTerminalRunAsAdmin, - ); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _connectAction(BuildContext context) { - return _connectCommonAction( - context, - (peer.alias.isEmpty - ? translate('Connect') - : '${translate('Connect')} ${peer.id}'), - ); - } - - @protected - MenuEntryBase _transferFileAction(BuildContext context) { - return _connectCommonAction( - context, - translate('Transfer file'), - isFileTransfer: true, - ); - } - - @protected - MenuEntryBase _viewCameraAction(BuildContext context) { - return _connectCommonAction( - context, - translate('View camera'), - isViewCamera: true, - ); - } - - @protected - MenuEntryBase _terminalAction(BuildContext context) { - return _connectCommonAction( - context, - '${translate('Terminal')} (beta)', - isTerminal: true, - ); - } - - @protected - MenuEntryBase _terminalRunAsAdminAction(BuildContext context) { - return _connectCommonAction( - context, - '${translate('Terminal (Run as administrator)')} (beta)', - isTerminalRunAsAdmin: true, - ); - } - - @protected - MenuEntryBase _tcpTunnelingAction(BuildContext context) { - return _connectCommonAction( - context, - translate('TCP tunneling'), - isTcpTunneling: true, - ); - } - - @protected - MenuEntryBase _rdpAction(BuildContext context, String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Container( - alignment: AlignmentDirectional.center, - height: CustomPopupMenuTheme.height, - child: Row( - children: [ - Text( - translate('RDP'), - style: style, - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: 0.8, - child: IconButton( - icon: const Icon(Icons.edit), - padding: EdgeInsets.zero, - onPressed: () { - if (Navigator.canPop(context)) { - Navigator.pop(context); - } - _rdpDialog(id); - }, - )), - )) - ], - )), - proc: () { - connectInPeerTab(context, peer, tab, isRDP: true); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _wolAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('WOL'), - style: style, - ), - proc: () { - bind.mainWol(id: id); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - /// Only available on Windows. - @protected - MenuEntryBase _createShortCutAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Create desktop shortcut'), - style: style, - ), - proc: () { - bind.mainCreateShortcut(id: id); - showToast(translate('Successful')); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - Future> _openNewConnInAction( - String id, String label, String key) async { - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate(label), - getter: () async => mainGetPeerBoolOptionSync(id, key), - setter: (bool v) async { - await bind.mainSetPeerOption( - id: id, key: key, value: bool2option(key, v)); - showToast(translate('Successful')); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - _openInTabsAction(String id) async => - await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs); - - _openInWindowsAction(String id) async => await _openNewConnInAction( - id, 'Open in new window', kOptionOpenInWindows); - - // ignore: unused_element - _openNewConnInOptAction(String id) async => - mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs) - ? await _openInWindowsAction(id) - : await _openInTabsAction(id); - - @protected - Future _isForceAlwaysRelay(String id) async { - return option2bool(kOptionForceAlwaysRelay, - (await bind.mainGetPeerOption(id: id, key: kOptionForceAlwaysRelay))); - } - - @protected - Future> _forceAlwaysRelayAction(String id) async { - return MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Always connect via relay'), - getter: () async { - return await _isForceAlwaysRelay(id); - }, - setter: (bool v) async { - await bind.mainSetPeerOption( - id: id, - key: kOptionForceAlwaysRelay, - value: bool2option(kOptionForceAlwaysRelay, v)); - showToast(translate('Successful')); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _renameAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Rename'), - style: style, - ), - proc: () async { - String oldName = await _getAlias(id); - renameDialog( - oldName: oldName, - onSubmit: (String newName) async { - if (newName != oldName) { - if (tab == PeerTabIndex.ab) { - await gFFI.abModel.changeAlias(id: id, alias: newName); - await bind.mainSetPeerAlias(id: id, alias: newName); - } else { - await bind.mainSetPeerAlias(id: id, alias: newName); - showToast(translate('Successful')); - _update(); - } - } - }); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _removeAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Row( - children: [ - Text( - translate('Delete'), - style: style?.copyWith(color: Colors.red), - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: 0.8, - child: Icon(Icons.delete_forever, color: Colors.red), - ), - ).marginOnly(right: 4)), - ], - ), - proc: () { - onSubmit() async { - switch (tab) { - case PeerTabIndex.recent: - await bind.mainRemovePeer(id: id); - bind.mainLoadRecentPeers(); - break; - case PeerTabIndex.fav: - final favs = (await bind.mainGetFav()).toList(); - if (favs.remove(id)) { - await bind.mainStoreFav(favs: favs); - bind.mainLoadFavPeers(); - } - break; - case PeerTabIndex.lan: - await bind.mainRemoveDiscovered(id: id); - bind.mainLoadLanPeers(); - break; - case PeerTabIndex.ab: - await gFFI.abModel.deletePeers([id]); - break; - case PeerTabIndex.group: - break; - } - if (tab != PeerTabIndex.ab) { - showToast(translate('Successful')); - } - } - - deleteConfirmDialog(onSubmit, - '${translate('Delete')} "${peer.alias.isEmpty ? formatID(peer.id) : peer.alias}"?'); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _unrememberPasswordAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Forget Password'), - style: style, - ), - proc: () async { - bool succ = await gFFI.abModel.changePersonalHashPassword(id, ''); - await bind.mainForgetPassword(id: id); - if (succ) { - showToast(translate('Successful')); - } else { - if (tab.index == PeerTabIndex.ab.index) { - BotToast.showText( - contentColor: Colors.red, text: translate("Failed")); - } - } - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _addFavAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Row( - children: [ - Text( - translate('Add to Favorites'), - style: style, - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: 0.8, - child: Icon(Icons.star_outline), - ), - ).marginOnly(right: 4)), - ], - ), - proc: () { - () async { - final favs = (await bind.mainGetFav()).toList(); - if (!favs.contains(id)) { - favs.add(id); - await bind.mainStoreFav(favs: favs); - } - showToast(translate('Successful')); - }(); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _rmFavAction( - String id, Future Function() reloadFunc) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Row( - children: [ - Text( - translate('Remove from Favorites'), - style: style, - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Transform.scale( - scale: 0.8, - child: Icon(Icons.star), - ), - ).marginOnly(right: 4)), - ], - ), - proc: () { - () async { - final favs = (await bind.mainGetFav()).toList(); - if (favs.remove(id)) { - await bind.mainStoreFav(favs: favs); - await reloadFunc(); - } - showToast(translate('Successful')); - }(); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _addToAb(Peer peer) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Add to address book'), - style: style, - ), - proc: () { - () async { - addPeersToAbDialog([Peer.copy(peer)]); - }(); - }, - padding: menuPadding, - dismissOnClicked: true, - ); - } - - @protected - Future _getAlias(String id) async => - await bind.mainGetPeerOption(id: id, key: 'alias'); - - @protected - void _update(); -} - -class RecentPeerCard extends BasePeerCard { - RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super( - peer: peer, - tab: PeerTabIndex.recent, - menuPadding: menuPadding, - key: key); - - @override - Future>> _buildMenuItems( - BuildContext context) async { - final List> menuItems = [ - _connectAction(context), - _transferFileAction(context), - _viewCameraAction(context), - _terminalAction(context), - ]; - - if (peer.platform == kPeerPlatformWindows) { - menuItems.add(_terminalRunAsAdminAction(context)); - } - - final List favs = (await bind.mainGetFav()).toList(); - - if (isDesktop && peer.platform != kPeerPlatformAndroid) { - menuItems.add(_tcpTunnelingAction(context)); - } - // menuItems.add(await _openNewConnInOptAction(peer.id)); - if (!isWeb) { - menuItems.add(await _forceAlwaysRelayAction(peer.id)); - } - if (isWindows && peer.platform == kPeerPlatformWindows) { - menuItems.add(_rdpAction(context, peer.id)); - } - if (isWindows) { - menuItems.add(_createShortCutAction(peer.id)); - } - menuItems.add(MenuEntryDivider()); - if (isMobile || isDesktop || isWebDesktop) { - menuItems.add(_renameAction(peer.id)); - } - if (await bind.mainPeerHasPassword(id: peer.id)) { - menuItems.add(_unrememberPasswordAction(peer.id)); - } - - if (!favs.contains(peer.id)) { - menuItems.add(_addFavAction(peer.id)); - } else { - menuItems.add(_rmFavAction(peer.id, () async {})); - } - - if (gFFI.userModel.userName.isNotEmpty) { - menuItems.add(_addToAb(peer)); - } - - menuItems.add(MenuEntryDivider()); - menuItems.add(_removeAction(peer.id)); - return menuItems; - } - - @protected - @override - void _update() => bind.mainLoadRecentPeers(); -} - -class FavoritePeerCard extends BasePeerCard { - FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super( - peer: peer, - tab: PeerTabIndex.fav, - menuPadding: menuPadding, - key: key); - - @override - Future>> _buildMenuItems( - BuildContext context) async { - final List> menuItems = [ - _connectAction(context), - _transferFileAction(context), - _viewCameraAction(context), - _terminalAction(context), - ]; - - if (peer.platform == kPeerPlatformWindows) { - menuItems.add(_terminalRunAsAdminAction(context)); - } - - if (isDesktop && peer.platform != kPeerPlatformAndroid) { - menuItems.add(_tcpTunnelingAction(context)); - } - // menuItems.add(await _openNewConnInOptAction(peer.id)); - if (!isWeb) { - menuItems.add(await _forceAlwaysRelayAction(peer.id)); - } - if (isWindows && peer.platform == kPeerPlatformWindows) { - menuItems.add(_rdpAction(context, peer.id)); - } - if (isWindows) { - menuItems.add(_createShortCutAction(peer.id)); - } - menuItems.add(MenuEntryDivider()); - if (isMobile || isDesktop || isWebDesktop) { - menuItems.add(_renameAction(peer.id)); - } - if (await bind.mainPeerHasPassword(id: peer.id)) { - menuItems.add(_unrememberPasswordAction(peer.id)); - } - menuItems.add(_rmFavAction(peer.id, () async { - await bind.mainLoadFavPeers(); - })); - - if (gFFI.userModel.userName.isNotEmpty) { - menuItems.add(_addToAb(peer)); - } - - menuItems.add(MenuEntryDivider()); - menuItems.add(_removeAction(peer.id)); - return menuItems; - } - - @protected - @override - void _update() => bind.mainLoadFavPeers(); -} - -class DiscoveredPeerCard extends BasePeerCard { - DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super( - peer: peer, - tab: PeerTabIndex.lan, - menuPadding: menuPadding, - key: key); - - @override - Future>> _buildMenuItems( - BuildContext context) async { - final List> menuItems = [ - _connectAction(context), - _transferFileAction(context), - _viewCameraAction(context), - _terminalAction(context), - ]; - - if (peer.platform == kPeerPlatformWindows) { - menuItems.add(_terminalRunAsAdminAction(context)); - } - - final List favs = (await bind.mainGetFav()).toList(); - - if (isDesktop && peer.platform != kPeerPlatformAndroid) { - menuItems.add(_tcpTunnelingAction(context)); - } - // menuItems.add(await _openNewConnInOptAction(peer.id)); - if (!isWeb) { - menuItems.add(await _forceAlwaysRelayAction(peer.id)); - } - if (isWindows && peer.platform == kPeerPlatformWindows) { - menuItems.add(_rdpAction(context, peer.id)); - } - menuItems.add(_wolAction(peer.id)); - if (isWindows) { - menuItems.add(_createShortCutAction(peer.id)); - } - - if (!favs.contains(peer.id)) { - menuItems.add(_addFavAction(peer.id)); - } else { - menuItems.add(_rmFavAction(peer.id, () async {})); - } - - if (gFFI.userModel.userName.isNotEmpty) { - menuItems.add(_addToAb(peer)); - } - - menuItems.add(MenuEntryDivider()); - menuItems.add(_removeAction(peer.id)); - return menuItems; - } - - @protected - @override - void _update() => bind.mainLoadLanPeers(); -} - -class AddressBookPeerCard extends BasePeerCard { - AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super( - peer: peer, - tab: PeerTabIndex.ab, - menuPadding: menuPadding, - key: key); - - @override - Future>> _buildMenuItems( - BuildContext context) async { - final List> menuItems = [ - _connectAction(context), - _transferFileAction(context), - _viewCameraAction(context), - _terminalAction(context), - ]; - - if (peer.platform == kPeerPlatformWindows) { - menuItems.add(_terminalRunAsAdminAction(context)); - } - - if (isDesktop && peer.platform != kPeerPlatformAndroid) { - menuItems.add(_tcpTunnelingAction(context)); - } - // menuItems.add(await _openNewConnInOptAction(peer.id)); - if (!isWeb) { - menuItems.add(await _forceAlwaysRelayAction(peer.id)); - } - if (isWindows && peer.platform == kPeerPlatformWindows) { - menuItems.add(_rdpAction(context, peer.id)); - } - if (isWindows) { - menuItems.add(_createShortCutAction(peer.id)); - } - if (gFFI.abModel.current.canWrite()) { - menuItems.add(MenuEntryDivider()); - if (isMobile || isDesktop || isWebDesktop) { - menuItems.add(_renameAction(peer.id)); - } - if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) { - menuItems.add(_unrememberPasswordAction(peer.id)); - } - if (!gFFI.abModel.current.isPersonal()) { - menuItems.add(_changeSharedAbPassword()); - } - if (gFFI.abModel.currentAbTags.isNotEmpty) { - menuItems.add(_editTagAction(peer.id)); - } - menuItems.add(_editNoteAction(peer.id)); - } - final addressbooks = gFFI.abModel.addressBooksCanWrite(); - if (gFFI.peerTabModel.currentTab == PeerTabIndex.ab.index) { - addressbooks.remove(gFFI.abModel.currentName.value); - } - if (addressbooks.isNotEmpty) { - menuItems.add(_addToAb(peer)); - } - menuItems.add(_existIn()); - if (gFFI.abModel.current.canWrite()) { - menuItems.add(MenuEntryDivider()); - menuItems.add(_removeAction(peer.id)); - } - return menuItems; - } - - // address book does not need to update - @protected - @override - void _update() => - {}; //gFFI.abModel.pullAb(force: ForcePullAb.current, quiet: true); - - @protected - MenuEntryBase _editTagAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Edit Tag'), - style: style, - ), - proc: () { - editAbTagDialog(gFFI.abModel.getPeerTags(id), (selectedTag) async { - await gFFI.abModel.changeTagForPeers([id], selectedTag); - }); - }, - padding: super.menuPadding, - dismissOnClicked: true, - ); - } - - @protected - MenuEntryBase _editNoteAction(String id) { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Edit note'), - style: style, - ), - proc: () { - editAbPeerNoteDialog(id); - }, - padding: super.menuPadding, - dismissOnClicked: true, - ); - } - - @protected - @override - Future _getAlias(String id) async => - gFFI.abModel.find(id)?.alias ?? ''; - - MenuEntryBase _changeSharedAbPassword() { - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate( - peer.password.isEmpty ? 'Set shared password' : 'Change Password'), - style: style, - ), - proc: () { - setSharedAbPasswordDialog(gFFI.abModel.currentName.value, peer); - }, - padding: super.menuPadding, - dismissOnClicked: true, - ); - } - - MenuEntryBase _existIn() { - final names = gFFI.abModel.idExistIn(peer.id); - final text = names.join(', '); - return MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Exist in'), - style: style, - ), - proc: () { - gFFI.dialogManager.show((setState, close, context) { - return CustomAlertDialog( - title: Text(translate('Exist in')), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [Text(text)]), - actions: [ - dialogButton( - "OK", - icon: Icon(Icons.done_rounded), - onPressed: close, - ), - ], - onSubmit: close, - onCancel: close, - ); - }); - }, - padding: super.menuPadding, - dismissOnClicked: true, - ); - } -} - -class MyGroupPeerCard extends BasePeerCard { - MyGroupPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key}) - : super( - peer: peer, - tab: PeerTabIndex.group, - menuPadding: menuPadding, - key: key); - - @override - Future>> _buildMenuItems( - BuildContext context) async { - final List> menuItems = [ - _connectAction(context), - _transferFileAction(context), - _viewCameraAction(context), - _terminalAction(context), - ]; - - if (peer.platform == kPeerPlatformWindows) { - menuItems.add(_terminalRunAsAdminAction(context)); - } - - if (isDesktop && peer.platform != kPeerPlatformAndroid) { - menuItems.add(_tcpTunnelingAction(context)); - } - // menuItems.add(await _openNewConnInOptAction(peer.id)); - if (!isWeb) { - menuItems.add(await _forceAlwaysRelayAction(peer.id)); - } - if (isWindows && peer.platform == kPeerPlatformWindows) { - menuItems.add(_rdpAction(context, peer.id)); - } - if (isWindows) { - menuItems.add(_createShortCutAction(peer.id)); - } - // menuItems.add(MenuEntryDivider()); - // menuItems.add(_renameAction(peer.id)); - // if (await bind.mainPeerHasPassword(id: peer.id)) { - // menuItems.add(_unrememberPasswordAction(peer.id)); - // } - if (gFFI.userModel.userName.isNotEmpty) { - menuItems.add(_addToAb(peer)); - } - return menuItems; - } - - @protected - @override - void _update() => gFFI.groupModel.pull(); -} - -void _rdpDialog(String id) async { - final maxLength = bind.mainMaxEncryptLen(); - final port = await bind.mainGetPeerOption(id: id, key: 'rdp_port'); - final username = await bind.mainGetPeerOption(id: id, key: 'rdp_username'); - final portController = TextEditingController(text: port); - final userController = TextEditingController(text: username); - final passwordController = TextEditingController( - text: await bind.mainGetPeerOption(id: id, key: 'rdp_password')); - RxBool secure = true.obs; - - gFFI.dialogManager.show((setState, close, context) { - submit() async { - String port = portController.text.trim(); - String username = userController.text; - String password = passwordController.text; - await bind.mainSetPeerOption(id: id, key: 'rdp_port', value: port); - await bind.mainSetPeerOption( - id: id, key: 'rdp_username', value: username); - await bind.mainSetPeerOption( - id: id, key: 'rdp_password', value: password); - showToast(translate('Successful')); - close(); - } - - return CustomAlertDialog( - title: Text(translate('RDP Settings')), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - 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: InputDecoration( - labelText: isDesktop ? null : translate('Port'), - hintText: '3389'), - controller: portController, - autofocus: true, - ).workaroundFreezeLinuxMint(), - ), - ], - ).marginOnly(bottom: isDesktop ? 8 : 0), - Obx(() => Row( - children: [ - stateGlobal.isPortrait.isFalse - ? ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - "${translate('Username')}:", - textAlign: TextAlign.right, - ).marginOnly(right: 10)) - : SizedBox.shrink(), - Expanded( - child: TextField( - decoration: InputDecoration( - labelText: - isDesktop ? null : translate('Username')), - controller: userController, - ).workaroundFreezeLinuxMint(), - ), - ], - ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)), - Obx(() => Row( - children: [ - stateGlobal.isPortrait.isFalse - ? 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, - maxLength: maxLength, - decoration: InputDecoration( - labelText: - isDesktop ? null : translate('Password'), - suffixIcon: IconButton( - onPressed: () => - secure.value = !secure.value, - icon: Icon(secure.value - ? Icons.visibility_off - : Icons.visibility))), - controller: passwordController, - ).workaroundFreezeLinuxMint()), - ), - ], - )) - ], - ), - ), - actions: [ - dialogButton("Cancel", onPressed: close, isOutline: true), - dialogButton("OK", onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -Widget getOnline(double rightPadding, bool online) { - return Tooltip( - message: translate(online ? 'Online' : 'Offline'), - waitDuration: const Duration(seconds: 1), - child: Padding( - padding: EdgeInsets.fromLTRB(0, 4, rightPadding, 4), - child: CircleAvatar( - radius: 3, backgroundColor: online ? Colors.green : kColorWarn))); -} - -Widget build_more(BuildContext context, {bool invert = false}) { - final RxBool hover = false.obs; - return InkWell( - borderRadius: BorderRadius.circular(14), - onTap: () {}, - onHover: (value) => hover.value = value, - child: Obx(() => CircleAvatar( - radius: 14, - backgroundColor: hover.value - ? (invert - ? Theme.of(context).colorScheme.background - : Theme.of(context).scaffoldBackgroundColor) - : (invert - ? Theme.of(context).scaffoldBackgroundColor - : Theme.of(context).colorScheme.background), - child: Icon(Icons.more_vert, - size: 18, - color: hover.value - ? Theme.of(context).textTheme.titleLarge?.color - : Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5))))); -} - -class TagPainter extends CustomPainter { - final double radius; - late final List colors; - - TagPainter({required this.radius, required List colors}) { - this.colors = colors.reversed.toList(); - } - - @override - void paint(Canvas canvas, Size size) { - double x = 0; - double y = radius; - for (int i = 0; i < colors.length; i++) { - Paint paint = Paint(); - paint.color = colors[i]; - x -= radius + 1; - if (i == colors.length - 1) { - canvas.drawCircle(Offset(x, y), radius, paint); - } else { - Path path = Path(); - path.addArc(Rect.fromCircle(center: Offset(x, y), radius: radius), - math.pi * 4 / 3, math.pi * 4 / 3); - path.addArc( - Rect.fromCircle(center: Offset(x - radius, y), radius: radius), - math.pi * 5 / 3, - math.pi * 2 / 3); - path.fillType = PathFillType.evenOdd; - canvas.drawPath(path, paint); - } - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} - -void connectInPeerTab(BuildContext context, Peer peer, PeerTabIndex tab, - {bool isFileTransfer = false, - bool isViewCamera = false, - bool isTcpTunneling = false, - bool isRDP = false, - bool isTerminal = false}) async { - var password = ''; - bool isSharedPassword = false; - if (tab == PeerTabIndex.ab) { - // If recent peer's alias is empty, set it to ab's alias - // Because the platform is not set, it may not take effect, but it is more important not to display if the connection is not successful - if (peer.alias.isNotEmpty && - (await bind.mainGetPeerOption(id: peer.id, key: "alias")).isEmpty) { - await bind.mainSetPeerAlias( - id: peer.id, - alias: peer.alias, - ); - } - if (!gFFI.abModel.current.isPersonal()) { - if (peer.password.isNotEmpty) { - password = peer.password; - isSharedPassword = true; - } - if (password.isEmpty) { - final abPassword = gFFI.abModel.getdefaultSharedPassword(); - if (abPassword != null) { - password = abPassword; - isSharedPassword = true; - } - } - } - } - connect(context, peer.id, - password: password, - isSharedPassword: isSharedPassword, - isFileTransfer: isFileTransfer, - isTerminal: isTerminal, - isViewCamera: isViewCamera, - isTcpTunneling: isTcpTunneling, - isRDP: isRDP); -} diff --git a/flutter/lib/common/widgets/peer_tab_page.dart b/flutter/lib/common/widgets/peer_tab_page.dart deleted file mode 100644 index 4849f2783..000000000 --- a/flutter/lib/common/widgets/peer_tab_page.dart +++ /dev/null @@ -1,1039 +0,0 @@ -import 'dart:ui' as ui; - -import 'package:bot_toast/bot_toast.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/widgets/address_book.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/common/widgets/my_group.dart'; -import 'package:flutter_hbb/common/widgets/peers_view.dart'; -import 'package:flutter_hbb/common/widgets/peer_card.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' - as mod_menu; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/models/ab_model.dart'; -import 'package:flutter_hbb/models/peer_model.dart'; - -import 'package:flutter_hbb/models/peer_tab_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:pull_down_button/pull_down_button.dart'; - -import '../../common.dart'; -import '../../models/platform_model.dart'; - -class PeerTabPage extends StatefulWidget { - const PeerTabPage({Key? key}) : super(key: key); - @override - State createState() => _PeerTabPageState(); -} - -class _TabEntry { - final Widget widget; - final Function({dynamic hint})? load; - _TabEntry(this.widget, [this.load]); -} - -EdgeInsets? _menuPadding() { - return (isDesktop || isWebDesktop) ? kDesktopMenuPadding : null; -} - -class _PeerTabPageState extends State - with SingleTickerProviderStateMixin { - final List<_TabEntry> entries = [ - _TabEntry(RecentPeersView( - menuPadding: _menuPadding(), - )), - _TabEntry(FavoritePeersView( - menuPadding: _menuPadding(), - )), - _TabEntry(DiscoveredPeersView( - menuPadding: _menuPadding(), - )), - _TabEntry( - AddressBook( - menuPadding: _menuPadding(), - ), - ({dynamic hint}) => gFFI.abModel.pullAb( - force: hint == null ? ForcePullAb.listAndCurrent : null, - quiet: false)), - _TabEntry( - MyGroup( - menuPadding: _menuPadding(), - ), - ({dynamic hint}) => gFFI.groupModel.pull(force: hint == null), - ), - ]; - RelativeRect? mobileTabContextMenuPos; - - final isOptVisiableFixed = isOptionFixed(kOptionPeerTabVisible); - - _PeerTabPageState() { - _loadLocalOptions(); - } - - void _loadLocalOptions() { - final uiType = bind.getLocalFlutterOption(k: kOptionPeerCardUiType); - if (uiType != '') { - peerCardUiType.value = int.parse(uiType) == 0 - ? PeerUiType.grid - : int.parse(uiType) == 1 - ? PeerUiType.tile - : PeerUiType.list; - } - hideAbTagsPanel.value = - bind.mainGetLocalOption(key: kOptionHideAbTagsPanel) == 'Y'; - } - - Future handleTabSelection(int tabIndex) async { - if (tabIndex < entries.length) { - if (tabIndex != gFFI.peerTabModel.currentTab) { - gFFI.peerTabModel.setCurrentTabCachedPeers([]); - } - gFFI.peerTabModel.setCurrentTab(tabIndex); - entries[tabIndex].load?.call(hint: false); - } - } - - @override - Widget build(BuildContext context) { - final model = Provider.of(context); - Widget selectionWrap(Widget widget) { - return model.multiSelectionMode ? createMultiSelectionBar(model) : widget; - } - - return Column( - textBaseline: TextBaseline.ideographic, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx(() => SizedBox( - height: 32, - child: Container( - padding: stateGlobal.isPortrait.isTrue - ? EdgeInsets.symmetric(horizontal: 2) - : null, - child: selectionWrap(Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: visibleContextMenuListener( - _createSwitchBar(context))), - if (stateGlobal.isPortrait.isTrue) - ..._portraitRightActions(context) - else - ..._landscapeRightActions(context) - ], - )), - ), - ).paddingOnly(right: stateGlobal.isPortrait.isTrue ? 0 : 12)), - _createPeersView(), - ], - ); - } - - Widget _createSwitchBar(BuildContext context) { - final model = Provider.of(context); - var counter = -1; - return ReorderableListView( - buildDefaultDragHandles: false, - onReorder: model.reorder, - scrollDirection: Axis.horizontal, - physics: NeverScrollableScrollPhysics(), - children: model.visibleEnabledOrderedIndexs.map((t) { - final selected = model.currentTab == t; - final color = selected - ? MyTheme.tabbar(context).selectedTextColor - : MyTheme.tabbar(context).unSelectedTextColor - ?..withOpacity(0.5); - final hover = false.obs; - final deco = BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6)); - final decoBorder = BoxDecoration( - border: Border( - bottom: BorderSide(width: 2, color: color!), - )); - counter += 1; - return ReorderableDragStartListener( - key: ValueKey(t), - index: counter, - child: Obx(() => Tooltip( - preferBelow: false, - message: model.tabTooltip(t), - onTriggered: isMobile ? mobileShowTabVisibilityMenu : null, - child: InkWell( - child: Container( - decoration: (hover.value - ? (selected ? decoBorder : deco) - : (selected ? decoBorder : null)), - child: Icon(model.tabIcon(t), color: color) - .paddingSymmetric(horizontal: 4), - ).paddingSymmetric(horizontal: 4), - onTap: isOptionFixed(kOptionPeerTabIndex) - ? null - : () async { - await handleTabSelection(t); - await bind.setLocalFlutterOption( - k: kOptionPeerTabIndex, v: t.toString()); - }, - onHover: (value) => hover.value = value, - ), - ))); - }).toList()); - } - - Widget _createPeersView() { - final model = Provider.of(context); - Widget child; - if (model.visibleEnabledOrderedIndexs.isEmpty) { - child = visibleContextMenuListener(Row( - children: [Expanded(child: InkWell())], - )); - } else { - if (model.visibleEnabledOrderedIndexs.contains(model.currentTab)) { - child = entries[model.currentTab].widget; - } else { - debugPrint("should not happen! currentTab not in visibleIndexs"); - Future.delayed(Duration.zero, () { - model.setCurrentTab(model.visibleEnabledOrderedIndexs[0]); - }); - child = entries[0].widget; - } - } - return Expanded( - child: child.marginSymmetric( - vertical: (isDesktop || isWebDesktop) ? 12.0 : 6.0)); - } - - Widget _createRefresh( - {required PeerTabIndex index, required RxBool loading}) { - final model = Provider.of(context); - final textColor = Theme.of(context).textTheme.titleLarge?.color; - return Offstage( - offstage: model.currentTab != index.index, - child: Tooltip( - message: translate('Refresh'), - child: RefreshWidget( - onPressed: () { - if (gFFI.peerTabModel.currentTab < entries.length) { - entries[gFFI.peerTabModel.currentTab].load?.call(); - } - }, - spinning: loading, - child: RotatedBox( - quarterTurns: 2, - child: Icon( - Icons.refresh, - size: 18, - color: textColor, - ))), - ), - ); - } - - Widget _createPeerViewTypeSwitch(BuildContext context) { - return PeerViewDropdown(); - } - - Widget _createMultiSelection() { - final textColor = Theme.of(context).textTheme.titleLarge?.color; - final model = Provider.of(context); - return _hoverAction( - toolTip: translate('Select'), - context: context, - onTap: () { - model.setMultiSelectionMode(true); - if (isMobile && Navigator.canPop(context)) { - Navigator.pop(context); - } - }, - child: SvgPicture.asset( - "assets/checkbox-outline.svg", - width: 18, - height: 18, - colorFilter: svgColor(textColor), - ), - ); - } - - void mobileShowTabVisibilityMenu() { - final model = gFFI.peerTabModel; - final items = List.empty(growable: true); - for (int i = 0; i < PeerTabModel.maxTabCount; i++) { - if (!model.isEnabled[i]) continue; - items.add(PopupMenuItem( - height: kMinInteractiveDimension * 0.8, - onTap: isOptVisiableFixed - ? null - : () => model.setTabVisible(i, !model.isVisibleEnabled[i]), - enabled: !isOptVisiableFixed, - child: Row( - children: [ - Checkbox( - value: model.isVisibleEnabled[i], - onChanged: isOptVisiableFixed - ? null - : (_) { - model.setTabVisible(i, !model.isVisibleEnabled[i]); - if (Navigator.canPop(context)) { - Navigator.pop(context); - } - }), - Expanded(child: Text(model.tabTooltip(i))), - ], - ), - )); - } - if (mobileTabContextMenuPos != null) { - showMenu( - context: context, position: mobileTabContextMenuPos!, items: items); - } - } - - Widget visibleContextMenuListener(Widget child) { - if (!(isDesktop || isWebDesktop)) { - return GestureDetector( - onLongPressDown: (e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - mobileTabContextMenuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onLongPressUp: () { - mobileShowTabVisibilityMenu(); - }, - child: child, - ); - } else { - return Listener( - onPointerDown: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) { - return; - } - if (e.buttons == 2) { - showRightMenu( - (CancelFunc cancelFunc) { - return visibleContextMenu(cancelFunc); - }, - target: e.position, - ); - } - }, - child: child); - } - } - - Widget visibleContextMenu(CancelFunc cancelFunc) { - final model = Provider.of(context); - final menu = List.empty(growable: true); - for (int i = 0; i < model.orders.length; i++) { - int tabIndex = model.orders[i]; - if (tabIndex < 0 || tabIndex >= PeerTabModel.maxTabCount) continue; - if (!model.isEnabled[tabIndex]) continue; - menu.add(MenuEntrySwitchSync( - switchType: SwitchType.scheckbox, - text: model.tabTooltip(tabIndex), - currentValue: model.isVisibleEnabled[tabIndex], - setter: (show) async { - model.setTabVisible(tabIndex, show); - // Do not hide the current menu (checkbox) - // cancelFunc(); - }, - enabled: (!isOptVisiableFixed).obs)); - } - return mod_menu.PopupMenu( - items: menu - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: MyTheme.accent, - height: 20.0, - dividerHeight: 12.0, - ))) - .expand((i) => i) - .toList()); - } - - Widget createMultiSelectionBar(PeerTabModel model) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Offstage( - offstage: model.selectedPeers.isEmpty, - child: Row( - children: [ - deleteSelection(), - addSelectionToFav(), - addSelectionToAb(), - editSelectionTags(), - ], - ), - ), - Row( - children: [ - selectionCount(model.selectedPeers.length), - selectAll(model), - closeSelection(), - ], - ) - ], - ); - } - - Widget deleteSelection() { - final model = Provider.of(context); - if (model.currentTab == PeerTabIndex.group.index) { - return Offstage(); - } - return _hoverAction( - context: context, - toolTip: translate('Delete'), - onTap: () { - onSubmit() async { - final peers = model.selectedPeers; - switch (model.currentTab) { - case 0: - for (var p in peers) { - await bind.mainRemovePeer(id: p.id); - } - bind.mainLoadRecentPeers(); - break; - case 1: - final favs = (await bind.mainGetFav()).toList(); - peers.map((p) { - favs.remove(p.id); - }).toList(); - await bind.mainStoreFav(favs: favs); - bind.mainLoadFavPeers(); - break; - case 2: - for (var p in peers) { - await bind.mainRemoveDiscovered(id: p.id); - } - bind.mainLoadLanPeers(); - break; - case 3: - await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); - break; - default: - break; - } - gFFI.peerTabModel.setMultiSelectionMode(false); - if (model.currentTab != 3) showToast(translate('Successful')); - } - - deleteConfirmDialog(onSubmit, translate('Delete')); - }, - child: Icon(Icons.delete, color: Colors.red)); - } - - Widget addSelectionToFav() { - final model = Provider.of(context); - return Offstage( - offstage: - model.currentTab != PeerTabIndex.recent.index, // show based on recent - child: _hoverAction( - context: context, - toolTip: translate('Add to Favorites'), - onTap: () async { - final peers = model.selectedPeers; - final favs = (await bind.mainGetFav()).toList(); - for (var p in peers) { - if (!favs.contains(p.id)) { - favs.add(p.id); - } - } - await bind.mainStoreFav(favs: favs); - model.setMultiSelectionMode(false); - showToast(translate('Successful')); - }, - child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]), - ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), - ); - } - - Widget addSelectionToAb() { - final model = Provider.of(context); - final addressbooks = gFFI.abModel.addressBooksCanWrite(); - if (model.currentTab == PeerTabIndex.ab.index) { - addressbooks.remove(gFFI.abModel.currentName.value); - } - return Offstage( - offstage: !gFFI.userModel.isLogin || addressbooks.isEmpty, - child: _hoverAction( - context: context, - toolTip: translate('Add to address book'), - onTap: () { - final peers = model.selectedPeers.map((e) => Peer.copy(e)).toList(); - addPeersToAbDialog(peers); - model.setMultiSelectionMode(false); - }, - child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]), - ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), - ); - } - - Widget editSelectionTags() { - final model = Provider.of(context); - return Offstage( - offstage: !gFFI.userModel.isLogin || - model.currentTab != PeerTabIndex.ab.index || - gFFI.abModel.currentAbTags.isEmpty, - child: _hoverAction( - context: context, - toolTip: translate('Edit Tag'), - onTap: () { - editAbTagDialog(List.empty(), (selectedTags) async { - final peers = model.selectedPeers; - await gFFI.abModel.changeTagForPeers( - peers.map((p) => p.id).toList(), selectedTags); - model.setMultiSelectionMode(false); - showToast(translate('Successful')); - }); - }, - child: Icon(Icons.tag)) - .marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6), - ); - } - - Widget selectionCount(int count) { - return Align( - alignment: Alignment.center, - child: Text('$count ${translate('Selected')}'), - ); - } - - Widget selectAll(PeerTabModel model) { - return Offstage( - offstage: - model.selectedPeers.length >= model.currentTabCachedPeers.length, - child: _hoverAction( - context: context, - toolTip: translate('Select All'), - onTap: () { - model.selectAll(); - }, - child: Icon(Icons.select_all), - ).marginOnly(left: 6), - ); - } - - Widget closeSelection() { - final model = Provider.of(context); - return _hoverAction( - context: context, - toolTip: translate('Close'), - onTap: () { - model.setMultiSelectionMode(false); - }, - child: Icon(Icons.clear)) - .marginOnly(left: 6); - } - - Widget _toggleTags() { - return _hoverAction( - context: context, - toolTip: translate('Toggle Tags'), - hoverableWhenfalse: hideAbTagsPanel, - child: Icon( - Icons.tag_rounded, - size: 18, - ), - onTap: () async { - await bind.mainSetLocalOption( - key: kOptionHideAbTagsPanel, - value: hideAbTagsPanel.value ? defaultOptionNo : "Y"); - hideAbTagsPanel.value = !hideAbTagsPanel.value; - }); - } - - List _landscapeRightActions(BuildContext context) { - final model = Provider.of(context); - return [ - const PeerSearchBar().marginOnly(right: 13), - _createRefresh( - index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading), - _createRefresh( - index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), - Offstage( - offstage: model.currentTabCachedPeers.isEmpty, - child: _createMultiSelection(), - ), - _createPeerViewTypeSwitch(context), - Offstage( - offstage: model.currentTab == PeerTabIndex.recent.index, - child: PeerSortDropdown(), - ), - Offstage( - offstage: model.currentTab != PeerTabIndex.ab.index, - child: _toggleTags(), - ), - ]; - } - - List _portraitRightActions(BuildContext context) { - final model = Provider.of(context); - final screenWidth = MediaQuery.of(context).size.width; - final leftIconSize = Theme.of(context).iconTheme.size ?? 24; - final leftActionsSize = - (leftIconSize + (4 + 4) * 2) * model.visibleEnabledOrderedIndexs.length; - final availableWidth = screenWidth - 10 * 2 - leftActionsSize - 2 * 2; - final searchWidth = 120; - final otherActionWidth = 18 + 10; - - dropDown(List menus) { - final padding = 6.0; - final textColor = Theme.of(context).textTheme.titleLarge?.color; - return PullDownButton( - buttonBuilder: - (BuildContext context, Future Function() showMenu) { - return _hoverAction( - context: context, - toolTip: translate('More'), - child: SvgPicture.asset( - "assets/chevron_up_chevron_down.svg", - width: 18, - height: 18, - colorFilter: svgColor(textColor), - ), - onTap: showMenu, - ); - }, - routeTheme: PullDownMenuRouteTheme( - width: menus.length * (otherActionWidth + padding * 2) * 1.0), - itemBuilder: (context) => [ - PullDownMenuEntryImpl( - child: Row( - mainAxisSize: MainAxisSize.min, - children: menus - .map((e) => - Material(child: e.paddingSymmetric(horizontal: padding))) - .toList(), - ), - ) - ], - ); - } - - // Always show search, refresh - List actions = [ - const PeerSearchBar(), - if (model.currentTab == PeerTabIndex.ab.index) - _createRefresh( - index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading), - if (model.currentTab == PeerTabIndex.group.index) - _createRefresh( - index: PeerTabIndex.group, loading: gFFI.groupModel.groupLoading), - ]; - final List dynamicActions = [ - if (model.currentTabCachedPeers.isNotEmpty) _createMultiSelection(), - if (model.currentTab != PeerTabIndex.recent.index) PeerSortDropdown(), - if (model.currentTab == PeerTabIndex.ab.index) _toggleTags() - ]; - final rightWidth = availableWidth - - searchWidth - - (actions.length == 2 ? otherActionWidth : 0); - final availablePositions = rightWidth ~/ otherActionWidth; - - if (availablePositions < dynamicActions.length && - dynamicActions.length > 1) { - if (availablePositions < 2) { - actions.addAll([ - dropDown(dynamicActions), - ]); - } else { - actions.addAll([ - ...dynamicActions.sublist(0, availablePositions - 1), - dropDown(dynamicActions.sublist(availablePositions - 1)), - ]); - } - } else { - actions.addAll(dynamicActions); - } - return actions; - } -} - -class PeerSearchBar extends StatefulWidget { - const PeerSearchBar({Key? key}) : super(key: key); - - @override - State createState() => _PeerSearchBarState(); -} - -class _PeerSearchBarState extends State { - var drawer = false; - - @override - Widget build(BuildContext context) { - return drawer - ? _buildSearchBar() - : _hoverAction( - context: context, - toolTip: translate('Search'), - padding: const EdgeInsets.only(right: 2), - onTap: () { - setState(() { - drawer = true; - }); - }, - child: Icon( - Icons.search_rounded, - color: Theme.of(context).hintColor, - )); - } - - Widget _buildSearchBar() { - RxBool focused = false.obs; - FocusNode focusNode = FocusNode(); - focusNode.addListener(() { - focused.value = focusNode.hasFocus; - peerSearchTextController.selection = TextSelection( - baseOffset: 0, - extentOffset: peerSearchTextController.value.text.length); - }); - return Obx(() => Container( - width: stateGlobal.isPortrait.isTrue ? 120 : 140, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6), - ), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - Icon( - Icons.search_rounded, - color: Theme.of(context).hintColor, - ).marginSymmetric(horizontal: 4), - Expanded( - child: TextField( - autofocus: true, - controller: peerSearchTextController, - onChanged: (searchText) { - peerSearchText.value = searchText; - }, - focusNode: focusNode, - textAlign: TextAlign.start, - maxLines: 1, - cursorColor: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5), - cursorHeight: 18, - cursorWidth: 1, - style: const TextStyle(fontSize: 14), - decoration: InputDecoration( - contentPadding: - const EdgeInsets.symmetric(vertical: 6), - hintText: - focused.value ? null : translate("Search ID"), - hintStyle: TextStyle( - fontSize: 14, color: Theme.of(context).hintColor), - border: InputBorder.none, - isDense: true, - ), - ).workaroundFreezeLinuxMint(), - ), - // Icon(Icons.close), - IconButton( - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 2), - onPressed: () { - setState(() { - peerSearchTextController.clear(); - peerSearchText.value = ""; - drawer = false; - }); - }, - icon: Tooltip( - message: translate('Close'), - child: Icon( - Icons.close, - color: Theme.of(context).hintColor, - )), - ), - ], - ), - ) - ], - ), - )); - } -} - -class PeerViewDropdown extends StatefulWidget { - const PeerViewDropdown({super.key}); - - @override - State createState() => _PeerViewDropdownState(); -} - -class _PeerViewDropdownState extends State { - @override - Widget build(BuildContext context) { - final List types = [ - PeerUiType.grid, - PeerUiType.tile, - PeerUiType.list - ]; - final style = TextStyle( - color: Theme.of(context).textTheme.titleLarge?.color, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal); - List items = List.empty(growable: true); - items.add(PopupMenuItem( - height: 36, - enabled: false, - child: Text(translate("Change view"), style: style))); - for (var e in PeerUiType.values) { - items.add(PopupMenuItem( - height: 36, - child: Obx(() => Center( - child: SizedBox( - height: 36, - child: getRadio( - Tooltip( - message: translate(types.indexOf(e) == 0 - ? 'Big tiles' - : types.indexOf(e) == 1 - ? 'Small tiles' - : 'List'), - child: Icon( - e == PeerUiType.grid - ? Icons.grid_view_rounded - : e == PeerUiType.list - ? Icons.view_list_rounded - : Icons.view_agenda_rounded, - size: 18, - )), - e, - peerCardUiType.value, - dense: true, - isOptionFixed(kOptionPeerCardUiType) - ? null - : (PeerUiType? v) async { - if (v != null) { - peerCardUiType.value = v; - setState(() {}); - await bind.setLocalFlutterOption( - k: kOptionPeerCardUiType, - v: peerCardUiType.value.index.toString(), - ); - if (Navigator.canPop(context)) { - Navigator.pop(context); - } - } - }), - ), - )))); - } - - var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0); - return _hoverAction( - context: context, - toolTip: translate('Change view'), - child: Icon( - peerCardUiType.value == PeerUiType.grid - ? Icons.grid_view_rounded - : peerCardUiType.value == PeerUiType.list - ? Icons.view_list_rounded - : Icons.view_agenda_rounded, - size: 18, - ), - onTapDown: (details) { - final x = details.globalPosition.dx; - final y = details.globalPosition.dy; - menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () => showMenu( - context: context, - position: menuPos, - items: items, - elevation: 8, - )); - } -} - -class PeerSortDropdown extends StatefulWidget { - const PeerSortDropdown({super.key}); - - @override - State createState() => _PeerSortDropdownState(); -} - -class _PeerSortDropdownState extends State { - _PeerSortDropdownState() { - if (!PeerSortType.values.contains(peerSort.value)) { - _loadLocalOptions(); - } - } - - void _loadLocalOptions() { - peerSort.value = PeerSortType.remoteId; - bind.setLocalFlutterOption( - k: kOptionPeerSorting, - v: peerSort.value, - ); - } - - @override - Widget build(BuildContext context) { - final style = TextStyle( - color: Theme.of(context).textTheme.titleLarge?.color, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal); - List items = List.empty(growable: true); - items.add(PopupMenuItem( - height: 36, - enabled: false, - child: Text(translate("Sort by"), style: style))); - for (var e in PeerSortType.values) { - items.add(PopupMenuItem( - height: 36, - child: Obx(() => Center( - child: SizedBox( - height: 36, - child: getRadio( - Text(translate(e), style: style), e, peerSort.value, - dense: true, (String? v) async { - if (v != null) { - peerSort.value = v; - await bind.setLocalFlutterOption( - k: kOptionPeerSorting, - v: peerSort.value, - ); - } - }), - ), - )))); - } - - var menuPos = RelativeRect.fromLTRB(0, 0, 0, 0); - return _hoverAction( - context: context, - toolTip: translate('Sort by'), - child: Icon( - Icons.sort_rounded, - size: 18, - ), - onTapDown: (details) { - final x = details.globalPosition.dx; - final y = details.globalPosition.dy; - menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () => showMenu( - context: context, - position: menuPos, - items: items, - elevation: 8, - ), - ); - } -} - -class RefreshWidget extends StatefulWidget { - final VoidCallback onPressed; - final Widget child; - final RxBool? spinning; - const RefreshWidget( - {super.key, required this.onPressed, required this.child, this.spinning}); - - @override - State createState() => RefreshWidgetState(); -} - -class RefreshWidgetState extends State { - double turns = 0.0; - bool hover = false; - - @override - void initState() { - super.initState(); - widget.spinning?.listen((v) { - if (v && mounted) { - setState(() { - turns += 1; - }); - } - }); - } - - @override - Widget build(BuildContext context) { - final deco = BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6), - ); - return AnimatedRotation( - turns: turns, - duration: const Duration(milliseconds: 200), - onEnd: () { - if (widget.spinning?.value == true && mounted) { - setState(() => turns += 1.0); - } - }, - child: Container( - padding: EdgeInsets.all(4.0), - margin: EdgeInsets.symmetric(horizontal: 1), - decoration: hover ? deco : null, - child: InkWell( - onTap: () { - if (mounted) setState(() => turns += 1.0); - widget.onPressed(); - }, - onHover: (value) { - if (mounted) { - setState(() { - hover = value; - }); - } - }, - child: widget.child), - )); - } -} - -Widget _hoverAction( - {required BuildContext context, - required Widget child, - required Function() onTap, - required String toolTip, - GestureTapDownCallback? onTapDown, - RxBool? hoverableWhenfalse, - EdgeInsetsGeometry padding = const EdgeInsets.all(4.0)}) { - final hover = false.obs; - final deco = BoxDecoration( - color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6), - ); - return Tooltip( - message: toolTip, - child: Obx( - () => Container( - margin: EdgeInsets.symmetric(horizontal: 1), - decoration: - (hover.value || hoverableWhenfalse?.value == false) ? deco : null, - child: InkWell( - onHover: (value) => hover.value = value, - onTap: onTap, - onTapDown: onTapDown, - child: Container(padding: padding, child: child))), - ), - ); -} - -class PullDownMenuEntryImpl extends StatelessWidget - implements PullDownMenuEntry { - final Widget child; - const PullDownMenuEntryImpl({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return child; - } -} diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart deleted file mode 100644 index 5be5af272..000000000 --- a/flutter/lib/common/widgets/peers_view.dart +++ /dev/null @@ -1,598 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; - -import 'package:dynamic_layouts/dynamic_layouts.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/ab_model.dart'; -import 'package:flutter_hbb/models/peer_tab_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:visibility_detector/visibility_detector.dart'; -import 'package:window_manager/window_manager.dart'; - -import '../../common.dart'; -import '../../models/peer_model.dart'; -import '../../models/platform_model.dart'; -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'; - static const String group = 'load_group_peers'; -} - -class PeersModelName { - static const String recent = 'recent peer'; - static const String favorite = 'fav peer'; - static const String lan = 'discovered peer'; - static const String addressBook = 'address book peer'; - static const String group = 'group peer'; -} - -/// for peer search text, global obs value -final peerSearchText = "".obs; - -/// for peer sort, global obs value -RxString? _peerSort; -RxString get peerSort { - _peerSort ??= bind.getLocalFlutterOption(k: kOptionPeerSorting).obs; - return _peerSort!; -} - -// list for listener -RxList get obslist => [peerSearchText, peerSort].obs; - -final peerSearchTextController = - TextEditingController(text: peerSearchText.value); - -class _PeersView extends StatefulWidget { - final Peers peers; - final PeerFilter? peerFilter; - final PeerCardBuilder peerCardBuilder; - final PeerTabIndex peerTabIndex; - - const _PeersView( - {required this.peers, - required this.peerCardBuilder, - required this.peerTabIndex, - this.peerFilter, - Key? key}) - : super(key: key); - - @override - _PeersViewState createState() => _PeersViewState(); -} - -/// State for the peer widget. -class _PeersViewState extends State<_PeersView> - with WindowListener, WidgetsBindingObserver { - 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 || isWebDesktop) ? 12.0 : 8.0; - final _curPeers = {}; - var _lastChangeTime = DateTime.now(); - var _lastQueryPeers = {}; - var _lastQueryTime = DateTime.now(); - var _lastWindowRestoreTime = DateTime.now(); - var _queryCount = 0; - var _exit = false; - bool _isActive = true; - - final _scrollController = ScrollController(); - - _PeersViewState() { - _startCheckOnlines(); - } - - @override - void initState() { - windowManager.addListener(this); - WidgetsBinding.instance.addObserver(this); - super.initState(); - } - - @override - void dispose() { - windowManager.removeListener(this); - WidgetsBinding.instance.removeObserver(this); - _exit = true; - super.dispose(); - } - - @override - void onWindowFocus() { - _queryCount = 0; - _isActive = true; - } - - @override - void onWindowBlur() { - // We need this comparison because window restore (on Windows) also triggers `onWindowBlur()`. - // Maybe it's a bug of the window manager, but the source code seems to be correct. - // - // Although `onWindowRestore()` is called after `onWindowBlur()` in my test, - // we need the following comparison to ensure that `_isActive` is true in the end. - if (isWindows && - DateTime.now().difference(_lastWindowRestoreTime) < - const Duration(milliseconds: 300)) { - return; - } - _queryCount = _maxQueryCount; - _isActive = false; - } - - @override - void onWindowRestore() { - // Window restore (on MacOS and Linux) also triggers `onWindowFocus()`. - // But on Windows, it triggers `onWindowBlur()`, mybe it's a bug of the window manager. - if (!isWindows) return; - _queryCount = 0; - _isActive = true; - _lastWindowRestoreTime = DateTime.now(); - } - - @override - void onWindowMinimize() { - // Window minimize also triggers `onWindowBlur()`. - } - - // This function is required for mobile. - // `onWindowFocus` works fine for desktop. - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (isDesktop || isWebDesktop) return; - if (state == AppLifecycleState.resumed) { - _isActive = true; - _queryCount = 0; - } else if (state == AppLifecycleState.inactive) { - _isActive = false; - } - } - - @override - Widget build(BuildContext context) { - // We should avoid too many rebuilds. MacOS(m1, 14.6.1) on Flutter 3.19.6. - // Continious rebuilds of `ChangeNotifierProvider` will cause memory leak. - // Simple demo can reproduce this issue. - return ChangeNotifierProvider.value( - value: widget.peers, - child: Consumer(builder: (context, peers, child) { - if (peers.peers.isEmpty) { - gFFI.peerTabModel.setCurrentTabCachedPeers([]); - return 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, - ), - ), - ], - ), - ); - } else { - return _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 updateEvent = peers.event; - final body = ObxValue((filters) { - return FutureBuilder>( - builder: (context, snapshot) { - if (snapshot.hasData) { - var peers = snapshot.data!; - if (peers.length > 1000) peers = peers.sublist(0, 1000); - gFFI.peerTabModel.setCurrentTabCachedPeers(peers); - buildOnePeer(Peer peer, bool isPortrait) { - final visibilityChild = VisibilityDetector( - key: ValueKey(_cardId(peer.id)), - onVisibilityChanged: onVisibilityChanged, - child: widget.peerCardBuilder(peer), - ); - // `Provider.of(context)` will causes infinete loop. - // Because `gFFI.peerTabModel.setCurrentTabCachedPeers(peers)` will trigger `notifyListeners()`. - // - // No need to listen the currentTab change event. - // Because the currentTab change event will trigger the peers change event, - // and the peers change event will trigger _buildPeersView(). - return !isPortrait - ? Obx(() => peerCardUiType.value == PeerUiType.list - ? Container(height: 45, child: visibilityChild) - : peerCardUiType.value == PeerUiType.grid - ? SizedBox( - width: 220, height: 140, child: visibilityChild) - : SizedBox( - width: 220, height: 42, child: visibilityChild)) - : Container(child: visibilityChild); - } - - // We should avoid too many rebuilds. Win10(Some machines) on Flutter 3.19.6. - // Continious rebuilds of `ListView.builder` will cause memory leak. - // Simple demo can reproduce this issue. - final Widget child = Obx(() => stateGlobal.isPortrait.isTrue - ? ListView.builder( - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index], true).marginOnly( - top: index == 0 ? 0 : space / 2, bottom: space / 2); - }, - ) - : peerCardUiType.value == PeerUiType.list - ? ListView.builder( - controller: _scrollController, - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index], false).marginOnly( - right: space, - top: index == 0 ? 0 : space / 2, - bottom: space / 2); - }, - ) - : DynamicGridView.builder( - gridDelegate: SliverGridDelegateWithWrapping( - mainAxisSpacing: space / 2, - crossAxisSpacing: space), - itemCount: peers.length, - itemBuilder: (BuildContext context, int index) { - return buildOnePeer(peers[index], false); - })); - - if (updateEvent == UpdateEvent.load) { - _curPeers.clear(); - _curPeers.addAll(peers.map((e) => e.id)); - _queryOnlines(true); - } - return child; - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - future: matchPeers(filters[0].value, filters[1].value, peers.peers), - ); - }, obslist); - - return body; - } - - var _queryInterval = const Duration(seconds: 20); - - void _startCheckOnlines() { - () async { - final p = await bind.mainIsUsingPublicServer(); - if (!p) { - _queryInterval = const Duration(seconds: 6); - } - while (!_exit) { - final now = DateTime.now(); - if (!setEquals(_curPeers, _lastQueryPeers)) { - if (now.difference(_lastChangeTime) > const Duration(seconds: 1)) { - _queryOnlines(false); - } - } else { - final skipIfIsWeb = - isWeb && !(stateGlobal.isWebVisible && stateGlobal.isInMainPage); - final skipIfMobile = - (isAndroid || isIOS) && !stateGlobal.isInMainPage; - final skipIfNotActive = skipIfIsWeb || skipIfMobile || !_isActive; - if (!skipIfNotActive && (_queryCount < _maxQueryCount || !p)) { - if (now.difference(_lastQueryTime) >= _queryInterval) { - if (_curPeers.isNotEmpty) { - bind.queryOnlines(ids: _curPeers.toList(growable: false)); - _lastQueryTime = DateTime.now(); - _queryCount += 1; - } - } - } - } - await Future.delayed(const Duration(milliseconds: 300)); - } - }(); - } - - _queryOnlines(bool isLoadEvent) { - if (_curPeers.isNotEmpty) { - bind.queryOnlines(ids: _curPeers.toList(growable: false)); - _queryCount = 0; - } - _lastQueryPeers = {..._curPeers}; - if (isLoadEvent) { - _lastChangeTime = DateTime.now(); - } else { - _lastQueryTime = DateTime.now().subtract(_queryInterval); - } - } - - 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.setLocalFlutterOption( - k: kOptionPeerSorting, - 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; - } - searchText = searchText.toLowerCase(); - final matches = await Future.wait( - peers.map((peer) => matchPeer(searchText, peer, widget.peerTabIndex))); - final filteredList = List.empty(growable: true); - for (var i = 0; i < peers.length; i++) { - if (matches[i]) { - filteredList.add(peers[i]); - } - } - - return filteredList; - } -} - -abstract class BasePeersView extends StatelessWidget { - final PeerTabIndex peerTabIndex; - final PeerFilter? peerFilter; - final PeerCardBuilder peerCardBuilder; - - const BasePeersView({ - Key? key, - required this.peerTabIndex, - this.peerFilter, - required this.peerCardBuilder, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - Peers peers; - switch (peerTabIndex) { - case PeerTabIndex.recent: - peers = gFFI.recentPeersModel; - break; - case PeerTabIndex.fav: - peers = gFFI.favoritePeersModel; - break; - case PeerTabIndex.lan: - peers = gFFI.lanPeersModel; - break; - case PeerTabIndex.ab: - peers = gFFI.abModel.peersModel; - break; - case PeerTabIndex.group: - peers = gFFI.groupModel.peersModel; - break; - } - return _PeersView( - peers: peers, - peerFilter: peerFilter, - peerCardBuilder: peerCardBuilder, - peerTabIndex: peerTabIndex); - } -} - -class RecentPeersView extends BasePeersView { - RecentPeersView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) - : super( - key: key, - peerTabIndex: PeerTabIndex.recent, - peerCardBuilder: (Peer peer) => RecentPeerCard( - peer: peer, - menuPadding: menuPadding, - ), - ); - - @override - Widget build(BuildContext context) { - final widget = super.build(context); - bind.mainLoadRecentPeers(); - return widget; - } -} - -class FavoritePeersView extends BasePeersView { - FavoritePeersView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) - : super( - key: key, - peerTabIndex: PeerTabIndex.fav, - peerCardBuilder: (Peer peer) => FavoritePeerCard( - peer: peer, - menuPadding: menuPadding, - ), - ); - - @override - Widget build(BuildContext context) { - final widget = super.build(context); - bind.mainLoadFavPeers(); - return widget; - } -} - -class DiscoveredPeersView extends BasePeersView { - DiscoveredPeersView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) - : super( - key: key, - peerTabIndex: PeerTabIndex.lan, - peerCardBuilder: (Peer peer) => DiscoveredPeerCard( - peer: peer, - menuPadding: menuPadding, - ), - ); - - @override - Widget build(BuildContext context) { - final widget = super.build(context); - bind.mainLoadLanPeers(); - bind.mainDiscover(); - return widget; - } -} - -class AddressBookPeersView extends BasePeersView { - AddressBookPeersView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) - : super( - key: key, - peerTabIndex: PeerTabIndex.ab, - peerFilter: (Peer peer) => - _hitTag(gFFI.abModel.selectedTags, peer.tags), - peerCardBuilder: (Peer peer) => AddressBookPeerCard( - peer: peer, - menuPadding: menuPadding, - ), - ); - - static bool _hitTag(List selectedTags, List idents) { - if (selectedTags.isEmpty) { - return true; - } - // The result of a no-tag union with normal tags, still allows normal tags to perform union or intersection operations. - final selectedNormalTags = - selectedTags.where((tag) => tag != kUntagged).toList(); - if (selectedTags.contains(kUntagged)) { - if (idents.isEmpty) return true; - if (selectedNormalTags.isEmpty) return false; - } - if (gFFI.abModel.filterByIntersection.value) { - for (final tag in selectedNormalTags) { - if (!idents.contains(tag)) { - return false; - } - } - return true; - } else { - for (final tag in selectedNormalTags) { - if (idents.contains(tag)) { - return true; - } - } - return false; - } - } -} - -class MyGroupPeerView extends BasePeersView { - MyGroupPeerView( - {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) - : super( - key: key, - peerTabIndex: PeerTabIndex.group, - peerFilter: filter, - peerCardBuilder: (Peer peer) => MyGroupPeerCard( - peer: peer, - menuPadding: menuPadding, - ), - ); - - static bool filter(Peer peer) { - final model = gFFI.groupModel; - if (model.searchAccessibleItemNameText.isNotEmpty) { - final text = model.searchAccessibleItemNameText.value.toLowerCase(); - final searchPeersOfUser = model.users.any((user) => - user.name == peer.loginName && - (user.name.toLowerCase().contains(text) || - user.displayNameOrName.toLowerCase().contains(text))); - final searchPeersOfDeviceGroup = - peer.device_group_name.toLowerCase().contains(text) && - model.deviceGroups.any((g) => g.name == peer.device_group_name); - if (!searchPeersOfUser && !searchPeersOfDeviceGroup) { - return false; - } - } - if (model.selectedAccessibleItemName.isNotEmpty) { - if (model.isSelectedDeviceGroup.value) { - if (model.selectedAccessibleItemName.value != peer.device_group_name) { - return false; - } - } else { - if (model.selectedAccessibleItemName.value != peer.loginName) { - return false; - } - } - } - return true; - } -} diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart deleted file mode 100644 index 9515ca759..000000000 --- a/flutter/lib/common/widgets/remote_input.dart +++ /dev/null @@ -1,692 +0,0 @@ -import 'dart:convert'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/gestures.dart'; - -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:flutter_hbb/models/input_model.dart'; - -import './gestures.dart'; - -class RawKeyFocusScope extends StatelessWidget { - final FocusNode? focusNode; - final ValueChanged? onFocusChange; - final InputModel inputModel; - final Widget child; - - RawKeyFocusScope({ - this.focusNode, - this.onFocusChange, - required this.inputModel, - required this.child, - }); - - @override - Widget build(BuildContext context) { - // https://github.com/flutter/flutter/issues/154053 - final useRawKeyEvents = isLinux && !isWeb; - // FIXME: On Windows, `AltGr` will generate `Alt` and `Control` key events, - // while `Alt` and `Control` are separated key events for en-US input method. - return FocusScope( - autofocus: true, - child: Focus( - autofocus: true, - canRequestFocus: true, - focusNode: focusNode, - onFocusChange: onFocusChange, - onKey: useRawKeyEvents - ? (FocusNode data, RawKeyEvent event) => - inputModel.handleRawKeyEvent(event) - : null, - onKeyEvent: useRawKeyEvents - ? null - : (FocusNode node, KeyEvent event) => - inputModel.handleKeyEvent(event), - child: child)); - } -} - -// For virtual mouse when using the mouse mode on mobile. -// Special hold-drag mode: one finger holds a button (left/right button), another finger pans. -// This flag is to override the scale gesture to a pan gesture. -bool isSpecialHoldDragActive = false; -// Cache the last focal point to calculate deltas in special hold-drag mode. -Offset _lastSpecialHoldDragFocalPoint = Offset.zero; - -class RawTouchGestureDetectorRegion extends StatefulWidget { - final Widget child; - final FFI ffi; - final bool isCamera; - late final InputModel inputModel = ffi.inputModel; - late final FfiModel ffiModel = ffi.ffiModel; - - RawTouchGestureDetectorRegion({ - required this.child, - required this.ffi, - this.isCamera = false, - }); - - @override - State createState() => - _RawTouchGestureDetectorRegionState(); -} - -/// touchMode only: -/// LongPress -> right click -/// OneFingerPan -> start/end -> left down start/end -/// onDoubleTapDown -> move to -/// onLongPressDown => move to -/// -/// mouseMode only: -/// DoubleFiner -> right click -/// HoldDrag -> left drag -class _RawTouchGestureDetectorRegionState - extends State { - Offset _cacheLongPressPosition = Offset(0, 0); - // Timestamp of the last long press event. - int _cacheLongPressPositionTs = 0; - double _mouseScrollIntegral = 0; // mouse scroll speed controller - double _scale = 1; - - // Workaround tap down event when two fingers are used to scale(mobile) - TapDownDetails? _lastTapDownDetails; - - PointerDeviceKind? lastDeviceKind; - - // For touch mode, onDoubleTap - // `onDoubleTap()` does not provide the position of the tap event. - Offset _lastPosOfDoubleTapDown = Offset.zero; - bool _touchModePanStarted = false; - Offset _doubleFinerTapPosition = Offset.zero; - - // For mouse mode, we need to block the events when the cursor is in a blocked area. - // So we need to cache the last tap down position. - Offset? _lastTapDownPositionForMouseMode; - // Cache global position for onTap (which lacks position info). - Offset? _lastTapDownGlobalPosition; - - FFI get ffi => widget.ffi; - FfiModel get ffiModel => widget.ffiModel; - InputModel get inputModel => widget.inputModel; - bool get handleTouch => (isDesktop || isWebDesktop) || ffiModel.touchMode; - SessionID get sessionId => ffi.sessionId; - - @override - Widget build(BuildContext context) { - return RawGestureDetector( - child: widget.child, - gestures: makeGestures(context), - ); - } - - bool isNotTouchBasedDevice() { - return !kTouchBasedDeviceKinds.contains(lastDeviceKind); - } - - // Mobile, mouse mode. - // Check if should block the mouse tap event (`_lastTapDownPositionForMouseMode`). - bool shouldBlockMouseModeEvent() { - return _lastTapDownPositionForMouseMode != null && - ffi.cursorModel.shouldBlock(_lastTapDownPositionForMouseMode!.dx, - _lastTapDownPositionForMouseMode!.dy); - } - - onTapDown(TapDownDetails d) async { - lastDeviceKind = d.kind; - _lastTapDownGlobalPosition = d.globalPosition; - if (isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - _lastPosOfDoubleTapDown = d.localPosition; - // Desktop or mobile "Touch mode" - _lastTapDownDetails = d; - } else { - _lastTapDownPositionForMouseMode = d.localPosition; - } - } - - onTapUp(TapUpDetails d) async { - final TapDownDetails? lastTapDownDetails = _lastTapDownDetails; - _lastTapDownDetails = null; - if (isNotTouchBasedDevice()) { - return; - } - // Filter duplicate touch tap events on iOS (Magic Mouse issue). - if (inputModel.shouldIgnoreTouchTap(d.globalPosition)) { - return; - } - if (handleTouch) { - final isMoved = - await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - if (isMoved) { - // If pan already handled 'down', don't send it again. - if (lastTapDownDetails != null && !_touchModePanStarted) { - await inputModel.tapDown(MouseButtons.left); - } - await inputModel.tapUp(MouseButtons.left); - } - } - } - - onTap() async { - if (isNotTouchBasedDevice()) { - return; - } - // Filter duplicate touch tap events on iOS (Magic Mouse issue). - final lastPos = _lastTapDownGlobalPosition; - if (lastPos != null && inputModel.shouldIgnoreTouchTap(lastPos)) { - return; - } - if (!handleTouch) { - // Cannot use `_lastTapDownDetails` because Flutter calls `onTapUp` before `onTap`, clearing the cached details. - // Using `_lastTapDownPositionForMouseMode` instead. - if (shouldBlockMouseModeEvent()) { - return; - } - // Mobile, "Mouse mode" - await inputModel.tap(MouseButtons.left); - } - } - - onDoubleTapDown(TapDownDetails d) async { - lastDeviceKind = d.kind; - if (isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - _lastPosOfDoubleTapDown = d.localPosition; - await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - } else { - _lastTapDownPositionForMouseMode = d.localPosition; - } - } - - onDoubleTap() async { - if (isNotTouchBasedDevice()) { - return; - } - if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) { - return; - } - if (handleTouch && - !ffi.cursorModel.isInRemoteRect(_lastPosOfDoubleTapDown)) { - return; - } - // Check if the position is in a blocked area when using the mouse mode. - if (!handleTouch) { - if (shouldBlockMouseModeEvent()) { - return; - } - } - await inputModel.tap(MouseButtons.left); - await inputModel.tap(MouseButtons.left); - } - - onLongPressDown(LongPressDownDetails d) async { - lastDeviceKind = d.kind; - if (isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - _lastPosOfDoubleTapDown = d.localPosition; - _cacheLongPressPosition = d.localPosition; - if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) { - return; - } - _cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch; - if (ffiModel.isPeerMobile) { - await ffi.cursorModel - .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); - await inputModel.tapDown(MouseButtons.left); - } - } else { - _lastTapDownPositionForMouseMode = d.localPosition; - } - } - - onLongPressUp() async { - if (isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - await inputModel.tapUp(MouseButtons.left); - } - } - - // for mobiles - onLongPress() async { - if (isNotTouchBasedDevice()) { - return; - } - if (!ffi.ffiModel.isPeerMobile) { - if (handleTouch) { - final isMoved = await ffi.cursorModel - .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); - if (!isMoved) { - return; - } - } else { - if (shouldBlockMouseModeEvent()) { - return; - } - } - await inputModel.tap(MouseButtons.right); - } else { - // It's better to send a message to tell the controlled device that the long press event is triggered. - // We're now using a `TimerTask` in `InputService.kt` to decide whether to trigger the long press event. - // It's not accurate and it's better to use the same detection logic in the controlling side. - } - } - - onLongPressMoveUpdate(LongPressMoveUpdateDetails d) async { - if (!ffiModel.isPeerMobile || isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) { - return; - } - await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - } - } - - onDoubleFinerTapDown(TapDownDetails d) async { - lastDeviceKind = d.kind; - if (isNotTouchBasedDevice()) { - return; - } - _doubleFinerTapPosition = d.localPosition; - // ignore for desktop and mobile - } - - onDoubleFinerTap(TapDownDetails d) async { - lastDeviceKind = d.kind; - if (isNotTouchBasedDevice()) { - return; - } - - // mobile mouse mode or desktop touch screen - final isMobileMouseMode = isMobile && !ffiModel.touchMode; - // We can't use `d.localPosition` here because it's always (0, 0) on desktop. - final isDesktopInRemoteRect = (isDesktop || isWebDesktop) && - ffi.cursorModel.isInRemoteRect(_doubleFinerTapPosition); - if (isMobileMouseMode || isDesktopInRemoteRect) { - await inputModel.tap(MouseButtons.right); - } - } - - onHoldDragStart(DragStartDetails d) async { - lastDeviceKind = d.kind; - if (isNotTouchBasedDevice()) { - return; - } - if (!handleTouch) { - if (isSpecialHoldDragActive) return; - await inputModel.sendMouse('down', MouseButtons.left); - } - } - - onHoldDragUpdate(DragUpdateDetails d) async { - if (isNotTouchBasedDevice()) { - return; - } - if (!handleTouch) { - if (isSpecialHoldDragActive) return; - await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch); - } - } - - onHoldDragEnd(DragEndDetails d) async { - if (isNotTouchBasedDevice()) { - return; - } - if (!handleTouch) { - await inputModel.sendMouse('up', MouseButtons.left); - } - } - - onOneFingerPanStart(BuildContext context, DragStartDetails d) async { - final TapDownDetails? lastTapDownDetails = _lastTapDownDetails; - _lastTapDownDetails = null; - lastDeviceKind = d.kind ?? lastDeviceKind; - if (isNotTouchBasedDevice()) { - return; - } - if (handleTouch) { - if (lastTapDownDetails != null) { - await ffi.cursorModel.move(lastTapDownDetails.localPosition.dx, - lastTapDownDetails.localPosition.dy); - } - if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { - return; - } - if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) { - return; - } - - _touchModePanStarted = true; - if (isDesktop || isWebDesktop) { - ffi.cursorModel.trySetRemoteWindowCoords(); - } - - // Workaround for the issue that the first pan event is sent a long time after the start event. - // If the time interval between the start event and the first pan event is less than 500ms, - // we consider to use the long press position as the start position. - // - // TODO: We should find a better way to send the first pan event as soon as possible. - if (DateTime.now().millisecondsSinceEpoch - _cacheLongPressPositionTs < - 500) { - await ffi.cursorModel - .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); - } - // In relative mouse mode, skip mouse down - only send movement via sendMobileRelativeMouseMove - if (!inputModel.relativeMouseMode.value) { - await inputModel.sendMouse('down', MouseButtons.left); - } - await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - } else { - final offset = ffi.cursorModel.offset; - final cursorX = offset.dx; - final cursorY = offset.dy; - final visible = - ffi.cursorModel.getVisibleRect().inflate(1); // extend edges - final size = MediaQueryData.fromView(View.of(context)).size; - if (!visible.contains(Offset(cursorX, cursorY))) { - await ffi.cursorModel.move(size.width / 2, size.height / 2); - } - } - } - - onOneFingerPanUpdate(DragUpdateDetails d) async { - if (isNotTouchBasedDevice()) { - return; - } - if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { - return; - } - if (handleTouch && !_touchModePanStarted) { - return; - } - // In relative mouse mode, send delta directly without position tracking. - if (inputModel.relativeMouseMode.value) { - await inputModel.sendMobileRelativeMouseMove(d.delta.dx, d.delta.dy); - } else { - await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch); - } - } - - onOneFingerPanEnd(DragEndDetails d) async { - _touchModePanStarted = false; - if (isNotTouchBasedDevice()) { - return; - } - if (isDesktop || isWebDesktop) { - ffi.cursorModel.clearRemoteWindowCoords(); - } - if (handleTouch) { - // In relative mouse mode, skip mouse up - matches the skipped mouse down in onOneFingerPanStart - if (!inputModel.relativeMouseMode.value) { - await inputModel.sendMouse('up', MouseButtons.left); - } - } - } - - // Reset `_touchModePanStarted` if the one-finger pan gesture is cancelled - // or rejected by the gesture arena. Without this, the flag can remain - // stuck in the "started" state and cause issues such as the Magic Mouse - // double-click problem on iPad with magic mouse. - onOneFingerPanCancel() { - _touchModePanStarted = false; - } - - // scale + pan event - onTwoFingerScaleStart(ScaleStartDetails d) { - _lastTapDownDetails = null; - if (isNotTouchBasedDevice()) { - return; - } - if (isSpecialHoldDragActive) { - // Initialize the last focal point to calculate deltas manually. - _lastSpecialHoldDragFocalPoint = d.focalPoint; - } - } - - onTwoFingerScaleUpdate(ScaleUpdateDetails d) async { - if (isNotTouchBasedDevice()) { - return; - } - - // If in special drag mode, perform a pan instead of a scale. - if (isSpecialHoldDragActive) { - // Calculate delta manually to avoid the jumpy behavior. - final delta = d.focalPoint - _lastSpecialHoldDragFocalPoint; - _lastSpecialHoldDragFocalPoint = d.focalPoint; - await ffi.cursorModel.updatePan(delta * 2.0, d.focalPoint, handleTouch); - return; - } - - if ((isDesktop || isWebDesktop)) { - final scale = ((d.scale - _scale) * 1000).toInt(); - _scale = d.scale; - - if (scale != 0) { - if (widget.isCamera) return; - await bind.sessionSendPointer( - sessionId: sessionId, - msg: json.encode( - PointerEventToRust(kPointerEventKindTouch, 'scale', scale) - .toJson())); - } - } else { - // mobile - ffi.canvasModel.updateScale(d.scale / _scale, d.focalPoint); - _scale = d.scale; - ffi.canvasModel.panX(d.focalPointDelta.dx); - ffi.canvasModel.panY(d.focalPointDelta.dy); - } - } - - onTwoFingerScaleEnd(ScaleEndDetails d) async { - if (isNotTouchBasedDevice()) { - return; - } - if ((isDesktop || isWebDesktop)) { - if (widget.isCamera) return; - await bind.sessionSendPointer( - sessionId: sessionId, - msg: json.encode( - PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson())); - } else { - // mobile - _scale = 1; - // No idea why we need to set the view style to "" here. - // bind.sessionSetViewStyle(sessionId: sessionId, value: ""); - } - if (!isSpecialHoldDragActive) { - await inputModel.sendMouse('up', MouseButtons.left); - } - } - - get onHoldDragCancel => null; - get onThreeFingerVerticalDragUpdate => ffi.ffiModel.isPeerAndroid - ? null - : (d) { - _mouseScrollIntegral += d.delta.dy / 4; - if (_mouseScrollIntegral > 1) { - inputModel.scroll(1); - _mouseScrollIntegral = 0; - } else if (_mouseScrollIntegral < -1) { - inputModel.scroll(-1); - _mouseScrollIntegral = 0; - } - }; - - makeGestures(BuildContext context) { - return { - // Official - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), (instance) { - instance - ..onTapDown = onTapDown - ..onTapUp = onTapUp - ..onTap = onTap; - }), - DoubleTapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => DoubleTapGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), (instance) { - instance - ..onDoubleTapDown = onDoubleTapDown - ..onDoubleTap = onDoubleTap; - }), - LongPressGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), (instance) { - instance - ..onLongPressDown = onLongPressDown - ..onLongPressUp = onLongPressUp - ..onLongPress = onLongPress - ..onLongPressMoveUpdate = onLongPressMoveUpdate; - }), - // Customized - HoldTapMoveGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => HoldTapMoveGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), - (instance) => instance - ..onHoldDragStart = onHoldDragStart - ..onHoldDragUpdate = onHoldDragUpdate - ..onHoldDragCancel = onHoldDragCancel - ..onHoldDragEnd = onHoldDragEnd), - DoubleFinerTapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => DoubleFinerTapGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), (instance) { - instance - ..onDoubleFinerTap = onDoubleFinerTap - ..onDoubleFinerTapDown = onDoubleFinerTapDown; - }), - CustomTouchGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => CustomTouchGestureRecognizer( - supportedDevices: kTouchBasedDeviceKinds, - ), (instance) { - instance.onOneFingerPanStart = - (DragStartDetails d) => onOneFingerPanStart(context, d); - instance - ..onOneFingerPanUpdate = onOneFingerPanUpdate - ..onOneFingerPanEnd = onOneFingerPanEnd - ..onOneFingerPanCancel = onOneFingerPanCancel - ..onTwoFingerScaleStart = onTwoFingerScaleStart - ..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate - ..onTwoFingerScaleEnd = onTwoFingerScaleEnd - ..onThreeFingerVerticalDragUpdate = onThreeFingerVerticalDragUpdate; - }), - }; - } -} - -class RawPointerMouseRegion extends StatelessWidget { - final InputModel inputModel; - final Widget child; - final MouseCursor? cursor; - final PointerEnterEventListener? onEnter; - final PointerExitEventListener? onExit; - final PointerDownEventListener? onPointerDown; - final PointerUpEventListener? onPointerUp; - - RawPointerMouseRegion({ - this.onEnter, - this.onExit, - this.cursor, - this.onPointerDown, - this.onPointerUp, - required this.inputModel, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Listener( - onPointerHover: inputModel.onPointHoverImage, - onPointerDown: (evt) { - onPointerDown?.call(evt); - inputModel.onPointDownImage(evt); - }, - onPointerUp: (evt) { - onPointerUp?.call(evt); - inputModel.onPointUpImage(evt); - }, - onPointerMove: inputModel.onPointMoveImage, - onPointerSignal: inputModel.onPointerSignalImage, - onPointerPanZoomStart: inputModel.onPointerPanZoomStart, - onPointerPanZoomUpdate: inputModel.onPointerPanZoomUpdate, - onPointerPanZoomEnd: inputModel.onPointerPanZoomEnd, - child: MouseRegion( - cursor: inputModel.isViewOnly - ? MouseCursor.defer - : (cursor ?? MouseCursor.defer), - onEnter: onEnter, - onExit: onExit, - child: child, - ), - ); - } -} - -class CameraRawPointerMouseRegion extends StatelessWidget { - final InputModel inputModel; - final Widget child; - final PointerEnterEventListener? onEnter; - final PointerExitEventListener? onExit; - final PointerDownEventListener? onPointerDown; - final PointerUpEventListener? onPointerUp; - - CameraRawPointerMouseRegion({ - this.onEnter, - this.onExit, - this.onPointerDown, - this.onPointerUp, - required this.inputModel, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Listener( - onPointerHover: (evt) { - final offset = evt.position; - double x = offset.dx; - double y = max(0.0, offset.dy); - inputModel.handlePointerDevicePos( - kPointerEventKindMouse, x, y, true, kMouseEventTypeDefault); - }, - onPointerDown: (evt) { - onPointerDown?.call(evt); - }, - onPointerUp: (evt) { - onPointerUp?.call(evt); - }, - child: MouseRegion( - cursor: MouseCursor.defer, - onEnter: onEnter, - onExit: onExit, - child: child, - ), - ); - } -} diff --git a/flutter/lib/common/widgets/setting_widgets.dart b/flutter/lib/common/widgets/setting_widgets.dart deleted file mode 100644 index f3be77003..000000000 --- a/flutter/lib/common/widgets/setting_widgets.dart +++ /dev/null @@ -1,340 +0,0 @@ -import 'package:debounce_throttle/debounce_throttle.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:get/get.dart'; - -customImageQualityWidget( - {required double initQuality, - required double initFps, - required Function(double)? setQuality, - required Function(double)? setFps, - required bool showFps, - required bool showMoreQuality}) { - if (initQuality < kMinQuality || - initQuality > (showMoreQuality ? kMaxMoreQuality : kMaxQuality)) { - initQuality = kDefaultQuality; - } - if (initFps < kMinFps || initFps > kMaxFps) { - initFps = kDefaultFps; - } - final qualityValue = initQuality.obs; - final fpsValue = initFps.obs; - - final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality); - final debouncerQuality = Debouncer( - Duration(milliseconds: 1000), - onChanged: setQuality, - initialValue: qualityValue.value, - ); - final debouncerFps = Debouncer( - Duration(milliseconds: 1000), - onChanged: setFps, - initialValue: fpsValue.value, - ); - - onMoreChanged(bool? value) { - if (value == null) return; - moreQualityChecked.value = value; - if (!value && qualityValue.value > 100) { - qualityValue.value = 100; - } - debouncerQuality.value = qualityValue.value; - } - - return Column( - children: [ - Obx(() => Row( - children: [ - Expanded( - flex: 3, - child: Slider( - value: qualityValue.value, - min: kMinQuality, - max: moreQualityChecked.value ? kMaxMoreQuality : kMaxQuality, - divisions: moreQualityChecked.value - ? ((kMaxMoreQuality - kMinQuality) / 10).round() - : ((kMaxQuality - kMinQuality) / 5).round(), - onChanged: setQuality == null - ? null - : (double value) async { - qualityValue.value = value; - debouncerQuality.value = value; - }, - ), - ), - Expanded( - flex: 1, - child: Text( - '${qualityValue.value.round()}%', - style: const TextStyle(fontSize: 15), - )), - Expanded( - flex: isMobile ? 2 : 1, - child: Text( - translate('Bitrate'), - style: const TextStyle(fontSize: 15), - )), - // mobile doesn't have enough space - if (showMoreQuality && !isMobile) - Expanded( - flex: 1, - child: Row( - children: [ - Checkbox( - value: moreQualityChecked.value, - onChanged: onMoreChanged, - ), - Expanded( - child: Text(translate('More')), - ) - ], - )) - ], - )), - if (showMoreQuality && isMobile) - Obx(() => Row( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Checkbox( - value: moreQualityChecked.value, - onChanged: onMoreChanged, - ), - ), - ), - Expanded( - child: Text(translate('More')), - ) - ], - )), - if (showFps) - Obx(() => Row( - children: [ - Expanded( - flex: 3, - child: Slider( - value: fpsValue.value, - min: kMinFps, - max: kMaxFps, - divisions: ((kMaxFps - kMinFps) / 5).round(), - onChanged: setFps == null - ? null - : (double value) async { - fpsValue.value = value; - debouncerFps.value = value; - }, - ), - ), - Expanded( - flex: 1, - child: Text( - '${fpsValue.value.round()}', - style: const TextStyle(fontSize: 15), - )), - Expanded( - flex: 2, - child: Text( - translate('FPS'), - style: const TextStyle(fontSize: 15), - )) - ], - )), - ], - ); -} - -customImageQualitySetting() { - final qualityKey = 'custom_image_quality'; - final fpsKey = 'custom-fps'; - - final initQuality = - (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? - kDefaultQuality); - final isQuanlityFixed = isOptionFixed(qualityKey); - final initFps = - (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? - kDefaultFps); - final isFpsFixed = isOptionFixed(fpsKey); - - return customImageQualityWidget( - initQuality: initQuality, - initFps: initFps, - setQuality: isQuanlityFixed - ? null - : (v) { - bind.mainSetUserDefaultOption( - key: qualityKey, value: v.toString()); - }, - setFps: isFpsFixed - ? null - : (v) { - bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString()); - }, - showFps: true, - showMoreQuality: true); -} - -List ServerConfigImportExportWidgets( - List controllers, - List errMsgs, -) { - import() { - Clipboard.getData(Clipboard.kTextPlain).then((value) { - importConfig(controllers, errMsgs, value?.text); - }); - } - - export() { - final text = ServerConfig( - idServer: controllers[0].text.trim(), - relayServer: controllers[1].text.trim(), - apiServer: controllers[2].text.trim(), - key: controllers[3].text.trim()) - .encode(); - debugPrint("ServerConfig export: $text"); - Clipboard.setData(ClipboardData(text: text)); - showToast(translate('Export server configuration successfully')); - } - - return [ - Tooltip( - message: translate('Import server config'), - child: IconButton( - icon: Icon(Icons.paste, color: Colors.grey), onPressed: import), - ), - Tooltip( - message: translate('Export Server Config'), - child: IconButton( - icon: Icon(Icons.copy, color: Colors.grey), onPressed: export)) - ]; -} - -List<(String, String)> otherDefaultSettings() { - List<(String, String)> v = [ - ('View Mode', kOptionViewOnly), - if ((isDesktop || isWebDesktop)) - ('show_monitors_tip', kKeyShowMonitorsToolbar), - if ((isDesktop || isWebDesktop)) - ('Collapse toolbar', kOptionCollapseToolbar), - ('Show remote cursor', kOptionShowRemoteCursor), - ('Follow remote cursor', kOptionFollowRemoteCursor), - ('Follow remote window focus', kOptionFollowRemoteWindow), - if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor), - ('Show quality monitor', kOptionShowQualityMonitor), - ('Mute', kOptionDisableAudio), - if (isDesktop) ('Enable file copy and paste', kOptionEnableFileCopyPaste), - ('Disable clipboard', kOptionDisableClipboard), - ('Lock after session end', kOptionLockAfterSessionEnd), - ('Privacy mode', kOptionPrivacyMode), - ('True color (4:4:4)', kOptionI444), - ('Reverse mouse wheel', kKeyReverseMouseWheel), - ('swap-left-right-mouse', kOptionSwapLeftRightMouse), - if (isDesktop) - ( - 'Show displays as individual windows', - kKeyShowDisplaysAsIndividualWindows - ), - if (isDesktop) - ( - 'Use all my displays for the remote session', - kKeyUseAllMyDisplaysForTheRemoteSession - ), - ('Keep terminal sessions on disconnect', kOptionTerminalPersistent), - ]; - - return v; -} - -class TrackpadSpeedWidget extends StatefulWidget { - final SimpleWrapper value; - // If null, no debouncer will be applied. - final Function(int)? onDebouncer; - - TrackpadSpeedWidget({Key? key, required this.value, this.onDebouncer}); - - @override - TrackpadSpeedWidgetState createState() => TrackpadSpeedWidgetState(); -} - -class TrackpadSpeedWidgetState extends State { - final TextEditingController _controller = TextEditingController(); - late final Debouncer debouncerSpeed; - - set value(int v) => widget.value.value = v; - int get value => widget.value.value; - - void updateValue(int newValue) { - setState(() { - value = newValue.clamp(kMinTrackpadSpeed, kMaxTrackpadSpeed); - // Scale the trackpad speed value to a percentage for display purposes. - _controller.text = value.toString(); - if (widget.onDebouncer != null) { - debouncerSpeed.setValue(value); - } - }); - } - - @override - void initState() { - super.initState(); - debouncerSpeed = Debouncer( - Duration(milliseconds: 1000), - onChanged: widget.onDebouncer, - initialValue: widget.value.value, - ); - } - - @override - Widget build(BuildContext context) { - if (_controller.text.isEmpty) { - _controller.text = value.toString(); - } - return Row( - children: [ - Expanded( - flex: 3, - child: Slider( - value: value.toDouble(), - min: kMinTrackpadSpeed.toDouble(), - max: kMaxTrackpadSpeed.toDouble(), - divisions: ((kMaxTrackpadSpeed - kMinTrackpadSpeed) / 10).round(), - onChanged: (double v) => updateValue(v.round()), - ), - ), - Expanded( - flex: 1, - child: Row( - children: [ - SizedBox( - width: 56, - child: TextField( - controller: _controller, - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - onSubmitted: (text) { - int? v = int.tryParse(text); - if (v != null) { - updateValue(v); - } - }, - style: const TextStyle(fontSize: 13), - decoration: InputDecoration( - contentPadding: - EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), - ), - ), - ).marginOnly(right: 8.0), - Text( - '%', - style: const TextStyle(fontSize: 15), - ) - ], - )), - ], - ); - } -} diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart deleted file mode 100644 index 537014246..000000000 --- a/flutter/lib/common/widgets/toolbar.dart +++ /dev/null @@ -1,1051 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/shared_state.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/common/widgets/login.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:get/get.dart'; - -bool isEditOsPassword = false; - -// macOS privacy mode blacks out all online displays, so switching the remote -// display does not weaken the local privacy protection. -bool allowDisplaySwitchInPrivacyMode(PeerInfo pi) { - return pi.platform == kPeerPlatformMacOS; -} - -class TTextMenu { - final Widget child; - final VoidCallback? onPressed; - Widget? trailingIcon; - bool divider; - TTextMenu( - {required this.child, - required this.onPressed, - this.trailingIcon, - this.divider = false}); - - Widget getChild() { - if (trailingIcon != null) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - child, - trailingIcon!, - ], - ); - } else { - return child; - } - } -} - -class TRadioMenu { - final Widget child; - final T value; - final T groupValue; - final ValueChanged? onChanged; - - TRadioMenu( - {required this.child, - required this.value, - required this.groupValue, - required this.onChanged}); -} - -class TToggleMenu { - final Widget child; - final bool value; - final ValueChanged? onChanged; - TToggleMenu( - {required this.child, required this.value, required this.onChanged}); -} - -handleOsPasswordEditIcon( - SessionID sessionId, OverlayDialogManager dialogManager) { - isEditOsPassword = true; - showSetOSPassword( - sessionId, false, dialogManager, null, () => isEditOsPassword = false); -} - -handleOsPasswordAction( - SessionID sessionId, OverlayDialogManager dialogManager) async { - if (isEditOsPassword) { - isEditOsPassword = false; - return; - } - final password = - await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ?? - ''; - if (password.isEmpty) { - showSetOSPassword(sessionId, true, dialogManager, password, - () => isEditOsPassword = false); - } else { - bind.sessionInputOsPassword(sessionId: sessionId, value: password); - } -} - -List toolbarControls(BuildContext context, String id, FFI ffi) { - final ffiModel = ffi.ffiModel; - final pi = ffiModel.pi; - final perms = ffiModel.permissions; - final sessionId = ffi.sessionId; - final isDefaultConn = ffi.connType == ConnType.defaultConn; - - List v = []; - // elevation - if (isDefaultConn && - perms['keyboard'] != false && - ffi.elevationModel.showRequestMenu) { - v.add( - TTextMenu( - child: Text(translate('Request Elevation')), - onPressed: () => - showRequestElevationDialog(sessionId, ffi.dialogManager)), - ); - } - // osAccount / osPassword - if (isDefaultConn && perms['keyboard'] != false) { - v.add( - TTextMenu( - child: Row(children: [ - Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')), - ]), - trailingIcon: Transform.scale( - scale: (isDesktop || isWebDesktop) ? 0.8 : 1, - child: IconButton( - onPressed: () { - if (isMobile && Navigator.canPop(context)) { - Navigator.pop(context); - } - if (pi.isHeadless) { - showSetOSAccount(sessionId, ffi.dialogManager); - } else { - handleOsPasswordEditIcon(sessionId, ffi.dialogManager); - } - }, - icon: Icon(Icons.edit, color: isMobile ? MyTheme.accent : null), - ), - ), - onPressed: () => pi.isHeadless - ? showSetOSAccount(sessionId, ffi.dialogManager) - : handleOsPasswordAction(sessionId, ffi.dialogManager), - ), - ); - } - // paste - if (isDefaultConn && - pi.platform != kPeerPlatformAndroid && - perms['keyboard'] != false) { - v.add(TTextMenu( - child: Text(translate('Send clipboard keystrokes')), - onPressed: () async { - ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); - if (data != null && data.text != null) { - bind.sessionInputString( - sessionId: sessionId, value: data.text ?? ""); - } - })); - } - // reset canvas - if (isDefaultConn && isMobile) { - v.add(TTextMenu( - child: Text(translate('Reset canvas')), - onPressed: () => ffi.cursorModel.reset())); - } - - // https://github.com/rustdesk/rustdesk/pull/9731 - // Does not work for connection established by "accept". - connectWithToken( - {bool isFileTransfer = false, - bool isViewCamera = false, - bool isTcpTunneling = false, - bool isTerminal = false}) { - final connToken = bind.sessionGetConnToken(sessionId: ffi.sessionId); - connect(context, id, - isFileTransfer: isFileTransfer, - isViewCamera: isViewCamera, - isTerminal: isTerminal, - isTcpTunneling: isTcpTunneling, - connToken: connToken); - } - - if (isDefaultConn && isDesktop) { - v.add( - TTextMenu( - child: Text(translate('Transfer file')), - onPressed: () => connectWithToken(isFileTransfer: true)), - ); - v.add( - TTextMenu( - child: Text(translate('View camera')), - onPressed: () => connectWithToken(isViewCamera: true)), - ); - v.add( - TTextMenu( - child: Text('${translate('Terminal')} (beta)'), - onPressed: () => connectWithToken(isTerminal: true)), - ); - v.add( - TTextMenu( - child: Text(translate('TCP tunneling')), - onPressed: () => connectWithToken(isTcpTunneling: true)), - ); - } - // note - if (isDefaultConn && !bind.isDisableAccount()) { - v.add( - TTextMenu( - child: Text(translate('Note')), - onPressed: () async { - bool isLogin = - bind.mainGetLocalOption(key: 'access_token').isNotEmpty; - if (!isLogin) { - final res = await loginDialog(); - if (res != true) return; - // Desktop: send message to main window to refresh login status - // Web: login is required before connection, so no need to refresh - // Mobile: same isolate, no need to send message - if (isDesktop) { - rustDeskWinManager.call( - WindowType.Main, kWindowRefreshCurrentUser, ""); - } - } - showAuditDialog(ffi); - }), - ); - } - // divider - if (isDefaultConn && (isDesktop || isWebDesktop)) { - v.add(TTextMenu(child: Offstage(), onPressed: () {}, divider: true)); - } - // ctrlAltDel - if (isDefaultConn && - !ffiModel.viewOnly && - ffiModel.keyboard && - (pi.platform == kPeerPlatformLinux || pi.sasEnabled)) { - v.add( - TTextMenu( - child: Text('${translate("Insert Ctrl + Alt + Del")}'), - onPressed: () => bind.sessionCtrlAltDel(sessionId: sessionId)), - ); - } - // restart - if (isDefaultConn && - perms['restart'] != false && - (pi.platform == kPeerPlatformLinux || - pi.platform == kPeerPlatformWindows || - pi.platform == kPeerPlatformMacOS)) { - v.add( - TTextMenu( - child: Text(translate('Restart remote device')), - onPressed: () => - showRestartRemoteDevice(pi, id, sessionId, ffi.dialogManager)), - ); - } - // insertLock - if (isDefaultConn && !ffiModel.viewOnly && ffi.ffiModel.keyboard) { - v.add( - TTextMenu( - child: Text(translate('Insert Lock')), - onPressed: () => bind.sessionLockScreen(sessionId: sessionId)), - ); - } - // blockUserInput - if (isDefaultConn && - ffi.ffiModel.keyboard && - ffi.ffiModel.permissions['block_input'] != false && - pi.platform == kPeerPlatformWindows) // privacy-mode != true ?? - { - v.add(TTextMenu( - child: Obx(() => Text(translate( - '${BlockInputState.find(id).value ? 'Unb' : 'B'}lock user input'))), - onPressed: () { - RxBool blockInput = BlockInputState.find(id); - bind.sessionToggleOption( - sessionId: sessionId, - value: '${blockInput.value ? 'un' : ''}block-input'); - blockInput.value = !blockInput.value; - })); - } - // switchSides - if (isDefaultConn && - isDesktop && - ffiModel.keyboard && - pi.platform != kPeerPlatformAndroid && - versionCmp(pi.version, '1.2.0') >= 0 && - bind.peerGetSessionsCount(id: id, connType: ffi.connType.index) == 1) { - v.add(TTextMenu( - child: Text(translate('Switch Sides')), - onPressed: () => - showConfirmSwitchSidesDialog(sessionId, id, ffi.dialogManager))); - } - // refresh - if (pi.version.isNotEmpty) { - v.add(TTextMenu( - child: Text(translate('Refresh')), - onPressed: () => sessionRefreshVideo(sessionId, pi), - )); - } - // record - if (!(isDesktop || isWeb) && - (ffi.recordingModel.start || (perms["recording"] != false))) { - v.add(TTextMenu( - child: Row( - children: [ - Text(translate(ffi.recordingModel.start - ? 'Stop session recording' - : 'Start session recording')), - Padding( - padding: EdgeInsets.only(left: 12), - child: Icon( - ffi.recordingModel.start - ? Icons.pause_circle_filled - : Icons.videocam_outlined, - color: MyTheme.accent), - ) - ], - ), - onPressed: () => ffi.recordingModel.toggle())); - } - - // to-do: - // 1. Web desktop - // 2. Mobile, copy the image to the clipboard - if (isDesktop) { - final isScreenshotSupported = bind.sessionGetCommonSync( - sessionId: sessionId, key: 'is_screenshot_supported', param: ''); - if ('true' == isScreenshotSupported) { - v.add(TTextMenu( - child: Text(ffi.ffiModel.timerScreenshot != null - ? '${translate('Taking screenshot')} ...' - : translate('Take screenshot')), - onPressed: ffi.ffiModel.timerScreenshot != null - ? null - : () { - if (pi.currentDisplay == kAllDisplayValue) { - msgBox( - sessionId, - 'custom-nook-nocancel-hasclose-info', - 'Take screenshot', - 'screenshot-merged-screen-not-supported-tip', - '', - ffi.dialogManager); - } else { - bind.sessionTakeScreenshot( - sessionId: sessionId, display: pi.currentDisplay); - ffi.ffiModel.timerScreenshot = - Timer(Duration(seconds: 30), () { - ffi.ffiModel.timerScreenshot = null; - }); - } - }, - )); - } - } - // fingerprint - if (!(isDesktop || isWebDesktop)) { - v.add(TTextMenu( - child: Text(translate('Copy Fingerprint')), - onPressed: () => onCopyFingerprint(FingerprintState.find(id).value), - )); - } - return v; -} - -Future>> toolbarViewStyle( - BuildContext context, String id, FFI ffi) async { - final groupValue = - await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? ''; - void onChanged(String? value) async { - if (value == null) return; - bind - .sessionSetViewStyle(sessionId: ffi.sessionId, value: value) - .then((_) => ffi.canvasModel.updateViewStyle()); - } - - return [ - TRadioMenu( - child: Text(translate('Scale original')), - value: kRemoteViewStyleOriginal, - groupValue: groupValue, - onChanged: onChanged), - TRadioMenu( - child: Text(translate('Scale adaptive')), - value: kRemoteViewStyleAdaptive, - groupValue: groupValue, - onChanged: onChanged), - TRadioMenu( - child: Text(translate('Scale custom')), - value: kRemoteViewStyleCustom, - groupValue: groupValue, - onChanged: onChanged) - ]; -} - -Future>> toolbarImageQuality( - BuildContext context, String id, FFI ffi) async { - final groupValue = - await bind.sessionGetImageQuality(sessionId: ffi.sessionId) ?? ''; - onChanged(String? value) async { - if (value == null) return; - await bind.sessionSetImageQuality(sessionId: ffi.sessionId, value: value); - } - - return [ - TRadioMenu( - child: Text(translate('Good image quality')), - value: kRemoteImageQualityBest, - groupValue: groupValue, - onChanged: onChanged), - TRadioMenu( - child: Text(translate('Balanced')), - value: kRemoteImageQualityBalanced, - groupValue: groupValue, - onChanged: onChanged), - TRadioMenu( - child: Text(translate('Optimize reaction time')), - value: kRemoteImageQualityLow, - groupValue: groupValue, - onChanged: onChanged), - TRadioMenu( - child: Text(translate('Custom')), - value: kRemoteImageQualityCustom, - groupValue: groupValue, - onChanged: (value) { - onChanged(value); - customImageQualityDialog(ffi.sessionId, id, ffi); - }, - ), - ]; -} - -Future>> toolbarCodec( - BuildContext context, String id, FFI ffi) async { - final sessionId = ffi.sessionId; - final alternativeCodecs = - await bind.sessionAlternativeCodecs(sessionId: sessionId); - final groupValue = await bind.sessionGetOption( - sessionId: sessionId, arg: kOptionCodecPreference) ?? - ''; - final List codecs = []; - try { - final Map codecsJson = jsonDecode(alternativeCodecs); - final vp8 = codecsJson['vp8'] ?? false; - final av1 = codecsJson['av1'] ?? false; - final h264 = codecsJson['h264'] ?? false; - final h265 = codecsJson['h265'] ?? false; - codecs.add(vp8); - codecs.add(av1); - codecs.add(h264); - codecs.add(h265); - } catch (e) { - debugPrint("Show Codec Preference err=$e"); - } - final visible = - codecs.length == 4 && (codecs[0] || codecs[1] || codecs[2] || codecs[3]); - if (!visible) return []; - onChanged(String? value) async { - if (value == null) return; - await bind.sessionPeerOption( - sessionId: sessionId, name: kOptionCodecPreference, value: value); - bind.sessionChangePreferCodec(sessionId: sessionId); - } - - TRadioMenu radio(String label, String value, bool enabled) { - return TRadioMenu( - child: Text(label), - value: value, - groupValue: groupValue, - onChanged: enabled ? onChanged : null); - } - - var autoLabel = translate('Auto'); - if (groupValue == 'auto' && - ffi.qualityMonitorModel.data.codecFormat != null) { - autoLabel = '$autoLabel (${ffi.qualityMonitorModel.data.codecFormat})'; - } - return [ - radio(autoLabel, 'auto', true), - if (codecs[0]) radio('VP8', 'vp8', codecs[0]), - radio('VP9', 'vp9', true), - if (codecs[1]) radio('AV1', 'av1', codecs[1]), - if (codecs[2]) radio('H264', 'h264', codecs[2]), - if (codecs[3]) radio('H265', 'h265', codecs[3]), - ]; -} - -Future> toolbarCursor( - BuildContext context, String id, FFI ffi) async { - List v = []; - final ffiModel = ffi.ffiModel; - final pi = ffiModel.pi; - final sessionId = ffi.sessionId; - - // show remote cursor - if (pi.platform != kPeerPlatformAndroid && - !ffi.canvasModel.cursorEmbedded && - !pi.isWayland) { - final state = ShowRemoteCursorState.find(id); - final lockState = ShowRemoteCursorLockState.find(id); - final enabled = !ffiModel.viewOnly; - final option = 'show-remote-cursor'; - if (pi.currentDisplay == kAllDisplayValue || - bind.sessionIsMultiUiSession(sessionId: sessionId)) { - lockState.value = false; - } - v.add(TToggleMenu( - child: Text(translate('Show remote cursor')), - value: state.value, - onChanged: enabled && !lockState.value - ? (value) async { - if (value == null) return; - await bind.sessionToggleOption( - sessionId: sessionId, value: option); - state.value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: option); - } - : null)); - } - // follow remote cursor - if (pi.platform != kPeerPlatformAndroid && - !ffi.canvasModel.cursorEmbedded && - !pi.isWayland && - versionCmp(pi.version, "1.2.4") >= 0 && - pi.displays.length > 1 && - pi.currentDisplay != kAllDisplayValue && - !bind.sessionIsMultiUiSession(sessionId: sessionId)) { - final option = 'follow-remote-cursor'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - final showCursorOption = 'show-remote-cursor'; - final showCursorState = ShowRemoteCursorState.find(id); - final showCursorLockState = ShowRemoteCursorLockState.find(id); - final showCursorEnabled = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: showCursorOption); - showCursorLockState.value = value; - if (value && !showCursorEnabled) { - await bind.sessionToggleOption( - sessionId: sessionId, value: showCursorOption); - showCursorState.value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: showCursorOption); - } - v.add(TToggleMenu( - child: Text(translate('Follow remote cursor')), - value: value, - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption(sessionId: sessionId, value: option); - value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: option); - showCursorLockState.value = value; - if (!showCursorEnabled) { - await bind.sessionToggleOption( - sessionId: sessionId, value: showCursorOption); - showCursorState.value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: showCursorOption); - } - })); - } - // follow remote window focus - if (pi.platform != kPeerPlatformAndroid && - !ffi.canvasModel.cursorEmbedded && - !pi.isWayland && - versionCmp(pi.version, "1.2.4") >= 0 && - pi.displays.length > 1 && - pi.currentDisplay != kAllDisplayValue && - !bind.sessionIsMultiUiSession(sessionId: sessionId)) { - final option = 'follow-remote-window'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - v.add(TToggleMenu( - child: Text(translate('Follow remote window focus')), - value: value, - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption(sessionId: sessionId, value: option); - value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: option); - })); - } - // zoom cursor - final viewStyle = await bind.sessionGetViewStyle(sessionId: sessionId) ?? ''; - if (!isMobile && - pi.platform != kPeerPlatformAndroid && - viewStyle != kRemoteViewStyleOriginal) { - final option = 'zoom-cursor'; - final peerState = PeerBoolOption.find(id, option); - v.add(TToggleMenu( - child: Text(translate('Zoom cursor')), - value: peerState.value, - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption(sessionId: sessionId, value: option); - peerState.value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - }, - )); - } - return v; -} - -Future> toolbarDisplayToggle( - BuildContext context, String id, FFI ffi) async { - List v = []; - final ffiModel = ffi.ffiModel; - final pi = ffiModel.pi; - final perms = ffiModel.permissions; - final sessionId = ffi.sessionId; - final isDefaultConn = ffi.connType == ConnType.defaultConn; - - // show quality monitor - final option = 'show-quality-monitor'; - v.add(TToggleMenu( - value: bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option), - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption(sessionId: sessionId, value: option); - ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); - }, - child: Text(translate('Show quality monitor')))); - // mute - if (isDefaultConn && perms['audio'] != false) { - final option = 'disable-audio'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - v.add(TToggleMenu( - value: value, - onChanged: (value) { - if (value == null) return; - bind.sessionToggleOption(sessionId: sessionId, value: option); - }, - child: Text(translate('Mute')))); - } - // file copy and paste - // If the version is less than 1.2.4, file copy and paste is supported on Windows only. - final isSupportIfPeer_1_2_3 = versionCmp(pi.version, '1.2.4') < 0 && - isWindows && - pi.platform == kPeerPlatformWindows; - // If the version is 1.2.4 or later, file copy and paste is supported when kPlatformAdditionsHasFileClipboard is set. - final isSupportIfPeer_1_2_4 = versionCmp(pi.version, '1.2.4') >= 0 && - bind.mainHasFileClipboard() && - pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard); - if (isDefaultConn && - ffiModel.keyboard && - perms['file'] != false && - (isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) { - final enabled = !ffiModel.viewOnly; - final value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: kOptionEnableFileCopyPaste); - v.add(TToggleMenu( - value: value, - onChanged: enabled - ? (value) { - if (value == null) return; - bind.sessionToggleOption( - sessionId: sessionId, value: kOptionEnableFileCopyPaste); - } - : null, - child: Text(translate('Enable file copy and paste')))); - } - // disable clipboard - if (isDefaultConn && ffiModel.keyboard && perms['clipboard'] != false) { - final enabled = !ffiModel.viewOnly; - final option = 'disable-clipboard'; - var value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - if (ffiModel.viewOnly) value = true; - v.add(TToggleMenu( - value: value, - onChanged: enabled - ? (value) { - if (value == null) return; - bind.sessionToggleOption(sessionId: sessionId, value: option); - } - : null, - child: Text(translate('Disable clipboard')))); - } - // lock after session end - if (isDefaultConn && ffiModel.keyboard && !ffiModel.isPeerAndroid) { - final enabled = !ffiModel.viewOnly; - final option = 'lock-after-session-end'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - v.add(TToggleMenu( - value: value, - onChanged: enabled - ? (value) { - if (value == null) return; - bind.sessionToggleOption(sessionId: sessionId, value: option); - } - : null, - child: Text(translate('Lock after session end')))); - } - - final privacyModeState = PrivacyModeState.find(id); - if (pi.isSupportMultiDisplay && - (privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) && - pi.displaysCount.value > 1 && - bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { - final value = - bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) == - 'Y'; - v.add(TToggleMenu( - value: value, - onChanged: (value) { - if (value == null) return; - bind.sessionSetDisplaysAsIndividualWindows( - sessionId: sessionId, value: value ? 'Y' : 'N'); - }, - child: Text(translate('Show displays as individual windows')))); - } - - final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1; - if (pi.isSupportMultiDisplay && isMultiScreens) { - final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession( - sessionId: ffi.sessionId) == - 'Y'; - v.add(TToggleMenu( - value: value, - onChanged: (value) { - if (value == null) return; - bind.sessionSetUseAllMyDisplaysForTheRemoteSession( - sessionId: sessionId, value: value ? 'Y' : 'N'); - }, - child: Text(translate('Use all my displays for the remote session')))); - } - - // 444 - final codec_format = ffi.qualityMonitorModel.data.codecFormat; - if (versionCmp(pi.version, "1.2.4") >= 0 && - (codec_format == "AV1" || codec_format == "VP9")) { - final option = 'i444'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - v.add(TToggleMenu( - value: value, - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption(sessionId: sessionId, value: option); - bind.sessionChangePreferCodec(sessionId: sessionId); - }, - child: Text(translate('True color (4:4:4)')))); - } - - if (isDefaultConn && isMobile) { - v.addAll(toolbarKeyboardToggles(ffi)); - } - - // view mode (mobile only, desktop is in keyboard menu) - if (isDefaultConn && isMobile && versionCmp(pi.version, '1.2.0') >= 0) { - v.add(TToggleMenu( - value: ffiModel.viewOnly, - onChanged: (value) async { - if (value == null) return; - await bind.sessionToggleOption( - sessionId: ffi.sessionId, value: kOptionToggleViewOnly); - ffiModel.setViewOnly(id, value); - }, - child: Text(translate('View Mode')))); - } - return v; -} - -var togglePrivacyModeTime = DateTime.now().subtract(const Duration(hours: 1)); - -List toolbarPrivacyMode( - RxString privacyModeState, BuildContext context, String id, FFI ffi) { - final ffiModel = ffi.ffiModel; - final pi = ffiModel.pi; - final sessionId = ffi.sessionId; - final hasPrivacyModePermission = ffiModel.permissions['privacy_mode'] != false; - - // Backend revocation already attempts to turn privacy mode off. - // Still keep this menu when privacy mode is active, so users can turn it off - // if there is a sync delay, version mismatch, or off attempt failure. - if (!hasPrivacyModePermission && privacyModeState.isEmpty) { - return []; // No permission and not active, hide options. - } - - getDefaultMenu(Future Function(SessionID sid, String opt) toggleFunc) { - final enabled = - !ffiModel.viewOnly && (hasPrivacyModePermission || privacyModeState.isNotEmpty); - return TToggleMenu( - value: privacyModeState.isNotEmpty, - onChanged: enabled - ? (value) { - if (value == null) return; - if (!allowDisplaySwitchInPrivacyMode(pi) && - ffiModel.pi.currentDisplay != 0 && - ffiModel.pi.currentDisplay != kAllDisplayValue) { - msgBox( - sessionId, - 'custom-nook-nocancel-hasclose', - 'info', - 'Please switch to Display 1 first', - '', - ffi.dialogManager); - return; - } - final option = 'privacy-mode'; - toggleFunc(sessionId, option); - } - : null, - child: Text(translate('Privacy mode'))); - } - - final privacyModeImpls = - pi.platformAdditions[kPlatformAdditionsSupportedPrivacyModeImpl] - as List?; - if (privacyModeImpls == null) { - return [ - getDefaultMenu((sid, opt) async { - bind.sessionToggleOption(sessionId: sid, value: opt); - togglePrivacyModeTime = DateTime.now(); - }) - ]; - } - if (privacyModeImpls.isEmpty) { - return []; - } - - if (privacyModeImpls.length == 1) { - final implKey = (privacyModeImpls[0] as List)[0] as String; - return [ - getDefaultMenu((sid, opt) async { - bind.sessionTogglePrivacyMode( - sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty); - togglePrivacyModeTime = DateTime.now(); - }) - ]; - } else { - final visibleImpls = hasPrivacyModePermission - ? privacyModeImpls - : privacyModeImpls.where((e) { - final implKey = (e as List)[0] as String; - return privacyModeState.value == implKey; - }).toList(); - return visibleImpls.map((e) { - final implKey = (e as List)[0] as String; - final implName = (e)[1] as String; - final enabled = !ffiModel.viewOnly && - (hasPrivacyModePermission || privacyModeState.value == implKey); - return TToggleMenu( - child: Text(translate(implName)), - value: privacyModeState.value == implKey, - onChanged: enabled - ? (value) { - if (value == null) return; - if (value && !hasPrivacyModePermission) return; - togglePrivacyModeTime = DateTime.now(); - bind.sessionTogglePrivacyMode( - sessionId: sessionId, implKey: implKey, on: value); - } - : null); - }).toList(); - } -} - -List toolbarKeyboardToggles(FFI ffi) { - final ffiModel = ffi.ffiModel; - final pi = ffiModel.pi; - final sessionId = ffi.sessionId; - final isDefaultConn = ffi.connType == ConnType.defaultConn; - List v = []; - - // swap key - if (ffiModel.keyboard && - ((isMacOS && pi.platform != kPeerPlatformMacOS) || - (!isMacOS && pi.platform == kPeerPlatformMacOS))) { - final option = 'allow_swap_key'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - onChanged(bool? value) { - if (value == null) return; - bind.sessionToggleOption(sessionId: sessionId, value: option); - } - - final enabled = !ffi.ffiModel.viewOnly; - v.add(TToggleMenu( - value: value, - onChanged: enabled ? onChanged : null, - child: Text(translate('Swap control-command key')))); - } - - // Relative mouse mode (gaming mode). - // Only show when server supports MOUSE_TYPE_MOVE_RELATIVE (version >= 1.4.5) - // Note: This feature is only available in Flutter client. Sciter client does not support this. - // Web client is not supported yet due to Pointer Lock API integration complexity with Flutter's input system. - // Wayland is not supported due to cursor warping limitations. - // Mobile: This option is now in GestureHelp widget, shown only when joystick is visible. - final isWayland = isDesktop && isLinux && bind.mainCurrentIsWayland(); - if (isDesktop && - isDefaultConn && - !isWeb && - !isWayland && - ffiModel.keyboard && - !ffiModel.viewOnly && - ffi.inputModel.isRelativeMouseModeSupported) { - v.add(TToggleMenu( - value: ffi.inputModel.relativeMouseMode.value, - onChanged: (value) { - if (value == null) return; - final previousValue = ffi.inputModel.relativeMouseMode.value; - final success = ffi.inputModel.setRelativeMouseMode(value); - if (!success) { - // Revert the observable toggle to reflect the actual state - ffi.inputModel.relativeMouseMode.value = previousValue; - } - }, - child: Text(translate('Relative mouse mode')))); - } - - // reverse mouse wheel - if (ffiModel.keyboard) { - var optionValue = - bind.sessionGetReverseMouseWheelSync(sessionId: sessionId) ?? ''; - if (optionValue == '') { - optionValue = bind.mainGetUserDefaultOption(key: kKeyReverseMouseWheel); - } - onChanged(bool? value) async { - if (value == null) return; - await bind.sessionSetReverseMouseWheel( - sessionId: sessionId, value: value ? 'Y' : 'N'); - } - - final enabled = !ffi.ffiModel.viewOnly; - v.add(TToggleMenu( - value: optionValue == 'Y', - onChanged: enabled ? onChanged : null, - child: Text(translate('Reverse mouse wheel')))); - } - - // swap left right mouse - if (ffiModel.keyboard) { - final option = 'swap-left-right-mouse'; - final value = - bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option); - onChanged(bool? value) { - if (value == null) return; - bind.sessionToggleOption(sessionId: sessionId, value: option); - } - - final enabled = !ffi.ffiModel.viewOnly; - v.add(TToggleMenu( - value: value, - onChanged: enabled ? onChanged : null, - child: Text(translate('swap-left-right-mouse')))); - } - return v; -} - -bool showVirtualDisplayMenu(FFI ffi) { - if (ffi.ffiModel.pi.platform != kPeerPlatformWindows) { - return false; - } - if (!ffi.ffiModel.pi.isInstalled) { - return false; - } - if (ffi.ffiModel.pi.isRustDeskIdd || ffi.ffiModel.pi.isAmyuniIdd) { - return true; - } - return false; -} - -List getVirtualDisplayMenuChildren( - FFI ffi, String id, VoidCallback? clickCallBack) { - if (!showVirtualDisplayMenu(ffi)) { - return []; - } - final pi = ffi.ffiModel.pi; - final privacyModeState = PrivacyModeState.find(id); - if (pi.isRustDeskIdd) { - final virtualDisplays = ffi.ffiModel.pi.RustDeskVirtualDisplays; - final children = []; - for (var i = 0; i < kMaxVirtualDisplayCount; i++) { - children.add(Obx(() => CkbMenuButton( - value: virtualDisplays.contains(i + 1), - onChanged: privacyModeState.isNotEmpty - ? null - : (bool? value) async { - if (value != null) { - bind.sessionToggleVirtualDisplay( - sessionId: ffi.sessionId, index: i + 1, on: value); - clickCallBack?.call(); - } - }, - child: Text('${translate('Virtual display')} ${i + 1}'), - ffi: ffi, - ))); - } - children.add(Divider()); - children.add(Obx(() => MenuButton( - onPressed: privacyModeState.isNotEmpty - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: ffi.sessionId, - index: kAllVirtualDisplay, - on: false); - clickCallBack?.call(); - }, - ffi: ffi, - child: Text(translate('Plug out all')), - ))); - return children; - } - if (pi.isAmyuniIdd) { - final count = ffi.ffiModel.pi.amyuniVirtualDisplayCount; - final children = [ - Obx(() => Row( - children: [ - TextButton( - onPressed: privacyModeState.isNotEmpty || count == 0 - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: ffi.sessionId, index: 0, on: false); - clickCallBack?.call(); - }, - child: Icon(Icons.remove), - ), - Text(count.toString()), - TextButton( - onPressed: privacyModeState.isNotEmpty || count == 4 - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: ffi.sessionId, index: 0, on: true); - clickCallBack?.call(); - }, - child: Icon(Icons.add), - ), - ], - )), - Divider(), - Obx(() => MenuButton( - onPressed: privacyModeState.isNotEmpty || count == 0 - ? null - : () { - bind.sessionToggleVirtualDisplay( - sessionId: ffi.sessionId, - index: kAllVirtualDisplay, - on: false); - clickCallBack?.call(); - }, - ffi: ffi, - child: Text(translate('Plug out all')), - )), - ]; - return children; - } - return []; -} diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart deleted file mode 100644 index adf7b1d45..000000000 --- a/flutter/lib/consts.dart +++ /dev/null @@ -1,695 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:get/get.dart'; - -const int kMaxVirtualDisplayCount = 4; -const int kAllVirtualDisplay = -1; - -const double kDesktopRemoteTabBarHeight = 28.0; -const int kInvalidWindowId = -1; -const int kMainWindowId = 0; - -const kAllDisplayValue = -1; - -const kKeyLegacyMode = 'legacy'; -const kKeyMapMode = 'map'; -const kKeyTranslateMode = 'translate'; - -const String kPlatformAdditionsIsWayland = "is_wayland"; -const String kPlatformAdditionsHeadless = "headless"; -const String kPlatformAdditionsIsInstalled = "is_installed"; -const String kPlatformAdditionsIddImpl = "idd_impl"; -const String kPlatformAdditionsRustDeskVirtualDisplays = - "rustdesk_virtual_displays"; -const String kPlatformAdditionsAmyuniVirtualDisplays = - "amyuni_virtual_displays"; -const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard"; -const String kPlatformAdditionsSupportedPrivacyModeImpl = - "supported_privacy_mode_impl"; - -const String kPeerPlatformWindows = "Windows"; -const String kPeerPlatformLinux = "Linux"; -const String kPeerPlatformMacOS = "Mac OS"; -const String kPeerPlatformAndroid = "Android"; -const String kPeerPlatformWebDesktop = "WebDesktop"; - -const double kScrollbarThickness = 12.0; - -/// [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 kAppTypeDesktopViewCamera = "view camera"; -const String kAppTypeDesktopPortForward = "port forward"; -const String kAppTypeDesktopTerminal = "terminal"; - -const String kWindowMainWindowOnTop = "main_window_on_top"; -const String kWindowRefreshCurrentUser = "refresh_current_user"; -const String kWindowGetWindowInfo = "get_window_info"; -const String kWindowGetScreenList = "get_screen_list"; -// This method is not used, maybe it can be removed. -const String kWindowDisableGrabKeyboard = "disable_grab_keyboard"; -const String kWindowActionRebuild = "rebuild"; -const String kWindowEventHide = "hide"; -const String kWindowEventShow = "show"; -const String kWindowConnect = "connect"; -const String kWindowBumpMouse = "bump_mouse"; - -const String kWindowEventNewRemoteDesktop = "new_remote_desktop"; -const String kWindowEventNewFileTransfer = "new_file_transfer"; -const String kWindowEventNewViewCamera = "new_view_camera"; -const String kWindowEventNewPortForward = "new_port_forward"; -const String kWindowEventNewTerminal = "new_terminal"; -const String kWindowEventRestoreTerminalSessions = "restore_terminal_sessions"; -const String kWindowEventActiveSession = "active_session"; -const String kWindowEventActiveDisplaySession = "active_display_session"; -const String kWindowEventGetRemoteList = "get_remote_list"; -const String kWindowEventGetSessionIdList = "get_session_id_list"; -const String kWindowEventRemoteWindowCoords = "remote_window_coords"; -const String kWindowEventSetFullscreen = "set_fullscreen"; - -const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; -const String kWindowEventGetCachedSessionData = "get_cached_session_data"; -const String kWindowEventOpenMonitorSession = "open_monitor_session"; - -const String kOptionViewStyle = "view_style"; -const String kOptionScrollStyle = "scroll_style"; -const String kOptionEdgeScrollEdgeThickness = "edge-scroll-edge-thickness"; -const String kOptionImageQuality = "image_quality"; -const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; -const String kOptionTextureRender = "use-texture-render"; -const String kOptionD3DRender = "allow-d3d-render"; -const String kOptionOpenInTabs = "allow-open-in-tabs"; -const String kOptionOpenInWindows = "allow-open-in-windows"; -const String kOptionForceAlwaysRelay = "force-always-relay"; -const String kOptionViewOnly = "view_only"; -const String kOptionEnableLanDiscovery = "enable-lan-discovery"; -const String kOptionWhitelist = "whitelist"; -const String kOptionEnableAbr = "enable-abr"; -const String kOptionEnableRecordSession = "enable-record-session"; -const String kOptionDirectServer = "direct-server"; -const String kOptionDirectAccessPort = "direct-access-port"; -const String kOptionAllowAutoDisconnect = "allow-auto-disconnect"; -const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout"; -const String kOptionEnableHwcodec = "enable-hwcodec"; -const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming"; -const String kOptionAllowAutoRecordOutgoing = "allow-auto-record-outgoing"; -const String kOptionVideoSaveDirectory = "video-save-directory"; -const String kOptionAccessMode = "access-mode"; -const String kOptionEnableKeyboard = "enable-keyboard"; -// "Settings -> Security -> Permissions" -const String kOptionEnableRemotePrinter = "enable-remote-printer"; -const String kOptionEnableClipboard = "enable-clipboard"; -const String kOptionEnableFileTransfer = "enable-file-transfer"; -const String kOptionEnableAudio = "enable-audio"; -const String kOptionEnableCamera = "enable-camera"; -const String kOptionEnableTerminal = "enable-terminal"; -const String kOptionTerminalPersistent = "terminal-persistent"; -const String kOptionEnableTunnel = "enable-tunnel"; -const String kOptionEnableRemoteRestart = "enable-remote-restart"; -const String kOptionEnableBlockInput = "enable-block-input"; -const String kOptionEnablePrivacyMode = "enable-privacy-mode"; -const String kOptionEnablePermChangeInAcceptWindow = - "enable-perm-change-in-accept-window"; -const String kOptionAllowRemoteConfigModification = - "allow-remote-config-modification"; -const String kOptionVerificationMethod = "verification-method"; -const String kOptionApproveMode = "approve-mode"; -const String kOptionAllowNumericOneTimePassword = - "allow-numeric-one-time-password"; -const String kOptionCollapseToolbar = "collapse_toolbar"; -const String kOptionHideToolbar = "hide-toolbar"; -const String kOptionShowRemoteCursor = "show_remote_cursor"; -const String kOptionFollowRemoteCursor = "follow_remote_cursor"; -const String kOptionFollowRemoteWindow = "follow_remote_window"; -const String kOptionZoomCursor = "zoom-cursor"; -const String kOptionShowQualityMonitor = "show_quality_monitor"; -const String kOptionDisableAudio = "disable_audio"; -const String kOptionEnableFileCopyPaste = "enable-file-copy-paste"; -// "Settings -> Display -> Other default options" -const String kOptionDisableClipboard = "disable_clipboard"; -const String kOptionLockAfterSessionEnd = "lock_after_session_end"; -const String kOptionPrivacyMode = "privacy_mode"; -const String kOptionTouchMode = "touch-mode"; -const String kOptionI444 = "i444"; -const String kOptionSwapLeftRightMouse = "swap-left-right-mouse"; -const String kOptionCodecPreference = "codec-preference"; -const String kOptionRemoteMenubarDragLeft = "remote-menubar-drag-left"; -const String kOptionRemoteMenubarDragRight = "remote-menubar-drag-right"; -const String kOptionRemoteMenubarEdge = "remote-menubar-edge"; -const String kOptionRemoteMenubarFraction = "remote-menubar-frac"; -const String kOptionAllowMultiEdgeToolbarDock = - "allow-multi-edge-toolbar-dock"; -const String kOptionHideAbTagsPanel = "hideAbTagsPanel"; -const String kOptionRemoteMenubarState = "remoteMenubarState"; -const String kOptionPeerSorting = "peer-sorting"; -const String kOptionPeerTabIndex = "peer-tab-index"; -const String kOptionPeerTabOrder = "peer-tab-order"; -const String kOptionPeerTabVisible = "peer-tab-visible"; -const String kOptionPeerCardUiType = "peer-card-ui-type"; -const String kOptionCurrentAbName = "current-ab-name"; -const String kOptionEnableConfirmClosingTabs = "enable-confirm-closing-tabs"; -const String kOptionAllowAlwaysSoftwareRender = "allow-always-software-render"; -const String kOptionEnableCheckUpdate = "enable-check-update"; -const String kOptionAllowAutoUpdate = "allow-auto-update"; -const String kOptionAllowLinuxHeadless = "allow-linux-headless"; -const String kOptionAllowRemoveWallpaper = "allow-remove-wallpaper"; -const String kOptionStopService = "stop-service"; -const String kOptionDirectxCapture = "enable-directx-capture"; -const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification"; -const String kOptionEnableUdpPunch = "enable-udp-punch"; -const String kOptionEnableIpv6Punch = "enable-ipv6-punch"; -const String kOptionEnableTrustedDevices = "enable-trusted-devices"; -const String kOptionShowVirtualMouse = "show-virtual-mouse"; -const String kOptionVirtualMouseScale = "virtual-mouse-scale"; -const String kOptionShowVirtualJoystick = "show-virtual-joystick"; -const String kOptionAllowAskForNoteAtEndOfConnection = "allow-ask-for-note"; -const String kOptionEnableShowTerminalExtraKeys = "enable-show-terminal-extra-keys"; - -// network options -const String kOptionAllowWebSocket = "allow-websocket"; -const String kOptionAllowInsecureTLSFallback = "allow-insecure-tls-fallback"; -const String kOptionDisableUdp = "disable-udp"; -const String kOptionEnableFlutterHttpOnRust = "enable-flutter-http-on-rust"; - -// builtin options -const String kOptionHideServerSetting = "hide-server-settings"; -const String kOptionHideProxySetting = "hide-proxy-settings"; -const String kOptionHideWebSocketSetting = "hide-websocket-settings"; -const String kOptionHideStopService = "hide-stop-service"; -const String kOptionHideRemotePrinterSetting = "hide-remote-printer-settings"; -const String kOptionHideSecuritySetting = "hide-security-settings"; -const String kOptionHideNetworkSetting = "hide-network-settings"; -const String kOptionRemovePresetPasswordWarning = - "remove-preset-password-warning"; -const String kOptionDisableChangePermanentPassword = - "disable-change-permanent-password"; -const String kOptionDisableChangeId = "disable-change-id"; -const String kOptionDisableUnlockPin = "disable-unlock-pin"; -const kHideUsernameOnCard = "hide-username-on-card"; -const String kOptionHideHelpCards = "hide-help-cards"; -const String kOptionAllowDeepLinkPassword = "allow-deep-link-password"; -const String kOptionAllowDeepLinkServerSettings = - "allow-deep-link-server-settings"; - -const String kOptionToggleViewOnly = "view-only"; -const String kOptionToggleShowMyCursor = "show-my-cursor"; - -const String kOptionDisableFloatingWindow = "disable-floating-window"; - -const String kOptionKeepScreenOn = "keep-screen-on"; - -const String kOptionKeepAwakeDuringIncomingSessions = "keep-awake-during-incoming-sessions"; -const String kOptionKeepAwakeDuringOutgoingSessions = "keep-awake-during-outgoing-sessions"; - -const String kOptionShowMobileAction = "showMobileActions"; - -const String kUrlActionClose = "close"; - -const String kTabLabelHomePage = "Home"; -const String kTabLabelSettingPage = "Settings"; - -const String kWindowPrefix = "wm_"; -const int kWindowMainId = 0; - -const String kPointerEventKindTouch = "touch"; -const String kPointerEventKindMouse = "mouse"; - -const String kMouseEventTypeDefault = ""; -const String kMouseEventTypePanStart = "pan_start"; -const String kMouseEventTypePanUpdate = "pan_update"; -const String kMouseEventTypePanEnd = "pan_end"; -const String kMouseEventTypeDown = "down"; -const String kMouseEventTypeUp = "up"; - -const String kKeyFlutterKey = "flutter_key"; - -const String kKeyShowDisplaysAsIndividualWindows = - 'displays_as_individual_windows'; -const String kKeyUseAllMyDisplaysForTheRemoteSession = - 'use_all_my_displays_for_the_remote_session'; -const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar'; -const String kKeyReverseMouseWheel = "reverse_mouse_wheel"; - -const String kMsgboxTextWaitingForImage = 'Connected, waiting for image...'; - -// the executable name of the portable version -const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; - -const Color kColorWarn = Color.fromARGB(255, 245, 133, 59); -const Color kColorCanvas = Colors.black; - -const int kMobileDefaultDisplayWidth = 720; -const int kMobileDefaultDisplayHeight = 1280; - -const int kDesktopDefaultDisplayWidth = 1080; -const int kDesktopDefaultDisplayHeight = 720; - -const int kMobileMaxDisplaySize = 1280; -const int kDesktopMaxDisplaySize = 3840; - -const double kDesktopFileTransferRowHeight = 30.0; -const double kDesktopFileTransferHeaderHeight = 25.0; - -const double kMinFps = 5; -const double kDefaultFps = 30; -const double kMaxFps = 120; - -const double kMinQuality = 10; -const double kDefaultQuality = 50; -const double kMaxQuality = 100; -const double kMaxMoreQuality = 2000; - -// trackpad speed -const String kKeyTrackpadSpeed = 'trackpad-speed'; -const int kMinTrackpadSpeed = 10; -const int kDefaultTrackpadSpeed = 100; -const int kMaxTrackpadSpeed = 1000; - -// relative mouse mode -/// Throttle duration (in milliseconds) for updating pointer lock center during -/// window move/resize events. Lower values provide more responsive updates but -/// may cause performance issues during rapid window operations. -const int kDefaultPointerLockCenterThrottleMs = 100; - -/// Minimum server version required for relative mouse mode (MOUSE_TYPE_MOVE_RELATIVE). -/// Servers older than this version will ignore relative mouse events. -/// -/// IMPORTANT: This value must be kept in sync with the Rust constant -/// `MIN_VERSION_RELATIVE_MOUSE_MODE` in `src/common.rs`. -const String kMinVersionForRelativeMouseMode = '1.4.5'; - -/// Maximum delta value for relative mouse movement. -/// Large values could cause issues with i32 overflow on server side, -/// and no reasonable mouse movement should exceed this bound. -/// -/// IMPORTANT: This value must be kept in sync with the Rust constant -/// `MAX_RELATIVE_MOUSE_DELTA` in `src/server/input_service.rs`. -const int kMaxRelativeMouseDelta = 10000; - -/// Debounce duration (in milliseconds) for relative mouse mode toggle. -/// This prevents double-toggle from race condition between Rust rdev grab loop -/// and Flutter keyboard handling. Value should be small enough to allow -/// intentional quick toggles but large enough to prevent accidental double-triggers. -const int kRelativeMouseModeToggleDebounceMs = 150; - -// incomming (should be incoming) is kept, because change it will break the previous setting. -const String kKeyPrinterIncomingJobAction = 'printer-incomming-job-action'; -const String kValuePrinterIncomingJobDismiss = 'dismiss'; -const String kValuePrinterIncomingJobDefault = ''; -const String kValuePrinterIncomingJobSelected = 'selected'; -const String kKeyPrinterSelected = 'printer-selected-name'; -const String kKeyPrinterSave = 'allow-printer-dialog-save'; -const String kKeyPrinterAllowAutoPrint = 'allow-printer-auto-print'; - -double kNewWindowOffset = isWindows - ? 56.0 - : isLinux - ? 50.0 - : isMacOS - ? 30.0 - : 50.0; - -EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && isLinux - ? stateGlobal.fullscreen.isTrue || stateGlobal.isMaximized.value - ? EdgeInsets.zero - : EdgeInsets.all(5.0) - : EdgeInsets.zero; -// https://en.wikipedia.org/wiki/Non-breaking_space -const int $nbsp = 0x00A0; - -extension StringExtension on String { - String get nonBreaking => replaceAll(' ', String.fromCharCode($nbsp)); -} - -const Size kConnectionManagerWindowSizeClosedChat = Size(300, 490); -const Size kConnectionManagerWindowSizeOpenChat = Size(700, 490); -// Tabbar transition duration, now we remove the duration -const Duration kTabTransitionDuration = Duration.zero; -const double kEmptyMarginTop = 50; -const double kDesktopIconButtonSplashRadius = 20; - -/// [kMinCursorSize] indicates min cursor (w, h) -const int kMinCursorSize = 12; - -const kFullScreenEdgeSize = 0.0; -const kMaximizeEdgeSize = 0.0; -// Do not use kWindowResizeEdgeSize directly. Use `windowResizeEdgeSize` in `common.dart` instead. -const kWindowResizeEdgeSize = 5.0; -final kWindowBorderWidth = isWindows ? 0.0 : 1.0; -const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0); -const kFrameBorderRadius = 12.0; -const kFrameClipRRectBorderRadius = 12.0; -const kFrameBoxShadowBlurRadius = 32.0; -const kFrameBoxShadowOffsetFocused = 4.0; -const kFrameBoxShadowOffsetUnfocused = 2.0; - -const kInvalidValueStr = 'InvalidValueStr'; - -// Config key shared by flutter and other ui. -const kCommConfKeyTheme = 'theme'; -const kCommConfKeyLang = 'lang'; - -const kMobilePageConstraints = BoxConstraints(maxWidth: 600); - -/// [kMouseControlDistance] indicates the distance that self-side move to get control of mouse. -const kMouseControlDistance = 12; - -/// [kMouseControlTimeoutMSec] indicates the timeout (in milliseconds) that self-side can get control of mouse. -const kMouseControlTimeoutMSec = 1000; - -/// [kRemoteViewStyleOriginal] Show remote image without scaling. -const kRemoteViewStyleOriginal = 'original'; - -/// [kRemoteViewStyleAdaptive] Show remote image scaling by ratio factor. -const kRemoteViewStyleAdaptive = 'adaptive'; - -/// [kRemoteViewStyleCustom] Show remote image at a user-defined scale percent. -const kRemoteViewStyleCustom = 'custom'; - -/// [kRemoteScrollStyleAuto] Scroll image auto by position. -const kRemoteScrollStyleAuto = 'scrollauto'; - -/// [kRemoteScrollStyleBar] Scroll image with scroll bar. -const kRemoteScrollStyleBar = 'scrollbar'; - -/// [kRemoteScrollStyleEdge] Scroll image auto at edges. -const kRemoteScrollStyleEdge = 'scrolledge'; - -/// [kScrollModeDefault] Mouse or touchpad, the default scroll mode. -const kScrollModeDefault = 'default'; - -/// [kScrollModeReverse] Mouse or touchpad, the reverse scroll mode. -const kScrollModeReverse = 'reverse'; - -/// [kRemoteImageQualityBest] Best image quality. -const kRemoteImageQualityBest = 'best'; - -/// [kRemoteImageQualityBalanced] Balanced image quality, mid performance. -const kRemoteImageQualityBalanced = 'balanced'; - -/// [kRemoteImageQualityLow] Low image quality, better performance. -const kRemoteImageQualityLow = 'low'; - -/// [kRemoteImageQualityCustom] Custom image quality. -const kRemoteImageQualityCustom = 'custom'; - -const kIgnoreDpi = true; - -const Set kTouchBasedDeviceKinds = { - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.invertedStylus, -}; - -// Scale custom related constants -const String kCustomScalePercentKey = - 'custom_scale_percent'; // Flutter option key for storing custom scale percent (integer 5-1000) -const int kScaleCustomMinPercent = 5; -const int kScaleCustomPivotPercent = 100; // 100% should be at 1/3 of track -const int kScaleCustomMaxPercent = 1000; -const double kScaleCustomPivotPos = 1.0 / 3.0; // first 1/3 → up to 100% -const double kScaleCustomDetentEpsilon = - 0.006; // snap range around pivot (~0.6%) -const Duration kDebounceCustomScaleDuration = Duration(milliseconds: 300); - -// ================================ mobile ================================ - -// Magic numbers, maybe need to avoid it or use a better way to get them. -const kMobileDelaySoftKeyboard = Duration(milliseconds: 30); -const kMobileDelaySoftKeyboardFocus = Duration(milliseconds: 30); - -/// 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"; -const kAndroid13Notification = "android.permission.POST_NOTIFICATIONS"; - -/// 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 = { - 0x00000000020: 'VK_SPACE', - 0x00000000022: 'VK_QUOTE', - 0x0000000002c: 'VK_COMMA', - 0x0000000002d: 'VK_MINUS', - 0x0000000002f: 'VK_SLASH', - 0x00000000030: 'VK_0', - 0x00000000031: 'VK_1', - 0x00000000032: 'VK_2', - 0x00000000033: 'VK_3', - 0x00000000034: 'VK_4', - 0x00000000035: 'VK_5', - 0x00000000036: 'VK_6', - 0x00000000037: 'VK_7', - 0x00000000038: 'VK_8', - 0x00000000039: 'VK_9', - 0x0000000003b: 'VK_SEMICOLON', - 0x0000000003d: 'VK_PLUS', // it is = - 0x0000000005b: 'VK_LBRACKET', - 0x0000000005c: 'VK_BACKSLASH', - 0x0000000005d: 'VK_RBRACKET', - 0x00000000061: 'VK_A', - 0x00000000062: 'VK_B', - 0x00000000063: 'VK_C', - 0x00000000064: 'VK_D', - 0x00000000065: 'VK_E', - 0x00000000066: 'VK_F', - 0x00000000067: 'VK_G', - 0x00000000068: 'VK_H', - 0x00000000069: 'VK_I', - 0x0000000006a: 'VK_J', - 0x0000000006b: 'VK_K', - 0x0000000006c: 'VK_L', - 0x0000000006d: 'VK_M', - 0x0000000006e: 'VK_N', - 0x0000000006f: 'VK_O', - 0x00000000070: 'VK_P', - 0x00000000071: 'VK_Q', - 0x00000000072: 'VK_R', - 0x00000000073: 'VK_S', - 0x00000000074: 'VK_T', - 0x00000000075: 'VK_U', - 0x00000000076: 'VK_V', - 0x00000000077: 'VK_W', - 0x00000000078: 'VK_X', - 0x00000000079: 'VK_Y', - 0x0000000007a: 'VK_Z', - 0x00100000008: 'VK_BACK', - 0x00100000009: 'VK_TAB', - 0x0010000000d: 'VK_ENTER', - 0x0010000001b: 'VK_ESCAPE', - 0x0010000007f: 'VK_DELETE', - 0x00100000104: 'VK_CAPITAL', - 0x00100000301: 'VK_DOWN', - 0x00100000302: 'VK_LEFT', - 0x00100000303: 'VK_RIGHT', - 0x00100000304: 'VK_UP', - 0x00100000305: 'VK_END', - 0x00100000306: 'VK_HOME', - 0x00100000307: 'VK_NEXT', - 0x00100000308: 'VK_PRIOR', - 0x00100000401: 'VK_CLEAR', - 0x00100000407: 'VK_INSERT', - 0x00100000504: 'VK_CANCEL', - 0x00100000506: 'VK_EXECUTE', - 0x00100000508: 'VK_HELP', - 0x00100000509: 'VK_PAUSE', - 0x0010000050c: 'VK_SELECT', - 0x00100000608: 'VK_PRINT', - 0x00100000705: 'VK_CONVERT', - 0x00100000706: 'VK_FINAL', - 0x00100000711: 'VK_HANGUL', - 0x00100000712: 'VK_HANJA', - 0x00100000713: 'VK_JUNJA', - 0x00100000718: 'VK_KANA', - 0x00100000719: 'VK_KANJI', - 0x00100000801: 'VK_F1', - 0x00100000802: 'VK_F2', - 0x00100000803: 'VK_F3', - 0x00100000804: 'VK_F4', - 0x00100000805: 'VK_F5', - 0x00100000806: 'VK_F6', - 0x00100000807: 'VK_F7', - 0x00100000808: 'VK_F8', - 0x00100000809: 'VK_F9', - 0x0010000080a: 'VK_F10', - 0x0010000080b: 'VK_F11', - 0x0010000080c: 'VK_F12', - 0x00100000d2b: 'Apps', - 0x00200000002: 'VK_SLEEP', - 0x00200000100: 'VK_CONTROL', - 0x00200000101: 'RControl', - 0x00200000102: 'VK_SHIFT', - 0x00200000103: 'RShift', - 0x00200000104: 'VK_MENU', - 0x00200000105: 'RAlt', - 0x002000001f0: 'VK_CONTROL', - 0x002000001f2: 'VK_SHIFT', - 0x002000001f4: 'VK_MENU', - 0x002000001f6: 'Meta', - 0x0020000022a: 'VK_MULTIPLY', - 0x0020000022b: 'VK_ADD', - 0x0020000022d: 'VK_SUBTRACT', - 0x0020000022e: 'VK_DECIMAL', - 0x0020000022f: 'VK_DIVIDE', - 0x00200000230: 'VK_NUMPAD0', - 0x00200000231: 'VK_NUMPAD1', - 0x00200000232: 'VK_NUMPAD2', - 0x00200000233: 'VK_NUMPAD3', - 0x00200000234: 'VK_NUMPAD4', - 0x00200000235: 'VK_NUMPAD5', - 0x00200000236: 'VK_NUMPAD6', - 0x00200000237: 'VK_NUMPAD7', - 0x00200000238: 'VK_NUMPAD8', - 0x00200000239: 'VK_NUMPAD9', -}; - -/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _debugName -/// see [PhysicalKeyboardKey.debugName] -> _debugName -const Map physicalKeyMap = { - 0x00010082: 'VK_SLEEP', - 0x00070004: 'VK_A', - 0x00070005: 'VK_B', - 0x00070006: 'VK_C', - 0x00070007: 'VK_D', - 0x00070008: 'VK_E', - 0x00070009: 'VK_F', - 0x0007000a: 'VK_G', - 0x0007000b: 'VK_H', - 0x0007000c: 'VK_I', - 0x0007000d: 'VK_J', - 0x0007000e: 'VK_K', - 0x0007000f: 'VK_L', - 0x00070010: 'VK_M', - 0x00070011: 'VK_N', - 0x00070012: 'VK_O', - 0x00070013: 'VK_P', - 0x00070014: 'VK_Q', - 0x00070015: 'VK_R', - 0x00070016: 'VK_S', - 0x00070017: 'VK_T', - 0x00070018: 'VK_U', - 0x00070019: 'VK_V', - 0x0007001a: 'VK_W', - 0x0007001b: 'VK_X', - 0x0007001c: 'VK_Y', - 0x0007001d: 'VK_Z', - 0x0007001e: 'VK_1', - 0x0007001f: 'VK_2', - 0x00070020: 'VK_3', - 0x00070021: 'VK_4', - 0x00070022: 'VK_5', - 0x00070023: 'VK_6', - 0x00070024: 'VK_7', - 0x00070025: 'VK_8', - 0x00070026: 'VK_9', - 0x00070027: 'VK_0', - 0x00070028: 'VK_ENTER', - 0x00070029: 'VK_ESCAPE', - 0x0007002a: 'VK_BACK', - 0x0007002b: 'VK_TAB', - 0x0007002c: 'VK_SPACE', - 0x0007002d: 'VK_MINUS', - 0x0007002e: 'VK_PLUS', // it is = - 0x0007002f: 'VK_LBRACKET', - 0x00070030: 'VK_RBRACKET', - 0x00070033: 'VK_SEMICOLON', - 0x00070034: 'VK_QUOTE', - 0x00070036: 'VK_COMMA', - 0x00070038: 'VK_SLASH', - 0x00070039: 'VK_CAPITAL', - 0x0007003a: 'VK_F1', - 0x0007003b: 'VK_F2', - 0x0007003c: 'VK_F3', - 0x0007003d: 'VK_F4', - 0x0007003e: 'VK_F5', - 0x0007003f: 'VK_F6', - 0x00070040: 'VK_F7', - 0x00070041: 'VK_F8', - 0x00070042: 'VK_F9', - 0x00070043: 'VK_F10', - 0x00070044: 'VK_F11', - 0x00070045: 'VK_F12', - 0x00070049: 'VK_INSERT', - 0x0007004a: 'VK_HOME', - 0x0007004b: 'VK_PRIOR', // Page Up - 0x0007004c: 'VK_DELETE', - 0x0007004d: 'VK_END', - 0x0007004e: 'VK_NEXT', // Page Down - 0x0007004f: 'VK_RIGHT', - 0x00070050: 'VK_LEFT', - 0x00070051: 'VK_DOWN', - 0x00070052: 'VK_UP', - 0x00070053: 'Num Lock', // TODO rust not impl - 0x00070054: 'VK_DIVIDE', // numpad - 0x00070055: 'VK_MULTIPLY', - 0x00070056: 'VK_SUBTRACT', - 0x00070057: 'VK_ADD', - 0x00070058: 'VK_ENTER', // num enter - 0x00070059: 'VK_NUMPAD1', - 0x0007005a: 'VK_NUMPAD2', - 0x0007005b: 'VK_NUMPAD3', - 0x0007005c: 'VK_NUMPAD4', - 0x0007005d: 'VK_NUMPAD5', - 0x0007005e: 'VK_NUMPAD6', - 0x0007005f: 'VK_NUMPAD7', - 0x00070060: 'VK_NUMPAD8', - 0x00070061: 'VK_NUMPAD9', - 0x00070062: 'VK_NUMPAD0', - 0x00070063: 'VK_DECIMAL', - 0x00070075: 'VK_HELP', - 0x00070077: 'VK_SELECT', - 0x00070088: 'VK_KANA', - 0x0007008a: 'VK_CONVERT', - 0x000700e0: 'VK_CONTROL', - 0x000700e1: 'VK_SHIFT', - 0x000700e2: 'VK_MENU', - 0x000700e3: 'Meta', - 0x000700e4: 'RControl', - 0x000700e5: 'RShift', - 0x000700e6: 'RAlt', - 0x000700e7: 'RWin', - 0x000c00b1: 'VK_PAUSE', - 0x000c00cd: 'VK_PAUSE', - 0x000c019e: 'LOCK_SCREEN', - 0x000c0208: 'VK_PRINT', -}; - -/// The windows targets in the publish time order. -enum WindowsTarget { - naw, // not a windows target - xp, - vista, - w7, - w8, - w8_1, - w10, - w11 -} - -/// A convenient method to transform a build number to the corresponding windows version. -extension WindowsTargetExt on int { - WindowsTarget get windowsVersion => getWindowsTarget(this); -} - -const kCheckSoftwareUpdateFinish = 'check_software_update_finish'; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart deleted file mode 100644 index bdf3829e1..000000000 --- a/flutter/lib/desktop/pages/connection_page.dart +++ /dev/null @@ -1,615 +0,0 @@ -// main window right pane - -import 'dart:async'; -import 'dart:convert'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/widgets/connection_page_title.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/popup_menu.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'; -import 'package:flutter_hbb/models/peer_model.dart'; - -import '../../common.dart'; -import '../../common/formatter/id_formatter.dart'; -import '../../common/widgets/peer_tab_page.dart'; -import '../../common/widgets/autocomplete.dart'; -import '../../models/platform_model.dart'; -import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; - -class OnlineStatusWidget extends StatefulWidget { - const OnlineStatusWidget({Key? key, this.onSvcStatusChanged}) - : super(key: key); - - final VoidCallback? onSvcStatusChanged; - - @override - State createState() => _OnlineStatusWidgetState(); -} - -/// State for the connection page. -class _OnlineStatusWidgetState extends State { - final _svcStopped = Get.find(tag: 'stop-service'); - final _svcIsUsingPublicServer = true.obs; - Timer? _updateTimer; - - double get em => 14.0; - double? get height => bind.isIncomingOnly() ? null : em * 3; - - void onUsePublicServerGuide() { - const url = "https://rustdesk.com/pricing"; - canLaunchUrlString(url).then((can) { - if (can) { - launchUrlString(url); - } - }); - } - - @override - void initState() { - super.initState(); - _updateTimer = periodic_immediate(Duration(seconds: 1), () async { - updateStatus(); - }); - } - - @override - void dispose() { - _updateTimer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final isIncomingOnly = bind.isIncomingOnly(); - startServiceWidget() => Offstage( - offstage: !_svcStopped.value, - child: InkWell( - onTap: () async { - await start_service(true); - }, - child: Text(translate("Start service"), - style: TextStyle( - decoration: TextDecoration.underline, fontSize: em))) - .marginOnly(left: em), - ); - - setupServerWidget() => Flexible( - child: Offstage( - offstage: !(!_svcStopped.value && - stateGlobal.svcStatus.value == SvcStatus.ready && - _svcIsUsingPublicServer.value), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(', ', style: TextStyle(fontSize: em)), - Flexible( - child: InkWell( - onTap: onUsePublicServerGuide, - child: Row( - children: [ - Flexible( - child: Text( - translate('setup_server_tip'), - style: TextStyle( - decoration: TextDecoration.underline, - fontSize: em), - ), - ), - ], - ), - ), - ) - ], - ), - ), - ); - - basicWidget() => Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - height: 8, - width: 8, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: _svcStopped.value || - stateGlobal.svcStatus.value == SvcStatus.connecting - ? kColorWarn - : (stateGlobal.svcStatus.value == SvcStatus.ready - ? Color.fromARGB(255, 50, 190, 166) - : Color.fromARGB(255, 224, 79, 95)), - ), - ).marginSymmetric(horizontal: em), - Container( - width: isIncomingOnly ? 226 : null, - child: _buildConnStatusMsg(), - ), - // stop - if (!isIncomingOnly) startServiceWidget(), - // ready && public - // No need to show the guide if is custom client. - if (!isIncomingOnly) setupServerWidget(), - ], - ); - - return Container( - height: height, - child: Obx(() => isIncomingOnly - ? Column( - children: [ - basicWidget(), - Align( - child: startServiceWidget(), - alignment: Alignment.centerLeft) - .marginOnly(top: 2.0, left: 22.0), - ], - ) - : basicWidget()), - ).paddingOnly(right: isIncomingOnly ? 8 : 0); - } - - _buildConnStatusMsg() { - widget.onSvcStatusChanged?.call(); - return Text( - _svcStopped.value - ? translate("Service is not running") - : stateGlobal.svcStatus.value == SvcStatus.connecting - ? translate("connecting_status") - : stateGlobal.svcStatus.value == SvcStatus.notReady - ? translate("not_ready_status") - : translate('Ready'), - style: TextStyle(fontSize: em), - ); - } - - updateStatus() async { - final status = - jsonDecode(await bind.mainGetConnectStatus()) as Map; - final statusNum = status['status_num'] as int; - if (statusNum == 0) { - stateGlobal.svcStatus.value = SvcStatus.connecting; - } else if (statusNum == -1) { - stateGlobal.svcStatus.value = SvcStatus.notReady; - } else if (statusNum == 1) { - stateGlobal.svcStatus.value = SvcStatus.ready; - } else { - stateGlobal.svcStatus.value = SvcStatus.notReady; - } - _svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer(); - try { - stateGlobal.videoConnCount.value = status['video_conn_count'] as int; - } catch (_) {} - } -} - -/// Connection page for connecting to a remote peer. -class ConnectionPage extends StatefulWidget { - const ConnectionPage({Key? key}) : super(key: key); - - @override - State createState() => _ConnectionPageState(); -} - -/// State for the connection page. -class _ConnectionPageState extends State - with SingleTickerProviderStateMixin, WindowListener { - /// Controller for the id input bar. - final _idController = IDTextEditingController(); - - final RxBool _idInputFocused = false.obs; - final FocusNode _idFocusNode = FocusNode(); - final TextEditingController _idEditingController = TextEditingController(); - - String selectedConnectionType = 'Connect'; - - bool isWindowMinimized = false; - - final AllPeersLoader _allPeersLoader = AllPeersLoader(); - - // https://github.com/flutter/flutter/issues/157244 - Iterable _autocompleteOpts = []; - - final _menuOpen = false.obs; - - @override - void initState() { - super.initState(); - _allPeersLoader.init(setState); - _idFocusNode.addListener(onFocusChanged); - if (_idController.text.isEmpty) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - final lastRemoteId = await bind.mainGetLastRemoteId(); - if (lastRemoteId != _idController.id) { - setState(() { - _idController.id = lastRemoteId; - }); - } - }); - } - Get.put(_idEditingController); - Get.put(_idController); - windowManager.addListener(this); - } - - @override - void dispose() { - _idController.dispose(); - windowManager.removeListener(this); - _allPeersLoader.clear(); - _idFocusNode.removeListener(onFocusChanged); - _idFocusNode.dispose(); - _idEditingController.dispose(); - if (Get.isRegistered()) { - Get.delete(); - } - if (Get.isRegistered()) { - Get.delete(); - } - super.dispose(); - } - - @override - void onWindowEvent(String eventName) { - super.onWindowEvent(eventName); - if (eventName == 'minimize') { - isWindowMinimized = true; - } else if (eventName == 'maximize' || eventName == 'restore') { - if (isWindowMinimized && isWindows) { - // windows can't update when minimized. - Get.forceAppUpdate(); - } - isWindowMinimized = false; - } - } - - @override - void onWindowEnterFullScreen() { - // Remove edge border by setting the value to zero. - stateGlobal.resizeEdgeSize.value = 0; - } - - @override - void onWindowLeaveFullScreen() { - // Restore edge border to default edge size. - stateGlobal.resizeEdgeSize.value = stateGlobal.isMaximized.isTrue - ? kMaximizeEdgeSize - : windowResizeEdgeSize; - } - - @override - void onWindowClose() { - super.onWindowClose(); - bind.mainOnMainWindowClose(); - } - - void onFocusChanged() { - _idInputFocused.value = _idFocusNode.hasFocus; - if (_idFocusNode.hasFocus) { - if (_allPeersLoader.needLoad) { - _allPeersLoader.getAllPeers(); - } - - final textLength = _idEditingController.value.text.length; - // Select all to facilitate removing text, just following the behavior of address input of chrome. - _idEditingController.selection = - TextSelection(baseOffset: 0, extentOffset: textLength); - } - } - - @override - Widget build(BuildContext context) { - final isOutgoingOnly = bind.isOutgoingOnly(); - return Column( - children: [ - Expanded( - child: Column( - children: [ - Row( - children: [ - Flexible(child: _buildRemoteIDTextField(context)), - ], - ).marginOnly(top: 22), - SizedBox(height: 12), - Divider().paddingOnly(right: 12), - Expanded(child: PeerTabPage()), - ], - ).paddingOnly(left: 12.0)), - if (!isOutgoingOnly) const Divider(height: 1), - if (!isOutgoingOnly) OnlineStatusWidget() - ], - ); - } - - /// Callback for the connect button. - /// Connects to the selected peer. - void onConnect( - {bool isFileTransfer = false, - bool isViewCamera = false, - bool isTerminal = false}) { - var id = _idController.id; - connect(context, id, - isFileTransfer: isFileTransfer, - isViewCamera: isViewCamera, - isTerminal: isTerminal); - } - - /// UI for the remote ID TextField. - /// Search for a peer. - Widget _buildRemoteIDTextField(BuildContext context) { - var w = Container( - width: 320 + 20 * 2, - padding: const EdgeInsets.fromLTRB(20, 24, 20, 22), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(13)), - border: Border.all(color: Theme.of(context).colorScheme.background)), - child: Ink( - child: Column( - children: [ - getConnectionPageTitle(context, false).marginOnly(bottom: 15), - Row( - children: [ - Expanded( - child: RawAutocomplete( - optionsBuilder: (TextEditingValue textEditingValue) { - if (textEditingValue.text == '') { - _autocompleteOpts = const Iterable.empty(); - } else if (_allPeersLoader.peers.isEmpty && - !_allPeersLoader.isPeersLoaded) { - Peer emptyPeer = Peer( - id: '', - username: '', - hostname: '', - alias: '', - platform: '', - tags: [], - hash: '', - password: '', - forceAlwaysRelay: false, - rdpPort: '', - rdpUsername: '', - loginName: '', - device_group_name: '', - note: '', - ); - _autocompleteOpts = [emptyPeer]; - } else { - String textWithoutSpaces = - textEditingValue.text.replaceAll(" ", ""); - if (int.tryParse(textWithoutSpaces) != null) { - textEditingValue = TextEditingValue( - text: textWithoutSpaces, - selection: textEditingValue.selection, - ); - } - String textToFind = textEditingValue.text.toLowerCase(); - _autocompleteOpts = _allPeersLoader.peers - .where((peer) => - peer.id.toLowerCase().contains(textToFind) || - peer.username - .toLowerCase() - .contains(textToFind) || - peer.hostname - .toLowerCase() - .contains(textToFind) || - peer.alias.toLowerCase().contains(textToFind)) - .toList(); - } - return _autocompleteOpts; - }, - focusNode: _idFocusNode, - textEditingController: _idEditingController, - fieldViewBuilder: ( - BuildContext context, - TextEditingController fieldTextEditingController, - FocusNode fieldFocusNode, - VoidCallback onFieldSubmitted, - ) { - updateTextAndPreserveSelection( - fieldTextEditingController, _idController.text); - return Obx(() => TextField( - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - focusNode: fieldFocusNode, - style: const TextStyle( - fontFamily: 'WorkSans', - fontSize: 22, - height: 1.4, - ), - maxLines: 1, - cursorColor: - Theme.of(context).textTheme.titleLarge?.color, - decoration: InputDecoration( - filled: false, - counterText: '', - hintText: _idInputFocused.value - ? null - : translate('Enter Remote ID'), - contentPadding: const EdgeInsets.symmetric( - horizontal: 15, vertical: 13)), - controller: fieldTextEditingController, - inputFormatters: [IDTextInputFormatter()], - onChanged: (v) { - _idController.id = v; - }, - onSubmitted: (_) { - onConnect(); - }, - ).workaroundFreezeLinuxMint()); - }, - onSelected: (option) { - setState(() { - _idController.id = option.id; - FocusScope.of(context).unfocus(); - }); - }, - optionsViewBuilder: (BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options) { - options = _autocompleteOpts; - double maxHeight = options.length * 50; - if (options.length == 1) { - maxHeight = 52; - } else if (options.length == 3) { - maxHeight = 146; - } else if (options.length == 4) { - maxHeight = 193; - } - maxHeight = maxHeight.clamp(0, 200); - - return Align( - alignment: Alignment.topLeft, - child: Container( - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 5, - spreadRadius: 1, - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Material( - elevation: 4, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: maxHeight, - maxWidth: 319, - ), - child: _allPeersLoader.peers.isEmpty && - !_allPeersLoader.isPeersLoaded - ? Container( - height: 80, - child: Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ), - )) - : Padding( - padding: - const EdgeInsets.only(top: 5), - child: ListView( - children: options - .map((peer) => - AutocompletePeerTile( - onSelect: () => - onSelected(peer), - peer: peer)) - .toList(), - ), - ), - ), - ))), - ); - }, - )), - ], - ), - Padding( - padding: const EdgeInsets.only(top: 13.0), - child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - SizedBox( - height: 28.0, - child: ElevatedButton( - onPressed: () { - onConnect(); - }, - child: Text(translate("Connect")), - ), - ), - const SizedBox(width: 8), - Container( - height: 28.0, - width: 28.0, - decoration: BoxDecoration( - border: Border.all(color: Theme.of(context).dividerColor), - borderRadius: BorderRadius.circular(8), - ), - child: Center( - child: StatefulBuilder( - builder: (context, setState) { - var offset = Offset(0, 0); - return Obx(() => InkWell( - child: _menuOpen.value - ? Transform.rotate( - angle: pi, - child: Icon(IconFont.more, size: 14), - ) - : Icon(IconFont.more, size: 14), - onTapDown: (e) { - offset = e.globalPosition; - }, - onTap: () async { - _menuOpen.value = true; - final x = offset.dx; - final y = offset.dy; - await mod_menu - .showMenu( - context: context, - position: RelativeRect.fromLTRB(x, y, x, y), - items: [ - ( - 'Transfer file', - () => onConnect(isFileTransfer: true) - ), - ( - 'View camera', - () => onConnect(isViewCamera: true) - ), - ( - '${translate('Terminal')} (beta)', - () => onConnect(isTerminal: true) - ), - ] - .map((e) => MenuEntryButton( - childBuilder: (TextStyle? style) => - Text( - translate(e.$1), - style: style, - ), - proc: () => e.$2(), - padding: EdgeInsets.symmetric( - horizontal: - kDesktopMenuPadding.left), - dismissOnClicked: true, - )) - .map((e) => e.build( - context, - const MenuConfig( - commonColor: CustomPopupMenuTheme - .commonColor, - height: - CustomPopupMenuTheme.height, - dividerHeight: - CustomPopupMenuTheme - .dividerHeight))) - .expand((i) => i) - .toList(), - elevation: 8, - ) - .then((_) { - _menuOpen.value = false; - }); - }, - )); - }, - ), - ), - ), - ]), - ), - ], - ), - ), - ); - return Container( - constraints: const BoxConstraints(maxWidth: 600), child: w); - } -} diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart deleted file mode 100644 index 42ec10032..000000000 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ /dev/null @@ -1,1146 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:convert'; - -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/widgets/animated_rotation_widget.dart'; -import 'package:flutter_hbb/common/widgets/custom_password.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/pages/connection_page.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; -import 'package:flutter_hbb/desktop/widgets/update_progress.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/plugin/ui_manager.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_hbb/utils/platform_channel.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:window_size/window_size.dart' as window_size; -import '../widgets/button.dart'; - -class DesktopHomePage extends StatefulWidget { - const DesktopHomePage({Key? key}) : super(key: key); - - @override - State createState() => _DesktopHomePageState(); -} - -const borderColor = Color(0xFF2F65BA); - -class _DesktopHomePageState extends State - with AutomaticKeepAliveClientMixin, WidgetsBindingObserver { - final _leftPaneScrollController = ScrollController(); - - @override - bool get wantKeepAlive => true; - var systemError = ''; - StreamSubscription? _uniLinksSubscription; - var svcStopped = false.obs; - var watchIsCanScreenRecording = false; - var watchIsProcessTrust = false; - var watchIsInputMonitoring = false; - var watchIsCanRecordAudio = false; - Timer? _updateTimer; - bool isCardClosed = false; - - final RxBool _editHover = false.obs; - final RxBool _block = false.obs; - - final GlobalKey _childKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - super.build(context); - final isIncomingOnly = bind.isIncomingOnly(); - return _buildBlock( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildLeftPane(context), - if (!isIncomingOnly) const VerticalDivider(width: 1), - if (!isIncomingOnly) Expanded(child: buildRightPane(context)), - ], - )); - } - - Widget _buildBlock({required Widget child}) { - return buildRemoteBlock( - block: _block, mask: true, use: canBeBlocked, child: child); - } - - Widget buildLeftPane(BuildContext context) { - final isIncomingOnly = bind.isIncomingOnly(); - final isOutgoingOnly = bind.isOutgoingOnly(); - final children = [ - if (!isOutgoingOnly) buildPresetPasswordWarning(), - if (bind.isCustomClient()) - Align( - alignment: Alignment.center, - child: loadPowered(context), - ), - Align( - alignment: Alignment.center, - child: loadLogo(), - ), - buildTip(context), - if (!isOutgoingOnly) buildIDBoard(context), - if (!isOutgoingOnly) buildPasswordBoard(context), - FutureBuilder( - future: Future.value( - Obx(() => buildHelpCards(stateGlobal.updateUrl.value))), - builder: (_, data) { - if (data.hasData) { - if (isIncomingOnly) { - if (isInHomePage()) { - Future.delayed(Duration(milliseconds: 300), () { - _updateWindowSize(); - }); - } - } - return data.data!; - } else { - return const Offstage(); - } - }, - ), - buildPluginEntry(), - ]; - if (isIncomingOnly) { - children.addAll([ - Divider(), - OnlineStatusWidget( - onSvcStatusChanged: () { - if (isInHomePage()) { - Future.delayed(Duration(milliseconds: 300), () { - _updateWindowSize(); - }); - } - }, - ).marginOnly(bottom: 6, right: 6) - ]); - } - final textColor = Theme.of(context).textTheme.titleLarge?.color; - return ChangeNotifierProvider.value( - value: gFFI.serverModel, - child: Container( - width: isIncomingOnly ? 280.0 : 200.0, - color: Theme.of(context).colorScheme.background, - child: Stack( - children: [ - Column( - children: [ - SingleChildScrollView( - controller: _leftPaneScrollController, - child: Column( - key: _childKey, - children: children, - ), - ), - Expanded(child: Container()) - ], - ), - if (isOutgoingOnly) - Positioned( - bottom: 6, - left: 12, - child: Align( - alignment: Alignment.centerLeft, - child: InkWell( - child: Obx( - () => Icon( - Icons.settings, - color: _editHover.value - ? textColor - : Colors.grey.withOpacity(0.5), - size: 22, - ), - ), - onTap: () => { - if (DesktopSettingPage.tabKeys.isNotEmpty) - { - DesktopSettingPage.switch2page( - DesktopSettingPage.tabKeys[0]) - } - }, - onHover: (value) => _editHover.value = value, - ), - ), - ) - ], - ), - ), - ); - } - - buildRightPane(BuildContext context) { - return Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: ConnectionPage(), - ); - } - - buildIDBoard(BuildContext context) { - final model = gFFI.serverModel; - return Container( - margin: const EdgeInsets.only(left: 20, right: 11), - height: 57, - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Container( - width: 2, - decoration: const BoxDecoration(color: MyTheme.accent), - ).marginOnly(top: 5), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 7), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 25, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - translate("ID"), - style: TextStyle( - fontSize: 14, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5)), - ).marginOnly(top: 5), - buildPopupMenu(context) - ], - ), - ), - Flexible( - child: GestureDetector( - onDoubleTap: () { - Clipboard.setData( - ClipboardData(text: model.serverId.text)); - showToast(translate("Copied")); - }, - child: TextFormField( - controller: model.serverId, - readOnly: true, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.only(top: 10, bottom: 10), - ), - style: TextStyle( - fontSize: 22, - ), - ).workaroundFreezeLinuxMint(), - ), - ) - ], - ), - ), - ), - ], - ), - ); - } - - Widget buildPopupMenu(BuildContext context) { - final textColor = Theme.of(context).textTheme.titleLarge?.color; - RxBool hover = false.obs; - return InkWell( - onTap: DesktopTabPage.onAddSetting, - child: Tooltip( - message: translate('Settings'), - child: Obx( - () => CircleAvatar( - radius: 15, - backgroundColor: hover.value - ? Theme.of(context).scaffoldBackgroundColor - : Theme.of(context).colorScheme.background, - child: Icon( - Icons.more_vert_outlined, - size: 20, - color: hover.value ? textColor : textColor?.withOpacity(0.5), - ), - ), - ), - ), - onHover: (value) => hover.value = value, - ); - } - - buildPasswordBoard(BuildContext context) { - return ChangeNotifierProvider.value( - value: gFFI.serverModel, - child: Consumer( - builder: (context, model, child) { - return buildPasswordBoard2(context, model); - }, - )); - } - - buildPasswordBoard2(BuildContext context, ServerModel model) { - RxBool refreshHover = false.obs; - RxBool editHover = false.obs; - final textColor = Theme.of(context).textTheme.titleLarge?.color; - final showOneTime = model.approveMode != 'click' && - model.verificationMethod != kUsePermanentPassword; - return Container( - margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13), - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Container( - width: 2, - height: 52, - decoration: BoxDecoration(color: MyTheme.accent), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 7), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AutoSizeText( - translate("One-time Password"), - style: TextStyle( - fontSize: 14, color: textColor?.withOpacity(0.5)), - maxLines: 1, - ), - Row( - children: [ - Expanded( - child: GestureDetector( - onDoubleTap: () { - if (showOneTime) { - Clipboard.setData( - ClipboardData(text: model.serverPasswd.text)); - showToast(translate("Copied")); - } - }, - child: TextFormField( - controller: model.serverPasswd, - readOnly: true, - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.only(top: 14, bottom: 10), - ), - style: TextStyle(fontSize: 15), - ).workaroundFreezeLinuxMint(), - ), - ), - if (showOneTime) - AnimatedRotationWidget( - onPressed: () => bind.mainUpdateTemporaryPassword(), - child: Tooltip( - message: translate('Refresh Password'), - child: Obx(() => RotatedBox( - quarterTurns: 2, - child: Icon( - Icons.refresh, - color: refreshHover.value - ? textColor - : Color(0xFFDDDDDD), - size: 22, - ))), - ), - onHover: (value) => refreshHover.value = value, - ).marginOnly(right: 8, top: 4), - if (!bind.isDisableSettings()) - InkWell( - child: Tooltip( - message: translate('Change Password'), - child: Obx( - () => Icon( - Icons.edit, - color: editHover.value - ? textColor - : Color(0xFFDDDDDD), - size: 22, - ).marginOnly(right: 8, top: 4), - ), - ), - onTap: () => DesktopSettingPage.switch2page( - SettingsTabKey.safety), - onHover: (value) => editHover.value = value, - ), - ], - ), - ], - ), - ), - ), - ], - ), - ); - } - - buildTip(BuildContext context) { - final isOutgoingOnly = bind.isOutgoingOnly(); - return Padding( - padding: - const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 5), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - if (!isOutgoingOnly) - Align( - alignment: Alignment.centerLeft, - child: Text( - translate("Your Desktop"), - style: Theme.of(context).textTheme.titleLarge, - ), - ), - ], - ), - SizedBox( - height: 10.0, - ), - if (!isOutgoingOnly) - Text( - translate("desk_tip"), - overflow: TextOverflow.clip, - style: Theme.of(context).textTheme.bodySmall, - ), - if (isOutgoingOnly) - Text( - translate("outgoing_only_desk_tip"), - overflow: TextOverflow.clip, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ); - } - - Widget buildHelpCards(String updateUrl) { - if (!bind.isCustomClient() && - updateUrl.isNotEmpty && - !isCardClosed && - bind.mainUriPrefixSync().contains('rustdesk')) { - final isToUpdate = (isWindows || isMacOS) && bind.mainIsInstalled(); - String btnText = isToUpdate ? 'Update' : 'Download'; - GestureTapCallback onPressed = () async { - final Uri url = Uri.parse('https://rustdesk.com/download'); - await launchUrl(url); - }; - if (isToUpdate) { - onPressed = () { - handleUpdate(updateUrl); - }; - } - return buildInstallCard( - "Status", - "${translate("new-version-of-{${bind.mainGetAppNameSync()}}-tip")} (${bind.mainGetNewVersion()}).", - btnText, - onPressed, - closeButton: true, - help: isToUpdate ? 'Changelog' : null, - link: isToUpdate - ? 'https://github.com/rustdesk/rustdesk/releases/tag/${bind.mainGetNewVersion()}' - : null); - } - if (systemError.isNotEmpty) { - return buildInstallCard("", systemError, "", () {}); - } - - if (isWindows && !bind.isDisableInstallation()) { - if (!bind.mainIsInstalled()) { - return buildInstallCard( - "", bind.isOutgoingOnly() ? "" : "install_tip", "Install", - () async { - await rustDeskWinManager.closeAllSubWindows(); - bind.mainGotoInstall(); - }); - } else if (bind.mainIsInstalledLowerVersion()) { - return buildInstallCard( - "Status", "Your installation is lower version.", "Click to upgrade", - () async { - await rustDeskWinManager.closeAllSubWindows(); - bind.mainUpdateMe(); - }); - } - } else if (isMacOS) { - final isOutgoingOnly = bind.isOutgoingOnly(); - if (!(isOutgoingOnly || bind.mainIsCanScreenRecording(prompt: false))) { - return buildInstallCard("Permissions", "config_screen", "Configure", - () async { - bind.mainIsCanScreenRecording(prompt: true); - watchIsCanScreenRecording = true; - }, help: 'Help', link: translate("doc_mac_permission")); - } else if (!isOutgoingOnly && !bind.mainIsProcessTrusted(prompt: false)) { - return buildInstallCard("Permissions", "config_acc", "Configure", - () async { - bind.mainIsProcessTrusted(prompt: true); - watchIsProcessTrust = true; - }, help: 'Help', link: translate("doc_mac_permission")); - } else if (!bind.mainIsCanInputMonitoring(prompt: false)) { - return buildInstallCard("Permissions", "config_input", "Configure", - () async { - bind.mainIsCanInputMonitoring(prompt: true); - watchIsInputMonitoring = true; - }, help: 'Help', link: translate("doc_mac_permission")); - } else if (!isOutgoingOnly && - !svcStopped.value && - bind.mainIsInstalled() && - !bind.mainIsInstalledDaemon(prompt: false)) { - return buildInstallCard("", "install_daemon_tip", "Install", () async { - bind.mainIsInstalledDaemon(prompt: true); - }); - } - //// Disable microphone configuration for macOS. We will request the permission when needed. - // else if ((await osxCanRecordAudio() != - // PermissionAuthorizeType.authorized)) { - // return buildInstallCard("Permissions", "config_microphone", "Configure", - // () async { - // osxRequestAudio(); - // watchIsCanRecordAudio = true; - // }); - // } - } else if (isLinux) { - if (bind.isOutgoingOnly()) { - return Container(); - } - final LinuxCards = []; - if (bind.isSelinuxEnforcing()) { - // Check is SELinux enforcing, but show user a tip of is SELinux enabled for simple. - final keyShowSelinuxHelpTip = "show-selinux-help-tip"; - if (bind.mainGetLocalOption(key: keyShowSelinuxHelpTip) != 'N') { - LinuxCards.add(buildInstallCard( - "Warning", - "selinux_tip", - "", - () async {}, - marginTop: LinuxCards.isEmpty ? 20.0 : 5.0, - help: 'Help', - link: - 'https://rustdesk.com/docs/en/client/linux/#permissions-issue', - closeButton: true, - closeOption: keyShowSelinuxHelpTip, - )); - } - } - if (bind.mainCurrentIsWayland()) { - LinuxCards.add(buildInstallCard( - "Warning", "wayland_experiment_tip", "", () async {}, - marginTop: LinuxCards.isEmpty ? 20.0 : 5.0, - help: 'Help', - link: 'https://rustdesk.com/docs/en/client/linux/#x11-required')); - } else if (bind.mainIsLoginWayland()) { - LinuxCards.add(buildInstallCard("Warning", - "Login screen using Wayland is not supported", "", () async {}, - marginTop: LinuxCards.isEmpty ? 20.0 : 5.0, - help: 'Help', - link: 'https://rustdesk.com/docs/en/client/linux/#login-screen')); - } - if (LinuxCards.isNotEmpty) { - return Column( - children: LinuxCards, - ); - } - } - if (bind.isIncomingOnly()) { - return Align( - alignment: Alignment.centerRight, - child: OutlinedButton( - onPressed: () { - SystemNavigator.pop(); // Close the application - // https://github.com/flutter/flutter/issues/66631 - if (isWindows) { - exit(0); - } - }, - child: Text(translate('Quit')), - ), - ).marginAll(14); - } - return Container(); - } - - Widget buildInstallCard(String title, String content, String btnText, - GestureTapCallback onPressed, - {double marginTop = 20.0, - String? help, - String? link, - bool? closeButton, - String? closeOption}) { - if (bind.mainGetBuildinOption(key: kOptionHideHelpCards) == 'Y' && - content != 'install_daemon_tip') { - return const SizedBox(); - } - void closeCard() async { - if (closeOption != null) { - await bind.mainSetLocalOption(key: closeOption, value: 'N'); - if (bind.mainGetLocalOption(key: closeOption) == 'N') { - setState(() { - isCardClosed = true; - }); - } - } else { - setState(() { - isCardClosed = true; - }); - } - } - - return Stack( - children: [ - Container( - margin: EdgeInsets.fromLTRB( - 0, marginTop, 0, bind.isIncomingOnly() ? marginTop : 0), - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - Color.fromARGB(255, 226, 66, 188), - Color.fromARGB(255, 244, 114, 124), - ], - )), - padding: EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: (title.isNotEmpty - ? [ - Center( - child: Text( - translate(title), - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 15), - ).marginOnly(bottom: 6)), - ] - : []) + - [ - if (content.isNotEmpty) - Text( - translate(content), - style: TextStyle( - height: 1.5, - color: Colors.white, - fontWeight: FontWeight.normal, - fontSize: 13), - ).marginOnly(bottom: 20) - ] + - (btnText.isNotEmpty - ? [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FixedWidthButton( - width: 150, - padding: 8, - isOutline: true, - text: translate(btnText), - textColor: Colors.white, - borderColor: Colors.white, - textSize: 20, - radius: 10, - onTap: onPressed, - ) - ]) - ] - : []) + - (help != null - ? [ - Center( - child: InkWell( - onTap: () async => - await launchUrl(Uri.parse(link!)), - child: Text( - translate(help), - style: TextStyle( - decoration: - TextDecoration.underline, - color: Colors.white, - fontSize: 12), - )).marginOnly(top: 6)), - ] - : []))), - ), - if (closeButton != null && closeButton == true) - Positioned( - top: 18, - right: 0, - child: IconButton( - icon: Icon( - Icons.close, - color: Colors.white, - size: 20, - ), - onPressed: closeCard, - ), - ), - ], - ); - } - - @override - void initState() { - super.initState(); - _updateTimer = periodic_immediate(const Duration(seconds: 1), () async { - await gFFI.serverModel.fetchID(); - final error = await bind.mainGetError(); - if (systemError != error) { - systemError = error; - setState(() {}); - } - final v = await mainGetBoolOption(kOptionStopService); - if (v != svcStopped.value) { - svcStopped.value = v; - setState(() {}); - } - if (watchIsCanScreenRecording) { - if (bind.mainIsCanScreenRecording(prompt: false)) { - watchIsCanScreenRecording = false; - setState(() {}); - } - } - if (watchIsProcessTrust) { - if (bind.mainIsProcessTrusted(prompt: false)) { - watchIsProcessTrust = false; - setState(() {}); - } - } - if (watchIsInputMonitoring) { - if (bind.mainIsCanInputMonitoring(prompt: false)) { - watchIsInputMonitoring = false; - // Do not notify for now. - // Monitoring may not take effect until the process is restarted. - // rustDeskWinManager.call( - // WindowType.RemoteDesktop, kWindowDisableGrabKeyboard, ''); - setState(() {}); - } - } - if (watchIsCanRecordAudio) { - if (isMacOS) { - Future.microtask(() async { - if ((await osxCanRecordAudio() == - PermissionAuthorizeType.authorized)) { - watchIsCanRecordAudio = false; - setState(() {}); - } - }); - } else { - watchIsCanRecordAudio = false; - setState(() {}); - } - } - }); - Get.put(svcStopped, tag: 'stop-service'); - rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged); - - screenToMap(window_size.Screen screen) => { - 'frame': { - 'l': screen.frame.left, - 't': screen.frame.top, - 'r': screen.frame.right, - 'b': screen.frame.bottom, - }, - 'visibleFrame': { - 'l': screen.visibleFrame.left, - 't': screen.visibleFrame.top, - 'r': screen.visibleFrame.right, - 'b': screen.visibleFrame.bottom, - }, - 'scaleFactor': screen.scaleFactor, - }; - - bool isChattyMethod(String methodName) { - switch (methodName) { - case kWindowBumpMouse: return true; - } - - return false; - } - - rustDeskWinManager.setMethodHandler((call, fromWindowId) async { - if (!isChattyMethod(call.method)) { - debugPrint( - "[Main] call ${call.method} with args ${call.arguments} from window $fromWindowId"); - } - if (call.method == kWindowMainWindowOnTop) { - windowOnTop(null); - } else if (call.method == kWindowRefreshCurrentUser) { - gFFI.userModel.refreshCurrentUser(); - } else if (call.method == kWindowGetWindowInfo) { - final screen = (await window_size.getWindowInfo()).screen; - if (screen == null) { - return ''; - } else { - return jsonEncode(screenToMap(screen)); - } - } else if (call.method == kWindowGetScreenList) { - return jsonEncode( - (await window_size.getScreenList()).map(screenToMap).toList()); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } else if (call.method == kWindowEventShow) { - await rustDeskWinManager.registerActiveWindow(call.arguments["id"]); - } else if (call.method == kWindowEventHide) { - await rustDeskWinManager.unregisterActiveWindow(call.arguments['id']); - } else if (call.method == kWindowConnect) { - await connectMainDesktop( - call.arguments['id'], - isFileTransfer: call.arguments['isFileTransfer'], - isViewCamera: call.arguments['isViewCamera'], - isTerminal: call.arguments['isTerminal'], - isTcpTunneling: call.arguments['isTcpTunneling'], - isRDP: call.arguments['isRDP'], - password: call.arguments['password'], - forceRelay: call.arguments['forceRelay'], - connToken: call.arguments['connToken'], - ); - } else if (call.method == kWindowBumpMouse) { - return RdPlatformChannel.instance.bumpMouse( - dx: call.arguments['dx'], - dy: call.arguments['dy']); - } else if (call.method == kWindowEventMoveTabToNewWindow) { - final args = call.arguments.split(','); - int? windowId; - try { - windowId = int.parse(args[0]); - } catch (e) { - debugPrint("Failed to parse window id '${call.arguments}': $e"); - } - WindowType? windowType; - try { - windowType = WindowType.values.byName(args[3]); - } catch (e) { - debugPrint("Failed to parse window type '${call.arguments}': $e"); - } - if (windowId != null && windowType != null) { - await rustDeskWinManager.moveTabToNewWindow( - windowId, args[1], args[2], windowType); - } - } else if (call.method == kWindowEventOpenMonitorSession) { - final args = jsonDecode(call.arguments); - final windowId = args['window_id'] as int; - final peerId = args['peer_id'] as String; - final display = args['display'] as int; - final displayCount = args['display_count'] as int; - final windowType = args['window_type'] as int; - final screenRect = parseParamScreenRect(args); - await rustDeskWinManager.openMonitorSession( - windowId, peerId, display, displayCount, screenRect, windowType); - } else if (call.method == kWindowEventRemoteWindowCoords) { - final windowId = int.tryParse(call.arguments); - if (windowId != null) { - return jsonEncode( - await rustDeskWinManager.getOtherRemoteWindowCoords(windowId)); - } - } - }); - _uniLinksSubscription = listenUniLinks(); - - if (bind.isIncomingOnly()) { - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateWindowSize(); - }); - } - WidgetsBinding.instance.addObserver(this); - } - - _updateWindowSize() { - RenderObject? renderObject = _childKey.currentContext?.findRenderObject(); - if (renderObject == null) { - return; - } - if (renderObject is RenderBox) { - final size = renderObject.size; - if (size != imcomingOnlyHomeSize) { - imcomingOnlyHomeSize = size; - windowManager.setSize(getIncomingOnlyHomeSize()); - } - } - } - - @override - void dispose() { - _uniLinksSubscription?.cancel(); - Get.delete(tag: 'stop-service'); - _updateTimer?.cancel(); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - shouldBeBlocked(_block, canBeBlocked); - } - } - - Widget buildPluginEntry() { - final entries = PluginUiManager.instance.entries.entries; - return Offstage( - offstage: entries.isEmpty, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...entries.map((entry) { - return entry.value; - }) - ], - ), - ); - } -} - -void setPasswordDialog({VoidCallback? notEmptyCallback}) async { - final p0 = TextEditingController(text: ""); - final p1 = TextEditingController(text: ""); - var errMsg0 = ""; - var errMsg1 = ""; - final localPasswordSet = - (await bind.mainGetCommon(key: "local-permanent-password-set")) == "true"; - final permanentPasswordSet = - (await bind.mainGetCommon(key: "permanent-password-set")) == "true"; - final presetPassword = permanentPasswordSet && !localPasswordSet; - var canSubmit = false; - final RxString rxPass = "".obs; - final rules = [ - DigitValidationRule(), - UppercaseValidationRule(), - LowercaseValidationRule(), - // SpecialCharacterValidationRule(), - MinCharactersValidationRule(8), - ]; - final maxLength = bind.mainMaxEncryptLen(); - final statusTip = localPasswordSet - ? translate('password-hidden-tip') - : (presetPassword ? translate('preset-password-in-use-tip') : ''); - final showStatusTipOnMobile = - statusTip.isNotEmpty && !isDesktop && !isWebDesktop; - - gFFI.dialogManager.show((setState, close, context) { - updateCanSubmit() { - canSubmit = p0.text.trim().isNotEmpty || p1.text.trim().isNotEmpty; - } - - submit() async { - if (!canSubmit) { - return; - } - setState(() { - errMsg0 = ""; - errMsg1 = ""; - }); - final pass = p0.text.trim(); - if (pass.isNotEmpty) { - final Iterable violations = rules.where((r) => !r.validate(pass)); - if (violations.isNotEmpty) { - setState(() { - errMsg0 = - '${translate('Prompt')}: ${violations.map((r) => r.name).join(', ')}'; - }); - return; - } - } - if (p1.text.trim() != pass) { - setState(() { - errMsg1 = - '${translate('Prompt')}: ${translate("The confirmation is not identical.")}'; - }); - return; - } - final ok = await bind.mainSetPermanentPasswordWithResult(password: pass); - if (!ok) { - setState(() { - errMsg0 = '${translate('Prompt')}: ${translate("Failed")}'; - }); - return; - } - if (pass.isNotEmpty) { - notEmptyCallback?.call(); - } - close(); - } - - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.key, color: MyTheme.accent), - Text(translate("Set Password")).paddingOnly(left: 10), - ], - ), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: showStatusTipOnMobile ? 0.0 : 6.0, - ), - Row( - children: [ - Expanded( - child: TextField( - obscureText: true, - decoration: InputDecoration( - labelText: translate('Password'), - errorText: errMsg0.isNotEmpty ? errMsg0 : null), - controller: p0, - autofocus: true, - onChanged: (value) { - rxPass.value = value.trim(); - setState(() { - errMsg0 = ''; - updateCanSubmit(); - }); - }, - maxLength: maxLength, - ).workaroundFreezeLinuxMint(), - ), - ], - ), - Row( - children: [ - Expanded(child: PasswordStrengthIndicator(password: rxPass)), - ], - ).marginOnly(top: 2, bottom: showStatusTipOnMobile ? 2 : 8), - SizedBox( - height: showStatusTipOnMobile ? 0.0 : 8.0, - ), - Row( - children: [ - Expanded( - child: TextField( - obscureText: true, - decoration: InputDecoration( - labelText: translate('Confirmation'), - errorText: errMsg1.isNotEmpty ? errMsg1 : null), - controller: p1, - onChanged: (value) { - setState(() { - errMsg1 = ''; - updateCanSubmit(); - }); - }, - maxLength: maxLength, - ).workaroundFreezeLinuxMint(), - ), - ], - ), - if (statusTip.isNotEmpty) - Row( - children: [ - Icon(Icons.info, color: Colors.amber, size: 18) - .marginOnly(right: 6), - Expanded( - child: Text( - statusTip, - style: const TextStyle(fontSize: 13, height: 1.1), - )) - ], - ).marginOnly(top: 6, bottom: 2), - SizedBox( - height: showStatusTipOnMobile ? 0.0 : 8.0, - ), - Obx(() => Wrap( - runSpacing: showStatusTipOnMobile ? 2.0 : 8.0, - spacing: 4, - children: rules.map((e) { - var checked = e.validate(rxPass.value.trim()); - 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(), - )) - ], - ), - ), - actions: (() { - final cancelButton = dialogButton( - "Cancel", - icon: Icon(Icons.close_rounded), - onPressed: close, - isOutline: true, - ); - final removeButton = dialogButton( - "Remove", - icon: Icon(Icons.delete_outline_rounded), - onPressed: () async { - setState(() { - errMsg0 = ""; - errMsg1 = ""; - }); - final ok = - await bind.mainSetPermanentPasswordWithResult(password: ""); - if (!ok) { - setState(() { - errMsg0 = '${translate('Prompt')}: ${translate("Failed")}'; - }); - return; - } - close(); - }, - buttonStyle: ButtonStyle( - backgroundColor: MaterialStatePropertyAll(Colors.red)), - ); - final okButton = dialogButton( - "OK", - icon: Icon(Icons.done_rounded), - onPressed: canSubmit ? submit : null, - ); - if (!isDesktop && !isWebDesktop && localPasswordSet) { - return [ - Align( - alignment: Alignment.centerRight, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - cancelButton, - const SizedBox(width: 4), - removeButton, - const SizedBox(width: 4), - okButton, - ], - ), - ), - ), - ]; - } - return [ - cancelButton, - if (localPasswordSet) removeButton, - okButton, - ]; - })(), - onSubmit: canSubmit ? submit : null, - onCancel: close, - ); - }); -} diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart deleted file mode 100644 index d1d620014..000000000 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ /dev/null @@ -1,3138 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/widgets/audio_input.dart'; -import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; -import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; -import 'package:flutter_hbb/mobile/widgets/dialog.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/printer_model.dart'; -import 'package:flutter_hbb/models/server_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/plugin/manager.dart'; -import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -import '../../common/widgets/dialog.dart'; -import '../../common/widgets/login.dart'; - -const double _kTabWidth = 200; -const double _kTabHeight = 42; -const double _kCardFixedWidth = 540; -const double _kCardLeftMargin = 15; -const double _kContentHMargin = 15; -const double _kContentHSubMargin = _kContentHMargin + 33; -const double _kCheckBoxLeftMargin = 10; -const double _kRadioLeftMargin = 10; -const double _kListViewBottomMargin = 15; -const double _kTitleFontSize = 20; -const double _kContentFontSize = 15; -const Color _accentColor = MyTheme.accent; -const String _kSettingPageControllerTag = 'settingPageController'; -const String _kSettingPageTabKeyTag = 'settingPageTabKey'; - -class _TabInfo { - late final SettingsTabKey key; - late final String label; - late final IconData unselected; - late final IconData selected; - _TabInfo(this.key, this.label, this.unselected, this.selected); -} - -enum SettingsTabKey { - general, - safety, - network, - display, - plugin, - account, - printer, - about, -} - -class DesktopSettingPage extends StatefulWidget { - final SettingsTabKey initialTabkey; - static final List tabKeys = [ - SettingsTabKey.general, - if (!isWeb && - !bind.isOutgoingOnly() && - !bind.isDisableSettings() && - bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y') - SettingsTabKey.safety, - if (!bind.isDisableSettings() && - bind.mainGetBuildinOption(key: kOptionHideNetworkSetting) != 'Y') - SettingsTabKey.network, - if (!bind.isIncomingOnly()) SettingsTabKey.display, - if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled()) - SettingsTabKey.plugin, - if (!bind.isDisableAccount()) SettingsTabKey.account, - if (isWindows && - bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y') - SettingsTabKey.printer, - SettingsTabKey.about, - ]; - - DesktopSettingPage({Key? key, required this.initialTabkey}) : super(key: key); - - @override - State createState() => - _DesktopSettingPageState(initialTabkey); - - static void switch2page(SettingsTabKey page) { - try { - int index = tabKeys.indexOf(page); - if (index == -1) { - return; - } - if (Get.isRegistered(tag: _kSettingPageControllerTag)) { - DesktopTabPage.onAddSetting(initialPage: page); - PageController controller = - Get.find(tag: _kSettingPageControllerTag); - Rx selected = - Get.find>(tag: _kSettingPageTabKeyTag); - selected.value = page; - controller.jumpToPage(index); - } else { - DesktopTabPage.onAddSetting(initialPage: page); - } - } catch (e) { - debugPrintStack(label: '$e'); - } - } -} - -class _DesktopSettingPageState extends State - with - TickerProviderStateMixin, - AutomaticKeepAliveClientMixin, - WidgetsBindingObserver { - late PageController controller; - late Rx selectedTab; - - @override - bool get wantKeepAlive => true; - - final RxBool _block = false.obs; - final RxBool _canBeBlocked = false.obs; - Timer? _videoConnTimer; - - _DesktopSettingPageState(SettingsTabKey initialTabkey) { - var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey); - if (initialIndex == -1) { - initialIndex = 0; - } - selectedTab = DesktopSettingPage.tabKeys[initialIndex].obs; - Get.put>(selectedTab, tag: _kSettingPageTabKeyTag); - controller = PageController(initialPage: initialIndex); - Get.put(controller, tag: _kSettingPageControllerTag); - controller.addListener(() { - if (controller.page != null) { - int page = controller.page!.toInt(); - if (page < DesktopSettingPage.tabKeys.length) { - selectedTab.value = DesktopSettingPage.tabKeys[page]; - } - } - }); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - shouldBeBlocked(_block, canBeBlocked); - } - } - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - _videoConnTimer = - periodic_immediate(Duration(milliseconds: 1000), () async { - if (!mounted) { - return; - } - _canBeBlocked.value = await canBeBlocked(); - }); - } - - @override - void dispose() { - super.dispose(); - Get.delete(tag: _kSettingPageControllerTag); - Get.delete(tag: _kSettingPageTabKeyTag); - WidgetsBinding.instance.removeObserver(this); - _videoConnTimer?.cancel(); - } - - List<_TabInfo> _settingTabs() { - final List<_TabInfo> settingTabs = <_TabInfo>[]; - for (final tab in DesktopSettingPage.tabKeys) { - switch (tab) { - case SettingsTabKey.general: - settingTabs.add(_TabInfo( - tab, 'General', Icons.settings_outlined, Icons.settings)); - break; - case SettingsTabKey.safety: - settingTabs.add(_TabInfo(tab, 'Security', - Icons.enhanced_encryption_outlined, Icons.enhanced_encryption)); - break; - case SettingsTabKey.network: - settingTabs - .add(_TabInfo(tab, 'Network', Icons.link_outlined, Icons.link)); - break; - case SettingsTabKey.display: - settingTabs.add(_TabInfo(tab, 'Display', - Icons.desktop_windows_outlined, Icons.desktop_windows)); - break; - case SettingsTabKey.plugin: - settingTabs.add(_TabInfo( - tab, 'Plugin', Icons.extension_outlined, Icons.extension)); - break; - case SettingsTabKey.account: - settingTabs.add( - _TabInfo(tab, 'Account', Icons.person_outline, Icons.person)); - break; - case SettingsTabKey.printer: - settingTabs - .add(_TabInfo(tab, 'Printer', Icons.print_outlined, Icons.print)); - break; - case SettingsTabKey.about: - settingTabs - .add(_TabInfo(tab, 'About', Icons.info_outline, Icons.info)); - break; - } - } - return settingTabs; - } - - List _children() { - final children = List.empty(growable: true); - for (final tab in DesktopSettingPage.tabKeys) { - switch (tab) { - case SettingsTabKey.general: - children.add(const _General()); - break; - case SettingsTabKey.safety: - children.add(const _Safety()); - break; - case SettingsTabKey.network: - children.add(const _Network()); - break; - case SettingsTabKey.display: - children.add(const _Display()); - break; - case SettingsTabKey.plugin: - children.add(const _Plugin()); - break; - case SettingsTabKey.account: - children.add(const _Account()); - break; - case SettingsTabKey.printer: - children.add(const _Printer()); - break; - case SettingsTabKey.about: - children.add(const _About()); - break; - } - } - return children; - } - - Widget _buildBlock({required List children}) { - // check both mouseMoveTime and videoConnCount - return Obx(() { - final videoConnBlock = - _canBeBlocked.value && stateGlobal.videoConnCount > 0; - return Stack(children: [ - buildRemoteBlock( - block: _block, - mask: false, - use: canBeBlocked, - child: preventMouseKeyBuilder( - child: Row(children: children), - block: videoConnBlock, - ), - ), - if (videoConnBlock) - Container( - color: Colors.black.withOpacity(0.5), - ) - ]); - }); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: _buildBlock( - children: [ - SizedBox( - width: _kTabWidth, - child: Column( - children: [ - _header(context), - Flexible(child: _listView(tabs: _settingTabs())), - ], - ), - ), - const VerticalDivider(width: 1), - Expanded( - child: Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: PageView( - controller: controller, - physics: NeverScrollableScrollPhysics(), - children: _children(), - ), - ), - ) - ], - ), - ); - } - - Widget _header(BuildContext context) { - final settingsText = Text( - translate('Settings'), - textAlign: TextAlign.left, - style: const TextStyle( - color: _accentColor, - fontSize: _kTitleFontSize, - fontWeight: FontWeight.w400, - ), - ); - return Row( - children: [ - if (isWeb) - IconButton( - onPressed: () { - if (Navigator.canPop(context)) { - Navigator.pop(context); - } - }, - icon: Icon(Icons.arrow_back), - ).marginOnly(left: 5), - if (isWeb) - SizedBox( - height: 62, - child: Align( - alignment: Alignment.center, - child: settingsText, - ), - ).marginOnly(left: 20), - if (!isWeb) - SizedBox( - height: 62, - child: settingsText, - ).marginOnly(left: 20, top: 10), - const Spacer(), - ], - ); - } - - Widget _listView({required List<_TabInfo> tabs}) { - final scrollController = ScrollController(); - return ListView( - controller: scrollController, - children: tabs.map((tab) => _listItem(tab: tab)).toList(), - ); - } - - Widget _listItem({required _TabInfo tab}) { - return Obx(() { - bool selected = tab.key == selectedTab.value; - return SizedBox( - width: _kTabWidth, - height: _kTabHeight, - child: InkWell( - onTap: () { - if (selectedTab.value != tab.key) { - int index = DesktopSettingPage.tabKeys.indexOf(tab.key); - if (index == -1) { - return; - } - controller.jumpToPage(index); - } - selectedTab.value = tab.key; - }, - child: Row(children: [ - Container( - width: 4, - height: _kTabHeight * 0.7, - color: selected ? _accentColor : null, - ), - Icon( - selected ? tab.selected : tab.unselected, - color: selected ? _accentColor : null, - size: 20, - ).marginOnly(left: 13, right: 10), - Text( - translate(tab.label), - style: TextStyle( - color: selected ? _accentColor : null, - fontWeight: FontWeight.w400, - fontSize: _kContentFontSize), - ), - ]), - ), - ); - }); - } -} - -//#region pages - -class _General extends StatefulWidget { - const _General({Key? key}) : super(key: key); - - @override - State<_General> createState() => _GeneralState(); -} - -class _GeneralState extends State<_General> { - final RxBool serviceStop = - isWeb ? RxBool(false) : Get.find(tag: 'stop-service'); - RxBool serviceBtnEnabled = true.obs; - - @override - Widget build(BuildContext context) { - final scrollController = ScrollController(); - return ListView( - controller: scrollController, - children: [ - if (!isWeb) service(), - theme(), - _Card(title: 'Language', children: [language()]), - if (!isWeb) hwcodec(), - if (!isWeb) audio(context), - if (!isWeb) record(context), - if (!isWeb) WaylandCard(), - other() - ], - ).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget theme() { - final current = MyTheme.getThemeModePreference().toShortString(); - onChanged(String value) async { - await MyTheme.changeDarkMode(MyTheme.themeModeFromString(value)); - setState(() {}); - } - - final isOptFixed = isOptionFixed(kCommConfKeyTheme); - return _Card(title: 'Theme', children: [ - _Radio(context, - value: 'light', - groupValue: current, - label: 'Light', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: 'dark', - groupValue: current, - label: 'Dark', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: 'system', - groupValue: current, - label: 'Follow System', - onChanged: isOptFixed ? null : onChanged), - ]); - } - - Widget service() { - if (bind.isOutgoingOnly()) { - return const Offstage(); - } - - final hideStopService = - bind.mainGetBuildinOption(key: kOptionHideStopService) == 'Y'; - - return Obx(() { - if (hideStopService && !serviceStop.value) { - return const Offstage(); - } - - return _Card(title: 'Service', children: [ - _Button(serviceStop.value ? 'Start' : 'Stop', () { - () async { - serviceBtnEnabled.value = false; - await start_service(serviceStop.value); - // enable the button after 1 second - Future.delayed(const Duration(seconds: 1), () { - serviceBtnEnabled.value = true; - }); - }(); - }, enabled: serviceBtnEnabled.value) - ]); - }); - } - - Widget other() { - final showAutoUpdate = isWindows && bind.mainIsInstalled(); - final children = [ - if (!isWeb && !bind.isIncomingOnly()) - _OptionCheckBox(context, 'Confirm before closing multiple tabs', - kOptionEnableConfirmClosingTabs, - isServer: false), - if (!bind.isIncomingOnly()) - _OptionCheckBox( - context, - 'allow-remote-toolbar-docking-any-edge', - kOptionAllowMultiEdgeToolbarDock, - isServer: false, - update: (_) { - reloadAllWindows(); - }, - ), - _OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr), - if (!isWeb) wallpaper(), - if (!isWeb && !bind.isIncomingOnly()) ...[ - _OptionCheckBox( - context, - 'Open connection in new tab', - kOptionOpenNewConnInTabs, - isServer: false, - ), - // though this is related to GUI, but opengl problem affects all users, so put in config rather than local - if (isLinux) - Tooltip( - message: translate('software_render_tip'), - child: _OptionCheckBox( - context, - "Always use software rendering", - kOptionAllowAlwaysSoftwareRender, - ), - ), - if (!isWeb) - Tooltip( - message: translate('texture_render_tip'), - child: _OptionCheckBox( - context, - "Use texture rendering", - kOptionTextureRender, - optGetter: bind.mainGetUseTextureRender, - optSetter: (k, v) async => - await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), - ), - ), - if (isWindows) - Tooltip( - message: translate('d3d_render_tip'), - child: _OptionCheckBox( - context, - "Use D3D rendering", - kOptionD3DRender, - isServer: false, - ), - ), - if (!isWeb && !bind.isCustomClient()) - _OptionCheckBox( - context, - 'Check for software update on startup', - kOptionEnableCheckUpdate, - isServer: false, - ), - if (showAutoUpdate) - _OptionCheckBox( - context, - 'Auto update', - kOptionAllowAutoUpdate, - isServer: true, - ), - if (isWindows && !bind.isOutgoingOnly()) - _OptionCheckBox( - context, - 'Capture screen using DirectX', - kOptionDirectxCapture, - ), - if (!bind.isIncomingOnly()) ...[ - _OptionCheckBox( - context, - 'Enable UDP hole punching', - kOptionEnableUdpPunch, - isServer: false, - ), - _OptionCheckBox( - context, - 'Enable IPv6 P2P connection', - kOptionEnableIpv6Punch, - isServer: false, - ), - ], - ], - ]; - - // Add client-side wakelock option for desktop platforms - if (!bind.isIncomingOnly()) { - children.add(_OptionCheckBox( - context, - 'keep-awake-during-outgoing-sessions-label', - kOptionKeepAwakeDuringOutgoingSessions, - isServer: false, - )); - } - - if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { - children.add(_OptionCheckBox( - context, 'Allow linux headless', kOptionAllowLinuxHeadless)); - } - if (!bind.isDisableAccount()) { - children.add(_OptionCheckBox( - context, - 'note-at-conn-end-tip', - kOptionAllowAskForNoteAtEndOfConnection, - isServer: false, - optSetter: (key, value) async { - if (value && !gFFI.userModel.isLogin) { - final res = await loginDialog(); - if (res != true) return; - } - await mainSetLocalBoolOption(key, value); - }, - )); - } - return _Card(title: 'Other', children: children); - } - - Widget wallpaper() { - if (bind.isOutgoingOnly()) { - return const Offstage(); - } - - return futureBuilder(future: () async { - final support = await bind.mainSupportRemoveWallpaper(); - return support; - }(), hasData: (data) { - if (data is bool && data == true) { - bool value = mainGetBoolOptionSync(kOptionAllowRemoveWallpaper); - return Row( - children: [ - Flexible( - child: _OptionCheckBox( - context, - 'Remove wallpaper during incoming sessions', - kOptionAllowRemoveWallpaper, - update: (bool v) { - setState(() {}); - }, - ), - ), - if (value) - _CountDownButton( - text: 'Test', - second: 5, - onPressed: () { - bind.mainTestWallpaper(second: 5); - }, - ) - ], - ); - } - - return Offstage(); - }); - } - - Widget hwcodec() { - final hwcodec = bind.mainHasHwcodec(); - final vram = bind.mainHasVram(); - return Offstage( - offstage: !(hwcodec || vram), - child: _Card(title: 'Hardware Codec', children: [ - _OptionCheckBox( - context, - 'Enable hardware codec', - kOptionEnableHwcodec, - update: (bool v) { - if (v) { - bind.mainCheckHwcodec(); - } - }, - ) - ]), - ); - } - - Widget audio(BuildContext context) { - if (bind.isOutgoingOnly()) { - return const Offstage(); - } - - builder(devices, currentDevice, setDevice) { - final child = ComboBox( - keys: devices, - values: devices, - initialKey: currentDevice, - onChanged: (key) async { - setDevice(key); - setState(() {}); - }, - ).marginOnly(left: _kContentHMargin); - return _Card(title: 'Audio Input Device', children: [child]); - } - - return AudioInput(builder: builder, isCm: false, isVoiceCall: false); - } - - Widget record(BuildContext context) { - final showRootDir = isWindows && bind.mainIsInstalled(); - return futureBuilder(future: () async { - String user_dir = bind.mainVideoSaveDirectory(root: false); - String root_dir = - showRootDir ? bind.mainVideoSaveDirectory(root: true) : ''; - bool user_dir_exists = await Directory(user_dir).exists(); - bool root_dir_exists = - showRootDir ? await Directory(root_dir).exists() : false; - return { - 'user_dir': user_dir, - 'root_dir': root_dir, - 'user_dir_exists': user_dir_exists, - 'root_dir_exists': root_dir_exists, - }; - }(), hasData: (data) { - Map map = data as Map; - String user_dir = map['user_dir']!; - String root_dir = map['root_dir']!; - bool root_dir_exists = map['root_dir_exists']!; - bool user_dir_exists = map['user_dir_exists']!; - return _Card(title: 'Recording', children: [ - if (!bind.isOutgoingOnly()) - _OptionCheckBox(context, 'Automatically record incoming sessions', - kOptionAllowAutoRecordIncoming), - if (!bind.isIncomingOnly()) - _OptionCheckBox(context, 'Automatically record outgoing sessions', - kOptionAllowAutoRecordOutgoing, - isServer: false), - if (showRootDir && !bind.isOutgoingOnly()) - Row( - children: [ - Text( - '${translate(bind.isIncomingOnly() ? "Directory" : "Incoming")}:'), - Expanded( - child: GestureDetector( - onTap: root_dir_exists - ? () => launchUrl(Uri.file(root_dir)) - : null, - child: Text( - root_dir, - softWrap: true, - style: root_dir_exists - ? const TextStyle( - decoration: TextDecoration.underline) - : null, - )).marginOnly(left: 10), - ), - ], - ).marginOnly(left: _kContentHMargin), - if (!(showRootDir && bind.isIncomingOnly())) - Row( - children: [ - Text( - '${translate((showRootDir && !bind.isOutgoingOnly()) ? "Outgoing" : "Directory")}:'), - Expanded( - child: GestureDetector( - onTap: user_dir_exists - ? () => launchUrl(Uri.file(user_dir)) - : null, - child: Text( - user_dir, - softWrap: true, - style: user_dir_exists - ? const TextStyle( - decoration: TextDecoration.underline) - : null, - )).marginOnly(left: 10), - ), - ElevatedButton( - onPressed: isOptionFixed(kOptionVideoSaveDirectory) - ? null - : () async { - String? initialDirectory; - if (await Directory.fromUri( - Uri.directory(user_dir)) - .exists()) { - initialDirectory = user_dir; - } - String? selectedDirectory = - await FilePicker.platform.getDirectoryPath( - initialDirectory: initialDirectory); - if (selectedDirectory != null) { - await bind.mainSetLocalOption( - key: kOptionVideoSaveDirectory, - value: selectedDirectory); - setState(() {}); - } - }, - child: Text(translate('Change'))) - .marginOnly(left: 5), - ], - ).marginOnly(left: _kContentHMargin), - ]); - }); - } - - Widget language() { - return futureBuilder(future: () async { - String langs = await bind.mainGetLangs(); - return {'langs': langs}; - }(), hasData: (res) { - Map data = res as Map; - List langsList = jsonDecode(data['langs']!); - Map langsMap = {for (var v in langsList) v[0]: v[1]}; - List keys = langsMap.keys.toList(); - List values = langsMap.values.toList(); - keys.insert(0, defaultOptionLang); - values.insert(0, translate('Default')); - String currentKey = bind.mainGetLocalOption(key: kCommConfKeyLang); - if (!keys.contains(currentKey)) { - currentKey = defaultOptionLang; - } - final isOptFixed = isOptionFixed(kCommConfKeyLang); - return ComboBox( - keys: keys, - values: values, - initialKey: currentKey, - onChanged: (key) async { - await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key); - if (isWeb) reloadCurrentWindow(); - if (!isWeb) reloadAllWindows(); - if (!isWeb) bind.mainChangeLanguage(lang: key); - }, - enabled: !isOptFixed, - ).marginOnly(left: _kContentHMargin); - }); - } -} - -enum _AccessMode { - custom, - full, - view, -} - -class _Safety extends StatefulWidget { - const _Safety({Key? key}) : super(key: key); - - @override - State<_Safety> createState() => _SafetyState(); -} - -class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - bool locked = bind.mainIsInstalled(); - final scrollController = ScrollController(); - - @override - Widget build(BuildContext context) { - super.build(context); - return SingleChildScrollView( - controller: scrollController, - child: Column( - children: [ - _lock(locked, 'Unlock Security Settings', () { - locked = false; - setState(() => {}); - }), - preventMouseKeyBuilder( - block: locked, - child: Column(children: [ - permissions(context), - password(context), - _Card(title: '2FA', children: [tfa()]), - if (!isChangeIdDisabled()) - _Card(title: 'ID', children: [changeId()]), - more(context), - ]), - ), - ], - )).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget tfa() { - bool enabled = !locked; - // Simple temp wrapper for PR check - tmpWrapper() { - RxBool has2fa = bind.mainHasValid2FaSync().obs; - RxBool hasBot = bind.mainHasValidBotSync().obs; - update() async { - has2fa.value = bind.mainHasValid2FaSync(); - setState(() {}); - } - - onChanged(bool? checked) async { - if (checked == false) { - CommonConfirmDialog( - gFFI.dialogManager, translate('cancel-2fa-confirm-tip'), () { - change2fa(callback: update); - }); - } else { - change2fa(callback: update); - } - } - - final tfa = GestureDetector( - child: InkWell( - child: Obx(() => Row( - children: [ - Checkbox( - value: has2fa.value, - onChanged: enabled ? onChanged : null) - .marginOnly(right: 5), - Expanded( - child: Text( - translate('enable-2fa-title'), - style: - TextStyle(color: disabledTextColor(context, enabled)), - )) - ], - )), - ), - onTap: () { - onChanged(!has2fa.value); - }, - ).marginOnly(left: _kCheckBoxLeftMargin); - if (!has2fa.value) { - return tfa; - } - updateBot() async { - hasBot.value = bind.mainHasValidBotSync(); - setState(() {}); - } - - onChangedBot(bool? checked) async { - if (checked == false) { - CommonConfirmDialog( - gFFI.dialogManager, translate('cancel-bot-confirm-tip'), () { - changeBot(callback: updateBot); - }); - } else { - changeBot(callback: updateBot); - } - } - - final bot = GestureDetector( - child: Tooltip( - waitDuration: Duration(milliseconds: 300), - message: translate("enable-bot-tip"), - child: InkWell( - child: Obx(() => Row( - children: [ - Checkbox( - value: hasBot.value, - onChanged: enabled ? onChangedBot : null) - .marginOnly(right: 5), - Expanded( - child: Text( - translate('Telegram bot'), - style: TextStyle( - color: disabledTextColor(context, enabled)), - )) - ], - ))), - ), - onTap: () { - onChangedBot(!hasBot.value); - }, - ).marginOnly(left: _kCheckBoxLeftMargin + 30); - - final trust = Row( - children: [ - Flexible( - child: Tooltip( - waitDuration: Duration(milliseconds: 300), - message: translate("enable-trusted-devices-tip"), - child: _OptionCheckBox(context, "Enable trusted devices", - kOptionEnableTrustedDevices, - enabled: !locked, update: (v) { - setState(() {}); - }), - ), - ), - if (mainGetBoolOptionSync(kOptionEnableTrustedDevices)) - ElevatedButton( - onPressed: locked - ? null - : () { - manageTrustedDeviceDialog(); - }, - child: Text(translate('Manage trusted devices'))) - ], - ).marginOnly(left: 30); - - return Column( - children: [tfa, bot, trust], - ); - } - - return tmpWrapper(); - } - - Widget changeId() { - return ChangeNotifierProvider.value( - value: gFFI.serverModel, - child: Consumer(builder: ((context, model, child) { - return _Button('Change ID', changeIdDialog, - enabled: !locked && model.connectStatus > 0); - }))); - } - - Widget permissions(context) { - bool enabled = !locked; - // Simple temp wrapper for PR check - tmpWrapper() { - String accessMode = bind.mainGetOptionSync(key: kOptionAccessMode); - _AccessMode mode; - if (accessMode == 'full') { - mode = _AccessMode.full; - } else if (accessMode == 'view') { - mode = _AccessMode.view; - } else { - mode = _AccessMode.custom; - } - String initialKey; - bool? fakeValue; - switch (mode) { - case _AccessMode.custom: - initialKey = ''; - fakeValue = null; - break; - case _AccessMode.full: - initialKey = 'full'; - fakeValue = true; - break; - case _AccessMode.view: - initialKey = 'view'; - fakeValue = false; - break; - } - - return _Card(title: 'Permissions', children: [ - ComboBox( - keys: [ - defaultOptionAccessMode, - 'full', - 'view', - ], - values: [ - translate('Custom'), - translate('Full Access'), - translate('Screen Share'), - ], - enabled: enabled && !isOptionFixed(kOptionAccessMode), - initialKey: initialKey, - onChanged: (mode) async { - await bind.mainSetOption(key: kOptionAccessMode, value: mode); - setState(() {}); - }).marginOnly(left: _kContentHMargin), - Column( - children: [ - _OptionCheckBox( - context, 'Enable keyboard/mouse', kOptionEnableKeyboard, - enabled: enabled, fakeValue: fakeValue), - if (isWindows) - _OptionCheckBox( - context, 'Enable remote printer', kOptionEnableRemotePrinter, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable clipboard', kOptionEnableClipboard, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable file transfer', kOptionEnableFileTransfer, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable audio', kOptionEnableAudio, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable camera', kOptionEnableCamera, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable terminal', kOptionEnableTerminal, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable TCP tunneling', kOptionEnableTunnel, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable remote restart', kOptionEnableRemoteRestart, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox( - context, 'Enable recording session', kOptionEnableRecordSession, - enabled: enabled, fakeValue: fakeValue), - if (isWindows) - _OptionCheckBox(context, 'Enable blocking user input', - kOptionEnableBlockInput, - enabled: enabled, fakeValue: fakeValue), - if (bind.mainSupportedPrivacyModeImpls() != '[]') - _OptionCheckBox( - context, 'Enable privacy mode', kOptionEnablePrivacyMode, - enabled: enabled, fakeValue: fakeValue), - _OptionCheckBox(context, 'Enable remote configuration modification', - kOptionAllowRemoteConfigModification, - enabled: enabled, fakeValue: fakeValue), - ], - ), - ]); - } - - return tmpWrapper(); - } - - Widget password(BuildContext context) { - return ChangeNotifierProvider.value( - value: gFFI.serverModel, - child: Consumer(builder: ((context, model, child) { - List passwordKeys = [ - kUseTemporaryPassword, - kUsePermanentPassword, - kUseBothPasswords, - ]; - List passwordValues = [ - translate('Use one-time password'), - translate('Use permanent password'), - translate('Use both passwords'), - ]; - bool tmpEnabled = model.verificationMethod != kUsePermanentPassword; - bool permEnabled = model.verificationMethod != kUseTemporaryPassword; - String currentValue = - passwordValues[passwordKeys.indexOf(model.verificationMethod)]; - List radios = passwordValues - .map((value) => _Radio( - context, - value: value, - groupValue: currentValue, - label: value, - onChanged: locked - ? null - : ((value) async { - callback() async { - await model.setVerificationMethod( - passwordKeys[passwordValues.indexOf(value)]); - await model.updatePasswordModel(); - } - - if (value == - passwordValues[passwordKeys - .indexOf(kUsePermanentPassword)] && - (await bind.mainGetCommon( - key: "permanent-password-set")) != - "true") { - if (isChangePermanentPasswordDisabled()) { - await callback(); - return; - } - setPasswordDialog(notEmptyCallback: callback); - } else { - await callback(); - } - }), - )) - .toList(); - - var onChanged = tmpEnabled && !locked - ? (value) { - if (value != null) { - () async { - await model.setTemporaryPasswordLength(value.toString()); - await model.updatePasswordModel(); - }(); - } - } - : null; - List lengthRadios = ['6', '8', '10'] - .map((value) => GestureDetector( - child: Row( - children: [ - Radio( - value: value, - groupValue: model.temporaryPasswordLength, - onChanged: onChanged), - Text( - value, - style: TextStyle( - color: disabledTextColor( - context, onChanged != null)), - ), - ], - ).paddingOnly(right: 10), - onTap: () => onChanged?.call(value), - )) - .toList(); - - final isOptFixedNumOTP = - isOptionFixed(kOptionAllowNumericOneTimePassword); - final isNumOPTChangable = !isOptFixedNumOTP && tmpEnabled && !locked; - final numericOneTimePassword = GestureDetector( - child: InkWell( - child: Row( - children: [ - Checkbox( - value: model.allowNumericOneTimePassword, - onChanged: isNumOPTChangable - ? (bool? v) { - model.switchAllowNumericOneTimePassword(); - } - : null) - .marginOnly(right: 5), - Expanded( - child: Text( - translate('Numeric one-time password'), - style: TextStyle( - color: disabledTextColor(context, isNumOPTChangable)), - )) - ], - )), - onTap: isNumOPTChangable - ? () => model.switchAllowNumericOneTimePassword() - : null, - ).marginOnly(left: _kContentHSubMargin - 5); - - final modeKeys = [ - 'password', - 'click', - defaultOptionApproveMode - ]; - final modeValues = [ - translate('Accept sessions via password'), - translate('Accept sessions via click'), - translate('Accept sessions via both'), - ]; - var modeInitialKey = model.approveMode; - if (!modeKeys.contains(modeInitialKey)) { - modeInitialKey = defaultOptionApproveMode; - } - final usePassword = model.approveMode != 'click'; - - final isApproveModeFixed = isOptionFixed(kOptionApproveMode); - return _Card(title: 'Password', children: [ - ComboBox( - enabled: !locked && !isApproveModeFixed, - keys: modeKeys, - values: modeValues, - initialKey: modeInitialKey, - onChanged: (key) => model.setApproveMode(key), - ).marginOnly(left: _kContentHMargin), - if (usePassword) radios[0], - if (usePassword) - _SubLabeledWidget( - context, - 'One-time password length', - Row( - children: [ - ...lengthRadios, - ], - ), - enabled: tmpEnabled && !locked), - if (usePassword) numericOneTimePassword, - if (usePassword) radios[1], - if (usePassword && !isChangePermanentPasswordDisabled()) - _SubButton('Set permanent password', setPasswordDialog, - permEnabled && !locked), - // if (usePassword) - // hide_cm(!locked).marginOnly(left: _kContentHSubMargin - 6), - if (usePassword) radios[2], - ]); - }))); - } - - Widget more(BuildContext context) { - bool enabled = !locked; - return _Card(title: 'Security', children: [ - shareRdp(context, enabled), - _OptionCheckBox(context, 'Deny LAN discovery', 'enable-lan-discovery', - reverse: true, enabled: enabled), - ...directIp(context), - whitelist(), - ...autoDisconnect(context), - _OptionCheckBox(context, 'keep-awake-during-incoming-sessions-label', - kOptionKeepAwakeDuringIncomingSessions, - reverse: false, enabled: enabled), - if (bind.mainIsInstalled()) - _OptionCheckBox(context, 'allow-only-conn-window-open-tip', - 'allow-only-conn-window-open', - reverse: false, enabled: enabled), - if (bind.mainIsInstalled() && !isUnlockPinDisabled()) unlockPin() - ]); - } - - shareRdp(BuildContext context, bool enabled) { - onChanged(bool b) async { - await bind.mainSetShareRdp(enable: b); - setState(() {}); - } - - bool value = bind.mainIsShareRdp(); - return Offstage( - offstage: !(isWindows && bind.mainIsInstalled()), - child: GestureDetector( - child: Row( - children: [ - Checkbox( - value: value, - onChanged: enabled ? (_) => onChanged(!value) : null) - .marginOnly(right: 5), - Expanded( - child: Text(translate('Enable RDP session sharing'), - style: - TextStyle(color: disabledTextColor(context, enabled))), - ) - ], - ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: enabled ? () => onChanged(!value) : null), - ); - } - - List directIp(BuildContext context) { - TextEditingController controller = TextEditingController(); - update(bool v) => setState(() {}); - RxBool applyEnabled = false.obs; - return [ - _OptionCheckBox(context, 'Enable direct IP access', kOptionDirectServer, - update: update, enabled: !locked), - () { - // Simple temp wrapper for PR check - tmpWrapper() { - bool enabled = option2bool(kOptionDirectServer, - bind.mainGetOptionSync(key: kOptionDirectServer)); - if (!enabled) applyEnabled.value = false; - controller.text = - bind.mainGetOptionSync(key: kOptionDirectAccessPort); - final isOptFixed = isOptionFixed(kOptionDirectAccessPort); - return Offstage( - offstage: !enabled, - child: _SubLabeledWidget( - context, - 'Port', - Row(children: [ - SizedBox( - width: 95, - child: TextField( - controller: controller, - enabled: enabled && !locked && !isOptFixed, - onChanged: (_) => applyEnabled.value = true, - 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( - hintText: '21118', - contentPadding: - EdgeInsets.symmetric(vertical: 12, horizontal: 12), - ), - ).workaroundFreezeLinuxMint().marginOnly(right: 15), - ), - Obx(() => ElevatedButton( - onPressed: applyEnabled.value && - enabled && - !locked && - !isOptFixed - ? () async { - applyEnabled.value = false; - await bind.mainSetOption( - key: kOptionDirectAccessPort, - value: controller.text); - } - : null, - child: Text( - translate('Apply'), - ), - )) - ]), - enabled: enabled && !locked && !isOptFixed, - ), - ); - } - - return tmpWrapper(); - }(), - ]; - } - - Widget whitelist() { - bool enabled = !locked; - // Simple temp wrapper for PR check - tmpWrapper() { - RxBool hasWhitelist = whitelistNotEmpty().obs; - update() async { - hasWhitelist.value = whitelistNotEmpty(); - } - - onChanged(bool? checked) async { - changeWhiteList(callback: update); - } - - final isOptFixed = isOptionFixed(kOptionWhitelist); - return GestureDetector( - child: Tooltip( - message: translate('whitelist_tip'), - child: Obx(() => Row( - children: [ - Checkbox( - value: hasWhitelist.value, - onChanged: enabled && !isOptFixed ? onChanged : null) - .marginOnly(right: 5), - Offstage( - offstage: !hasWhitelist.value, - child: MouseRegion( - child: const Icon(Icons.warning_amber_rounded, - color: Color.fromARGB(255, 255, 204, 0)) - .marginOnly(right: 5), - cursor: SystemMouseCursors.click, - ), - ), - Expanded( - child: Text( - translate('Use IP Whitelisting'), - style: - TextStyle(color: disabledTextColor(context, enabled)), - )) - ], - )), - ), - onTap: enabled - ? () { - onChanged(!hasWhitelist.value); - } - : null, - ).marginOnly(left: _kCheckBoxLeftMargin); - } - - return tmpWrapper(); - } - - Widget hide_cm(bool enabled) { - return ChangeNotifierProvider.value( - value: gFFI.serverModel, - child: Consumer(builder: (context, model, child) { - final enableHideCm = model.approveMode == 'password' && - model.verificationMethod == kUsePermanentPassword; - onHideCmChanged(bool? b) { - if (b != null) { - bind.mainSetOption( - key: 'allow-hide-cm', value: bool2option('allow-hide-cm', b)); - } - } - - return Tooltip( - message: enableHideCm ? "" : translate('hide_cm_tip'), - child: GestureDetector( - onTap: - enableHideCm ? () => onHideCmChanged(!model.hideCm) : null, - child: Row( - children: [ - Checkbox( - value: model.hideCm, - onChanged: enabled && enableHideCm - ? onHideCmChanged - : null) - .marginOnly(right: 5), - Expanded( - child: Text( - translate('Hide connection management window'), - style: TextStyle( - color: disabledTextColor( - context, enabled && enableHideCm)), - ), - ), - ], - ), - )); - })); - } - - List autoDisconnect(BuildContext context) { - TextEditingController controller = TextEditingController(); - update(bool v) => setState(() {}); - RxBool applyEnabled = false.obs; - return [ - _OptionCheckBox( - context, 'auto_disconnect_option_tip', kOptionAllowAutoDisconnect, - update: update, enabled: !locked), - () { - bool enabled = option2bool(kOptionAllowAutoDisconnect, - bind.mainGetOptionSync(key: kOptionAllowAutoDisconnect)); - if (!enabled) applyEnabled.value = false; - controller.text = - bind.mainGetOptionSync(key: kOptionAutoDisconnectTimeout); - final isOptFixed = isOptionFixed(kOptionAutoDisconnectTimeout); - return Offstage( - offstage: !enabled, - child: _SubLabeledWidget( - context, - 'Timeout in minutes', - Row(children: [ - SizedBox( - width: 95, - child: TextField( - controller: controller, - enabled: enabled && !locked && !isOptFixed, - onChanged: (_) => applyEnabled.value = true, - 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( - hintText: '10', - contentPadding: - EdgeInsets.symmetric(vertical: 12, horizontal: 12), - ), - ).workaroundFreezeLinuxMint().marginOnly(right: 15), - ), - Obx(() => ElevatedButton( - onPressed: - applyEnabled.value && enabled && !locked && !isOptFixed - ? () async { - applyEnabled.value = false; - await bind.mainSetOption( - key: kOptionAutoDisconnectTimeout, - value: controller.text); - } - : null, - child: Text( - translate('Apply'), - ), - )) - ]), - enabled: enabled && !locked && !isOptFixed, - ), - ); - }(), - ]; - } - - Widget unlockPin() { - bool enabled = !locked; - RxString unlockPin = bind.mainGetUnlockPin().obs; - update() async { - unlockPin.value = bind.mainGetUnlockPin(); - } - - onChanged(bool? checked) async { - changeUnlockPinDialog(unlockPin.value, update); - } - - final isOptFixed = isOptionFixed(kOptionWhitelist); - return GestureDetector( - child: Obx(() => Row( - children: [ - Checkbox( - value: unlockPin.isNotEmpty, - onChanged: enabled && !isOptFixed ? onChanged : null) - .marginOnly(right: 5), - Expanded( - child: Text( - translate('Unlock with PIN'), - style: TextStyle(color: disabledTextColor(context, enabled)), - )) - ], - )), - onTap: enabled - ? () { - onChanged(!unlockPin.isNotEmpty); - } - : null, - ).marginOnly(left: _kCheckBoxLeftMargin); - } -} - -class _Network extends StatefulWidget { - const _Network({Key? key}) : super(key: key); - - @override - State<_Network> createState() => _NetworkState(); -} - -class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - bool locked = !isWeb && bind.mainIsInstalled(); - - final scrollController = ScrollController(); - - @override - Widget build(BuildContext context) { - super.build(context); - return ListView(controller: scrollController, children: [ - _lock(locked, 'Unlock Network Settings', () { - locked = false; - setState(() => {}); - }), - preventMouseKeyBuilder( - block: locked, - child: Column(children: [ - network(context), - ]), - ), - ]).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget network(BuildContext context) { - final hideServer = - bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; - final hideProxy = - isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; - final hideWebSocket = isWeb || - bind.mainGetBuildinOption(key: kOptionHideWebSocketSetting) == 'Y'; - - if (hideServer && hideProxy && hideWebSocket) { - return Offstage(); - } - - // Helper function to create network setting ListTiles - Widget listTile({ - required IconData icon, - required String title, - VoidCallback? onTap, - Widget? trailing, - bool showTooltip = false, - String tooltipMessage = '', - }) { - final titleWidget = showTooltip - ? Row( - children: [ - Tooltip( - waitDuration: Duration(milliseconds: 1000), - message: translate(tooltipMessage), - child: Row( - children: [ - Text( - translate(title), - style: TextStyle(fontSize: _kContentFontSize), - ), - SizedBox(width: 5), - Icon( - Icons.help_outline, - size: 14, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.7), - ), - ], - ), - ), - ], - ) - : Text( - translate(title), - style: TextStyle(fontSize: _kContentFontSize), - ); - - return ListTile( - leading: Icon(icon, color: _accentColor), - title: titleWidget, - enabled: !locked, - onTap: onTap, - trailing: trailing, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - contentPadding: EdgeInsets.symmetric(horizontal: 16), - minLeadingWidth: 0, - horizontalTitleGap: 10, - ); - } - - Widget switchWidget(IconData icon, String title, String tooltipMessage, - String optionKey) => - listTile( - icon: icon, - title: title, - showTooltip: true, - tooltipMessage: tooltipMessage, - trailing: Switch( - value: mainGetBoolOptionSync(optionKey), - onChanged: locked || isOptionFixed(optionKey) - ? null - : (value) { - mainSetBoolOption(optionKey, value); - setState(() {}); - }, - ), - ); - - final outgoingOnly = bind.isOutgoingOnly(); - - final divider = const Divider(height: 1, indent: 16, endIndent: 16); - return _Card( - title: 'Network', - children: [ - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!hideServer) - listTile( - icon: Icons.dns_outlined, - title: 'ID/Relay Server', - onTap: () => showServerSettings(gFFI.dialogManager, setState), - ), - if (!hideProxy && !hideServer) divider, - if (!hideProxy) - listTile( - icon: Icons.network_ping_outlined, - title: 'Socks5/Http(s) Proxy', - onTap: changeSocks5Proxy, - ), - if (!hideWebSocket && (!hideServer || !hideProxy)) divider, - if (!hideWebSocket) - switchWidget( - Icons.web_asset_outlined, - 'Use WebSocket', - '${translate('websocket_tip')}\n\n${translate('server-oss-not-support-tip')}', - kOptionAllowWebSocket), - if (!isWeb) - futureBuilder( - future: bind.mainIsUsingPublicServer(), - hasData: (isUsingPublicServer) { - if (isUsingPublicServer) { - return Offstage(); - } else { - return Column( - children: [ - if (!hideServer || !hideProxy || !hideWebSocket) - divider, - switchWidget( - Icons.no_encryption_outlined, - 'Allow insecure TLS fallback', - 'allow-insecure-tls-fallback-tip', - kOptionAllowInsecureTLSFallback), - if (!outgoingOnly) divider, - if (!outgoingOnly) - listTile( - icon: Icons.lan_outlined, - title: 'Disable UDP', - showTooltip: true, - tooltipMessage: - '${translate('disable-udp-tip')}\n\n${translate('server-oss-not-support-tip')}', - trailing: Switch( - value: bind.mainGetOptionSync( - key: kOptionDisableUdp) == - 'Y', - onChanged: - locked || isOptionFixed(kOptionDisableUdp) - ? null - : (value) async { - await bind.mainSetOption( - key: kOptionDisableUdp, - value: value ? 'Y' : 'N'); - setState(() {}); - }, - ), - ), - ], - ); - } - }, - ), - ], - ), - ), - ], - ); - } -} - -class _Display extends StatefulWidget { - const _Display({Key? key}) : super(key: key); - - @override - State<_Display> createState() => _DisplayState(); -} - -class _DisplayState extends State<_Display> { - @override - Widget build(BuildContext context) { - final scrollController = ScrollController(); - return ListView(controller: scrollController, children: [ - viewStyle(context), - scrollStyle(context), - imageQuality(context), - codec(context), - if (isDesktop) trackpadSpeed(context), - if (!isWeb) privacyModeImpl(context), - other(context), - ]).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget viewStyle(BuildContext context) { - final isOptFixed = isOptionFixed(kOptionViewStyle); - onChanged(String value) async { - await bind.mainSetUserDefaultOption(key: kOptionViewStyle, value: value); - setState(() {}); - } - - final groupValue = bind.mainGetUserDefaultOption(key: kOptionViewStyle); - return _Card(title: 'Default View Style', children: [ - _Radio(context, - value: kRemoteViewStyleOriginal, - groupValue: groupValue, - label: 'Scale original', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: kRemoteViewStyleAdaptive, - groupValue: groupValue, - label: 'Scale adaptive', - onChanged: isOptFixed ? null : onChanged), - ]); - } - - Widget scrollStyle(BuildContext context) { - final isOptFixed = isOptionFixed(kOptionScrollStyle); - onChanged(String value) async { - await bind.mainSetUserDefaultOption( - key: kOptionScrollStyle, value: value); - setState(() {}); - } - - final groupValue = bind.mainGetUserDefaultOption(key: kOptionScrollStyle); - - onEdgeScrollEdgeThicknessChanged(double value) async { - await bind.mainSetUserDefaultOption( - key: kOptionEdgeScrollEdgeThickness, value: value.round().toString()); - setState(() {}); - } - - return _Card(title: 'Default Scroll Style', children: [ - _Radio(context, - value: kRemoteScrollStyleAuto, - groupValue: groupValue, - label: 'ScrollAuto', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: kRemoteScrollStyleBar, - groupValue: groupValue, - label: 'Scrollbar', - onChanged: isOptFixed ? null : onChanged), - if (!isWeb) ...[ - _Radio(context, - value: kRemoteScrollStyleEdge, - groupValue: groupValue, - label: 'ScrollEdge', - onChanged: isOptFixed ? null : onChanged), - Offstage( - offstage: groupValue != kRemoteScrollStyleEdge, - child: EdgeThicknessControl( - value: double.tryParse(bind.mainGetUserDefaultOption( - key: kOptionEdgeScrollEdgeThickness)) ?? - 100.0, - onChanged: isOptionFixed(kOptionEdgeScrollEdgeThickness) - ? null - : onEdgeScrollEdgeThicknessChanged, - )), - ], - ]); - } - - Widget imageQuality(BuildContext context) { - onChanged(String value) async { - await bind.mainSetUserDefaultOption( - key: kOptionImageQuality, value: value); - setState(() {}); - } - - final isOptFixed = isOptionFixed(kOptionImageQuality); - final groupValue = bind.mainGetUserDefaultOption(key: kOptionImageQuality); - return _Card(title: 'Default Image Quality', children: [ - _Radio(context, - value: kRemoteImageQualityBest, - groupValue: groupValue, - label: 'Good image quality', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: kRemoteImageQualityBalanced, - groupValue: groupValue, - label: 'Balanced', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: kRemoteImageQualityLow, - groupValue: groupValue, - label: 'Optimize reaction time', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: kRemoteImageQualityCustom, - groupValue: groupValue, - label: 'Custom', - onChanged: isOptFixed ? null : onChanged), - Offstage( - offstage: groupValue != kRemoteImageQualityCustom, - child: customImageQualitySetting(), - ) - ]); - } - - Widget trackpadSpeed(BuildContext context) { - final initSpeed = - (int.tryParse(bind.mainGetUserDefaultOption(key: kKeyTrackpadSpeed)) ?? - kDefaultTrackpadSpeed); - final curSpeed = SimpleWrapper(initSpeed); - void onDebouncer(int v) { - bind.mainSetUserDefaultOption( - key: kKeyTrackpadSpeed, value: v.toString()); - // It's better to notify all sessions that the default speed is changed. - // But it may also be ok to take effect in the next connection. - } - - return _Card(title: 'Default trackpad speed', children: [ - TrackpadSpeedWidget( - value: curSpeed, - onDebouncer: onDebouncer, - ), - ]); - } - - Widget codec(BuildContext context) { - onChanged(String value) async { - await bind.mainSetUserDefaultOption( - key: kOptionCodecPreference, value: value); - setState(() {}); - } - - final groupValue = - bind.mainGetUserDefaultOption(key: kOptionCodecPreference); - var hwRadios = []; - final isOptFixed = isOptionFixed(kOptionCodecPreference); - try { - final Map codecsJson = jsonDecode(bind.mainSupportedHwdecodings()); - final h264 = codecsJson['h264'] ?? false; - final h265 = codecsJson['h265'] ?? false; - if (h264) { - hwRadios.add(_Radio(context, - value: 'h264', - groupValue: groupValue, - label: 'H264', - onChanged: isOptFixed ? null : onChanged)); - } - if (h265) { - hwRadios.add(_Radio(context, - value: 'h265', - groupValue: groupValue, - label: 'H265', - onChanged: isOptFixed ? null : onChanged)); - } - } catch (e) { - debugPrint("failed to parse supported hwdecodings, err=$e"); - } - return _Card(title: 'Default Codec', children: [ - _Radio(context, - value: 'auto', - groupValue: groupValue, - label: 'Auto', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: 'vp8', - groupValue: groupValue, - label: 'VP8', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: 'vp9', - groupValue: groupValue, - label: 'VP9', - onChanged: isOptFixed ? null : onChanged), - _Radio(context, - value: 'av1', - groupValue: groupValue, - label: 'AV1', - onChanged: isOptFixed ? null : onChanged), - ...hwRadios, - ]); - } - - Widget privacyModeImpl(BuildContext context) { - final supportedPrivacyModeImpls = bind.mainSupportedPrivacyModeImpls(); - late final List privacyModeImpls; - try { - privacyModeImpls = jsonDecode(supportedPrivacyModeImpls); - } catch (e) { - debugPrint('failed to parse supported privacy mode impls, err=$e'); - return Offstage(); - } - if (privacyModeImpls.length < 2) { - return Offstage(); - } - - final key = 'privacy-mode-impl-key'; - onChanged(String value) async { - await bind.mainSetOption(key: key, value: value); - setState(() {}); - } - - String groupValue = bind.mainGetOptionSync(key: key); - if (groupValue.isEmpty) { - groupValue = bind.mainDefaultPrivacyModeImpl(); - } - return _Card( - title: 'Privacy mode', - children: privacyModeImpls.map((impl) { - final d = impl as List; - return _Radio(context, - value: d[0] as String, - groupValue: groupValue, - label: d[1] as String, - onChanged: onChanged); - }).toList(), - ); - } - - Widget otherRow(String label, String key) { - final value = bind.mainGetUserDefaultOption(key: key) == 'Y'; - final isOptFixed = isOptionFixed(key); - onChanged(bool b) async { - await bind.mainSetUserDefaultOption( - key: key, - value: b - ? 'Y' - : (key == kOptionEnableFileCopyPaste ? 'N' : defaultOptionNo)); - setState(() {}); - } - - return GestureDetector( - child: Row( - children: [ - Checkbox( - value: value, - onChanged: isOptFixed ? null : (_) => onChanged(!value)) - .marginOnly(right: 5), - Expanded( - child: Text(translate(label)), - ) - ], - ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: isOptFixed ? null : () => onChanged(!value)); - } - - Widget other(BuildContext context) { - final children = - otherDefaultSettings().map((e) => otherRow(e.$1, e.$2)).toList(); - return _Card(title: 'Other Default Options', children: children); - } -} - -class _Account extends StatefulWidget { - const _Account({Key? key}) : super(key: key); - - @override - State<_Account> createState() => _AccountState(); -} - -class _AccountState extends State<_Account> { - @override - Widget build(BuildContext context) { - final scrollController = ScrollController(); - return ListView( - controller: scrollController, - children: [ - _Card(title: 'Account', children: [accountAction(), useInfo()]), - ], - ).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget accountAction() { - return Obx(() => _Button( - gFFI.userModel.userName.value.isEmpty - ? 'Login' - : '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})', - () => { - gFFI.userModel.userName.value.isEmpty - ? loginDialog() - : logOutConfirmDialog() - })); - } - - Widget useInfo() { - return Obx(() => Offstage( - offstage: gFFI.userModel.userName.value.isEmpty, - child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(10), - ), - child: Builder(builder: (context) { - final avatarWidget = _buildUserAvatar(); - return Row( - children: [ - if (avatarWidget != null) avatarWidget, - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - gFFI.userModel.displayNameOrUserName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 2), - SelectionArea( - child: Text( - '@${gFFI.userModel.userName.value}', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 13, - color: - Theme.of(context).textTheme.bodySmall?.color, - ), - ), - ), - ], - ), - ), - ], - ); - }), - ), - )).marginOnly(left: 18, top: 16); - } - - Widget? _buildUserAvatar() { - // Resolve relative avatar path at display time - final avatar = - bind.mainResolveAvatarUrl(avatar: gFFI.userModel.avatar.value); - return buildAvatarWidget( - avatar: avatar, - size: 44, - ); - } -} - -class _Checkbox extends StatefulWidget { - final String label; - final bool Function() getValue; - final Future Function(bool) setValue; - - const _Checkbox( - {Key? key, - required this.label, - required this.getValue, - required this.setValue}) - : super(key: key); - - @override - State<_Checkbox> createState() => _CheckboxState(); -} - -class _CheckboxState extends State<_Checkbox> { - var value = false; - - @override - initState() { - super.initState(); - value = widget.getValue(); - } - - @override - Widget build(BuildContext context) { - onChanged(bool b) async { - await widget.setValue(b); - setState(() { - value = widget.getValue(); - }); - } - - return GestureDetector( - child: Row( - children: [ - Checkbox( - value: value, - onChanged: (_) => onChanged(!value), - ).marginOnly(right: 5), - Expanded( - child: Text(translate(widget.label)), - ) - ], - ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: () => onChanged(!value), - ); - } -} - -class _Plugin extends StatefulWidget { - const _Plugin({Key? key}) : super(key: key); - - @override - State<_Plugin> createState() => _PluginState(); -} - -class _PluginState extends State<_Plugin> { - @override - Widget build(BuildContext context) { - bind.pluginListReload(); - final scrollController = ScrollController(); - return ChangeNotifierProvider.value( - value: pluginManager, - child: Consumer(builder: (context, model, child) { - return ListView( - controller: scrollController, - children: model.plugins.map((entry) => pluginCard(entry)).toList(), - ).marginOnly(bottom: _kListViewBottomMargin); - }), - ); - } - - Widget pluginCard(PluginInfo plugin) { - return ChangeNotifierProvider.value( - value: plugin, - child: Consumer( - builder: (context, model, child) => DesktopSettingsCard(plugin: model), - ), - ); - } - - Widget accountAction() { - return Obx(() => _Button( - gFFI.userModel.userName.value.isEmpty - ? 'Login' - : '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})', - () => { - gFFI.userModel.userName.value.isEmpty - ? loginDialog() - : logOutConfirmDialog() - })); - } -} - -class _Printer extends StatefulWidget { - const _Printer({super.key}); - - @override - State<_Printer> createState() => __PrinterState(); -} - -class __PrinterState extends State<_Printer> { - @override - Widget build(BuildContext context) { - final scrollController = ScrollController(); - return ListView(controller: scrollController, children: [ - outgoing(context), - incoming(context), - ]).marginOnly(bottom: _kListViewBottomMargin); - } - - Widget outgoing(BuildContext context) { - final isSupportPrinterDriver = - bind.mainGetCommonSync(key: 'is-support-printer-driver') == 'true'; - - Widget tipOsNotSupported() { - return Align( - alignment: Alignment.topLeft, - child: Text(translate('printer-os-requirement-tip')), - ).marginOnly(left: _kCardLeftMargin); - } - - Widget tipClientNotInstalled() { - return Align( - alignment: Alignment.topLeft, - child: - Text(translate('printer-requires-installed-{$appName}-client-tip')), - ).marginOnly(left: _kCardLeftMargin); - } - - Widget tipPrinterNotInstalled() { - final failedMsg = ''.obs; - platformFFI.registerEventHandler( - 'install-printer-res', 'install-printer-res', (evt) async { - if (evt['success'] as bool) { - setState(() {}); - } else { - failedMsg.value = evt['msg'] as String; - } - }, replace: true); - return Column(children: [ - Obx( - () => failedMsg.value.isNotEmpty - ? Offstage() - : Align( - alignment: Alignment.topLeft, - child: Text(translate('printer-{$appName}-not-installed-tip')) - .marginOnly(bottom: 10.0), - ), - ), - Obx( - () => failedMsg.value.isEmpty - ? Offstage() - : Align( - alignment: Alignment.topLeft, - child: Text(failedMsg.value, - style: DefaultTextStyle.of(context) - .style - .copyWith(color: Colors.red)) - .marginOnly(bottom: 10.0)), - ), - _Button('Install {$appName} Printer', () { - failedMsg.value = ''; - bind.mainSetCommon(key: 'install-printer', value: ''); - }) - ]).marginOnly(left: _kCardLeftMargin, bottom: 2.0); - } - - Widget tipReady() { - return Align( - alignment: Alignment.topLeft, - child: Text(translate('printer-{$appName}-ready-tip')), - ).marginOnly(left: _kCardLeftMargin); - } - - final installed = bind.mainIsInstalled(); - // `is-printer-installed` may fail, but it's rare case. - // Add additional error message here if it's really needed. - final isPrinterInstalled = - bind.mainGetCommonSync(key: 'is-printer-installed') == 'true'; - - final List children = []; - if (!isSupportPrinterDriver) { - children.add(tipOsNotSupported()); - } else { - children.addAll([ - if (!installed) tipClientNotInstalled(), - if (installed && !isPrinterInstalled) tipPrinterNotInstalled(), - if (installed && isPrinterInstalled) tipReady() - ]); - } - return _Card(title: 'Outgoing Print Jobs', children: children); - } - - Widget incoming(BuildContext context) { - onRadioChanged(String value) async { - await bind.mainSetLocalOption( - key: kKeyPrinterIncomingJobAction, value: value); - setState(() {}); - } - - PrinterOptions printerOptions = PrinterOptions.load(); - return _Card(title: 'Incoming Print Jobs', children: [ - _Radio(context, - value: kValuePrinterIncomingJobDismiss, - groupValue: printerOptions.action, - label: 'Dismiss', - onChanged: onRadioChanged), - _Radio(context, - value: kValuePrinterIncomingJobDefault, - groupValue: printerOptions.action, - label: 'use-the-default-printer-tip', - onChanged: onRadioChanged), - _Radio(context, - value: kValuePrinterIncomingJobSelected, - groupValue: printerOptions.action, - label: 'use-the-selected-printer-tip', - onChanged: onRadioChanged), - if (printerOptions.printerNames.isNotEmpty) - ComboBox( - initialKey: printerOptions.printerName, - keys: printerOptions.printerNames, - values: printerOptions.printerNames, - enabled: printerOptions.action == kValuePrinterIncomingJobSelected, - onChanged: (value) async { - await bind.mainSetLocalOption( - key: kKeyPrinterSelected, value: value); - setState(() {}); - }, - ).marginOnly(left: 10), - _OptionCheckBox( - context, - 'auto-print-tip', - kKeyPrinterAllowAutoPrint, - isServer: false, - enabled: printerOptions.action != kValuePrinterIncomingJobDismiss, - ) - ]); - } -} - -class _About extends StatefulWidget { - const _About({Key? key}) : super(key: key); - - @override - State<_About> createState() => _AboutState(); -} - -class _AboutState extends State<_About> { - @override - Widget build(BuildContext context) { - return futureBuilder(future: () async { - final license = await bind.mainGetLicense(); - final version = await bind.mainGetVersion(); - final buildDate = await bind.mainGetBuildDate(); - final fingerprint = await bind.mainGetFingerprint(); - return { - 'license': license, - 'version': version, - 'buildDate': buildDate, - 'fingerprint': fingerprint - }; - }(), hasData: (data) { - final license = data['license'].toString(); - final version = data['version'].toString(); - final buildDate = data['buildDate'].toString(); - final fingerprint = data['fingerprint'].toString(); - const linkStyle = TextStyle(decoration: TextDecoration.underline); - final scrollController = ScrollController(); - return SingleChildScrollView( - controller: scrollController, - child: _Card(title: translate('About RustDesk'), children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 8.0, - ), - SelectionArea( - child: Text('${translate('Version')}: $version') - .marginSymmetric(vertical: 4.0)), - SelectionArea( - child: Text('${translate('Build Date')}: $buildDate') - .marginSymmetric(vertical: 4.0)), - if (!isWeb) - SelectionArea( - child: Text('${translate('Fingerprint')}: $fingerprint') - .marginSymmetric(vertical: 4.0)), - InkWell( - onTap: () { - launchUrlString('https://rustdesk.com/privacy.html'); - }, - child: Text( - translate('Privacy Statement'), - style: linkStyle, - ).marginSymmetric(vertical: 4.0)), - InkWell( - onTap: () { - launchUrlString('https://rustdesk.com'); - }, - child: Text( - translate('Website'), - style: linkStyle, - ).marginSymmetric(vertical: 4.0)), - Container( - decoration: const BoxDecoration(color: Color(0xFF2c8cff)), - padding: - const EdgeInsets.symmetric(vertical: 24, horizontal: 8), - child: SelectionArea( - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license', - style: const TextStyle(color: Colors.white), - ), - Text( - translate('Slogan_tip'), - style: TextStyle( - fontWeight: FontWeight.w800, - color: Colors.white), - ) - ], - ), - ), - ], - )), - ).marginSymmetric(vertical: 4.0) - ], - ).marginOnly(left: _kContentHMargin) - ]), - ); - }); - } -} - -//#endregion - -//#region components - -// ignore: non_constant_identifier_names -Widget _Card( - {required String title, - required List children, - List? title_suffix}) { - return Row( - children: [ - Flexible( - child: SizedBox( - width: _kCardFixedWidth, - child: Card( - child: Column( - children: [ - Row( - children: [ - Expanded( - child: Text( - translate(title), - textAlign: TextAlign.start, - style: const TextStyle( - fontSize: _kTitleFontSize, - ), - )), - ...?title_suffix - ], - ).marginOnly(left: _kContentHMargin, top: 10, bottom: 10), - ...children - .map((e) => e.marginOnly(top: 4, right: _kContentHMargin)), - ], - ).marginOnly(bottom: 10), - ).marginOnly(left: _kCardLeftMargin, top: 15), - ), - ), - ], - ); -} - -// ignore: non_constant_identifier_names -Widget _OptionCheckBox( - BuildContext context, - String label, - String key, { - Function(bool)? update, - bool reverse = false, - bool enabled = true, - Icon? checkedIcon, - bool? fakeValue, - bool isServer = true, - bool Function()? optGetter, - Future Function(String, bool)? optSetter, -}) { - getOpt() => optGetter != null - ? optGetter() - : (isServer - ? mainGetBoolOptionSync(key) - : mainGetLocalBoolOptionSync(key)); - bool value = getOpt(); - final isOptFixed = isOptionFixed(key); - if (reverse) value = !value; - var ref = value.obs; - onChanged(option) async { - if (option != null) { - if (reverse) option = !option; - final setter = - optSetter ?? (isServer ? mainSetBoolOption : mainSetLocalBoolOption); - await setter(key, option); - final readOption = getOpt(); - if (reverse) { - ref.value = !readOption; - } else { - ref.value = readOption; - } - update?.call(readOption); - } - } - - if (fakeValue != null) { - ref.value = fakeValue; - enabled = false; - } - - return GestureDetector( - child: Obx( - () => Row( - children: [ - Checkbox( - value: ref.value, - onChanged: enabled && !isOptFixed ? onChanged : null) - .marginOnly(right: 5), - Offstage( - offstage: !ref.value || checkedIcon == null, - child: checkedIcon?.marginOnly(right: 5), - ), - Expanded( - child: Text( - translate(label), - style: TextStyle(color: disabledTextColor(context, enabled)), - )) - ], - ), - ).marginOnly(left: _kCheckBoxLeftMargin), - onTap: enabled && !isOptFixed - ? () { - onChanged(!ref.value); - } - : null, - ); -} - -// ignore: non_constant_identifier_names -Widget _Radio(BuildContext context, - {required T value, - required T groupValue, - required String label, - required Function(T value)? onChanged, - bool autoNewLine = true}) { - final onChange2 = onChanged != null - ? (T? value) { - if (value != null) { - onChanged(value); - } - } - : null; - return GestureDetector( - child: Row( - children: [ - Radio(value: value, groupValue: groupValue, onChanged: onChange2), - Expanded( - child: Text(translate(label), - overflow: autoNewLine ? null : TextOverflow.ellipsis, - style: TextStyle( - fontSize: _kContentFontSize, - color: disabledTextColor(context, onChange2 != null))) - .marginOnly(left: 5), - ), - ], - ).marginOnly(left: _kRadioLeftMargin), - onTap: () => onChange2?.call(value), - ); -} - -class WaylandCard extends StatefulWidget { - const WaylandCard({Key? key}) : super(key: key); - - @override - State createState() => _WaylandCardState(); -} - -class _WaylandCardState extends State { - final restoreTokenKey = 'wayland-restore-token'; - static const _kClearShortcutsInhibitorEventKey = - 'clear-gnome-shortcuts-inhibitor-permission-res'; - final _clearShortcutsInhibitorFailedMsg = ''.obs; - // Don't show the shortcuts permission reset button for now. - // Users can change it manually: - // "Settings" -> "Apps" -> "RustDesk" -> "Permissions" -> "Inhibit Shortcuts". - // For resetting(clearing) the permission from the portal permission store, you can - // use (replace with the RustDesk desktop file ID): - // busctl --user call org.freedesktop.impl.portal.PermissionStore \ - // /org/freedesktop/impl/portal/PermissionStore org.freedesktop.impl.portal.PermissionStore \ - // DeletePermission sss "gnome" "shortcuts-inhibitor" "" - // On a native install this is typically "rustdesk.desktop"; on Flatpak it is usually - // the exported desktop ID derived from the Flatpak app-id (e.g. "com.rustdesk.RustDesk.desktop"). - // - // We may add it back in the future if needed. - final showResetInhibitorPermission = false; - - @override - void initState() { - super.initState(); - if (showResetInhibitorPermission) { - platformFFI.registerEventHandler( - _kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey, - (evt) async { - if (!mounted) return; - if (evt['success'] == true) { - setState(() {}); - } else { - _clearShortcutsInhibitorFailedMsg.value = - evt['msg'] as String? ?? 'Unknown error'; - } - }); - } - } - - @override - void dispose() { - if (showResetInhibitorPermission) { - platformFFI.unregisterEventHandler( - _kClearShortcutsInhibitorEventKey, _kClearShortcutsInhibitorEventKey); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return futureBuilder( - future: bind.mainHandleWaylandScreencastRestoreToken( - key: restoreTokenKey, value: "get"), - hasData: (restoreToken) { - final hasShortcutsPermission = showResetInhibitorPermission && - bind.mainGetCommonSync( - key: "has-gnome-shortcuts-inhibitor-permission") == - "true"; - - final children = [ - if (restoreToken.isNotEmpty) - _buildClearScreenSelection(context, restoreToken), - if (hasShortcutsPermission) - _buildClearShortcutsInhibitorPermission(context), - ]; - return Offstage( - offstage: children.isEmpty, - child: _Card(title: 'Wayland', children: children), - ); - }, - ); - } - - Widget _buildClearScreenSelection(BuildContext context, String restoreToken) { - onConfirm() async { - final msg = await bind.mainHandleWaylandScreencastRestoreToken( - key: restoreTokenKey, value: "clear"); - gFFI.dialogManager.dismissAll(); - if (msg.isNotEmpty) { - msgBox(gFFI.sessionId, 'custom-nocancel', 'Error', msg, '', - gFFI.dialogManager); - } else { - setState(() {}); - } - } - - showConfirmMsgBox() => msgBoxCommon( - gFFI.dialogManager, - 'Confirmation', - Text( - translate('confirm_clear_Wayland_screen_selection_tip'), - ), - [ - dialogButton('OK', onPressed: onConfirm), - dialogButton('Cancel', - onPressed: () => gFFI.dialogManager.dismissAll()) - ]); - - return _Button( - 'Clear Wayland screen selection', - showConfirmMsgBox, - tip: 'clear_Wayland_screen_selection_tip', - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.error.withOpacity(0.75)), - ), - ); - } - - Widget _buildClearShortcutsInhibitorPermission(BuildContext context) { - onConfirm() { - _clearShortcutsInhibitorFailedMsg.value = ''; - bind.mainSetCommon( - key: "clear-gnome-shortcuts-inhibitor-permission", value: ""); - gFFI.dialogManager.dismissAll(); - } - - showConfirmMsgBox() => msgBoxCommon( - gFFI.dialogManager, - 'Confirmation', - Text( - translate('confirm-clear-shortcuts-inhibitor-permission-tip'), - ), - [ - dialogButton('OK', onPressed: onConfirm), - dialogButton('Cancel', - onPressed: () => gFFI.dialogManager.dismissAll()) - ]); - - return Column(children: [ - Obx( - () => _clearShortcutsInhibitorFailedMsg.value.isEmpty - ? Offstage() - : Align( - alignment: Alignment.topLeft, - child: Text(_clearShortcutsInhibitorFailedMsg.value, - style: DefaultTextStyle.of(context) - .style - .copyWith(color: Colors.red)) - .marginOnly(bottom: 10.0)), - ), - _Button( - 'Reset keyboard shortcuts permission', - showConfirmMsgBox, - tip: 'clear-shortcuts-inhibitor-permission-tip', - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).colorScheme.error.withOpacity(0.75)), - ), - ), - ]); - } -} - -// ignore: non_constant_identifier_names -Widget _Button(String label, Function() onPressed, - {bool enabled = true, String? tip, ButtonStyle? style}) { - var button = ElevatedButton( - onPressed: enabled ? onPressed : null, - child: Text( - translate(label), - ).marginSymmetric(horizontal: 15), - style: style, - ); - StatefulWidget child; - if (tip == null) { - child = button; - } else { - child = Tooltip(message: translate(tip), child: button); - } - return Row(children: [ - child, - ]).marginOnly(left: _kContentHMargin); -} - -// ignore: non_constant_identifier_names -Widget _SubButton(String label, Function() onPressed, [bool enabled = true]) { - return Row( - children: [ - ElevatedButton( - onPressed: enabled ? onPressed : null, - child: Text( - translate(label), - ).marginSymmetric(horizontal: 15), - ), - ], - ).marginOnly(left: _kContentHSubMargin); -} - -// ignore: non_constant_identifier_names -Widget _SubLabeledWidget(BuildContext context, String label, Widget child, - {bool enabled = true}) { - return Row( - children: [ - Text( - '${translate(label)}: ', - style: TextStyle(color: disabledTextColor(context, enabled)), - ), - SizedBox( - width: 10, - ), - child, - ], - ).marginOnly(left: _kContentHSubMargin); -} - -Widget _lock( - bool locked, - String label, - Function() onUnlock, -) { - return Offstage( - offstage: !locked, - child: Row( - children: [ - Flexible( - child: SizedBox( - width: _kCardFixedWidth, - child: Card( - child: ElevatedButton( - child: SizedBox( - height: 25, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.security_sharp, - size: 20, - ), - Text(translate(label)).marginOnly(left: 5), - ]).marginSymmetric(vertical: 2)), - onPressed: () async { - final unlockPin = bind.mainGetUnlockPin(); - if (unlockPin.isEmpty || isUnlockPinDisabled()) { - bool checked = await callMainCheckSuperUserPermission(); - if (checked) { - onUnlock(); - } - } else { - checkUnlockPinDialog(unlockPin, onUnlock); - } - }, - ).marginSymmetric(horizontal: 2, vertical: 4), - ).marginOnly(left: _kCardLeftMargin), - ).marginOnly(top: 10), - ), - ], - )); -} - -_LabeledTextField( - BuildContext context, - String label, - TextEditingController controller, - String errorText, - bool enabled, - bool secure) { - return Table( - columnWidths: const { - 0: FixedColumnWidth(150), - 1: FlexColumnWidth(), - }, - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - TableRow( - children: [ - Padding( - padding: const EdgeInsets.only(right: 10), - child: Text( - '${translate(label)}:', - textAlign: TextAlign.right, - style: TextStyle( - fontSize: 16, - color: disabledTextColor(context, enabled), - ), - ), - ), - TextField( - controller: controller, - enabled: enabled, - obscureText: secure, - autocorrect: false, - decoration: InputDecoration( - errorText: errorText.isNotEmpty ? errorText : null, - ), - style: TextStyle( - color: disabledTextColor(context, enabled), - ), - ).workaroundFreezeLinuxMint(), - ], - ), - ], - ).marginOnly(bottom: 8); -} - -class _CountDownButton extends StatefulWidget { - _CountDownButton({ - Key? key, - required this.text, - required this.second, - required this.onPressed, - }) : super(key: key); - final String text; - final VoidCallback? onPressed; - final int second; - - @override - State<_CountDownButton> createState() => _CountDownButtonState(); -} - -class _CountDownButtonState extends State<_CountDownButton> { - bool _isButtonDisabled = false; - - late int _countdownSeconds = widget.second; - - Timer? _timer; - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _startCountdownTimer() { - _timer = Timer.periodic(Duration(seconds: 1), (timer) { - if (_countdownSeconds <= 0) { - setState(() { - _isButtonDisabled = false; - }); - timer.cancel(); - } else { - setState(() { - _countdownSeconds--; - }); - } - }); - } - - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: _isButtonDisabled - ? null - : () { - widget.onPressed?.call(); - setState(() { - _isButtonDisabled = true; - _countdownSeconds = widget.second; - }); - _startCountdownTimer(); - }, - child: Text( - _isButtonDisabled ? '$_countdownSeconds s' : translate(widget.text), - ), - ); - } -} - -//#endregion - -//#region dialogs - -void changeSocks5Proxy() async { - var socks = await bind.mainGetSocks(); - - String proxy = ''; - String proxyMsg = ''; - String username = ''; - String password = ''; - if (socks.length == 3) { - proxy = socks[0]; - username = socks[1]; - password = socks[2]; - } - var proxyController = TextEditingController(text: proxy); - var userController = TextEditingController(text: username); - var pwdController = TextEditingController(text: password); - RxBool obscure = true.obs; - - // proxy settings - // The following option is a not real key, it is just used for custom client advanced settings. - const String optionProxyUrl = "proxy-url"; - final isOptFixed = isOptionFixed(optionProxyUrl); - - var isInProgress = false; - gFFI.dialogManager.show((setState, close, context) { - submit() async { - setState(() { - proxyMsg = ''; - isInProgress = true; - }); - cancel() { - setState(() { - isInProgress = false; - }); - } - - proxy = proxyController.text.trim(); - username = userController.text.trim(); - password = pwdController.text.trim(); - - if (proxy.isNotEmpty) { - String domainPort = proxy; - if (domainPort.contains('://')) { - domainPort = domainPort.split('://')[1]; - } - proxyMsg = translate(await bind.mainTestIfValidServer( - server: domainPort, testWithProxy: false)); - if (proxyMsg.isEmpty) { - // ignore - } else { - cancel(); - return; - } - } - await bind.mainSetSocks( - proxy: proxy, username: username, password: password); - close(); - } - - return CustomAlertDialog( - title: Text(translate('Socks5/Http(s) Proxy')), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (!isMobile) - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Align( - alignment: Alignment.centerRight, - child: Row( - children: [ - Text( - translate('Server'), - ).marginOnly(right: 4), - Tooltip( - waitDuration: Duration(milliseconds: 0), - message: translate("default_proxy_tip"), - child: Icon( - Icons.help_outline_outlined, - size: 16, - color: Theme.of(context) - .textTheme - .titleLarge - ?.color - ?.withOpacity(0.5), - ), - ), - ], - )).marginOnly(right: 10), - ), - Expanded( - child: TextField( - decoration: InputDecoration( - errorText: proxyMsg.isNotEmpty ? proxyMsg : null, - labelText: isMobile ? translate('Server') : null, - helperText: - isMobile ? translate("default_proxy_tip") : null, - helperMaxLines: isMobile ? 3 : null, - ), - controller: proxyController, - autofocus: true, - enabled: !isOptFixed, - ).workaroundFreezeLinuxMint(), - ), - ], - ).marginOnly(bottom: 8), - Row( - children: [ - if (!isMobile) - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - '${translate("Username")}:', - textAlign: TextAlign.right, - ).marginOnly(right: 10)), - Expanded( - child: TextField( - controller: userController, - decoration: InputDecoration( - labelText: isMobile ? translate('Username') : null, - ), - enabled: !isOptFixed, - ).workaroundFreezeLinuxMint(), - ), - ], - ).marginOnly(bottom: 8), - Row( - children: [ - if (!isMobile) - ConstrainedBox( - constraints: const BoxConstraints(minWidth: 140), - child: Text( - '${translate("Password")}:', - textAlign: TextAlign.right, - ).marginOnly(right: 10)), - Expanded( - child: Obx(() => TextField( - obscureText: obscure.value, - decoration: InputDecoration( - labelText: isMobile ? translate('Password') : null, - suffixIcon: IconButton( - onPressed: () => obscure.value = !obscure.value, - icon: Icon(obscure.value - ? Icons.visibility_off - : Icons.visibility))), - controller: pwdController, - enabled: !isOptFixed, - maxLength: bind.mainMaxEncryptLen(), - ).workaroundFreezeLinuxMint()), - ), - ], - ), - // NOT use Offstage to wrap LinearProgressIndicator - if (isInProgress) - const LinearProgressIndicator().marginOnly(top: 8), - ], - ), - ), - actions: [ - dialogButton('Cancel', onPressed: close, isOutline: true), - if (!isOptFixed) dialogButton('OK', onPressed: submit), - ], - onSubmit: submit, - onCancel: close, - ); - }); -} - -//#endregion diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart deleted file mode 100644 index 6440e55a1..000000000 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_setting_page.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:window_manager/window_manager.dart'; -// import 'package:flutter/services.dart'; - -import '../../common/shared_state.dart'; - -class DesktopTabPage extends StatefulWidget { - const DesktopTabPage({Key? key}) : super(key: key); - - @override - State createState() => _DesktopTabPageState(); - - static void onAddSetting( - {SettingsTabKey initialPage = SettingsTabKey.general}) { - try { - DesktopTabController tabController = Get.find(); - tabController.add(TabInfo( - key: kTabLabelSettingPage, - label: kTabLabelSettingPage, - selectedIcon: Icons.build_sharp, - unselectedIcon: Icons.build_outlined, - page: DesktopSettingPage( - key: const ValueKey(kTabLabelSettingPage), - initialTabkey: initialPage, - ))); - } catch (e) { - debugPrintStack(label: '$e'); - } - } -} - -class _DesktopTabPageState extends State { - final tabController = DesktopTabController(tabType: DesktopTabType.main); - - _DesktopTabPageState() { - RemoteCountState.init(); - Get.put(tabController); - tabController.add(TabInfo( - key: kTabLabelHomePage, - label: kTabLabelHomePage, - selectedIcon: Icons.home_sharp, - unselectedIcon: Icons.home_outlined, - closable: false, - page: DesktopHomePage( - key: const ValueKey(kTabLabelHomePage), - ))); - if (bind.isIncomingOnly()) { - tabController.onSelected = (key) { - if (key == kTabLabelHomePage) { - windowManager.setSize(getIncomingOnlyHomeSize()); - setResizable(false); - } else { - windowManager.setSize(getIncomingOnlySettingsSize()); - setResizable(true); - } - }; - } - } - - @override - void initState() { - super.initState(); - // HardwareKeyboard.instance.addHandler(_handleKeyEvent); - } - - /* - bool _handleKeyEvent(KeyEvent event) { - if (!mouseIn && event is KeyDownEvent) { - print('key down: ${event.logicalKey}'); - shouldBeBlocked(_block, canBeBlocked); - } - return false; // allow it to propagate - } - */ - - @override - void dispose() { - // HardwareKeyboard.instance.removeHandler(_handleKeyEvent); - Get.delete(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final tabWidget = Container( - child: Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: DesktopTab( - controller: tabController, - tail: Offstage( - offstage: bind.isIncomingOnly() || bind.isDisableSettings(), - child: ActionIcon( - message: 'Settings', - icon: IconFont.menu, - onTap: DesktopTabPage.onAddSetting, - isClose: false, - ), - ), - ))); - return isMacOS || kUseCompatibleUiMode - ? tabWidget - : Obx( - () => DragToResizeArea( - resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - enableResizeEdges: windowManagerEnableResizeEdges, - child: tabWidget, - ), - ); - } -} diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart deleted file mode 100644 index cf97351b3..000000000 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ /dev/null @@ -1,1694 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:extended_text/extended_text.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/desktop/widgets/dragable_divider.dart'; -import 'package:percent_indicator/percent_indicator.dart'; -import 'package:desktop_drop/desktop_drop.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; -import 'package:flutter_hbb/desktop/widgets/list_search_action_listener.dart'; -import 'package:flutter_hbb/desktop/widgets/menu_button.dart'; -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:flutter_hbb/web/dummy.dart' - if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart'; - -import '../../consts.dart'; -import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; -import '../../common.dart'; -import '../../models/model.dart'; -import '../../models/platform_model.dart'; -import '../widgets/popup_menu.dart'; - -/// status of location bar -enum LocationStatus { - /// normal bread crumb bar - bread, - - /// show path text field - pathLocation, - - /// show file search bar text field - fileSearchBar -} - -/// The status of currently focused scope of the mouse -enum MouseFocusScope { - /// Mouse is in local field. - local, - - /// Mouse is in remote field. - remote, - - /// Mouse is not in local field, remote neither. - none -} - -class FileManagerPage extends StatefulWidget { - FileManagerPage( - {Key? key, - required this.id, - required this.password, - required this.isSharedPassword, - this.tabController, - this.connToken, - this.forceRelay}) - : super(key: key); - final String id; - final String? password; - final bool? isSharedPassword; - final bool? forceRelay; - final String? connToken; - final DesktopTabController? tabController; - final SimpleWrapper?> _lastState = SimpleWrapper(null); - - FFI get ffi => (_lastState.value! as _FileManagerPageState)._ffi; - - @override - State createState() { - final state = _FileManagerPageState(); - _lastState.value = state; - return state; - } -} - -class _FileManagerPageState extends State - with AutomaticKeepAliveClientMixin, WidgetsBindingObserver { - final _mouseFocusScope = Rx(MouseFocusScope.none); - - final _dropMaskVisible = false.obs; // TODO impl drop mask - final _overlayKeyState = OverlayKeyState(); - final _uniqueKey = UniqueKey(); - - late FFI _ffi; - - FileModel get model => _ffi.fileModel; - JobController get jobController => model.jobController; - - @override - void initState() { - super.initState(); - _ffi = FFI(null); - _ffi.start(widget.id, - isFileTransfer: true, - password: widget.password, - isSharedPassword: widget.isSharedPassword, - connToken: widget.connToken, - forceRelay: widget.forceRelay); - WidgetsBinding.instance.addPostFrameCallback((_) { - _ffi.dialogManager - .showLoading(translate('Connecting...'), onCancel: closeConnection); - }); - Get.put(_ffi, tag: 'ft_${widget.id}'); - WakelockManager.enable(_uniqueKey); - if (isWeb) { - _ffi.ffiModel.updateEventListener(_ffi.sessionId, widget.id); - } - debugPrint("File manager page init success with id ${widget.id}"); - _ffi.dialogManager.setOverlayState(_overlayKeyState); - // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController?.onSelected?.call(widget.id); - }); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - model.close().whenComplete(() { - _ffi.close(); - _ffi.dialogManager.dismissAll(); - WakelockManager.disable(_uniqueKey); - Get.delete(tag: 'ft_${widget.id}'); - }); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - bool get wantKeepAlive => true; - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - jobController.jobTable.refresh(); - } - } - - Widget willPopScope(Widget child) { - if (isWeb) { - return WillPopScope( - onWillPop: () async { - clientClose(_ffi.sessionId, _ffi); - return false; - }, - child: child, - ); - } else { - return child; - } - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Overlay(key: _overlayKeyState.key, initialEntries: [ - OverlayEntry(builder: (_) { - return willPopScope(Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: Row( - children: [ - if (!isWeb) - 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 dropArea(FileManagerView fileView) { - return DropTarget( - onDragDone: (detail) => - handleDragDone(detail, fileView.controller.isLocal), - onDragEntered: (enter) { - _dropMaskVisible.value = true; - }, - onDragExited: (exit) { - _dropMaskVisible.value = false; - }, - child: fileView); - } - - Widget generateCard(Widget child) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.all( - Radius.circular(15.0), - ), - ), - child: child, - ); - } - - /// transfer status list - /// watch transfer status - Widget statusList() { - Widget getIcon(JobProgress job) { - final color = Theme.of(context).tabBarTheme.labelColor; - switch (job.type) { - case JobType.deleteDir: - case JobType.deleteFile: - return Icon(Icons.delete_outline, color: color); - default: - return Transform.rotate( - angle: isWeb - ? job.isRemoteToLocal - ? pi / 2 - : pi / 2 * 3 - : job.isRemoteToLocal - ? pi - : 0, - child: Icon(Icons.arrow_forward_ios, color: color), - ); - } - } - - statusListView(List jobs) => ListView.builder( - controller: ScrollController(), - itemBuilder: (BuildContext context, int index) { - final item = jobs[index]; - final status = item.getStatus(); - return Padding( - padding: const EdgeInsets.only(bottom: 5), - child: generateCard( - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - getIcon(item) - .marginSymmetric(horizontal: 10, vertical: 12), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Tooltip( - waitDuration: Duration(milliseconds: 500), - message: item.jobName, - child: ExtendedText( - item.jobName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - overflowWidget: TextOverflowWidget( - child: Text("..."), - position: TextOverflowPosition.start), - ), - ), - Tooltip( - waitDuration: Duration(milliseconds: 500), - message: status, - child: Text(status, - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - )).marginOnly(top: 6), - ), - Offstage( - offstage: item.type != JobType.transfer || - item.state != JobState.inProgress, - child: LinearPercentIndicator( - animateFromLastPercent: true, - center: Text(item.percentText), - barRadius: Radius.circular(15), - percent: item.percent, - progressColor: MyTheme.accent, - backgroundColor: Theme.of(context).hoverColor, - lineHeight: kDesktopFileTransferRowHeight, - ).paddingSymmetric(vertical: 8), - ), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Offstage( - offstage: item.state != JobState.paused, - child: MenuButton( - tooltip: translate("Resume"), - onPressed: () { - jobController.resumeJob(item.id); - }, - child: SvgPicture.asset( - "assets/refresh.svg", - colorFilter: svgColor(Colors.white), - ), - color: MyTheme.accent, - hoverColor: MyTheme.accent80, - ), - ), - MenuButton( - tooltip: translate("Delete"), - child: SvgPicture.asset( - "assets/close.svg", - colorFilter: svgColor(Colors.white), - ), - onPressed: () { - jobController.jobTable.removeAt(index); - jobController.cancelJob(item.id); - }, - color: MyTheme.accent, - hoverColor: MyTheme.accent80, - ), - ], - ).marginAll(12), - ], - ), - ], - ), - ), - ); - }, - 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", - colorFilter: svgColor( - Theme.of(context).tabBarTheme.labelColor), - height: 40, - ).paddingOnly(bottom: 10), - Text( - translate("No transfers in progress"), - textAlign: TextAlign.center, - textScaler: TextScaler.linear(1.20), - style: TextStyle( - color: - Theme.of(context).tabBarTheme.labelColor), - ), - ], - ), - ), - ) - : statusListView(jobController.jobTable), - )), - ); - } - - 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 = 0.0.obs; - final _modifiedColWidth = 0.0.obs; - final _sizeColWidth = 0.0.obs; - final _fileListScrollController = ScrollController(); - final _globalHeaderKey = GlobalKey(); - - /// [_lastClickTime], [_lastClickEntry] help to handle double click - var _lastClickTime = - DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000; - Entry? _lastClickEntry; - - double? _windowWidthPrev; - double _fileTransferMinimumWidth = 0.0; - - 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) { - _handleColumnPorportions(); - 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 _handleColumnPorportions() { - final windowWidthNow = MediaQuery.of(context).size.width; - if (_windowWidthPrev == null) { - _windowWidthPrev = windowWidthNow; - final defaultColumnWidth = windowWidthNow * 0.115; - _fileTransferMinimumWidth = defaultColumnWidth / 3; - _nameColWidth.value = defaultColumnWidth; - _modifiedColWidth.value = defaultColumnWidth; - _sizeColWidth.value = defaultColumnWidth; - } - - if (_windowWidthPrev != windowWidthNow) { - final difference = windowWidthNow / _windowWidthPrev!; - _windowWidthPrev = windowWidthNow; - _fileTransferMinimumWidth *= difference; - _nameColWidth.value *= difference; - _modifiedColWidth.value *= difference; - _sizeColWidth.value *= difference; - } - } - - 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() { - var uploadButtonTapPosition = RelativeRect.fill; - RxBool isUploadFolder = - (bind.mainGetLocalOption(key: 'upload-folder-button') == 'Y').obs; - return Container( - child: Column( - children: [ - // symbols - PreferredSize( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8)), - color: MyTheme.accent, - ), - padding: EdgeInsets.all(8.0), - child: FutureBuilder( - future: bind.sessionGetPlatform( - sessionId: _ffi.sessionId, - isRemote: !isLocal), - builder: (context, snapshot) { - if (snapshot.hasData && - snapshot.data!.isNotEmpty) { - return getPlatformImage('${snapshot.data}'); - } else { - return CircularProgressIndicator( - color: Theme.of(context) - .tabBarTheme - .labelColor, - ); - } - })), - Text(isLocal - ? translate("Local Computer") - : translate("Remote Computer")) - .marginOnly(left: 8.0) - ], - ), - preferredSize: Size(double.infinity, 70)) - .paddingOnly(bottom: 15), - // buttons - Row( - children: [ - Row( - children: [ - MenuButton( - tooltip: translate('Back'), - padding: EdgeInsets.only( - right: 3, - ), - child: RotatedBox( - quarterTurns: 2, - child: SvgPicture.asset( - "assets/arrow.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - onPressed: () { - selectedItems.clear(); - controller.goBack(); - }, - ), - MenuButton( - tooltip: translate('Parent directory'), - child: RotatedBox( - quarterTurns: 3, - child: SvgPicture.asset( - "assets/arrow.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - onPressed: () { - selectedItems.clear(); - controller.goToParentDirectory(); - }, - ), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 3.0), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.all( - Radius.circular(8.0), - ), - ), - child: Padding( - padding: EdgeInsets.symmetric(vertical: 2.5), - child: GestureDetector( - onTap: () { - _locationStatus.value = - _locationStatus.value == LocationStatus.bread - ? LocationStatus.pathLocation - : LocationStatus.bread; - Future.delayed(Duration.zero, () { - if (_locationStatus.value == - LocationStatus.pathLocation) { - _locationNode.requestFocus(); - } - }); - }, - child: Obx( - () => Container( - child: Row( - children: [ - Expanded( - child: _locationStatus.value == - LocationStatus.bread - ? buildBread() - : buildPathLocation()), - ], - ), - ), - ), - ), - ), - ), - ), - ), - Obx(() { - switch (_locationStatus.value) { - case LocationStatus.bread: - return MenuButton( - tooltip: translate('Search'), - onPressed: () { - _locationStatus.value = LocationStatus.fileSearchBar; - Future.delayed( - Duration.zero, () => _locationNode.requestFocus()); - }, - child: SvgPicture.asset( - "assets/search.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - ); - case LocationStatus.pathLocation: - return MenuButton( - onPressed: null, - child: SvgPicture.asset( - "assets/close.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).disabledColor, - hoverColor: Theme.of(context).hoverColor, - ); - case LocationStatus.fileSearchBar: - return MenuButton( - tooltip: translate('Clear'), - onPressed: () { - onSearchText("", isLocal); - _locationStatus.value = LocationStatus.bread; - }, - child: SvgPicture.asset( - "assets/close.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - ); - } - }), - MenuButton( - tooltip: translate('Refresh File'), - padding: EdgeInsets.only( - left: 3, - ), - onPressed: () { - controller.refresh(); - }, - child: SvgPicture.asset( - "assets/refresh.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - ), - ], - ), - Row( - textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl, - children: [ - Expanded( - child: Row( - mainAxisAlignment: - isLocal ? MainAxisAlignment.start : MainAxisAlignment.end, - children: [ - MenuButton( - tooltip: translate('Home'), - padding: EdgeInsets.only( - right: 3, - ), - onPressed: () { - controller.goToHomeDirectory(); - }, - child: SvgPicture.asset( - "assets/home.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - ), - MenuButton( - tooltip: translate('Create Folder'), - onPressed: () { - final name = TextEditingController(); - String? errorText; - _ffi.dialogManager.show((setState, close, context) { - name.addListener(() { - if (errorText != null) { - setState(() { - errorText = null; - }); - } - }); - submit() { - if (name.value.text.isNotEmpty) { - if (!PathUtil.validName(name.value.text, - controller.options.value.isWindows)) { - setState(() { - errorText = translate("Invalid folder name"); - }); - return; - } - controller.createDir(PathUtil.join( - controller.directory.value.path, - name.value.text, - controller.options.value.isWindows, - )); - close(); - } - } - - cancel() => close(false); - return CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset("assets/folder_new.svg", - colorFilter: svgColor(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", - ), - errorText: errorText, - ), - controller: name, - autofocus: true, - ).workaroundFreezeLinuxMint(), - ], - ), - 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, - ); - }); - }, - child: SvgPicture.asset( - "assets/folder_new.svg", - colorFilter: - svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - ), - Obx(() => MenuButton( - tooltip: translate('Delete'), - onPressed: SelectedItems.valid(selectedItems.items) - ? () async { - await (controller - .removeAction(selectedItems)); - selectedItems.clear(); - } - : null, - child: SvgPicture.asset( - "assets/trash.svg", - colorFilter: svgColor( - Theme.of(context).tabBarTheme.labelColor), - ), - color: Theme.of(context).cardColor, - hoverColor: Theme.of(context).hoverColor, - )), - menu(isLocal: isLocal), - ], - ), - ), - if (isWeb) - 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: () => - {webselectFiles(is_folder: isUploadFolder.value)}, - label: InkWell( - hoverColor: Colors.transparent, - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - focusColor: Colors.transparent, - onTapDown: (e) { - final x = e.globalPosition.dx; - final y = e.globalPosition.dy; - uploadButtonTapPosition = - RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () async { - final value = await showMenu( - context: context, - position: uploadButtonTapPosition, - items: [ - PopupMenuItem( - value: false, - child: Text(translate('Upload files')), - ), - PopupMenuItem( - value: true, - child: Text(translate('Upload folder')), - ), - ]); - if (value != null) { - isUploadFolder.value = value; - bind.mainSetLocalOption( - key: 'upload-folder-button', - value: value ? 'Y' : ''); - webselectFiles(is_folder: value); - } - }, - child: Icon(Icons.arrow_drop_down), - ), - icon: Text( - translate(isUploadFolder.isTrue - ? 'Upload folder' - : 'Upload files'), - textAlign: TextAlign.right, - style: TextStyle( - color: Colors.white, - ), - ).marginOnly(left: 8), - )).marginOnly(left: 16), - 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: 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, - ), - ) - : isWeb - ? Offstage() - : RotatedBox( - quarterTurns: 2, - child: SvgPicture.asset( - "assets/arrow.svg", - colorFilter: svgColor( - 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", - colorFilter: svgColor(selectedItems.items.isEmpty - ? Theme.of(context).brightness == - Brightness.light - ? MyTheme.grayBg - : MyTheme.darkGray - : Colors.white), - ) - : Text( - translate(isWeb ? 'Download' : 'Receive'), - style: TextStyle( - color: selectedItems.items.isEmpty - ? Theme.of(context).brightness == - Brightness.light - ? MyTheme.grayBg - : MyTheme.darkGray - : Colors.white, - ), - ), - )), - ], - ).marginOnly(top: 8.0) - ], - ), - ); - } - - 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.showHidden; - }, - 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( - tooltip: translate('More'), - 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", - colorFilter: svgColor(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; - Rx rightClickEntry = Rx(null); - - 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", "")} "; - var secondaryPosition = RelativeRect.fromLTRB(0, 0, 0, 0); - onTap() { - final items = selectedItems; - // handle double click - if (_checkDoubleClick(entry)) { - controller.openDirectory(entry.path); - items.clear(); - return; - } - _onSelectedChanged(items, filteredEntries, entry, isLocal); - } - - onSecondaryTap() { - final items = [ - if (!entry.isDrive && - versionCmp(_ffi.ffiModel.pi.version, "1.3.0") >= 0) - mod_menu.PopupMenuItem( - child: Text(translate("Rename")), - height: CustomPopupMenuTheme.height, - onTap: () { - controller.renameAction(entry, isLocal); - }, - ) - ]; - if (items.isNotEmpty) { - rightClickEntry.value = entry; - final future = mod_menu.showMenu( - context: context, - position: secondaryPosition, - items: items, - ); - future.then((value) { - rightClickEntry.value = null; - }); - future.onError((error, stackTrace) { - rightClickEntry.value = null; - }); - } - } - - onSecondaryTapDown(details) { - secondaryPosition = RelativeRect.fromLTRB( - details.globalPosition.dx, - details.globalPosition.dy, - details.globalPosition.dx, - details.globalPosition.dy); - } - - return Padding( - padding: EdgeInsets.symmetric(vertical: 1), - child: Obx(() => Container( - decoration: BoxDecoration( - color: selectedItems.items.contains(entry) - ? MyTheme.button - : Theme.of(context).cardColor, - borderRadius: BorderRadius.all( - Radius.circular(5.0), - ), - border: rightClickEntry.value == entry - ? Border.all( - color: MyTheme.button, - width: 1.0, - ) - : null, - ), - 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", - colorFilter: svgColor( - Theme.of(context) - .tabBarTheme - .labelColor), - ), - Expanded( - child: Text(entry.name.nonBreaking, - style: TextStyle( - color: selectedItems.items - .contains(entry) - ? Colors.white - : null), - overflow: - TextOverflow.ellipsis)) - ]), - )), - ), - onTap: onTap, - onSecondaryTap: onSecondaryTap, - onSecondaryTapDown: onSecondaryTapDown, - ), - 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: selectedItems.items - .contains(entry) - ? Colors.white70 - : MyTheme.darkGray, - ), - )), - ), - ), - onTap: onTap, - onSecondaryTap: onSecondaryTap, - onSecondaryTapDown: onSecondaryTapDown, - ), - // 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: - selectedItems.items.contains(entry) - ? Colors.white70 - : MyTheme.darkGray), - ), - ), - onTap: onTap, - onSecondaryTap: onSecondaryTap, - onSecondaryTapDown: onSecondaryTapDown, - ), - ), - ], - ), - ), - ), - ], - ))), - ); - }).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) || - RawKeyboard.instance.keysPressed - .contains(LogicalKeyboardKey.controlRight); - final isShiftDown = RawKeyboard.instance.keysPressed - .contains(LogicalKeyboardKey.shiftLeft) || - RawKeyboard.instance.keysPressed - .contains(LogicalKeyboardKey.shiftRight); - 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; - } - - void _onDrag(double dx, RxDouble column1, RxDouble column2) { - if (column1.value + dx <= _fileTransferMinimumWidth || - column2.value - dx <= _fileTransferMinimumWidth) { - return; - } - column1.value += dx; - column2.value -= dx; - column1.value = max(_fileTransferMinimumWidth, column1.value); - column2.value = max(_fileTransferMinimumWidth, column2.value); - } - - Widget _buildFileBrowserHeader(BuildContext context) { - final padding = EdgeInsets.all(1.0); - return SizedBox( - key: _globalHeaderKey, - height: kDesktopFileTransferHeaderHeight, - child: Row( - children: [ - Obx( - () => headerItemFunc( - _nameColWidth.value, SortBy.name, translate("Name")), - ), - DraggableDivider( - axis: Axis.vertical, - onPointerMove: (dx) => - _onDrag(dx, _nameColWidth, _modifiedColWidth), - padding: padding, - ), - Obx( - () => headerItemFunc(_modifiedColWidth.value, SortBy.modified, - translate("Modified")), - ), - DraggableDivider( - axis: Axis.vertical, - onPointerMove: (dx) => - _onDrag(dx, _modifiedColWidth, _sizeColWidth), - padding: padding), - Expanded( - child: headerItemFunc( - _sizeColWidth.value, SortBy.size, translate("Size"))) - ], - ), - ); - } - - 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: [ - Expanded( - child: Text( - name, - style: headerTextStyle, - overflow: TextOverflow.ellipsis, - ).marginOnly(left: 4), - ), - ascending.value != null - ? Icon( - ascending.value! - ? Icons.keyboard_arrow_up_rounded - : Icons.keyboard_arrow_down_rounded, - ) - : SizedBox() - ], - ), - ), - ), () { - if (controller.sortBy.value == sortBy) { - return controller.sortAscending.obs; - } else { - return Rx(null); - } - }()); - } - - Widget buildBread() { - final items = getPathBreadCrumbItems(isLocal, (list) { - var path = ""; - for (var item in list) { - path = PathUtil.join(path, item, controller.options.value.isWindows); - } - controller.openDirectory(path); - }); - - return items.isEmpty - ? Offstage() - : Row( - key: _locationBarKey, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Listener( - // handle mouse wheel - onPointerSignal: (e) { - if (e is PointerScrollEvent) { - final sc = _breadCrumbScroller; - final scale = isWindows ? 2 : 4; - sc.jumpTo(sc.offset + e.scrollDelta.dy / scale); - } - }, - child: BreadCrumb( - items: items, - divider: const Icon(Icons.keyboard_arrow_right_rounded), - overflow: ScrollableOverflow( - controller: _breadCrumbScroller, - ), - ), - ), - ), - ActionIcon( - message: "", - icon: Icons.keyboard_arrow_down_rounded, - onTap: () async { - final renderBox = _locationBarKey.currentContext - ?.findRenderObject() as RenderBox; - _locationBarKey.currentContext?.size; - - final size = renderBox.size; - final offset = renderBox.localToGlobal(Offset.zero); - - final x = offset.dx; - final y = offset.dy + size.height + 1; - - final isPeerWindows = controller.options.value.isWindows; - final List menuItems = [ - MenuEntryButton( - childBuilder: (TextStyle? style) => isPeerWindows - ? buildWindowsThisPC(context, style) - : Text( - '/', - style: style, - ), - proc: () { - controller.openDirectory('/'); - }, - dismissOnClicked: true), - MenuEntryDivider() - ]; - if (isPeerWindows) { - var loadingTag = ""; - if (!isLocal) { - loadingTag = _ffi.dialogManager.showLoading("Waiting"); - } - try { - 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) => - Row(children: [ - Image( - image: iconHardDrive, - fit: BoxFit.scaleDown, - color: Theme.of(context) - .iconTheme - .color - ?.withOpacity(0.7)), - SizedBox(width: 10), - Text( - entry.name, - style: style, - ) - ]), - proc: () { - controller.openDirectory('${entry.name}\\'); - }, - dismissOnClicked: true)); - } - menuItems.add(MenuEntryDivider()); - } catch (e) { - debugPrint("buildBread fetchDirectory err=$e"); - } finally { - if (!isLocal) { - _ffi.dialogManager.dismissByTag(loadingTag); - } - } - } - mod_menu.showMenu( - context: context, - position: RelativeRect.fromLTRB(x, y, x, y), - elevation: 4, - items: menuItems - .map((e) => e.build( - context, - MenuConfig( - commonColor: - CustomPopupMenuTheme.commonColor, - height: CustomPopupMenuTheme.height, - dividerHeight: - CustomPopupMenuTheme.dividerHeight, - boxWidth: size.width))) - .expand((i) => i) - .toList()); - }, - iconSize: 20, - ) - ]); - } - - List getPathBreadCrumbItems( - bool isLocal, void Function(List) onPressed) { - final path = controller.directory.value.path; - final breadCrumbList = List.empty(growable: true); - final isWindows = controller.options.value.isWindows; - if (isWindows && path == '/') { - breadCrumbList.add(BreadCrumbItem( - content: TextButton( - child: buildWindowsThisPC(context), - style: ButtonStyle( - minimumSize: MaterialStateProperty.all(Size(0, 0))), - onPressed: () => onPressed(['/'])) - .marginSymmetric(horizontal: 4))); - } else { - final list = PathUtil.split(path, isWindows); - breadCrumbList.addAll( - list.asMap().entries.map( - (e) => BreadCrumbItem( - content: TextButton( - child: Text(e.value), - style: ButtonStyle( - minimumSize: MaterialStateProperty.all( - Size(0, 0), - ), - ), - onPressed: () => onPressed( - list.sublist(0, e.key + 1), - ), - ).marginSymmetric(horizontal: 4), - ), - ), - ); - } - return breadCrumbList; - } - - breadCrumbScrollToEnd() { - Future.delayed(Duration(milliseconds: 200), () { - if (_breadCrumbScroller.hasClients) { - _breadCrumbScroller.animateTo( - _breadCrumbScroller.position.maxScrollExtent, - duration: Duration(milliseconds: 200), - curve: Curves.fastLinearToSlowEaseIn); - } - }); - } - - 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 - ? "assets/folder.svg" - : "assets/search.svg", - colorFilter: svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - Expanded( - child: TextField( - focusNode: _locationNode, - decoration: InputDecoration( - border: InputBorder.none, - isDense: true, - prefix: Padding( - padding: EdgeInsets.only(left: 4.0), - ), - ), - controller: textController, - onSubmitted: (path) { - controller.openDirectory(path); - }, - onChanged: _locationStatus.value == LocationStatus.fileSearchBar - ? (searchText) => onSearchText(searchText, isLocal) - : null, - ).workaroundFreezeLinuxMint(), - ) - ], - ); - } - - // 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 deleted file mode 100644 index ed3e9682d..000000000 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ /dev/null @@ -1,177 +0,0 @@ -import 'dart:convert'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/pages/file_manager_page.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:get/get.dart'; - -import '../../models/platform_model.dart'; - -/// File Transfer for multi tabs -class FileManagerTabPage extends StatefulWidget { - final Map params; - - const FileManagerTabPage({Key? key, required this.params}) : super(key: key); - - @override - State createState() => _FileManagerTabPageState(params); -} - -class _FileManagerTabPageState extends State { - DesktopTabController get tabController => Get.find(); - - static const IconData selectedIcon = Icons.file_copy_sharp; - static const IconData unselectedIcon = Icons.file_copy_outlined; - - _FileManagerTabPageState(Map params) { - Get.put(DesktopTabController(tabType: DesktopTabType.fileTransfer)); - tabController.onSelected = (id) { - WindowController.fromWindowId(windowId()) - .setTitle(getWindowNameWithId(id)); - }; - tabController.onRemoved = (_, id) => onRemoveId(id); - tabController.add(TabInfo( - key: params['id'], - label: params['id'], - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: params['id'], - tabController: tabController, - )) { - return; - } - tabController.closeBy(params['id']); - }, - page: FileManagerPage( - key: ValueKey(params['id']), - id: params['id'], - password: params['password'], - isSharedPassword: params['isSharedPassword'], - tabController: tabController, - forceRelay: params['forceRelay'], - connToken: params['connToken'], - ))); - } - - @override - void initState() { - super.initState(); - - rustDeskWinManager.setMethodHandler((call, fromWindowId) async { - debugPrint( - "[FileTransfer] call ${call.method} with args ${call.arguments} from window $fromWindowId to ${windowId()}"); - // for simplify, just replace connectionId - if (call.method == kWindowEventNewFileTransfer) { - final args = jsonDecode(call.arguments); - final id = args['id']; - windowOnTop(windowId()); - tabController.add(TabInfo( - key: id, - label: id, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: id, - tabController: tabController, - )) { - return; - } - tabController.closeBy(id); - }, - page: FileManagerPage( - key: ValueKey(id), - id: id, - password: args['password'], - isSharedPassword: args['isSharedPassword'], - tabController: tabController, - forceRelay: args['forceRelay'], - connToken: args['connToken'], - ))); - } else if (call.method == "onDestroy") { - tabController.clear(); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } - }); - Future.delayed(Duration.zero, () { - restoreWindowPosition(WindowType.FileTransfer, windowId: windowId()); - }); - } - - @override - Widget build(BuildContext context) { - final child = Scaffold( - backgroundColor: Theme.of(context).cardColor, - body: DesktopTab( - controller: tabController, - onWindowCloseButton: handleWindowCloseButton, - tail: const AddButton(), - selectedBorderColor: MyTheme.accent, - labelGetter: DesktopTab.tablabelGetter, - )); - final tabWidget = isLinux - ? buildVirtualWindowFrame(context, child) - : workaroundWindowBorder( - context, - Container( - decoration: BoxDecoration( - border: Border.all(color: MyTheme.color(context).border!)), - child: child, - )); - return isMacOS || kUseCompatibleUiMode - ? tabWidget - : SubWindowDragToResizeArea( - child: tabWidget, - resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - enableResizeEdges: subWindowManagerEnableResizeEdges, - windowId: stateGlobal.windowId, - ); - } - - void onRemoveId(String id) { - if (tabController.state.value.tabs.isEmpty) { - WindowController.fromWindowId(windowId()).close(); - } - } - - int windowId() { - return widget.params["windowId"]; - } - - Future handleWindowCloseButton() async { - final connLength = tabController.state.value.tabs.length; - if (connLength == 1) { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: tabController.state.value.tabs[0].key, - tabController: tabController, - )) { - return false; - } - } - if (connLength <= 1) { - tabController.clear(); - return true; - } else { - final bool res; - if (!option2bool(kOptionEnableConfirmClosingTabs, - bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { - res = true; - } else { - res = await closeConfirmDialog(); - } - if (res) { - tabController.clear(); - } - return res; - } - } -} diff --git a/flutter/lib/desktop/pages/install_page.dart b/flutter/lib/desktop/pages/install_page.dart deleted file mode 100644 index 5bf6bafee..000000000 --- a/flutter/lib/desktop/pages/install_page.dart +++ /dev/null @@ -1,274 +0,0 @@ -import 'dart:convert'; - -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:path/path.dart'; -import 'package:url_launcher/url_launcher_string.dart'; -import 'package:window_manager/window_manager.dart'; - -class InstallPage extends StatefulWidget { - const InstallPage({Key? key}) : super(key: key); - - @override - State createState() => _InstallPageState(); -} - -class _InstallPageState extends State { - final tabController = DesktopTabController(tabType: DesktopTabType.main); - - _InstallPageState() { - Get.put(tabController); - const label = "install"; - tabController.add(TabInfo( - key: label, - label: label, - closable: false, - page: _InstallPageBody( - key: const ValueKey(label), - ))); - } - - @override - void dispose() { - super.dispose(); - Get.delete(); - } - - @override - Widget build(BuildContext context) { - return DragToResizeArea( - resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - enableResizeEdges: windowManagerEnableResizeEdges, - 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 printer = true.obs; - final RxBool showProgress = false.obs; - final RxBool btnEnabled = true.obs; - - // todo move to theme. - final buttonStyle = OutlinedButton.styleFrom( - textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.normal), - padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12), - ); - - _InstallPageBodyState() { - controller = TextEditingController(text: bind.installInstallPath()); - final installOptions = jsonDecode(bind.installInstallOptions()); - startmenu.value = installOptions['STARTMENUSHORTCUTS'] != '0'; - desktopicon.value = installOptions['DESKTOPSHORTCUTS'] != '0'; - printer.value = installOptions['PRINTER'] != '0'; - } - - @override - void initState() { - windowManager.addListener(this); - super.initState(); - } - - @override - void dispose() { - windowManager.removeListener(this); - super.dispose(); - } - - @override - void onWindowClose() { - gFFI.close(); - super.onWindowClose(); - windowManager.setPreventClose(false); - windowManager.close(); - } - - InkWell Option(RxBool option, {String label = ''}) { - return InkWell( - // todo mouseCursor: "SystemMouseCursors.forbidden" or no cursor on btnEnabled == false - borderRadius: BorderRadius.circular(6), - onTap: () => btnEnabled.value ? option.value = !option.value : null, - child: Row( - children: [ - Obx( - () => Checkbox( - visualDensity: VisualDensity(horizontal: -4, vertical: -4), - value: option.value, - onChanged: (v) => - btnEnabled.value ? option.value = !option.value : null, - ).marginOnly(right: 8), - ), - Expanded( - child: Text(translate(label)), - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - final double em = 13; - final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark; - return Scaffold( - backgroundColor: null, - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate('Installation'), - style: Theme.of(context).textTheme.headlineMedium), - Row( - children: [ - Text('${translate('Installation Path')}:') - .marginOnly(right: 10), - Expanded( - child: TextField( - controller: controller, - readOnly: true, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(0.75 * em), - ), - ).workaroundFreezeLinuxMint().marginOnly(right: 10), - ), - Obx( - () => OutlinedButton.icon( - icon: Icon(Icons.folder_outlined, size: 16), - onPressed: btnEnabled.value ? selectInstallPath : null, - style: buttonStyle, - label: Text(translate('Change Path')), - ), - ) - ], - ).marginSymmetric(vertical: 2 * em), - Option(startmenu, label: 'Create start menu shortcuts') - .marginOnly(bottom: 7), - Option(desktopicon, label: 'Create desktop icon') - .marginOnly(bottom: 7), - Option(printer, label: 'Install {$appName} Printer'), - Container( - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - color: isDarkTheme - ? Color.fromARGB(135, 87, 87, 90) - : Colors.grey[100], - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey), - ), - child: Row( - children: [ - Icon(Icons.info_outline_rounded, size: 32) - .marginOnly(right: 16), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate('agreement_tip')) - .marginOnly(bottom: em), - InkWell( - hoverColor: Colors.transparent, - onTap: () => launchUrlString( - 'https://rustdesk.com/privacy.html'), - child: Tooltip( - message: 'https://rustdesk.com/privacy.html', - child: Row(children: [ - Icon(Icons.launch_outlined, size: 16) - .marginOnly(right: 5), - Text( - translate('End-user license agreement'), - style: const TextStyle( - decoration: TextDecoration.underline), - ) - ]), - ), - ), - ], - ) - ], - )).marginSymmetric(vertical: 2 * em), - Row( - children: [ - Expanded( - // NOT use Offstage to wrap LinearProgressIndicator - child: Obx(() => showProgress.value - ? LinearProgressIndicator().marginOnly(right: 10) - : Offstage()), - ), - Obx( - () => OutlinedButton.icon( - icon: Icon(Icons.close_rounded, size: 16), - label: Text(translate('Cancel')), - onPressed: - btnEnabled.value ? () => windowManager.close() : null, - style: buttonStyle, - ).marginOnly(right: 10), - ), - Obx( - () => ElevatedButton.icon( - icon: Icon(Icons.done_rounded, size: 16), - label: Text(translate('Accept and Install')), - onPressed: btnEnabled.value ? install : null, - style: buttonStyle, - ), - ), - Offstage( - offstage: bind.installShowRunWithoutInstall(), - child: Obx( - () => OutlinedButton.icon( - icon: Icon(Icons.screen_share_outlined, size: 16), - label: Text(translate('Run without install')), - onPressed: btnEnabled.value - ? () => bind.installRunWithoutInstall() - : null, - style: buttonStyle, - ).marginOnly(left: 10), - ), - ), - ], - ) - ], - ).paddingSymmetric(horizontal: 4 * em, vertical: 3 * em), - )); - } - - void install() { - do_install() { - btnEnabled.value = false; - showProgress.value = true; - String args = ''; - if (startmenu.value) args += ' startmenu'; - if (desktopicon.value) args += ' desktopicon'; - if (printer.value) args += ' printer'; - bind.installInstallMe(options: args, path: controller.text); - } - - do_install(); - } - - void selectInstallPath() async { - String? install_path = await FilePicker.platform - .getDirectoryPath(initialDirectory: controller.text); - if (install_path != null) { - controller.text = join(install_path, await bind.mainGetAppName()); - } - } -} diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart deleted file mode 100644 index 13dca0eaf..000000000 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ /dev/null @@ -1,357 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:get/get.dart'; - -const double _kColumn1Width = 30; -const double _kColumn4Width = 100; -const double _kRowHeight = 60; -const double _kTextLeftMargin = 20; - -class _PortForward { - int localPort; - String remoteHost; - int remotePort; - - _PortForward.fromJson(List json) - : localPort = json[0] as int, - remoteHost = json[1] as String, - remotePort = json[2] as int; -} - -class PortForwardPage extends StatefulWidget { - PortForwardPage({ - Key? key, - required this.id, - required this.password, - required this.tabController, - required this.isRDP, - required this.isSharedPassword, - this.forceRelay, - this.connToken, - }) : super(key: key); - final String id; - final String? password; - final DesktopTabController tabController; - final bool isRDP; - final bool? forceRelay; - final bool? isSharedPassword; - final String? connToken; - final SimpleWrapper?> _lastState = SimpleWrapper(null); - - FFI get ffi => (_lastState.value! as _PortForwardPageState)._ffi; - - @override - State createState() { - final state = _PortForwardPageState(); - _lastState.value = state; - return state; - } -} - -class _PortForwardPageState extends State - with AutomaticKeepAliveClientMixin { - final TextEditingController localPortController = TextEditingController(); - final TextEditingController remoteHostController = TextEditingController(); - final TextEditingController remotePortController = TextEditingController(); - RxList<_PortForward> pfs = RxList.empty(growable: true); - late FFI _ffi; - - @override - void initState() { - super.initState(); - _ffi = FFI(null); - _ffi.start(widget.id, - isPortForward: true, - password: widget.password, - isSharedPassword: widget.isSharedPassword, - forceRelay: widget.forceRelay, - connToken: widget.connToken, - isRdp: widget.isRDP); - Get.put(_ffi, tag: 'pf_${widget.id}'); - debugPrint("Port forward page init success with id ${widget.id}"); - // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController.onSelected?.call(widget.id); - }); - } - - @override - void dispose() { - _ffi.close(); - _ffi.dialogManager.dismissAll(); - Get.delete(tag: 'pf_${widget.id}'); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: FutureBuilder(future: () async { - if (!widget.isRDP) { - refreshTunnelConfig(); - } - }(), builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return Container( - decoration: BoxDecoration( - border: Border.all( - width: 20, - color: Theme.of(context).scaffoldBackgroundColor)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildPrompt(context), - Flexible( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background, - border: Border.all(width: 1, color: MyTheme.border)), - child: - widget.isRDP ? buildRdp(context) : buildTunnel(context), - ), - ), - ], - ), - ); - } - return const Offstage(); - }), - ); - } - - buildPrompt(BuildContext context) { - return Obx(() => Offstage( - offstage: pfs.isEmpty && !widget.isRDP, - child: Container( - height: 45, - color: const Color(0xFF007F00), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - translate('Listening ...'), - style: const TextStyle(fontSize: 16, color: Colors.white), - ), - Text( - translate('not_close_tcp_tip'), - style: const TextStyle( - fontSize: 10, color: Color(0xFFDDDDDD), height: 1.2), - ) - ])).marginOnly(bottom: 8), - )); - } - - buildTunnel(BuildContext context) { - text(String label) => Expanded( - child: Text(translate(label)).marginOnly(left: _kTextLeftMargin)); - - return Theme( - data: Theme.of(context).copyWith( - colorScheme: Theme.of(context).colorScheme, - ), - child: Obx(() => ListView.builder( - controller: ScrollController(), - itemCount: pfs.length + 2, - itemBuilder: ((context, index) { - if (index == 0) { - return Container( - height: 25, - color: Theme.of(context).scaffoldBackgroundColor, - child: Row(children: [ - text('Local Port'), - const SizedBox(width: _kColumn1Width), - text('Remote Host'), - text('Remote Port'), - SizedBox( - width: _kColumn4Width, child: Text(translate('Action'))) - ]), - ); - } else if (index == 1) { - return buildTunnelAddRow(context); - } else { - return buildTunnelDataRow(context, pfs[index - 2], index - 2); - } - }))), - ); - } - - buildTunnelAddRow(BuildContext context) { - var portInputFormatter = [ - 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])$')) - ]; - - return Container( - height: _kRowHeight, - decoration: - BoxDecoration(color: Theme.of(context).colorScheme.background), - child: Row(children: [ - buildTunnelInputCell(context, - controller: localPortController, - inputFormatters: portInputFormatter), - const SizedBox( - width: _kColumn1Width, child: Icon(Icons.arrow_forward_sharp)), - buildTunnelInputCell(context, - controller: remoteHostController, hint: 'localhost'), - buildTunnelInputCell(context, - controller: remotePortController, - inputFormatters: portInputFormatter), - ElevatedButton( - onPressed: () async { - int? localPort = int.tryParse(localPortController.text); - int? remotePort = int.tryParse(remotePortController.text); - if (localPort != null && - remotePort != null && - (remoteHostController.text.isEmpty || - remoteHostController.text.trim().isNotEmpty)) { - await bind.sessionAddPortForward( - sessionId: _ffi.sessionId, - localPort: localPort, - remoteHost: remoteHostController.text.trim().isEmpty - ? 'localhost' - : remoteHostController.text.trim(), - remotePort: remotePort); - localPortController.clear(); - remoteHostController.clear(); - remotePortController.clear(); - refreshTunnelConfig(); - } - }, - child: Text( - translate('Add'), - ), - ).marginSymmetric(horizontal: 10), - ]), - ); - } - - buildTunnelInputCell(BuildContext context, - {required TextEditingController controller, - List? inputFormatters, - String? hint}) { - return Expanded( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: TextField( - controller: controller, - inputFormatters: inputFormatters, - decoration: InputDecoration( - hintText: hint, - )).workaroundFreezeLinuxMint()), - ); - } - - Widget buildTunnelDataRow(BuildContext context, _PortForward pf, int index) { - text(String label) => Expanded( - child: Text(label, style: const TextStyle(fontSize: 20)) - .marginOnly(left: _kTextLeftMargin)); - - return Container( - height: _kRowHeight, - decoration: BoxDecoration( - color: index % 2 == 0 - ? MyTheme.currentThemeMode() == ThemeMode.dark - ? const Color(0xFF202020) - : const Color(0xFFF4F5F6) - : Theme.of(context).colorScheme.background), - child: Row(children: [ - text(pf.localPort.toString()), - const SizedBox(width: _kColumn1Width), - text(pf.remoteHost), - text(pf.remotePort.toString()), - SizedBox( - width: _kColumn4Width, - child: IconButton( - icon: const Icon(Icons.close), - onPressed: () async { - await bind.sessionRemovePortForward( - sessionId: _ffi.sessionId, localPort: pf.localPort); - refreshTunnelConfig(); - }, - ), - ), - ]), - ); - } - - void refreshTunnelConfig() async { - String peer = bind.mainGetPeerSync(id: widget.id); - Map config = jsonDecode(peer); - List infos = config['port_forwards'] as List; - List<_PortForward> result = List.empty(growable: true); - for (var e in infos) { - result.add(_PortForward.fromJson(e)); - } - pfs.value = result; - } - - buildRdp(BuildContext context) { - text1(String label) => Expanded( - child: Text(translate(label)).marginOnly(left: _kTextLeftMargin)); - text2(String label) => Expanded( - child: Text( - label, - style: const TextStyle(fontSize: 20), - ).marginOnly(left: _kTextLeftMargin)); - return Theme( - data: Theme.of(context) - .copyWith(colorScheme: Theme.of(context).colorScheme), - child: ListView.builder( - controller: ScrollController(), - itemCount: 2, - itemBuilder: ((context, index) { - if (index == 0) { - return Container( - height: 25, - color: Theme.of(context).scaffoldBackgroundColor, - child: Row(children: [ - text1('Local Port'), - const SizedBox(width: _kColumn1Width), - text1('Remote Host'), - text1('Remote Port'), - ]), - ); - } else { - return Container( - height: _kRowHeight, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.background), - child: Row(children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: SizedBox( - width: 120, - child: ElevatedButton( - onPressed: () => - bind.sessionNewRdp(sessionId: _ffi.sessionId), - child: Text( - translate('New RDP'), - ), - ).marginSymmetric(vertical: 10), - ).marginOnly(left: 20), - ), - ), - const SizedBox( - width: _kColumn1Width, - child: Icon(Icons.arrow_forward_sharp)), - text2('localhost'), - text2('RDP'), - ]), - ); - } - })), - ); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart deleted file mode 100644 index 9d366bcb0..000000000 --- a/flutter/lib/desktop/pages/port_forward_tab_page.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:convert'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/pages/port_forward_page.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:get/get.dart'; - -class PortForwardTabPage extends StatefulWidget { - final Map params; - - const PortForwardTabPage({Key? key, required this.params}) : super(key: key); - - @override - State createState() => _PortForwardTabPageState(params); -} - -class _PortForwardTabPageState extends State { - late final DesktopTabController tabController; - late final bool isRDP; - - static const IconData selectedIcon = Icons.forward_sharp; - static const IconData unselectedIcon = Icons.forward_outlined; - - _PortForwardTabPageState(Map params) { - isRDP = params['isRDP']; - tabController = - Get.put(DesktopTabController(tabType: DesktopTabType.portForward)); - tabController.onSelected = (id) { - WindowController.fromWindowId(windowId()) - .setTitle(getWindowNameWithId(id)); - }; - tabController.onRemoved = (_, id) => onRemoveId(id); - tabController.add(TabInfo( - key: params['id'], - label: params['id'], - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - page: PortForwardPage( - key: ValueKey(params['id']), - id: params['id'], - password: params['password'], - isSharedPassword: params['isSharedPassword'], - tabController: tabController, - isRDP: isRDP, - forceRelay: params['forceRelay'], - connToken: params['connToken'], - ))); - } - - @override - void initState() { - super.initState(); - - rustDeskWinManager.setMethodHandler((call, fromWindowId) async { - debugPrint( - "[Port Forward] call ${call.method} with args ${call.arguments} from window $fromWindowId"); - // for simplify, just replace connectionId - if (call.method == kWindowEventNewPortForward) { - final args = jsonDecode(call.arguments); - final id = args['id']; - final isRDP = args['isRDP']; - windowOnTop(windowId()); - if (tabController.state.value.tabs.indexWhere((e) => e.key == id) >= - 0) { - debugPrint("port forward $id exists"); - return; - } - tabController.add(TabInfo( - key: id, - label: id, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - page: PortForwardPage( - key: ValueKey(args['id']), - id: id, - password: args['password'], - isSharedPassword: args['isSharedPassword'], - isRDP: isRDP, - tabController: tabController, - forceRelay: args['forceRelay'], - connToken: args['connToken'], - ))); - } else if (call.method == "onDestroy") { - tabController.clear(); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } - }); - Future.delayed(Duration.zero, () { - restoreWindowPosition(WindowType.PortForward, windowId: windowId()); - }); - } - - @override - Widget build(BuildContext context) { - final child = Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: DesktopTab( - controller: tabController, - onWindowCloseButton: () async { - tabController.clear(); - return true; - }, - tail: AddButton(), - selectedBorderColor: MyTheme.accent, - labelGetter: DesktopTab.tablabelGetter, - ), - ); - final tabWidget = isLinux - ? buildVirtualWindowFrame( - context, - Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: child), - ) - : workaroundWindowBorder( - context, - Container( - decoration: BoxDecoration( - border: Border.all(color: MyTheme.color(context).border!)), - child: child, - )); - return isMacOS || kUseCompatibleUiMode - ? tabWidget - : Obx( - () => SubWindowDragToResizeArea( - child: tabWidget, - resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - enableResizeEdges: subWindowManagerEnableResizeEdges, - windowId: stateGlobal.windowId, - ), - ); - } - - void onRemoveId(String id) { - if (tabController.state.value.tabs.isEmpty) { - WindowController.fromWindowId(windowId()).close(); - } - } - - int windowId() { - return widget.params["windowId"]; - } -} diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart deleted file mode 100644 index 29e710bbc..000000000 --- a/flutter/lib/desktop/pages/remote_page.dart +++ /dev/null @@ -1,1054 +0,0 @@ -import 'dart:async'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_hbb/models/state_model.dart'; - -import '../../consts.dart'; -import '../../common/widgets/overlay.dart'; -import '../../common/widgets/remote_input.dart'; -import '../../common.dart'; -import '../../common/widgets/dialog.dart'; -import '../../common/widgets/toolbar.dart'; -import '../../models/model.dart'; -import '../../models/input_model.dart'; -import '../../models/platform_model.dart'; -import '../../common/shared_state.dart'; -import '../../utils/image.dart'; -import '../widgets/remote_toolbar.dart'; -import '../widgets/kb_layout_type_chooser.dart'; -import '../widgets/tabbar_widget.dart'; - -import 'package:flutter_hbb/native/custom_cursor.dart' - if (dart.library.html) 'package:flutter_hbb/web/custom_cursor.dart'; - -final SimpleWrapper _firstEnterImage = SimpleWrapper(false); - -// Used to skip session close if "move to new window" is clicked. -final Map closeSessionOnDispose = {}; - -class RemotePage extends StatefulWidget { - RemotePage({ - Key? key, - required this.id, - required this.toolbarState, - this.sessionId, - this.tabWindowId, - this.password, - this.display, - this.displays, - this.tabController, - this.switchUuid, - this.forceRelay, - this.isSharedPassword, - }) : super(key: key) { - initSharedStates(id); - } - - final String id; - final SessionID? sessionId; - final int? tabWindowId; - final int? display; - final List? displays; - final String? password; - final ToolbarState toolbarState; - final String? switchUuid; - final bool? forceRelay; - final bool? isSharedPassword; - final SimpleWrapper?> _lastState = SimpleWrapper(null); - final DesktopTabController? tabController; - - FFI get ffi => (_lastState.value! as _RemotePageState)._ffi; - - @override - State createState() { - final state = _RemotePageState(id); - _lastState.value = state; - return state; - } -} - -class _RemotePageState extends State - with - AutomaticKeepAliveClientMixin, - MultiWindowListener, - TickerProviderStateMixin { - Timer? _timer; - String keyboardMode = "legacy"; - bool _isWindowBlur = false; - final _cursorOverImage = false.obs; - late RxBool _showRemoteCursor; - late RxBool _zoomCursor; - late RxBool _remoteCursorMoved; - late RxBool _keyboardEnabled; - final _uniqueKey = UniqueKey(); - - var _blockableOverlayState = BlockableOverlayState(); - - final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode"); - - // Debounce timer for pointer lock center updates during window events. - // Uses kDefaultPointerLockCenterThrottleMs from consts.dart for the duration. - Timer? _pointerLockCenterDebounceTimer; - - // We need `_instanceIdOnEnterOrLeaveImage4Toolbar` together with `_onEnterOrLeaveImage4Toolbar` - // to identify the toolbar instance and its callback function. - int? _instanceIdOnEnterOrLeaveImage4Toolbar; - Function(bool)? _onEnterOrLeaveImage4Toolbar; - - late FFI _ffi; - - SessionID get sessionId => _ffi.sessionId; - - _RemotePageState(String id) { - _initStates(id); - } - - void _initStates(String id) { - _zoomCursor = PeerBoolOption.find(id, kOptionZoomCursor); - _showRemoteCursor = ShowRemoteCursorState.find(id); - _keyboardEnabled = KeyboardEnabledState.find(id); - _remoteCursorMoved = RemoteCursorMovedState.find(id); - } - - @override - void initState() { - super.initState(); - _ffi = FFI(widget.sessionId); - Get.put(_ffi, tag: widget.id); - _ffi.imageModel.addCallbackOnFirstImage((String peerId) { - _ffi.canvasModel.activateLocalCursor(); - showKBLayoutTypeChooserIfNeeded( - _ffi.ffiModel.pi.platform, _ffi.dialogManager); - _ffi.recordingModel - .updateStatus(bind.sessionGetIsRecording(sessionId: _ffi.sessionId)); - }); - _ffi.canvasModel.initializeEdgeScrollFallback(this); - _ffi.start( - widget.id, - password: widget.password, - isSharedPassword: widget.isSharedPassword, - switchUuid: widget.switchUuid, - forceRelay: widget.forceRelay, - tabWindowId: widget.tabWindowId, - display: widget.display, - displays: widget.displays, - ); - WidgetsBinding.instance.addPostFrameCallback((_) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); - _ffi.dialogManager - .showLoading(translate('Connecting...'), onCancel: closeConnection); - }); - WakelockManager.enable(_uniqueKey); - - _ffi.ffiModel.updateEventListener(sessionId, widget.id); - if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); - _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); - _ffi.dialogManager.loadMobileActionsOverlayVisible(); - WidgetsBinding.instance.addPostFrameCallback((_) { - // Session option should be set after models.dart/FFI.start - _showRemoteCursor.value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: 'show-remote-cursor'); - _zoomCursor.value = bind.sessionGetToggleOptionSync( - sessionId: sessionId, arg: kOptionZoomCursor); - }); - DesktopMultiWindow.addListener(this); - // if (!_isCustomCursorInited) { - // customCursorController.registerNeedUpdateCursorCallback( - // (String? lastKey, String? currentKey) async { - // if (_firstEnterImage.value) { - // _firstEnterImage.value = false; - // return true; - // } - // return lastKey == null || lastKey != currentKey; - // }); - // _isCustomCursorInited = true; - // } - - _blockableOverlayState.applyFfi(_ffi); - // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController?.onSelected?.call(widget.id); - }); - - // Register callback to cancel debounce timer when relative mouse mode is disabled - _ffi.inputModel.onRelativeMouseModeDisabled = - _cancelPointerLockCenterDebounceTimer; - } - - /// Cancel the pointer lock center debounce timer - void _cancelPointerLockCenterDebounceTimer() { - _pointerLockCenterDebounceTimer?.cancel(); - _pointerLockCenterDebounceTimer = null; - } - - @override - void onWindowBlur() { - super.onWindowBlur(); - // On windows, we use `focus` way to handle keyboard better. - // Now on Linux, there's some rdev issues which will break the input. - // We disable the `focus` way for non-Windows temporarily. - if (isWindows) { - _isWindowBlur = true; - // unfocus the primary-focus when the whole window is lost focus, - // and let OS to handle events instead. - _rawKeyFocusNode.unfocus(); - } - stateGlobal.isFocused.value = false; - - // When window loses focus, temporarily release relative mouse mode constraints - // to allow user to interact with other applications normally. - // The cursor will be re-hidden and re-centered when window regains focus. - if (_ffi.inputModel.relativeMouseMode.value) { - _ffi.inputModel.onWindowBlur(); - } - } - - @override - void onWindowFocus() { - super.onWindowFocus(); - // See [onWindowBlur]. - if (isWindows) { - _isWindowBlur = false; - } - stateGlobal.isFocused.value = true; - - // Restore relative mouse mode constraints when window regains focus. - if (_ffi.inputModel.relativeMouseMode.value) { - _rawKeyFocusNode.requestFocus(); - _ffi.inputModel.onWindowFocus(); - } - } - - @override - void onWindowRestore() { - super.onWindowRestore(); - // On windows, we use `onWindowRestore` way to handle window restore from - // a minimized state. - if (isWindows) { - _isWindowBlur = false; - } - WakelockManager.enable(_uniqueKey); - // Update pointer lock center when window is restored - _updatePointerLockCenterIfNeeded(); - } - - // When the window is unminimized, onWindowMaximize or onWindowRestore can be called when the old state was maximized or not. - @override - void onWindowMaximize() { - super.onWindowMaximize(); - WakelockManager.enable(_uniqueKey); - // Update pointer lock center when window is maximized - _updatePointerLockCenterIfNeeded(); - } - - @override - void onWindowResize() { - super.onWindowResize(); - // Update pointer lock center when window is resized - _updatePointerLockCenterIfNeeded(); - } - - @override - void onWindowMove() { - super.onWindowMove(); - // Update pointer lock center when window is moved - _updatePointerLockCenterIfNeeded(); - } - - /// Update pointer lock center with debouncing to avoid excessive updates - /// during rapid window move/resize events. - void _updatePointerLockCenterIfNeeded() { - if (!_ffi.inputModel.relativeMouseMode.value) return; - - // Cancel any pending update and schedule a new one (debounce pattern) - _pointerLockCenterDebounceTimer?.cancel(); - _pointerLockCenterDebounceTimer = Timer( - const Duration(milliseconds: kDefaultPointerLockCenterThrottleMs), - () { - if (!mounted) return; - if (_ffi.inputModel.relativeMouseMode.value) { - _ffi.inputModel.updatePointerLockCenter(); - } - }, - ); - } - - @override - void onWindowMinimize() { - super.onWindowMinimize(); - WakelockManager.disable(_uniqueKey); - // Release cursor constraints when minimized - if (_ffi.inputModel.relativeMouseMode.value) { - _ffi.inputModel.onWindowBlur(); - } - } - - @override - void onWindowEnterFullScreen() { - super.onWindowEnterFullScreen(); - if (isMacOS) { - stateGlobal.setFullscreen(true); - } - } - - @override - void onWindowLeaveFullScreen() { - super.onWindowLeaveFullScreen(); - if (isMacOS) { - stateGlobal.setFullscreen(false); - } - } - - @override - Future dispose() async { - final closeSession = closeSessionOnDispose.remove(widget.id) ?? true; - - // https://github.com/flutter/flutter/issues/64935 - super.dispose(); - debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}"); - - // Defensive cleanup: ensure host system-key propagation is reset even if - // MouseRegion.onExit never fired (e.g., tab closed while cursor inside). - if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: true); - - _pointerLockCenterDebounceTimer?.cancel(); - _pointerLockCenterDebounceTimer = null; - // Clear callback reference to prevent memory leaks and stale references - _ffi.inputModel.onRelativeMouseModeDisabled = null; - // Relative mouse mode cleanup is centralized in FFI.close(closeSession: ...). - _ffi.textureModel.onRemotePageDispose(closeSession); - if (closeSession) { - // ensure we leave this session, this is a double check - _ffi.inputModel.enterOrLeave(false); - } - DesktopMultiWindow.removeListener(this); - _ffi.dialogManager.hideMobileActionsOverlay(); - _ffi.imageModel.disposeImage(); - _ffi.cursorModel.disposeImages(); - _rawKeyFocusNode.dispose(); - await _ffi.close(closeSession: closeSession); - _timer?.cancel(); - _ffi.dialogManager.dismissAll(); - if (closeSession) { - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, - overlays: SystemUiOverlay.values); - } - WakelockManager.disable(_uniqueKey); - await Get.delete(tag: widget.id); - removeSharedStates(widget.id); - } - - Widget emptyOverlay() => BlockableOverlay( - /// the Overlay key will be set with _blockableOverlayState in BlockableOverlay - /// see override build() in [BlockableOverlay] - state: _blockableOverlayState, - underlying: Container( - color: Colors.transparent, - ), - ); - - Widget buildBody(BuildContext context) { - remoteToolbar(BuildContext context) => RemoteToolbar( - id: widget.id, - ffi: _ffi, - state: widget.toolbarState, - onEnterOrLeaveImageSetter: (id, func) { - _instanceIdOnEnterOrLeaveImage4Toolbar = id; - _onEnterOrLeaveImage4Toolbar = func; - }, - onEnterOrLeaveImageCleaner: (id) { - // If _instanceIdOnEnterOrLeaveImage4Toolbar != id - // it means `_onEnterOrLeaveImage4Toolbar` is not set or it has been changed to another toolbar. - if (_instanceIdOnEnterOrLeaveImage4Toolbar == id) { - _instanceIdOnEnterOrLeaveImage4Toolbar = null; - _onEnterOrLeaveImage4Toolbar = null; - } - }, - setRemoteState: setState, - ); - - bodyWidget() { - return Stack( - children: [ - Container( - color: kColorCanvas, - child: RawKeyFocusScope( - focusNode: _rawKeyFocusNode, - onFocusChange: (bool imageFocused) { - debugPrint( - "onFocusChange(window active:${!_isWindowBlur}) $imageFocused"); - // See [onWindowBlur]. - if (isWindows) { - if (_isWindowBlur) { - imageFocused = false; - Future.delayed(Duration.zero, () { - _rawKeyFocusNode.unfocus(); - }); - } - if (imageFocused) { - _ffi.inputModel.enterOrLeave(true); - } else { - _ffi.inputModel.enterOrLeave(false); - } - } - }, - inputModel: _ffi.inputModel, - child: getBodyForDesktop(context))), - Stack( - children: [ - _ffi.ffiModel.pi.isSet.isTrue && - _ffi.ffiModel.waitForFirstImage.isTrue - ? emptyOverlay() - : () { - if (!_ffi.ffiModel.isPeerAndroid) { - return Offstage(); - } else { - return Obx(() => Offstage( - offstage: _ffi.dialogManager - .mobileActionsOverlayVisible.isFalse, - child: Overlay(initialEntries: [ - makeMobileActionsOverlayEntry( - () => _ffi.dialogManager - .setMobileActionsOverlayVisible(false), - ffi: _ffi, - ) - ]), - )); - } - }(), - // Use Overlay to enable rebuild every time on menu button click. - // Hide toolbar when relative mouse mode is active to prevent - // cursor from escaping to toolbar area. - Obx(() => _ffi.inputModel.relativeMouseMode.value - ? const Offstage() - : _ffi.ffiModel.pi.isSet.isTrue - ? Overlay(initialEntries: [ - OverlayEntry(builder: remoteToolbar) - ]) - : remoteToolbar(context)), - _ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(), - ], - ), - ], - ); - } - - return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: Obx(() { - final imageReady = _ffi.ffiModel.pi.isSet.isTrue && - _ffi.ffiModel.waitForFirstImage.isFalse; - if (imageReady) { - // If the privacy mode(disable physical displays) is switched, - // we should not dismiss the dialog immediately. - if (DateTime.now().difference(togglePrivacyModeTime) > - const Duration(milliseconds: 3000)) { - // `dismissAll()` is to ensure that the state is clean. - // It's ok to call dismissAll() here. - _ffi.dialogManager.dismissAll(); - // Recreate the block state to refresh the state. - _blockableOverlayState = BlockableOverlayState(); - _blockableOverlayState.applyFfi(_ffi); - } - // Block the whole `bodyWidget()` when dialog shows. - return BlockableOverlay( - underlying: bodyWidget(), - state: _blockableOverlayState, - ); - } else { - // `_blockableOverlayState` is not recreated here. - // The toolbar's block state won't work properly when reconnecting, but that's okay. - return bodyWidget(); - } - }), - ); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return WillPopScope( - onWillPop: () async { - clientClose(sessionId, _ffi); - return false; - }, - child: MultiProvider(providers: [ - ChangeNotifierProvider.value(value: _ffi.ffiModel), - ChangeNotifierProvider.value(value: _ffi.imageModel), - ChangeNotifierProvider.value(value: _ffi.cursorModel), - ChangeNotifierProvider.value(value: _ffi.canvasModel), - ChangeNotifierProvider.value(value: _ffi.recordingModel), - ], child: buildBody(context))); - } - - void enterView(PointerEnterEvent evt) { - _ffi.canvasModel.rearmEdgeScroll(); - - _cursorOverImage.value = true; - _firstEnterImage.value = true; - if (_onEnterOrLeaveImage4Toolbar != null) { - try { - _onEnterOrLeaveImage4Toolbar!(true); - } catch (e) { - // - } - } - - // See [onWindowBlur]. - if (!isWindows) { - if (!_rawKeyFocusNode.hasFocus) { - _rawKeyFocusNode.requestFocus(); - } - _ffi.inputModel.enterOrLeave(true); - } - } - - void leaveView(PointerExitEvent evt) { - _ffi.canvasModel.disableEdgeScroll(); - - if (_ffi.ffiModel.keyboard) { - _ffi.inputModel.tryMoveEdgeOnExit(evt.position); - } - - _cursorOverImage.value = false; - _firstEnterImage.value = false; - if (_onEnterOrLeaveImage4Toolbar != null) { - try { - _onEnterOrLeaveImage4Toolbar!(false); - } catch (e) { - // - } - } - - // See [onWindowBlur]. - if (!isWindows) { - _ffi.inputModel.enterOrLeave(false); - } - } - - Widget _buildRawTouchAndPointerRegion( - Widget child, - PointerEnterEventListener? onEnter, - PointerExitEventListener? onExit, - ) { - return RawTouchGestureDetectorRegion( - child: _buildRawPointerMouseRegion(child, onEnter, onExit), - ffi: _ffi, - ); - } - - Widget _buildRawPointerMouseRegion( - Widget child, - PointerEnterEventListener? onEnter, - PointerExitEventListener? onExit, - ) { - return RawPointerMouseRegion( - 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. - // Sometimes the system does not send the necessary focus event to flutter. We should manually - // handle this inconsistent status by setting `_isWindowBlur` to false. So we can - // ensure the grab-key thread is running when our users are clicking the remote canvas. - if (_isWindowBlur) { - debugPrint( - "Unexpected status: onPointerDown is triggered while the remote window is in blur status"); - _isWindowBlur = false; - } - if (!_rawKeyFocusNode.hasFocus) { - _rawKeyFocusNode.requestFocus(); - } - }, - inputModel: _ffi.inputModel, - child: child, - ); - } - - Widget getBodyForDesktop(BuildContext context) { - var paints = [ - MouseRegion( - onEnter: (evt) { - if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: false); - }, - onExit: (evt) { - if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: true); - }, - child: _ViewStyleUpdater( - canvasModel: _ffi.canvasModel, - inputModel: _ffi.inputModel, - child: Builder(builder: (context) { - final peerDisplay = CurrentDisplayState.find(widget.id); - return Obx( - () => _ffi.ffiModel.pi.isSet.isFalse - ? Container(color: Colors.transparent) - : Obx(() { - _ffi.textureModel.updateCurrentDisplay(peerDisplay.value); - return ImagePaint( - id: widget.id, - zoomCursor: _zoomCursor, - cursorOverImage: _cursorOverImage, - keyboardEnabled: _keyboardEnabled, - remoteCursorMoved: _remoteCursorMoved, - listenerBuilder: (child) => - _buildRawTouchAndPointerRegion( - child, enterView, leaveView), - ffi: _ffi, - ); - }), - ); - }), - ), - ) - ]; - - if (!_ffi.canvasModel.cursorEmbedded) { - paints - .add(Obx(() => _showRemoteCursor.isFalse || _remoteCursorMoved.isFalse - ? Offstage() - : CursorPaint( - id: widget.id, - zoomCursor: _zoomCursor, - ))); - } - paints.add( - Positioned( - top: 10, - right: 10, - child: _buildRawTouchAndPointerRegion( - QualityMonitor(_ffi.qualityMonitorModel), null, null), - ), - ); - return Stack( - children: paints, - ); - } - - @override - bool get wantKeepAlive => true; -} - -/// A widget that tracks the view size and updates CanvasModel.updateViewStyle() -/// and InputModel.updateImageWidgetSize() only when size actually changes. -/// This avoids scheduling post-frame callbacks on every LayoutBuilder rebuild. -class _ViewStyleUpdater extends StatefulWidget { - final CanvasModel canvasModel; - final InputModel inputModel; - final Widget child; - - const _ViewStyleUpdater({ - Key? key, - required this.canvasModel, - required this.inputModel, - required this.child, - }) : super(key: key); - - @override - State<_ViewStyleUpdater> createState() => _ViewStyleUpdaterState(); -} - -class _ViewStyleUpdaterState extends State<_ViewStyleUpdater> { - Size? _lastSize; - bool _callbackScheduled = false; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - final maxWidth = constraints.maxWidth; - final maxHeight = constraints.maxHeight; - // Guard against infinite constraints (e.g., unconstrained ancestor). - if (!maxWidth.isFinite || !maxHeight.isFinite) { - return widget.child; - } - final newSize = Size(maxWidth, maxHeight); - if (_lastSize != newSize) { - _lastSize = newSize; - // Schedule the update for after the current frame to avoid setState during build. - // Use _callbackScheduled flag to prevent accumulating multiple callbacks - // when size changes rapidly before any callback executes. - if (!_callbackScheduled) { - _callbackScheduled = true; - SchedulerBinding.instance.addPostFrameCallback((_) { - _callbackScheduled = false; - final currentSize = _lastSize; - if (mounted && currentSize != null) { - widget.canvasModel.updateViewStyle(); - widget.inputModel.updateImageWidgetSize(currentSize); - } - }); - } - } - return widget.child; - }, - ); - } -} - -class ImagePaint extends StatefulWidget { - final FFI ffi; - final String id; - final RxBool zoomCursor; - final RxBool cursorOverImage; - final RxBool keyboardEnabled; - final RxBool remoteCursorMoved; - final Widget Function(Widget)? listenerBuilder; - - ImagePaint( - {Key? key, - required this.ffi, - required this.id, - required this.zoomCursor, - required this.cursorOverImage, - required this.keyboardEnabled, - required this.remoteCursorMoved, - this.listenerBuilder}) - : super(key: key); - - @override - State createState() => _ImagePaintState(); -} - -class _ImagePaintState extends State { - bool _lastRemoteCursorMoved = false; - - String get id => widget.id; - RxBool get zoomCursor => widget.zoomCursor; - RxBool get cursorOverImage => widget.cursorOverImage; - RxBool get keyboardEnabled => widget.keyboardEnabled; - RxBool get remoteCursorMoved => widget.remoteCursorMoved; - Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder; - - @override - Widget build(BuildContext context) { - final m = Provider.of(context); - var c = Provider.of(context); - final s = c.scale; - - bool isViewAdaptive() => c.viewStyle.style == kRemoteViewStyleAdaptive; - bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal; - - mouseRegion({child}) => Obx(() { - double getCursorScale() { - var c = Provider.of(context); - var cursorScale = 1.0; - if (isWindows) { - // debug win10 - if (zoomCursor.value && isViewAdaptive()) { - cursorScale = s * c.devicePixelRatio; - } - } else { - if (zoomCursor.value || isViewOriginal()) { - cursorScale = s; - } - } - return cursorScale; - } - - return MouseRegion( - cursor: cursorOverImage.isTrue - ? c.cursorEmbedded - ? SystemMouseCursors.none - // Hide cursor when relative mouse mode is active - : widget.ffi.inputModel.relativeMouseMode.value - ? SystemMouseCursors.none - : keyboardEnabled.isTrue - ? (() { - if (remoteCursorMoved.isTrue) { - _lastRemoteCursorMoved = true; - return SystemMouseCursors.none; - } else { - if (_lastRemoteCursorMoved) { - _lastRemoteCursorMoved = false; - _firstEnterImage.value = true; - } - return _buildCustomCursor( - context, getCursorScale()); - } - }()) - : _buildDisabledCursor(context, getCursorScale()) - : MouseCursor.defer, - onHover: (evt) {}, - child: child); - }); - if (c.imageOverflow.isTrue && c.scrollStyle != ScrollStyle.scrollauto) { - final paintWidth = c.getDisplayWidth() * s; - final paintHeight = c.getDisplayHeight() * s; - final paintSize = Size(paintWidth, paintHeight); - final paintWidget = - m.useTextureRender || widget.ffi.ffiModel.pi.forceTextureRender - ? _BuildPaintTextureRender( - c, s, Offset.zero, paintSize, isViewOriginal()) - : _buildScrollbarNonTextureRender(m, paintSize, s); - return NotificationListener( - onNotification: (notification) { - c.updateScrollPercent(); - return false; - }, - child: mouseRegion( - child: Obx(() => _buildCrossScrollbarFromLayout( - context, - _buildListener(paintWidget), - c.size, - paintSize, - c.scrollHorizontal, - c.scrollVertical, - )), - )); - } else { - if (c.size.width > 0 && c.size.height > 0) { - final paintWidget = - m.useTextureRender || widget.ffi.ffiModel.pi.forceTextureRender - ? _BuildPaintTextureRender( - c, - s, - Offset( - isLinux ? c.x.toInt().toDouble() : c.x, - isLinux ? c.y.toInt().toDouble() : c.y, - ), - c.size, - isViewOriginal()) - : _buildScrollAutoNonTextureRender(m, c, s); - return mouseRegion(child: _buildListener(paintWidget)); - } else { - return Container(); - } - } - } - - Widget _buildScrollbarNonTextureRender( - ImageModel m, Size imageSize, double s) { - return CustomPaint( - size: imageSize, - painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), - ); - } - - Widget _buildScrollAutoNonTextureRender( - ImageModel m, CanvasModel c, double s) { - double sizeScale = s; - if (widget.ffi.ffiModel.isPeerLinux) { - final displays = widget.ffi.ffiModel.pi.getCurDisplays(); - if (displays.isNotEmpty) { - sizeScale = s / displays[0].scale; - } - } - return CustomPaint( - size: Size(c.size.width, c.size.height), - painter: ImagePainter( - image: m.image, - x: c.x / sizeScale, - y: c.y / sizeScale, - scale: sizeScale), - ); - } - - Widget _BuildPaintTextureRender( - CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) { - final ffiModel = c.parent.target!.ffiModel; - final displays = ffiModel.pi.getCurDisplays(); - final children = []; - final rect = ffiModel.rect; - if (rect == null) { - return Container(); - } - final isPeerLinux = ffiModel.isPeerLinux; - final curDisplay = ffiModel.pi.currentDisplay; - for (var i = 0; i < displays.length; i++) { - final textureId = widget.ffi.textureModel - .getTextureId(curDisplay == kAllDisplayValue ? i : curDisplay); - if (true) { - // both "textureId.value != -1" and "true" seems ok - final sizeScale = isPeerLinux ? s / displays[i].scale : s; - children.add(Positioned( - left: (displays[i].x - rect.left) * s + offset.dx, - top: (displays[i].y - rect.top) * s + offset.dy, - width: displays[i].width * sizeScale, - height: displays[i].height * sizeScale, - child: Obx(() => Texture( - textureId: textureId.value, - filterQuality: - isViewOriginal ? FilterQuality.none : FilterQuality.low, - )), - )); - } - } - return SizedBox( - width: size.width, - height: size.height, - child: Stack(children: children), - ); - } - - MouseCursor _buildCustomCursor(BuildContext context, double scale) { - final cursor = Provider.of(context); - final cache = cursor.cache ?? preDefaultCursor.cache; - return buildCursorOfCache(cursor, scale, cache); - } - - MouseCursor _buildDisabledCursor(BuildContext context, double scale) { - final cursor = Provider.of(context); - final cache = preForbiddenCursor.cache; - return buildCursorOfCache(cursor, scale, cache); - } - - Widget _buildCrossScrollbarFromLayout( - BuildContext context, - Widget child, - Size layoutSize, - Size size, - ScrollController horizontal, - ScrollController vertical, - ) { - var widget = child; - if (layoutSize.width < size.width) { - widget = ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: SingleChildScrollView( - controller: horizontal, - scrollDirection: Axis.horizontal, - physics: cursorOverImage.isTrue - ? const NeverScrollableScrollPhysics() - : null, - child: widget, - ), - ); - } else { - widget = Row( - children: [ - Container( - width: ((layoutSize.width - size.width) ~/ 2).toDouble(), - ), - widget, - ], - ); - } - if (layoutSize.height < size.height) { - widget = ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: SingleChildScrollView( - controller: vertical, - physics: cursorOverImage.isTrue - ? const NeverScrollableScrollPhysics() - : null, - child: widget, - ), - ); - } else { - widget = Column( - children: [ - Container( - height: ((layoutSize.height - size.height) ~/ 2).toDouble(), - ), - widget, - ], - ); - } - if (layoutSize.width < size.width) { - widget = RawScrollbar( - thickness: kScrollbarThickness, - thumbColor: Colors.grey, - controller: horizontal, - thumbVisibility: false, - trackVisibility: false, - notificationPredicate: layoutSize.height < size.height - ? (notification) => notification.depth == 1 - : defaultScrollNotificationPredicate, - child: widget, - ); - } - if (layoutSize.height < size.height) { - widget = RawScrollbar( - thickness: kScrollbarThickness, - thumbColor: Colors.grey, - controller: vertical, - thumbVisibility: false, - trackVisibility: false, - child: widget, - ); - } - - return Container( - child: widget, - width: layoutSize.width, - height: layoutSize.height, - ); - } - - Widget _buildListener(Widget child) { - if (listenerBuilder != null) { - return listenerBuilder!(child); - } else { - return child; - } - } -} - -class CursorPaint extends StatelessWidget { - final String id; - final RxBool zoomCursor; - - const CursorPaint({ - Key? key, - required this.id, - required this.zoomCursor, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final m = Provider.of(context); - final c = Provider.of(context); - double hotx = m.hotx; - double hoty = m.hoty; - if (m.image == null) { - if (preDefaultCursor.image != null) { - hotx = preDefaultCursor.image!.width / 2; - hoty = preDefaultCursor.image!.height / 2; - } - } - - double cx = c.x; - double cy = c.y; - if (c.viewStyle.style == kRemoteViewStyleOriginal && - c.scrollStyle == ScrollStyle.scrollbar) { - final rect = c.parent.target!.ffiModel.rect; - if (rect == null) { - // unreachable! - debugPrint('unreachable! The displays rect is null.'); - return Container(); - } - if (cx < 0) { - final imageWidth = rect.width * c.scale; - cx = -imageWidth * c.scrollX; - } - if (cy < 0) { - final imageHeight = rect.height * c.scale; - cy = -imageHeight * c.scrollY; - } - } - - double x = (m.x - hotx) * c.scale + cx; - double y = (m.y - hoty) * c.scale + cy; - double scale = 1.0; - final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal; - if (zoomCursor.value || isViewOriginal) { - x = m.x - hotx + cx / c.scale; - y = m.y - hoty + cy / c.scale; - scale = c.scale; - } - - return CustomPaint( - painter: ImagePainter( - image: m.image ?? preDefaultCursor.image, - x: x, - y: y, - scale: scale, - ), - ); - } -} diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart deleted file mode 100644 index ccd5935ce..000000000 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ /dev/null @@ -1,624 +0,0 @@ -import 'dart:convert'; -import 'dart:async'; -import 'dart:ui' as ui; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/shared_state.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/input_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/pages/remote_page.dart'; -import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' - as mod_menu; -import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:bot_toast/bot_toast.dart'; - -import '../../common/widgets/dialog.dart'; -import '../../models/platform_model.dart'; - -class _MenuTheme { - static const Color blueColor = MyTheme.button; - // kMinInteractiveDimension - static const double height = 20.0; - static const double dividerHeight = 12.0; -} - -class ConnectionTabPage extends StatefulWidget { - final Map params; - - const ConnectionTabPage({Key? key, required this.params}) : super(key: key); - - @override - State createState() => _ConnectionTabPageState(params); -} - -class _ConnectionTabPageState extends State { - final tabController = - Get.put(DesktopTabController(tabType: DesktopTabType.remoteScreen)); - final contentKey = UniqueKey(); - static const IconData selectedIcon = Icons.desktop_windows_sharp; - static const IconData unselectedIcon = Icons.desktop_windows_outlined; - - String? peerId; - bool _isScreenRectSet = false; - int? _display; - - var connectionMap = RxList.empty(growable: true); - - _ConnectionTabPageState(Map params) { - RemoteCountState.init(); - peerId = params['id']; - final sessionId = params['session_id']; - final tabWindowId = params['tab_window_id']; - final display = params['display']; - final displays = params['displays']; - final screenRect = parseParamScreenRect(params); - _isScreenRectSet = screenRect != null; - _display = display as int?; - tryMoveToScreenAndSetFullscreen(screenRect); - if (peerId != null) { - ConnectionTypeState.init(peerId!); - tabController.onSelected = (id) { - final remotePage = tabController.widget(id); - if (remotePage is RemotePage) { - final ffi = remotePage.ffi; - bind.setCurSessionId(sessionId: ffi.sessionId); - } - WindowController.fromWindowId(params['windowId']) - .setTitle(getWindowNameWithId(id)); - UnreadChatCountState.find(id).value = 0; - }; - tabController.add(TabInfo( - key: peerId!, - label: peerId!, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: peerId!, - tabController: tabController, - )) { - return; - } - tabController.closeBy(peerId!); - }, - page: RemotePage( - key: ValueKey(peerId), - id: peerId!, - sessionId: sessionId == null ? null : SessionID(sessionId), - tabWindowId: tabWindowId, - display: display, - displays: displays?.cast(), - password: params['password'], - toolbarState: ToolbarState(), - tabController: tabController, - switchUuid: params['switch_uuid'], - forceRelay: params['forceRelay'], - isSharedPassword: params['isSharedPassword'], - ), - )); - _update_remote_count(); - } - tabController.onRemoved = (_, id) => onRemoveId(id); - rustDeskWinManager.setMethodHandler(_remoteMethodHandler); - } - - @override - void initState() { - super.initState(); - - if (!_isScreenRectSet) { - Future.delayed(Duration.zero, () { - restoreWindowPosition( - WindowType.RemoteDesktop, - windowId: windowId(), - peerId: tabController.state.value.tabs.isEmpty - ? null - : tabController.state.value.tabs[0].key, - display: _display, - ); - }); - } - } - - @override - Widget build(BuildContext context) { - final child = Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: DesktopTab( - controller: tabController, - onWindowCloseButton: handleWindowCloseButton, - tail: Row( - mainAxisSize: MainAxisSize.min, - children: [ - _RelativeMouseModeHint(tabController: tabController), - const AddButton(), - ], - ), - selectedBorderColor: MyTheme.accent, - pageViewBuilder: (pageView) => pageView, - labelGetter: DesktopTab.tablabelGetter, - tabBuilder: (key, icon, label, themeConf) => Obx(() { - final connectionType = ConnectionTypeState.find(key); - if (!connectionType.isValid()) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - label, - ], - ); - } else { - bool secure = - connectionType.secure.value == ConnectionType.strSecure; - bool direct = - connectionType.direct.value == ConnectionType.strDirect; - String msgConn = getConnectionText( - secure, direct, connectionType.stream_type.value); - var msgFingerprint = '${translate('Fingerprint')}:\n'; - var fingerprint = FingerprintState.find(key).value; - if (fingerprint.isEmpty) { - fingerprint = 'N/A'; - } - if (fingerprint.length > 5 * 8) { - var first = fingerprint.substring(0, 39); - var second = fingerprint.substring(40); - msgFingerprint += '$first\n$second'; - } else { - msgFingerprint += fingerprint; - } - - final tab = Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - Tooltip( - message: '$msgConn\n$msgFingerprint', - child: SvgPicture.asset( - 'assets/${connectionType.secure.value}${connectionType.direct.value}.svg', - width: themeConf.iconSize, - height: themeConf.iconSize, - ).paddingOnly(right: 5), - ), - label, - unreadMessageCountBuilder(UnreadChatCountState.find(key)) - .marginOnly(left: 4), - ], - ); - - return Listener( - onPointerDown: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) { - return; - } - final remotePage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == key) - .page as RemotePage; - if (remotePage.ffi.ffiModel.pi.isSet.isTrue && e.buttons == 2) { - showRightMenu( - (CancelFunc cancelFunc) { - return _tabMenuBuilder(key, cancelFunc); - }, - target: e.position, - ); - } - }, - child: tab, - ); - } - }), - ), - ); - final tabWidget = isLinux - ? buildVirtualWindowFrame(context, child) - : workaroundWindowBorder( - context, - Obx(() => Container( - decoration: BoxDecoration( - border: Border.all( - color: MyTheme.color(context).border!, - width: stateGlobal.windowBorderWidth.value), - ), - child: child, - ))); - return 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, - enableResizeEdges: subWindowManagerEnableResizeEdges, - windowId: stateGlobal.windowId, - )); - } - - // Note: Some dup code to ../widgets/remote_toolbar - Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) { - final List> menu = []; - const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); - final remotePage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == key) - .page as RemotePage; - final ffi = remotePage.ffi; - final pi = ffi.ffiModel.pi; - final perms = ffi.ffiModel.permissions; - final sessionId = ffi.sessionId; - final toolbarState = remotePage.toolbarState; - menu.addAll([ - MenuEntryButton( - childBuilder: (TextStyle? style) => Obx(() => Text( - translate( - toolbarState.hide.isTrue ? 'Show Toolbar' : 'Hide Toolbar'), - style: style, - )), - proc: () { - toolbarState.switchHide(sessionId); - cancelFunc(); - }, - padding: padding, - ), - ]); - - if (tabController.state.value.tabs.length > 1) { - final splitAction = MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Move tab to new window'), - style: style, - ), - proc: () async { - await DesktopMultiWindow.invokeMethod( - kMainWindowId, - kWindowEventMoveTabToNewWindow, - '${windowId()},$key,$sessionId,RemoteDesktop'); - cancelFunc(); - }, - padding: padding, - ); - menu.insert(1, splitAction); - } - - if (perms['restart'] != false && - (pi.platform == kPeerPlatformLinux || - pi.platform == kPeerPlatformWindows || - pi.platform == kPeerPlatformMacOS)) { - menu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Restart remote device'), - style: style, - ), - proc: () => showRestartRemoteDevice( - pi, peerId ?? '', sessionId, ffi.dialogManager), - padding: padding, - dismissOnClicked: true, - dismissCallback: cancelFunc, - )); - } - - if (perms['keyboard'] != false && !ffi.ffiModel.viewOnly) { - menu.add(RemoteMenuEntry.insertLock(sessionId, padding, - dismissFunc: cancelFunc)); - - if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) { - menu.add(RemoteMenuEntry.insertCtrlAltDel(sessionId, padding, - dismissFunc: cancelFunc)); - } - } - - menu.addAll([ - MenuEntryDivider(), - MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Copy Fingerprint'), - style: style, - ), - proc: () => onCopyFingerprint(FingerprintState.find(key).value), - padding: padding, - dismissOnClicked: true, - dismissCallback: cancelFunc, - ), - MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Close'), - style: style, - ), - proc: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: key, - tabController: tabController, - )) { - return; - } - tabController.closeBy(key); - cancelFunc(); - }, - padding: padding, - ) - ]); - - return mod_menu.PopupMenu( - items: menu - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: _MenuTheme.blueColor, - height: _MenuTheme.height, - dividerHeight: _MenuTheme.dividerHeight, - ))) - .expand((i) => i) - .toList(), - ); - } - - void onRemoveId(String id) async { - if (tabController.state.value.tabs.isEmpty) { - // Keep calling until the window status is hidden. - // - // Workaround for Windows: - // If you click other buttons and close in msgbox within a very short period of time, the close may fail. - // `await WindowController.fromWindowId(windowId()).close();`. - Future loopCloseWindow() async { - int c = 0; - final windowController = WindowController.fromWindowId(windowId()); - while (c < 20 && - tabController.state.value.tabs.isEmpty && - (!await windowController.isHidden())) { - await windowController.close(); - await Future.delayed(Duration(milliseconds: 100)); - c++; - } - } - - loopCloseWindow(); - } - ConnectionTypeState.delete(id); - // Clean up relative mouse mode state for this peer. - stateGlobal.relativeMouseModeState.remove(id); - _update_remote_count(); - } - - int windowId() { - return widget.params["windowId"]; - } - - Future handleWindowCloseButton() async { - final connLength = tabController.length; - if (connLength == 1) { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: tabController.state.value.tabs[0].key, - tabController: tabController, - )) { - return false; - } - } - if (connLength <= 1) { - tabController.clear(); - return true; - } else { - final bool res; - if (!option2bool(kOptionEnableConfirmClosingTabs, - bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { - res = true; - } else { - res = await closeConfirmDialog(); - } - if (res) { - tabController.clear(); - } - return res; - } - } - - _update_remote_count() => - RemoteCountState.find().value = tabController.length; - - Future _remoteMethodHandler(call, fromWindowId) async { - debugPrint( - "[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId"); - - dynamic returnValue; - // for simplify, just replace connectionId - if (call.method == kWindowEventNewRemoteDesktop) { - final args = jsonDecode(call.arguments); - final id = args['id']; - final switchUuid = args['switch_uuid']; - final sessionId = args['session_id']; - final tabWindowId = args['tab_window_id']; - final display = args['display']; - final displays = args['displays']; - final screenRect = parseParamScreenRect(args); - final prePeerCount = tabController.length; - Future.delayed(Duration.zero, () async { - if (stateGlobal.fullscreen.isTrue) { - await WindowController.fromWindowId(windowId()).setFullscreen(false); - stateGlobal.setFullscreen(false, procWnd: false); - } - await setNewConnectWindowFrame(windowId(), id!, prePeerCount, - WindowType.RemoteDesktop, display, screenRect); - Future.delayed(Duration(milliseconds: isWindows ? 100 : 0), () async { - await windowOnTop(windowId()); - }); - }); - ConnectionTypeState.init(id); - tabController.add(TabInfo( - key: id, - label: id, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: id, - tabController: tabController, - )) { - return; - } - tabController.closeBy(id); - }, - page: RemotePage( - key: ValueKey(id), - id: id, - sessionId: sessionId == null ? null : SessionID(sessionId), - tabWindowId: tabWindowId, - display: display, - displays: displays?.cast(), - password: args['password'], - toolbarState: ToolbarState(), - tabController: tabController, - switchUuid: switchUuid, - forceRelay: args['forceRelay'], - isSharedPassword: args['isSharedPassword'], - ), - )); - } else if (call.method == kWindowDisableGrabKeyboard) { - // ??? - } else if (call.method == "onDestroy") { - tabController.clear(); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } else if (call.method == kWindowEventActiveSession) { - final jumpOk = tabController.jumpToByKey(call.arguments); - if (jumpOk) { - windowOnTop(windowId()); - } - return jumpOk; - } else if (call.method == kWindowEventActiveDisplaySession) { - final args = jsonDecode(call.arguments); - final id = args['id']; - final display = args['display']; - final jumpOk = tabController.jumpToByKeyAndDisplay(id, display); - if (jumpOk) { - windowOnTop(windowId()); - } - return jumpOk; - } else if (call.method == kWindowEventGetRemoteList) { - return tabController.state.value.tabs - .map((e) => e.key) - .toList() - .join(','); - } else if (call.method == kWindowEventGetSessionIdList) { - return tabController.state.value.tabs - .map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}') - .toList() - .join(';'); - } else if (call.method == kWindowEventGetCachedSessionData) { - // Ready to show new window and close old tab. - final args = jsonDecode(call.arguments); - final id = args['id']; - final close = args['close']; - try { - final remotePage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == id) - .page as RemotePage; - returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString(); - } catch (e) { - debugPrint('Failed to get cached session data: $e'); - } - if (close && returnValue != null) { - closeSessionOnDispose[id] = false; - tabController.closeBy(id); - } - } else if (call.method == kWindowEventRemoteWindowCoords) { - final remotePage = - tabController.state.value.selectedTabInfo.page as RemotePage; - final ffi = remotePage.ffi; - final displayRect = ffi.ffiModel.displaysRect(); - if (displayRect != null) { - final wc = WindowController.fromWindowId(windowId()); - Rect? frame; - try { - frame = await wc.getFrame(); - } catch (e) { - debugPrint( - "Failed to get frame of window $windowId, it may be hidden"); - } - if (frame != null) { - ffi.cursorModel.moveLocal(0, 0); - final coords = RemoteWindowCoords( - frame, - CanvasCoords.fromCanvasModel(ffi.canvasModel), - CursorCoords.fromCursorModel(ffi.cursorModel), - displayRect); - returnValue = jsonEncode(coords.toJson()); - } - } - } else if (call.method == kWindowEventSetFullscreen) { - stateGlobal.setFullscreen(call.arguments == 'true'); - } - _update_remote_count(); - return returnValue; - } -} - -/// A widget that displays a hint in the tab bar when relative mouse mode is active. -/// This helps users remember how to exit relative mouse mode. -class _RelativeMouseModeHint extends StatelessWidget { - final DesktopTabController tabController; - - const _RelativeMouseModeHint({Key? key, required this.tabController}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return Obx(() { - // Check if there are any tabs - if (tabController.state.value.tabs.isEmpty) { - return const SizedBox.shrink(); - } - - // Get current selected tab's RemotePage - final selectedTabInfo = tabController.state.value.selectedTabInfo; - if (selectedTabInfo.page is! RemotePage) { - return const SizedBox.shrink(); - } - - final remotePage = selectedTabInfo.page as RemotePage; - final String peerId = remotePage.id; - - // Use global state to check relative mouse mode (synced from InputModel). - // This avoids timing issues with FFI registration. - final isRelativeMouseMode = - stateGlobal.relativeMouseModeState[peerId] ?? false; - - if (!isRelativeMouseMode) { - return const SizedBox.shrink(); - } - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - margin: const EdgeInsets.only(right: 8), - decoration: BoxDecoration( - color: Colors.orange.withOpacity(0.2), - borderRadius: BorderRadius.circular(4), - border: Border.all(color: Colors.orange.withOpacity(0.5)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.mouse, - size: 14, - color: Colors.orange[700], - ), - const SizedBox(width: 4), - Text( - translate( - 'rel-mouse-exit-{${isMacOS ? "Cmd+G" : "Ctrl+Alt"}}-tip'), - style: TextStyle( - fontSize: 11, - color: Colors.orange[700], - ), - ), - ], - ), - ); - }); - } -} diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart deleted file mode 100644 index 8bd7df08b..000000000 --- a/flutter/lib/desktop/pages/server_page.dart +++ /dev/null @@ -1,1448 +0,0 @@ -// original cm window in Sciter version. - -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common/widgets/audio_input.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/models/chat_model.dart'; -import 'package:flutter_hbb/models/cm_file_model.dart'; -import 'package:flutter_hbb/utils/platform_channel.dart'; -import 'package:get/get.dart'; -import 'package:percent_indicator/linear_percent_indicator.dart'; -import 'package:provider/provider.dart'; -import 'package:window_manager/window_manager.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -import '../../common.dart'; -import '../../common/widgets/chat_page.dart'; -import '../../models/file_model.dart'; -import '../../models/platform_model.dart'; -import '../../models/server_model.dart'; - -class DesktopServerPage extends StatefulWidget { - const DesktopServerPage({Key? key}) : super(key: key); - - @override - State createState() => _DesktopServerPageState(); -} - -class _DesktopServerPageState extends State - with WindowListener, AutomaticKeepAliveClientMixin { - final tabController = gFFI.serverModel.tabController; - - _DesktopServerPageState() { - gFFI.ffiModel.updateEventListener(gFFI.sessionId, ""); - Get.put(tabController); - tabController.onRemoved = (_, id) { - onRemoveId(id); - }; - } - - @override - void initState() { - windowManager.addListener(this); - super.initState(); - } - - @override - void dispose() { - windowManager.removeListener(this); - super.dispose(); - } - - @override - void onWindowClose() { - Future.wait([gFFI.serverModel.closeAll(), gFFI.close()]).then((_) { - if (isMacOS) { - RdPlatformChannel.instance.terminate(); - } else { - windowManager.setPreventClose(false); - windowManager.close(); - } - }); - super.onWindowClose(); - } - - void onRemoveId(String id) { - if (tabController.state.value.tabs.isEmpty) { - windowManager.close(); - } - } - - @override - Widget build(BuildContext context) { - super.build(context); - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.serverModel), - ChangeNotifierProvider.value(value: gFFI.chatModel), - ], - child: Consumer( - builder: (context, serverModel, child) { - final body = Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: ConnectionManager(), - ); - return isLinux - ? buildVirtualWindowFrame(context, body) - : workaroundWindowBorder( - context, - Container( - decoration: BoxDecoration( - border: - Border.all(color: MyTheme.color(context).border!)), - child: body, - )); - }, - ), - ); - } - - @override - bool get wantKeepAlive => true; -} - -class ConnectionManager extends StatefulWidget { - @override - State createState() => ConnectionManagerState(); -} - -class ConnectionManagerState extends State - with WidgetsBindingObserver { - final RxBool _controlPageBlock = false.obs; - final RxBool _sidePageBlock = false.obs; - - ConnectionManagerState() { - gFFI.serverModel.tabController.onSelected = (client_id_str) { - final client_id = int.tryParse(client_id_str); - if (client_id != null) { - final client = - gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == client_id); - if (client != null) { - gFFI.chatModel.changeCurrentKey(MessageKey(client.peerId, client.id)); - if (client.unreadChatMessageCount.value > 0) { - WidgetsBinding.instance.addPostFrameCallback((_) { - client.unreadChatMessageCount.value = 0; - gFFI.chatModel.showChatPage(MessageKey(client.peerId, client.id)); - }); - } - windowManager.setTitle(getWindowNameWithId(client.peerId)); - gFFI.cmFileModel.updateCurrentClientId(client.id); - } - } - }; - gFFI.chatModel.isConnManager = true; - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - if (!allowRemoteCMModification()) { - shouldBeBlocked(_controlPageBlock, null); - shouldBeBlocked(_sidePageBlock, null); - } - } - } - - @override - void initState() { - gFFI.serverModel.updateClientState(); - WidgetsBinding.instance.addObserver(this); - super.initState(); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final serverModel = Provider.of(context); - pointerHandler(PointerEvent e) { - if (serverModel.cmHiddenTimer != null) { - serverModel.cmHiddenTimer!.cancel(); - serverModel.cmHiddenTimer = null; - debugPrint("CM hidden timer has been canceled"); - } - } - - return serverModel.clients.isEmpty - ? Column( - children: [ - buildTitleBar(), - Expanded( - child: Center( - child: Text(translate("Waiting")), - ), - ), - ], - ) - : Listener( - onPointerDown: pointerHandler, - onPointerMove: pointerHandler, - child: DesktopTab( - showTitle: false, - showMaximize: false, - showMinimize: true, - showClose: true, - onWindowCloseButton: handleWindowCloseButton, - controller: serverModel.tabController, - selectedBorderColor: MyTheme.accent, - maxLabelWidth: 100, - tail: null, //buildScrollJumper(), - tabBuilder: (key, icon, label, themeConf) { - final client = serverModel.clients - .firstWhereOrNull((client) => client.id.toString() == key); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Tooltip( - message: key, - waitDuration: Duration(seconds: 1), - child: label), - unreadMessageCountBuilder(client?.unreadChatMessageCount) - .marginOnly(left: 4), - ], - ); - }, - pageViewBuilder: (pageView) => LayoutBuilder( - builder: (context, constrains) { - var borderWidth = 0.0; - if (constrains.maxWidth > - kConnectionManagerWindowSizeClosedChat.width) { - borderWidth = kConnectionManagerWindowSizeOpenChat.width - - constrains.maxWidth; - } else { - borderWidth = kConnectionManagerWindowSizeClosedChat.width - - constrains.maxWidth; - } - if (borderWidth < 0 || borderWidth > 50) { - borderWidth = 0; - } - final realClosedWidth = - kConnectionManagerWindowSizeClosedChat.width - - borderWidth; - final realChatPageWidth = - constrains.maxWidth - realClosedWidth; - final row = Row(children: [ - if (constrains.maxWidth > - kConnectionManagerWindowSizeClosedChat.width) - Consumer( - builder: (_, model, child) => SizedBox( - width: realChatPageWidth, - child: allowRemoteCMModification() - ? buildSidePage() - : buildRemoteBlock( - child: buildSidePage(), - block: _sidePageBlock, - mask: true), - )), - SizedBox( - width: realClosedWidth, - child: SizedBox( - width: realClosedWidth, - child: allowRemoteCMModification() - ? pageView - : buildRemoteBlock( - child: _buildKeyEventBlock(pageView), - block: _controlPageBlock, - mask: false, - ))), - ]); - return Container( - color: Theme.of(context).scaffoldBackgroundColor, - child: row, - ); - }, - ), - ), - ); - } - - Widget buildSidePage() { - final selected = gFFI.serverModel.tabController.state.value.selected; - if (selected < 0 || selected >= gFFI.serverModel.clients.length) { - return Offstage(); - } - final clientType = gFFI.serverModel.clients[selected].type_(); - if (clientType == ClientType.file) { - return _FileTransferLogPage(); - } else { - return ChatPage(type: ChatPageType.desktopCM); - } - } - - Widget _buildKeyEventBlock(Widget child) { - return ExcludeFocus(child: child, excluding: true); - } - - Widget buildTitleBar() { - return SizedBox( - height: kDesktopRemoteTabBarHeight, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const _AppIcon(), - Expanded( - child: GestureDetector( - onPanStart: (d) { - windowManager.startDragging(); - }, - child: Container( - color: Theme.of(context).colorScheme.background, - ), - ), - ), - const SizedBox( - width: 4.0, - ), - const _CloseButton() - ], - ), - ); - } - - Widget buildScrollJumper() { - final offstage = gFFI.serverModel.clients.length < 2; - final sc = gFFI.serverModel.tabController.state.value.scrollController; - return Offstage( - offstage: offstage, - child: Row( - children: [ - ActionIcon( - icon: Icons.arrow_left, iconSize: 22, onTap: sc.backward), - ActionIcon( - icon: Icons.arrow_right, iconSize: 22, onTap: sc.forward), - ], - )); - } - - Future handleWindowCloseButton() async { - var tabController = gFFI.serverModel.tabController; - final connLength = tabController.length; - if (connLength <= 1) { - windowManager.close(); - return true; - } else { - final bool res; - if (!option2bool(kOptionEnableConfirmClosingTabs, - bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { - res = true; - } else { - res = await closeConfirmDialog(); - } - if (res) { - windowManager.close(); - } - return res; - } - } -} - -Widget buildConnectionCard(Client client) { - return Consumer( - builder: (context, value, child) => Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - key: ValueKey(client.id), - children: [ - _CmHeader(client: client), - client.type_() == ClientType.file || - client.type_() == ClientType.portForward || - client.type_() == ClientType.terminal || - client.disconnected - ? Offstage() - : _PrivilegeBoard(client: client), - Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: _CmControlPanel(client: client), - ), - ) - ], - ).paddingSymmetric(vertical: 4.0, horizontal: 8.0), - ); -} - -class _AppIcon extends StatelessWidget { - const _AppIcon({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.symmetric(horizontal: 4.0), - child: loadIcon(30), - ); - } -} - -class _CloseButton extends StatelessWidget { - const _CloseButton({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return IconButton( - onPressed: () { - windowManager.close(); - }, - icon: const Icon( - IconFont.close, - size: 18, - ), - splashColor: Colors.transparent, - hoverColor: Colors.transparent, - ); - } -} - -class _CmHeader extends StatefulWidget { - final Client client; - - const _CmHeader({Key? key, required this.client}) : super(key: key); - - @override - State<_CmHeader> createState() => _CmHeaderState(); -} - -class _CmHeaderState extends State<_CmHeader> - with AutomaticKeepAliveClientMixin { - Client get client => widget.client; - - final _time = 0.obs; - Timer? _timer; - - @override - void initState() { - super.initState(); - _timer = Timer.periodic(Duration(seconds: 1), (_) { - if (client.authorized && !client.disconnected) { - _time.value = _time.value + 1; - } - }); - // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. - WidgetsBinding.instance.addPostFrameCallback((_) { - gFFI.serverModel.tabController.onSelected?.call(client.id.toString()); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - gradient: LinearGradient( - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: [ - Color(0xff00bfe1), - Color(0xff0071ff), - ], - ), - ), - margin: EdgeInsets.symmetric(horizontal: 5.0, vertical: 10.0), - padding: EdgeInsets.only( - top: 10.0, - bottom: 10.0, - left: 10.0, - right: 5.0, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildClientAvatar().marginOnly(right: 10.0), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FittedBox( - child: Text( - client.name, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 20, - overflow: TextOverflow.ellipsis, - ), - maxLines: 1, - )), - FittedBox( - child: Text( - "(${client.peerId})", - style: TextStyle(color: Colors.white, fontSize: 14), - ), - ), - if (client.type_() == ClientType.terminal) - FittedBox( - child: Text( - translate("Terminal"), - style: TextStyle(color: Colors.white70, fontSize: 12), - ), - ), - if (client.type_() == ClientType.file) - FittedBox( - child: Text( - translate("File Transfer"), - style: TextStyle(color: Colors.white70, fontSize: 12), - ), - ), - if (client.type_() == ClientType.camera) - FittedBox( - child: Text( - translate("View Camera"), - style: TextStyle(color: Colors.white70, fontSize: 12), - ), - ), - if (client.portForward.isNotEmpty) - FittedBox( - child: Text( - "Port Forward: ${client.portForward}", - style: TextStyle(color: Colors.white70, fontSize: 12), - ), - ), - SizedBox(height: 10.0), - FittedBox( - child: Row( - children: [ - Text( - client.authorized - ? client.disconnected - ? translate("Disconnected") - : translate("Connected") - : "${translate("Request access to your device")}...", - style: TextStyle(color: Colors.white), - ).marginOnly(right: 8.0), - if (client.authorized) - Obx( - () => Text( - formatDurationToTime( - Duration(seconds: _time.value), - ), - style: TextStyle(color: Colors.white), - ), - ) - ], - )) - ], - ), - ), - Offstage( - offstage: !client.authorized || - (client.type_() != ClientType.remote && - client.type_() != ClientType.file && - client.type_() != ClientType.camera), - child: IconButton( - onPressed: () => checkClickTime(client.id, () { - if (client.type_() == ClientType.file) { - gFFI.chatModel.toggleCMFilePage(); - } else { - gFFI.chatModel - .toggleCMChatPage(MessageKey(client.peerId, client.id)); - } - }), - icon: SvgPicture.asset(client.type_() == ClientType.file - ? 'assets/file_transfer.svg' - : 'assets/chat2.svg'), - splashRadius: kDesktopIconButtonSplashRadius, - ), - ) - ], - ), - ); - } - - @override - bool get wantKeepAlive => true; - - Widget _buildClientAvatar() { - return buildAvatarWidget( - avatar: client.avatar, - size: 70, - borderRadius: 15, - fallback: _buildInitialAvatar(), - ) ?? - _buildInitialAvatar(); - } - - Widget _buildInitialAvatar() { - return Container( - width: 70, - height: 70, - alignment: Alignment.center, - decoration: BoxDecoration( - color: str2color(client.name), - borderRadius: BorderRadius.circular(15.0), - ), - child: Text( - client.name.isNotEmpty ? client.name[0] : '?', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - fontSize: 55, - ), - ), - ); - } -} - -class _PrivilegeBoard extends StatefulWidget { - final Client client; - - const _PrivilegeBoard({Key? key, required this.client}) : super(key: key); - - @override - State createState() => _PrivilegeBoardState(); -} - -class _PrivilegeBoardState extends State<_PrivilegeBoard> { - late final client = widget.client; - Widget buildPermissionIcon(bool enabled, IconData iconData, - Function(bool)? onTap, String tooltipText, - {required bool canModify}) { - return Tooltip( - message: "$tooltipText: ${enabled ? "ON" : "OFF"}", - waitDuration: Duration.zero, - child: Container( - decoration: BoxDecoration( - color: enabled - ? (canModify ? MyTheme.accent : MyTheme.accent.withOpacity(0.6)) - : Colors.grey[700], - borderRadius: BorderRadius.circular(10.0), - ), - padding: EdgeInsets.all(8.0), - child: InkWell( - onTap: canModify - ? () => - checkClickTime(widget.client.id, () => onTap?.call(!enabled)) - : null, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Icon( - iconData, - color: Colors.white, - ), - ), - ], - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - final crossAxisCount = 4; - final spacing = 10.0; - final canModifyPermission = - bind.mainGetBuildinOption(key: kOptionEnablePermChangeInAcceptWindow) != - 'N'; - return Container( - width: double.infinity, - height: 160.0, - margin: EdgeInsets.all(5.0), - padding: EdgeInsets.all(5.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.0), - color: Theme.of(context).colorScheme.background, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - spreadRadius: 1, - blurRadius: 1, - offset: Offset(0, 1.5), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - translate("Permissions"), - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ).marginOnly(left: 4.0, bottom: 8.0), - Expanded( - child: GridView.count( - crossAxisCount: crossAxisCount, - padding: EdgeInsets.symmetric(horizontal: spacing), - mainAxisSpacing: spacing, - crossAxisSpacing: spacing, - children: client.type_() == ClientType.camera - ? [ - buildPermissionIcon( - client.audio, - Icons.volume_up_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "audio", - enabled: enabled); - setState(() { - client.audio = enabled; - }); - }, - translate('Enable audio'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.recording, - Icons.videocam_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "recording", - enabled: enabled); - setState(() { - client.recording = enabled; - }); - }, - translate('Enable recording session'), - canModify: canModifyPermission, - ), - ] - : [ - buildPermissionIcon( - client.keyboard, - Icons.keyboard, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "keyboard", - enabled: enabled); - setState(() { - client.keyboard = enabled; - }); - }, - translate('Enable keyboard/mouse'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.clipboard, - Icons.assignment_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "clipboard", - enabled: enabled); - setState(() { - client.clipboard = enabled; - }); - }, - translate('Enable clipboard'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.audio, - Icons.volume_up_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "audio", - enabled: enabled); - setState(() { - client.audio = enabled; - }); - }, - translate('Enable audio'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.file, - Icons.upload_file_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "file", - enabled: enabled); - setState(() { - client.file = enabled; - }); - }, - translate('Enable file copy and paste'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.restart, - Icons.restart_alt_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "restart", - enabled: enabled); - setState(() { - client.restart = enabled; - }); - }, - translate('Enable remote restart'), - canModify: canModifyPermission, - ), - buildPermissionIcon( - client.recording, - Icons.videocam_rounded, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "recording", - enabled: enabled); - setState(() { - client.recording = enabled; - }); - }, - translate('Enable recording session'), - canModify: canModifyPermission, - ), - // only windows support block input - if (isWindows) - buildPermissionIcon( - client.blockInput, - Icons.block, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "block_input", - enabled: enabled); - setState(() { - client.blockInput = enabled; - }); - }, - translate('Enable blocking user input'), - canModify: canModifyPermission, - ), - if (bind.mainSupportedPrivacyModeImpls() != '[]') - buildPermissionIcon( - client.privacyMode, - Icons.visibility_off, - (enabled) { - bind.cmSwitchPermission( - connId: client.id, - name: "privacy_mode", - enabled: enabled); - setState(() { - client.privacyMode = enabled; - }); - }, - translate('Enable privacy mode'), - canModify: canModifyPermission, - ) - ], - ), - ), - ], - ), - ); - } -} - -const double buttonBottomMargin = 8; - -class _CmControlPanel extends StatelessWidget { - final Client client; - - const _CmControlPanel({Key? key, required this.client}) : super(key: key); - - @override - Widget build(BuildContext context) { - return client.authorized - ? client.disconnected - ? buildDisconnected(context) - : buildAuthorized(context) - : buildUnAuthorized(context); - } - - buildAuthorized(BuildContext context) { - final bool canElevate = bind.cmCanElevate(); - final model = Provider.of(context); - final showElevation = canElevate && - model.showElevation && - client.type_() == ClientType.remote; - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Offstage( - offstage: !client.inVoiceCall, - child: Row( - children: [ - Expanded( - child: buildButton(context, - color: MyTheme.accent, - onClick: null, onTapDown: (details) async { - final devicesInfo = - await AudioInput.getDevicesInfo(true, true); - List devices = devicesInfo['devices'] as List; - if (devices.isEmpty) { - msgBox( - gFFI.sessionId, - 'custom-nocancel-info', - 'Prompt', - 'no_audio_input_device_tip', - '', - gFFI.dialogManager, - ); - return; - } - - String currentDevice = devicesInfo['current'] as String; - final x = details.globalPosition.dx; - final y = details.globalPosition.dy; - final position = RelativeRect.fromLTRB(x, y, x, y); - showMenu( - context: context, - position: position, - items: devices - .map((d) => PopupMenuItem( - value: d, - height: 18, - padding: EdgeInsets.zero, - onTap: () => AudioInput.setDevice(d, true, true), - child: IgnorePointer( - child: RadioMenuButton( - value: d, - groupValue: currentDevice, - onChanged: (v) { - if (v != null) - AudioInput.setDevice(v, true, true); - }, - child: Container( - child: Text( - d, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - constraints: BoxConstraints( - maxWidth: - kConnectionManagerWindowSizeClosedChat - .width - - 80), - ), - )), - )) - .toList(), - ); - }, - icon: Icon( - Icons.call_rounded, - color: Colors.white, - size: 14, - ), - text: "Audio input", - textColor: Colors.white), - ), - Expanded( - child: buildButton( - context, - color: Colors.red, - onClick: () => closeVoiceCall(), - icon: Icon( - Icons.call_end_rounded, - color: Colors.white, - size: 14, - ), - text: "Stop voice call", - textColor: Colors.white, - ), - ) - ], - ), - ), - Offstage( - offstage: !client.incomingVoiceCall, - child: Row( - children: [ - Expanded( - child: buildButton(context, - color: MyTheme.accent, - onClick: () => handleVoiceCall(true), - icon: Icon( - Icons.call_rounded, - color: Colors.white, - size: 14, - ), - text: "Accept", - textColor: Colors.white), - ), - Expanded( - child: buildButton( - context, - color: Colors.red, - onClick: () => handleVoiceCall(false), - icon: Icon( - Icons.phone_disabled_rounded, - color: Colors.white, - size: 14, - ), - text: "Dismiss", - textColor: Colors.white, - ), - ) - ], - ), - ), - Offstage( - offstage: !client.fromSwitch, - child: buildButton(context, - color: Colors.purple, - onClick: () => handleSwitchBack(context), - icon: Icon(Icons.reply, color: Colors.white), - text: "Switch Sides", - textColor: Colors.white), - ), - Offstage( - offstage: !showElevation, - child: buildButton( - context, - color: MyTheme.accent, - onClick: () { - handleElevate(context); - windowManager.minimize(); - }, - icon: Icon( - Icons.security_rounded, - color: Colors.white, - size: 14, - ), - text: 'Elevate', - textColor: Colors.white, - ), - ), - Row( - children: [ - Expanded( - child: buildButton(context, - color: Colors.redAccent, - onClick: handleDisconnect, - text: 'Disconnect', - icon: Icon( - Icons.link_off_rounded, - color: Colors.white, - size: 14, - ), - textColor: Colors.white), - ), - ], - ) - ], - ).marginOnly(bottom: buttonBottomMargin); - } - - buildDisconnected(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: buildButton(context, - color: MyTheme.accent, - onClick: handleClose, - text: 'Close', - textColor: Colors.white)), - ], - ).marginOnly(bottom: buttonBottomMargin); - } - - buildUnAuthorized(BuildContext context) { - final bool canElevate = bind.cmCanElevate(); - final model = Provider.of(context); - final showElevation = canElevate && - model.showElevation && - client.type_() == ClientType.remote; - final showAccept = model.approveMode != 'password'; - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Offstage( - offstage: !showElevation || !showAccept, - child: buildButton(context, color: Colors.green[700], onClick: () { - handleAccept(context); - handleElevate(context); - windowManager.minimize(); - }, - text: 'Accept and Elevate', - icon: Icon( - Icons.security_rounded, - color: Colors.white, - size: 14, - ), - textColor: Colors.white, - tooltip: 'accept_and_elevate_btn_tooltip'), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (showAccept) - Expanded( - child: Column( - children: [ - buildButton( - context, - color: MyTheme.accent, - onClick: () { - handleAccept(context); - windowManager.minimize(); - }, - text: 'Accept', - textColor: Colors.white, - ), - ], - ), - ), - Expanded( - child: buildButton( - context, - color: Colors.transparent, - border: Border.all(color: Colors.grey), - onClick: handleDisconnect, - text: 'Cancel', - textColor: null, - ), - ), - ], - ), - ], - ).marginOnly(bottom: buttonBottomMargin); - } - - Widget buildButton(BuildContext context, - {required Color? color, - GestureTapCallback? onClick, - Widget? icon, - BoxBorder? border, - required String text, - required Color? textColor, - String? tooltip, - GestureTapDownCallback? onTapDown}) { - assert(!(onClick == null && onTapDown == null)); - Widget textWidget; - if (icon != null) { - textWidget = Text( - translate(text), - style: TextStyle(color: textColor), - textAlign: TextAlign.center, - ); - } else { - textWidget = Expanded( - child: Text( - translate(text), - style: TextStyle(color: textColor), - textAlign: TextAlign.center, - ), - ); - } - final borderRadius = BorderRadius.circular(10.0); - final btn = Container( - height: 28, - decoration: BoxDecoration( - color: color, borderRadius: borderRadius, border: border), - child: InkWell( - borderRadius: borderRadius, - onTap: () { - if (onClick == null) return; - checkClickTime(client.id, onClick); - }, - onTapDown: (details) { - if (onTapDown == null) return; - checkClickTime(client.id, () { - onTapDown.call(details); - }); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Offstage(offstage: icon == null, child: icon).marginOnly(right: 5), - textWidget, - ], - ), - ), - ); - return (tooltip != null - ? Tooltip( - message: translate(tooltip), - child: btn, - ) - : btn) - .marginAll(4); - } - - void handleDisconnect() { - bind.cmCloseConnection(connId: client.id); - } - - void handleAccept(BuildContext context) { - final model = Provider.of(context, listen: false); - model.sendLoginResponse(client, true); - } - - void handleElevate(BuildContext context) { - final model = Provider.of(context, listen: false); - model.setShowElevation(false); - bind.cmElevatePortable(connId: client.id); - } - - void handleClose() async { - await bind.cmRemoveDisconnectedConnection(connId: client.id); - if (await bind.cmGetClientsLength() == 0) { - windowManager.close(); - } - } - - void handleSwitchBack(BuildContext context) { - bind.cmSwitchBack(connId: client.id); - } - - void handleVoiceCall(bool accept) { - bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept); - } - - void closeVoiceCall() { - bind.cmCloseVoiceCall(id: client.id); - } -} - -void checkClickTime(int id, Function() callback) async { - if (allowRemoteCMModification()) { - callback(); - return; - } - var clickCallbackTime = DateTime.now().millisecondsSinceEpoch; - await bind.cmCheckClickTime(connId: id); - Timer(const Duration(milliseconds: 120), () async { - var d = clickCallbackTime - await bind.cmGetClickTime(); - if (d > 120) callback(); - }); -} - -bool allowRemoteCMModification() { - return option2bool(kOptionAllowRemoteCmModification, - bind.mainGetLocalOption(key: kOptionAllowRemoteCmModification)); -} - -class _FileTransferLogPage extends StatefulWidget { - _FileTransferLogPage({Key? key}) : super(key: key); - - @override - State<_FileTransferLogPage> createState() => __FileTransferLogPageState(); -} - -class __FileTransferLogPageState extends State<_FileTransferLogPage> { - @override - Widget build(BuildContext context) { - return statusList(); - } - - Widget generateCard(Widget child) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.all( - Radius.circular(15.0), - ), - ), - child: child, - ); - } - - iconLabel(CmFileLog item) { - switch (item.action) { - case CmFileAction.none: - return Container(); - case CmFileAction.localToRemote: - case CmFileAction.remoteToLocal: - return Column( - children: [ - Transform.rotate( - angle: item.action == CmFileAction.remoteToLocal ? 0 : pi, - child: SvgPicture.asset( - "assets/arrow.svg", - colorFilter: svgColor(Theme.of(context).tabBarTheme.labelColor), - ), - ), - Text(item.action == CmFileAction.remoteToLocal - ? translate('Send') - : translate('Receive')) - ], - ); - case CmFileAction.remove: - return Column( - children: [ - Icon( - Icons.delete, - color: Theme.of(context).tabBarTheme.labelColor, - ), - Text(translate('Delete')) - ], - ); - case CmFileAction.createDir: - return Column( - children: [ - Icon( - Icons.create_new_folder, - color: Theme.of(context).tabBarTheme.labelColor, - ), - Text(translate('Create Folder')) - ], - ); - case CmFileAction.rename: - return Column( - children: [ - Icon( - Icons.drive_file_move_outlined, - color: Theme.of(context).tabBarTheme.labelColor, - ), - Text(translate('Rename')) - ], - ); - } - } - - Widget statusList() { - return PreferredSize( - preferredSize: const Size(200, double.infinity), - child: Container( - padding: const EdgeInsets.all(12.0), - child: Obx( - () { - final jobTable = gFFI.cmFileModel.currentJobTable; - 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: [ - SizedBox( - width: 50, - child: iconLabel(item), - ).paddingOnly(left: 15), - const SizedBox( - width: 16.0, - ), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - item.fileName, - ).paddingSymmetric(vertical: 10), - if (item.totalSize > 0) - Text( - '${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}', - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), - ), - if (item.totalSize > 0) - Offstage( - offstage: item.state != - JobState.inProgress, - child: Text( - '${translate("Speed")} ${readableFileSize(item.speed)}/s', - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), - ), - ), - Offstage( - offstage: !(item.isTransfer() && - item.state != - JobState.inProgress), - child: Text( - translate( - item.display(), - ), - style: TextStyle( - fontSize: 12, - color: MyTheme.darkGray, - ), - ), - ), - if (item.totalSize > 0) - 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), - ), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [], - ), - ], - ), - ], - ).paddingSymmetric(vertical: 10), - ), - ); - }, - itemCount: jobTable.length, - ); - - return jobTable.isEmpty - ? generateCard( - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - "assets/transfer.svg", - colorFilter: svgColor( - Theme.of(context).tabBarTheme.labelColor), - height: 40, - ).paddingOnly(bottom: 10), - Text( - translate("No transfers in progress"), - textAlign: TextAlign.center, - textScaler: TextScaler.linear(1.20), - style: TextStyle( - color: - Theme.of(context).tabBarTheme.labelColor), - ), - ], - ), - ), - ) - : statusListView(jobTable); - }, - )), - ); - } -} diff --git a/flutter/lib/desktop/pages/terminal_connection_manager.dart b/flutter/lib/desktop/pages/terminal_connection_manager.dart deleted file mode 100644 index 91b8baa97..000000000 --- a/flutter/lib/desktop/pages/terminal_connection_manager.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:get/get.dart'; -import '../../models/model.dart'; - -/// Manages terminal connections to ensure one FFI instance per peer -class TerminalConnectionManager { - static final Map _connections = {}; - static final Map _connectionRefCount = {}; - - // Track service IDs per peer - static final Map _serviceIds = {}; - - /// Get or create an FFI instance for a peer - static FFI getConnection({ - required String peerId, - required String? password, - required bool? isSharedPassword, - required bool? forceRelay, - required String? connToken, - }) { - final existingFfi = _connections[peerId]; - if (existingFfi != null && !existingFfi.closed) { - // Increment reference count - _connectionRefCount[peerId] = (_connectionRefCount[peerId] ?? 0) + 1; - debugPrint('[TerminalConnectionManager] Reusing existing connection for peer $peerId. Reference count: ${_connectionRefCount[peerId]}'); - return existingFfi; - } - - // Create new FFI instance for first terminal - debugPrint('[TerminalConnectionManager] Creating new terminal connection for peer $peerId'); - final ffi = FFI(null); - ffi.start( - peerId, - password: password, - isSharedPassword: isSharedPassword, - forceRelay: forceRelay, - connToken: connToken, - isTerminal: true, - ); - - _connections[peerId] = ffi; - _connectionRefCount[peerId] = 1; - - // Register the FFI instance with Get for dependency injection - Get.put(ffi, tag: 'terminal_$peerId'); - - debugPrint('[TerminalConnectionManager] New connection created. Total connections: ${_connections.length}'); - return ffi; - } - - /// Release a connection reference - static void releaseConnection(String peerId) { - final refCount = _connectionRefCount[peerId] ?? 0; - debugPrint('[TerminalConnectionManager] Releasing connection for peer $peerId. Current ref count: $refCount'); - - if (refCount <= 1) { - // Last reference, close the connection - final ffi = _connections[peerId]; - if (ffi != null) { - debugPrint('[TerminalConnectionManager] Closing connection for peer $peerId (last reference)'); - ffi.close(); - _connections.remove(peerId); - _connectionRefCount.remove(peerId); - Get.delete(tag: 'terminal_$peerId'); - } - } else { - // Decrement reference count - _connectionRefCount[peerId] = refCount - 1; - debugPrint('[TerminalConnectionManager] Connection still in use. New ref count: ${_connectionRefCount[peerId]}'); - } - } - - /// Check if a connection exists for a peer - static bool hasConnection(String peerId) { - final ffi = _connections[peerId]; - return ffi != null && !ffi.closed; - } - - /// Get existing connection without creating new one - static FFI? getExistingConnection(String peerId) { - return _connections[peerId]; - } - - /// Get connection count for debugging - static int getConnectionCount() => _connections.length; - - /// Get terminal count for a peer - static int getTerminalCount(String peerId) => _connectionRefCount[peerId] ?? 0; - - /// Get service ID for a peer - static String? getServiceId(String peerId) => _serviceIds[peerId]; - - /// Set service ID for a peer - static void setServiceId(String peerId, String serviceId) { - _serviceIds[peerId] = serviceId; - debugPrint('[TerminalConnectionManager] Service ID for $peerId: $serviceId'); - } -} \ No newline at end of file diff --git a/flutter/lib/desktop/pages/terminal_page.dart b/flutter/lib/desktop/pages/terminal_page.dart deleted file mode 100644 index d38dc4a8b..000000000 --- a/flutter/lib/desktop/pages/terminal_page.dart +++ /dev/null @@ -1,223 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:flutter_hbb/models/terminal_model.dart'; -import 'package:xterm/xterm.dart'; -import 'terminal_connection_manager.dart'; - -class TerminalPage extends StatefulWidget { - TerminalPage({ - Key? key, - required this.id, - required this.password, - required this.tabController, - required this.isSharedPassword, - required this.terminalId, - required this.tabKey, - this.forceRelay, - this.connToken, - }) : super(key: key); - final String id; - final String? password; - final DesktopTabController tabController; - final bool? forceRelay; - final bool? isSharedPassword; - final String? connToken; - final int terminalId; - - /// Tab key for focus management, passed from parent to avoid duplicate construction - final String tabKey; - final SimpleWrapper?> _lastState = SimpleWrapper(null); - - FFI get ffi => (_lastState.value! as _TerminalPageState)._ffi; - - @override - State createState() { - final state = _TerminalPageState(); - _lastState.value = state; - return state; - } -} - -class _TerminalPageState extends State - with AutomaticKeepAliveClientMixin { - static const EdgeInsets _defaultTerminalPadding = - EdgeInsets.symmetric(horizontal: 5.0, vertical: 2.0); - - late FFI _ffi; - late TerminalModel _terminalModel; - double? _cellHeight; - final FocusNode _terminalFocusNode = FocusNode(canRequestFocus: false); - StreamSubscription? _tabStateSubscription; - - @override - void initState() { - super.initState(); - - // Listen for tab selection changes to request focus - _tabStateSubscription = widget.tabController.state.listen(_onTabStateChanged); - - // Use shared FFI instance from connection manager - _ffi = TerminalConnectionManager.getConnection( - peerId: widget.id, - password: widget.password, - isSharedPassword: widget.isSharedPassword, - forceRelay: widget.forceRelay, - connToken: widget.connToken, - ); - - // Create terminal model with specific terminal ID - _terminalModel = TerminalModel(_ffi, widget.terminalId); - debugPrint( - '[TerminalPage] Terminal model created for terminal ${widget.terminalId}'); - - _terminalModel.onResizeExternal = (w, h, pw, ph) { - _cellHeight = ph * 1.0; - - // Enable focus once terminal has valid dimensions (first valid resize) - if (!_terminalFocusNode.canRequestFocus && w > 0 && h > 0) { - _terminalFocusNode.canRequestFocus = true; - // Auto-focus if this tab is currently selected - _requestFocusIfSelected(); - } - - // Schedule the setState for the next frame - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) { - setState(() {}); - } - }); - }; - - // Register this terminal model with FFI for event routing - _ffi.registerTerminalModel(widget.terminalId, _terminalModel); - - // Initialize terminal connection - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController.onSelected?.call(widget.id); - - // Check if this is a new connection or additional terminal - // Note: When a connection exists, the ref count will be > 1 after this terminal is added - final isExistingConnection = - TerminalConnectionManager.hasConnection(widget.id) && - TerminalConnectionManager.getTerminalCount(widget.id) > 1; - - if (!isExistingConnection) { - // First terminal - show loading dialog, wait for onReady - _ffi.dialogManager - .showLoading(translate('Connecting...'), onCancel: closeConnection); - } else { - // Additional terminal - connection already established - // Open the terminal directly - _terminalModel.openTerminal(); - } - }); - } - - @override - void dispose() { - // Cancel tab state subscription to prevent memory leak - _tabStateSubscription?.cancel(); - // Unregister terminal model from FFI - _ffi.unregisterTerminalModel(widget.terminalId); - _terminalModel.dispose(); - _terminalFocusNode.dispose(); - // Release connection reference instead of closing directly - TerminalConnectionManager.releaseConnection(widget.id); - super.dispose(); - } - - void _onTabStateChanged(DesktopTabState state) { - // Check if this tab is now selected and request focus - if (state.selected >= 0 && state.selected < state.tabs.length) { - final selectedTab = state.tabs[state.selected]; - if (selectedTab.key == widget.tabKey && mounted) { - _requestFocusIfSelected(); - } - } - } - - void _requestFocusIfSelected() { - if (!mounted || !_terminalFocusNode.canRequestFocus) return; - // Use post-frame callback to ensure widget is fully laid out in focus tree - WidgetsBinding.instance.addPostFrameCallback((_) { - // Re-check conditions after frame: mounted, focusable, still selected, not already focused - if (!mounted || !_terminalFocusNode.canRequestFocus || _terminalFocusNode.hasFocus) return; - final state = widget.tabController.state.value; - if (state.selected >= 0 && state.selected < state.tabs.length) { - if (state.tabs[state.selected].key == widget.tabKey) { - _terminalFocusNode.requestFocus(); - } - } - }); - } - - // This method ensures that the number of visible rows is an integer by computing the - // extra space left after dividing the available height by the height of a single - // terminal row (`_cellHeight`) and distributing it evenly as top and bottom padding. - EdgeInsets _calculatePadding(double heightPx) { - final cellHeight = _cellHeight; - if (!heightPx.isFinite || - heightPx <= 0 || - cellHeight == null || - !cellHeight.isFinite || - cellHeight <= 0) { - return _defaultTerminalPadding; - } - final rows = (heightPx / cellHeight).floor(); - if (rows <= 0) { - return _defaultTerminalPadding; - } - final extraSpace = heightPx - rows * cellHeight; - if (!extraSpace.isFinite || extraSpace < 0) { - return _defaultTerminalPadding; - } - final topBottom = extraSpace / 2.0; - return EdgeInsets.symmetric( - horizontal: _defaultTerminalPadding.horizontal / 2, - vertical: topBottom, - ); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: LayoutBuilder( - builder: (context, constraints) { - final heightPx = constraints.maxHeight; - return TerminalView( - _terminalModel.terminal, - controller: _terminalModel.terminalController, - focusNode: _terminalFocusNode, - // Note: autofocus is not used here because focus is managed manually - // via _onTabStateChanged() to handle tab switching properly. - backgroundOpacity: 0.7, - padding: _calculatePadding(heightPx), - onSecondaryTapDown: (details, offset) async { - final selection = _terminalModel.terminalController.selection; - if (selection != null) { - final text = _terminalModel.terminal.buffer.getText(selection); - _terminalModel.terminalController.clearSelection(); - await Clipboard.setData(ClipboardData(text: text)); - } else { - final data = await Clipboard.getData('text/plain'); - final text = data?.text; - if (text != null) { - _terminalModel.terminal.paste(text); - } - } - }, - ); - }, - ), - ); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/flutter/lib/desktop/pages/terminal_tab_page.dart b/flutter/lib/desktop/pages/terminal_tab_page.dart deleted file mode 100644 index 63289e94d..000000000 --- a/flutter/lib/desktop/pages/terminal_tab_page.dart +++ /dev/null @@ -1,625 +0,0 @@ -import 'dart:convert'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_hbb/models/model.dart'; -import 'package:get/get.dart'; - -import '../../models/platform_model.dart'; -import 'terminal_page.dart'; -import 'terminal_connection_manager.dart'; -import '../widgets/material_mod_popup_menu.dart' as mod_menu; -import '../widgets/popup_menu.dart'; -import 'package:bot_toast/bot_toast.dart'; - -class TerminalTabPage extends StatefulWidget { - final Map params; - - const TerminalTabPage({Key? key, required this.params}) : super(key: key); - - @override - State createState() => _TerminalTabPageState(params); -} - -class _TerminalTabPageState extends State { - DesktopTabController get tabController => Get.find(); - - static const IconData selectedIcon = Icons.terminal; - static const IconData unselectedIcon = Icons.terminal_outlined; - int _nextTerminalId = 1; - // Lightweight idempotency guard for async close operations - final Set _closingTabs = {}; - // When true, all session cleanup should persist (window-level close in progress) - bool _windowClosing = false; - - _TerminalTabPageState(Map params) { - Get.put(DesktopTabController(tabType: DesktopTabType.terminal)); - tabController.onSelected = (id) { - WindowController.fromWindowId(windowId()) - .setTitle(getWindowNameWithId(id)); - }; - tabController.onRemoved = (_, id) => onRemoveId(id); - tabController.onCloseWindow = _closeWindowFromConnection; - final terminalId = params['terminalId'] ?? _nextTerminalId++; - tabController.add(_createTerminalTab( - peerId: params['id'], - terminalId: terminalId, - password: params['password'], - isSharedPassword: params['isSharedPassword'], - forceRelay: params['forceRelay'], - connToken: params['connToken'], - )); - } - - TabInfo _createTerminalTab({ - required String peerId, - required int terminalId, - String? password, - bool? isSharedPassword, - bool? forceRelay, - String? connToken, - }) { - final tabKey = '${peerId}_$terminalId'; - final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias'); - final tabLabel = - alias.isNotEmpty ? '$alias #$terminalId' : '$peerId #$terminalId'; - return TabInfo( - key: tabKey, - label: tabLabel, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () => _closeTab(tabKey), - page: TerminalPage( - key: ValueKey(tabKey), - id: peerId, - terminalId: terminalId, - tabKey: tabKey, - password: password, - isSharedPassword: isSharedPassword, - tabController: tabController, - forceRelay: forceRelay, - connToken: connToken, - ), - ); - } - - /// Unified tab close handler for all close paths (button, shortcut, programmatic). - /// Shows audit dialog, cleans up session if not persistent, then removes the UI tab. - Future _closeTab(String tabKey) async { - // Idempotency guard: skip if already closing this tab - if (_closingTabs.contains(tabKey)) return; - _closingTabs.add(tabKey); - - try { - // Snapshot peerTabCount BEFORE any await to avoid race with concurrent - // _closeAllTabs clearing tabController (which would make the live count - // drop to 0 and incorrectly trigger session persistence). - // Note: the snapshot may become stale if other individual tabs are closed - // during the audit dialog, but this is an acceptable trade-off. - int? snapshotPeerTabCount; - final parsed = _parseTabKey(tabKey); - if (parsed != null) { - final (peerId, _) = parsed; - snapshotPeerTabCount = tabController.state.value.tabs.where((t) { - final p = _parseTabKey(t.key); - return p != null && p.$1 == peerId; - }).length; - } - - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: tabKey, - tabController: tabController, - )) { - return; - } - - // Close terminal session if not in persistent mode. - // Wrapped separately so session cleanup failure never blocks UI tab removal. - try { - await _closeTerminalSessionIfNeeded(tabKey, - peerTabCount: snapshotPeerTabCount); - } catch (e) { - debugPrint('[TerminalTabPage] Session cleanup failed for $tabKey: $e'); - } - // Always close the tab from UI, regardless of session cleanup result - tabController.closeBy(tabKey); - } catch (e) { - debugPrint('[TerminalTabPage] Error closing tab $tabKey: $e'); - } finally { - _closingTabs.remove(tabKey); - } - } - - /// Close all tabs with session cleanup. - /// Used for window-level close operations (onDestroy, handleWindowCloseButton). - /// UI tabs are removed immediately; session cleanup runs in parallel with a - /// bounded timeout so window close is not blocked indefinitely. - Future _closeAllTabs() async { - _windowClosing = true; - final tabKeys = tabController.state.value.tabs.map((t) => t.key).toList(); - // Remove all UI tabs immediately (same instant behavior as the old tabController.clear()) - // Keep the cleanup target lookup below synchronous before its first await: - // it relies on the current frame still retaining each TerminalPage's FFI/model. - tabController.clear(); - // Run session cleanup in parallel with bounded timeout (closeTerminal() has internal 3s timeout). - // Skip tabs already being closed by a concurrent _closeTab() to avoid duplicate FFI calls. - final futures = tabKeys - .where((tabKey) => !_closingTabs.contains(tabKey)) - .map((tabKey) async { - try { - await _closeTerminalSessionIfNeeded(tabKey, persistAll: true); - } catch (e) { - debugPrint('[TerminalTabPage] Session cleanup failed for $tabKey: $e'); - } - }).toList(); - if (futures.isNotEmpty) { - await Future.wait(futures).timeout( - const Duration(seconds: 4), - onTimeout: () { - debugPrint( - '[TerminalTabPage] Session cleanup timed out for batch close'); - return []; - }, - ); - } - } - - /// Close the terminal session on server side based on persistent mode. - /// - /// [persistAll] controls behavior when persistent mode is enabled: - /// - `true` (window close): persist all sessions, don't close any. - /// - `false` (tab close): only persist the last session for the peer, - /// close others so only the most recent disconnected session survives. - /// - /// Note: if [_windowClosing] is true, persistAll is forced to true so that - /// in-flight _closeTab() calls don't accidentally close sessions that the - /// window-close flow intends to preserve. - Future _closeTerminalSessionIfNeeded(String tabKey, - {bool persistAll = false, int? peerTabCount}) async { - // If window close is in progress, override to persist all sessions - // even if this call originated from an individual tab close. - if (_windowClosing) { - persistAll = true; - } - final parsed = _parseTabKey(tabKey); - if (parsed == null) return; - final (peerId, terminalId) = parsed; - - final ffi = TerminalConnectionManager.getExistingConnection(peerId); - if (ffi == null) return; - - final isPersistent = bind.sessionGetToggleOptionSync( - sessionId: ffi.sessionId, - arg: kOptionTerminalPersistent, - ); - - if (isPersistent) { - if (persistAll) { - // Window close: persist all sessions - return; - } - // Tab close: only persist if this is the last tab for this peer. - // Use the snapshot value if provided (avoids race with concurrent tab removal). - final effectivePeerTabCount = peerTabCount ?? - tabController.state.value.tabs.where((t) { - final p = _parseTabKey(t.key); - return p != null && p.$1 == peerId; - }).length; - if (effectivePeerTabCount <= 1) { - // Last tab for this peer — persist the session - return; - } - // Not the last tab — fall through to close the session - } - - final terminalModel = ffi.terminalModels[terminalId]; - if (terminalModel != null) { - // closeTerminal() has internal 3s timeout, no need for external timeout - await terminalModel.closeTerminal(); - } - } - - /// Parse tabKey (format: "peerId_terminalId") into its components. - /// Note: peerId may contain underscores, so we use lastIndexOf('_'). - /// Returns null if tabKey format is invalid. - (String peerId, int terminalId)? _parseTabKey(String tabKey) { - final lastUnderscore = tabKey.lastIndexOf('_'); - if (lastUnderscore <= 0) { - debugPrint('[TerminalTabPage] Invalid tabKey format: $tabKey'); - return null; - } - final terminalIdStr = tabKey.substring(lastUnderscore + 1); - final terminalId = int.tryParse(terminalIdStr); - if (terminalId == null) { - debugPrint('[TerminalTabPage] Invalid terminalId in tabKey: $tabKey'); - return null; - } - final peerId = tabKey.substring(0, lastUnderscore); - return (peerId, terminalId); - } - - Widget _tabMenuBuilder(String peerId, CancelFunc cancelFunc) { - final List> menu = []; - const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); - - // New tab menu item - menu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('New tab'), - style: style, - ), - proc: () { - _addNewTerminal(peerId); - cancelFunc(); - // Also try to close any BotToast overlays - BotToast.cleanAll(); - }, - padding: padding, - )); - - menu.add(MenuEntryDivider()); - - menu.add(MenuEntrySwitch( - switchType: SwitchType.scheckbox, - text: translate('Keep terminal sessions on disconnect'), - getter: () async { - final ffi = Get.find(tag: 'terminal_$peerId'); - return bind.sessionGetToggleOptionSync( - sessionId: ffi.sessionId, - arg: kOptionTerminalPersistent, - ); - }, - setter: (bool v) async { - final ffi = Get.find(tag: 'terminal_$peerId'); - await bind.sessionToggleOption( - sessionId: ffi.sessionId, - value: kOptionTerminalPersistent, - ); - }, - padding: padding, - )); - - return mod_menu.PopupMenu( - items: menu - .map((e) => e.build( - context, - const MenuConfig( - commonColor: CustomPopupMenuTheme.commonColor, - height: CustomPopupMenuTheme.height, - dividerHeight: CustomPopupMenuTheme.dividerHeight, - ), - )) - .expand((i) => i) - .toList(), - ); - } - - @override - void initState() { - super.initState(); - - // Add keyboard shortcut handler - HardwareKeyboard.instance.addHandler(_handleKeyEvent); - - rustDeskWinManager.setMethodHandler((call, fromWindowId) async { - print( - "[Remote Terminal] call ${call.method} with args ${call.arguments} from window $fromWindowId"); - if (call.method == kWindowEventNewTerminal) { - final args = jsonDecode(call.arguments); - final id = args['id']; - windowOnTop(windowId()); - // Allow multiple terminals for the same connection - final terminalId = args['terminalId'] ?? _nextTerminalId++; - tabController.add(_createTerminalTab( - peerId: id, - terminalId: terminalId, - password: args['password'], - isSharedPassword: args['isSharedPassword'], - forceRelay: args['forceRelay'], - connToken: args['connToken'], - )); - } else if (call.method == kWindowEventRestoreTerminalSessions) { - _restoreSessions(call.arguments); - } else if (call.method == "onDestroy") { - // Clean up sessions before window destruction (bounded wait) - await _closeAllTabs(); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } else if (call.method == kWindowEventActiveSession) { - if (tabController.state.value.tabs.isEmpty) { - return false; - } - final currentTab = tabController.state.value.selectedTabInfo; - assert(call.arguments is String, - "Expected String arguments for kWindowEventActiveSession, got ${call.arguments.runtimeType}"); - // Use lastIndexOf to handle peerIds containing underscores - final lastUnderscore = currentTab.key.lastIndexOf('_'); - if (lastUnderscore > 0 && - currentTab.key.substring(0, lastUnderscore) == call.arguments) { - windowOnTop(windowId()); - return true; - } - return false; - } - }); - Future.delayed(Duration.zero, () { - restoreWindowPosition(WindowType.Terminal, windowId: windowId()); - }); - } - - @override - void dispose() { - HardwareKeyboard.instance.removeHandler(_handleKeyEvent); - super.dispose(); - } - - Future _restoreSessions(String arguments) async { - Map? args; - try { - args = jsonDecode(arguments) as Map; - } catch (e) { - debugPrint("Error parsing JSON arguments in _restoreSessions: $e"); - return; - } - final persistentSessions = - args['persistent_sessions'] as List? ?? []; - final sortedSessions = persistentSessions.whereType().toList()..sort(); - var peerId = args['peer_id'] as String? ?? ''; - if (peerId.isEmpty) { - if (tabController.state.value.tabs.isEmpty || - tabController.state.value.selected >= - tabController.state.value.tabs.length) { - debugPrint('[TerminalTabPage] Skip restore: no selected tab'); - return; - } - final currentTab = tabController.state.value.selectedTabInfo; - final parsed = _parseTabKey(currentTab.key); - if (parsed == null) return; - peerId = parsed.$1; - } - final existingTerminalIds = tabController.state.value.tabs - .map((tab) => _parseTabKey(tab.key)) - .where((parsed) => parsed != null && parsed.$1 == peerId) - .map((parsed) => parsed!.$2) - .toSet(); - if (existingTerminalIds.isEmpty) { - debugPrint( - '[TerminalTabPage] Skip restore: no seed tab for peer $peerId'); - return; - } - for (final terminalId in sortedSessions) { - if (!existingTerminalIds.add(terminalId)) { - continue; - } - _addNewTerminal(peerId, terminalId: terminalId); - // A delay is required to ensure the UI has sufficient time to update - // before adding the next terminal. Without this delay, `_TerminalPageState::dispose()` - // may be called prematurely while the tab widget is still in the tab controller. - // This behavior is likely due to a race condition between the UI rendering lifecycle - // and the addition of new tabs. Attempts to use `_TerminalPageState::addPostFrameCallback()` - // to wait for the previous page to be ready were unsuccessful, as the observed call sequence is: - // `initState() 2 -> dispose() 2 -> postFrameCallback() 2`, followed by `initState() 3`. - // The `Future.delayed` approach mitigates this issue by introducing a buffer period, - // allowing the UI to stabilize before proceeding. - await Future.delayed(const Duration(milliseconds: 300)); - } - } - - bool _handleKeyEvent(KeyEvent event) { - if (event is KeyDownEvent) { - // Use Cmd+T on macOS, Ctrl+Shift+T on other platforms - if (event.logicalKey == LogicalKeyboardKey.keyT) { - if (isMacOS && - HardwareKeyboard.instance.isMetaPressed && - !HardwareKeyboard.instance.isShiftPressed) { - // macOS: Cmd+T (standard for new tab) - _addNewTerminalForCurrentPeer(); - return true; - } else if (!isMacOS && - HardwareKeyboard.instance.isControlPressed && - HardwareKeyboard.instance.isShiftPressed) { - // Other platforms: Ctrl+Shift+T (to avoid conflict with Ctrl+T in terminal) - _addNewTerminalForCurrentPeer(); - return true; - } - } - - // Use Cmd+W on macOS, Ctrl+Shift+W on other platforms - if (event.logicalKey == LogicalKeyboardKey.keyW) { - if (isMacOS && - HardwareKeyboard.instance.isMetaPressed && - !HardwareKeyboard.instance.isShiftPressed) { - // macOS: Cmd+W (standard for close tab) - final currentTab = tabController.state.value.selectedTabInfo; - if (tabController.state.value.tabs.length > 1) { - _closeTab(currentTab.key); - return true; - } - } else if (!isMacOS && - HardwareKeyboard.instance.isControlPressed && - HardwareKeyboard.instance.isShiftPressed) { - // Other platforms: Ctrl+Shift+W (to avoid conflict with Ctrl+W word delete) - final currentTab = tabController.state.value.selectedTabInfo; - if (tabController.state.value.tabs.length > 1) { - _closeTab(currentTab.key); - return true; - } - } - } - - // Use Alt+Left/Right for tab navigation (avoids conflicts) - if (HardwareKeyboard.instance.isAltPressed) { - if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { - // Previous tab - final currentIndex = tabController.state.value.selected; - if (currentIndex > 0) { - tabController.jumpTo(currentIndex - 1); - } - return true; - } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - // Next tab - final currentIndex = tabController.state.value.selected; - if (currentIndex < tabController.length - 1) { - tabController.jumpTo(currentIndex + 1); - } - return true; - } - } - - // Check for Cmd/Ctrl + Number (switch to specific tab) - final numberKeys = [ - LogicalKeyboardKey.digit1, - LogicalKeyboardKey.digit2, - LogicalKeyboardKey.digit3, - LogicalKeyboardKey.digit4, - LogicalKeyboardKey.digit5, - LogicalKeyboardKey.digit6, - LogicalKeyboardKey.digit7, - LogicalKeyboardKey.digit8, - LogicalKeyboardKey.digit9, - ]; - - for (int i = 0; i < numberKeys.length; i++) { - if (event.logicalKey == numberKeys[i] && - ((isMacOS && HardwareKeyboard.instance.isMetaPressed) || - (!isMacOS && HardwareKeyboard.instance.isControlPressed))) { - if (i < tabController.length) { - tabController.jumpTo(i); - return true; - } - } - } - } - return false; - } - - void _addNewTerminal(String peerId, {int? terminalId}) { - // Find first tab for this peer to get connection parameters - final firstTab = tabController.state.value.tabs.firstWhere( - (tab) { - final last = tab.key.lastIndexOf('_'); - return last > 0 && tab.key.substring(0, last) == peerId; - }, - ); - if (firstTab.page is TerminalPage) { - final page = firstTab.page as TerminalPage; - final newTerminalId = terminalId ?? _nextTerminalId++; - if (terminalId != null && terminalId >= _nextTerminalId) { - _nextTerminalId = terminalId + 1; - } - tabController.add(_createTerminalTab( - peerId: peerId, - terminalId: newTerminalId, - password: page.password, - isSharedPassword: page.isSharedPassword, - forceRelay: page.forceRelay, - connToken: page.connToken, - )); - } - } - - void _addNewTerminalForCurrentPeer({int? terminalId}) { - final currentTab = tabController.state.value.selectedTabInfo; - final parsed = _parseTabKey(currentTab.key); - if (parsed == null) return; - final (peerId, _) = parsed; - _addNewTerminal(peerId, terminalId: terminalId); - } - - @override - Widget build(BuildContext context) { - final child = Scaffold( - backgroundColor: Theme.of(context).cardColor, - body: DesktopTab( - controller: tabController, - onWindowCloseButton: handleWindowCloseButton, - tail: _buildAddButton(), - selectedBorderColor: MyTheme.accent, - labelGetter: DesktopTab.tablabelGetter, - tabMenuBuilder: (key) { - final parsed = _parseTabKey(key); - if (parsed == null) return Container(); - final (peerId, _) = parsed; - return _tabMenuBuilder(peerId, () {}); - }, - )); - final tabWidget = isLinux - ? buildVirtualWindowFrame(context, child) - : workaroundWindowBorder( - context, - Container( - decoration: BoxDecoration( - border: Border.all(color: MyTheme.color(context).border!)), - child: child, - )); - return isMacOS || kUseCompatibleUiMode - ? tabWidget - : SubWindowDragToResizeArea( - child: tabWidget, - resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - enableResizeEdges: subWindowManagerEnableResizeEdges, - windowId: stateGlobal.windowId, - ); - } - - void onRemoveId(String id) { - if (tabController.state.value.tabs.isEmpty) { - WindowController.fromWindowId(windowId()).close(); - } - } - - Future _closeWindowFromConnection() async { - await _closeAllTabs(); - await WindowController.fromWindowId(windowId()).close(); - } - - int windowId() { - return widget.params["windowId"]; - } - - Widget _buildAddButton() { - return ActionIcon( - message: 'New tab', - icon: IconFont.add, - onTap: () { - _addNewTerminalForCurrentPeer(); - }, - isClose: false, - ); - } - - Future handleWindowCloseButton() async { - final connLength = tabController.state.value.tabs.length; - if (connLength == 1) { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: tabController.state.value.tabs[0].key, - tabController: tabController, - )) { - return false; - } - } - if (connLength <= 1) { - await _closeAllTabs(); - return true; - } else { - final bool res; - if (!option2bool(kOptionEnableConfirmClosingTabs, - bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { - res = true; - } else { - res = await closeConfirmDialog(); - } - if (res) { - await _closeAllTabs(); - } - return res; - } - } -} diff --git a/flutter/lib/desktop/pages/view_camera_page.dart b/flutter/lib/desktop/pages/view_camera_page.dart deleted file mode 100644 index c45ec4d86..000000000 --- a/flutter/lib/desktop/pages/view_camera_page.dart +++ /dev/null @@ -1,717 +0,0 @@ -import 'dart:async'; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hbb/common/widgets/remote_input.dart'; -import 'package:get/get.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_hbb/models/state_model.dart'; - -import '../../consts.dart'; -import '../../common/widgets/overlay.dart'; -import '../../common.dart'; -import '../../common/widgets/dialog.dart'; -import '../../common/widgets/toolbar.dart'; -import '../../models/model.dart'; -import '../../models/platform_model.dart'; -import '../../common/shared_state.dart'; -import '../../utils/image.dart'; -import '../widgets/remote_toolbar.dart'; -import '../widgets/kb_layout_type_chooser.dart'; -import '../widgets/tabbar_widget.dart'; - -import 'package:flutter_hbb/native/custom_cursor.dart' - if (dart.library.html) 'package:flutter_hbb/web/custom_cursor.dart'; - -final SimpleWrapper _firstEnterImage = SimpleWrapper(false); - -// Used to skip session close if "move to new window" is clicked. -final Map closeSessionOnDispose = {}; - -class ViewCameraPage extends StatefulWidget { - ViewCameraPage({ - Key? key, - required this.id, - required this.toolbarState, - this.sessionId, - this.tabWindowId, - this.password, - this.display, - this.displays, - this.tabController, - this.connToken, - this.forceRelay, - this.isSharedPassword, - }) : super(key: key) { - initSharedStates(id); - } - - final String id; - final SessionID? sessionId; - final int? tabWindowId; - final int? display; - final List? displays; - final String? password; - final ToolbarState toolbarState; - final bool? forceRelay; - final bool? isSharedPassword; - final String? connToken; - final SimpleWrapper?> _lastState = SimpleWrapper(null); - final DesktopTabController? tabController; - - FFI get ffi => (_lastState.value! as _ViewCameraPageState)._ffi; - - @override - State createState() { - final state = _ViewCameraPageState(id); - _lastState.value = state; - return state; - } -} - -class _ViewCameraPageState extends State - with AutomaticKeepAliveClientMixin, MultiWindowListener { - Timer? _timer; - String keyboardMode = "legacy"; - bool _isWindowBlur = false; - final _cursorOverImage = false.obs; - final _uniqueKey = UniqueKey(); - - var _blockableOverlayState = BlockableOverlayState(); - - final FocusNode _rawKeyFocusNode = FocusNode(debugLabel: "rawkeyFocusNode"); - - // We need `_instanceIdOnEnterOrLeaveImage4Toolbar` together with `_onEnterOrLeaveImage4Toolbar` - // to identify the toolbar instance and its callback function. - int? _instanceIdOnEnterOrLeaveImage4Toolbar; - Function(bool)? _onEnterOrLeaveImage4Toolbar; - - late FFI _ffi; - - SessionID get sessionId => _ffi.sessionId; - - _ViewCameraPageState(String id) { - _initStates(id); - } - - void _initStates(String id) {} - - @override - void initState() { - super.initState(); - _ffi = FFI(widget.sessionId); - Get.put(_ffi, tag: widget.id); - _ffi.imageModel.addCallbackOnFirstImage((String peerId) { - showKBLayoutTypeChooserIfNeeded( - _ffi.ffiModel.pi.platform, _ffi.dialogManager); - _ffi.recordingModel - .updateStatus(bind.sessionGetIsRecording(sessionId: _ffi.sessionId)); - }); - _ffi.start( - widget.id, - isViewCamera: true, - password: widget.password, - isSharedPassword: widget.isSharedPassword, - forceRelay: widget.forceRelay, - tabWindowId: widget.tabWindowId, - display: widget.display, - displays: widget.displays, - connToken: widget.connToken, - ); - WidgetsBinding.instance.addPostFrameCallback((_) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); - _ffi.dialogManager - .showLoading(translate('Connecting...'), onCancel: closeConnection); - }); - WakelockManager.enable(_uniqueKey); - - _ffi.ffiModel.updateEventListener(sessionId, widget.id); - if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); - _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); - _ffi.dialogManager.loadMobileActionsOverlayVisible(); - DesktopMultiWindow.addListener(this); - // if (!_isCustomCursorInited) { - // customCursorController.registerNeedUpdateCursorCallback( - // (String? lastKey, String? currentKey) async { - // if (_firstEnterImage.value) { - // _firstEnterImage.value = false; - // return true; - // } - // return lastKey == null || lastKey != currentKey; - // }); - // _isCustomCursorInited = true; - // } - - _blockableOverlayState.applyFfi(_ffi); - // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState. - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.tabController?.onSelected?.call(widget.id); - }); - } - - @override - void onWindowBlur() { - super.onWindowBlur(); - // On windows, we use `focus` way to handle keyboard better. - // Now on Linux, there's some rdev issues which will break the input. - // We disable the `focus` way for non-Windows temporarily. - if (isWindows) { - _isWindowBlur = true; - // unfocus the primary-focus when the whole window is lost focus, - // and let OS to handle events instead. - _rawKeyFocusNode.unfocus(); - } - stateGlobal.isFocused.value = false; - } - - @override - void onWindowFocus() { - super.onWindowFocus(); - // See [onWindowBlur]. - if (isWindows) { - _isWindowBlur = false; - } - stateGlobal.isFocused.value = true; - } - - @override - void onWindowRestore() { - super.onWindowRestore(); - // On windows, we use `onWindowRestore` way to handle window restore from - // a minimized state. - if (isWindows) { - _isWindowBlur = false; - } - WakelockManager.enable(_uniqueKey); - } - - // When the window is unminimized, onWindowMaximize or onWindowRestore can be called when the old state was maximized or not. - @override - void onWindowMaximize() { - super.onWindowMaximize(); - WakelockManager.enable(_uniqueKey); - } - - @override - void onWindowMinimize() { - super.onWindowMinimize(); - WakelockManager.disable(_uniqueKey); - } - - @override - void onWindowEnterFullScreen() { - super.onWindowEnterFullScreen(); - if (isMacOS) { - stateGlobal.setFullscreen(true); - } - } - - @override - void onWindowLeaveFullScreen() { - super.onWindowLeaveFullScreen(); - if (isMacOS) { - stateGlobal.setFullscreen(false); - } - } - - @override - Future dispose() async { - final closeSession = closeSessionOnDispose.remove(widget.id) ?? true; - - // https://github.com/flutter/flutter/issues/64935 - super.dispose(); - debugPrint("VIEW CAMERA PAGE dispose session $sessionId ${widget.id}"); - _ffi.textureModel.onViewCameraPageDispose(closeSession); - if (closeSession) { - // ensure we leave this session, this is a double check - _ffi.inputModel.enterOrLeave(false); - } - DesktopMultiWindow.removeListener(this); - _ffi.dialogManager.hideMobileActionsOverlay(); - _ffi.imageModel.disposeImage(); - _ffi.cursorModel.disposeImages(); - _rawKeyFocusNode.dispose(); - await _ffi.close(closeSession: closeSession); - _timer?.cancel(); - _ffi.dialogManager.dismissAll(); - if (closeSession) { - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, - overlays: SystemUiOverlay.values); - } - WakelockManager.disable(_uniqueKey); - await Get.delete(tag: widget.id); - removeSharedStates(widget.id); - } - - Widget emptyOverlay() => BlockableOverlay( - /// the Overlay key will be set with _blockableOverlayState in BlockableOverlay - /// see override build() in [BlockableOverlay] - state: _blockableOverlayState, - underlying: Container( - color: Colors.transparent, - ), - ); - - Widget buildBody(BuildContext context) { - remoteToolbar(BuildContext context) => RemoteToolbar( - id: widget.id, - ffi: _ffi, - state: widget.toolbarState, - onEnterOrLeaveImageSetter: (id, func) { - _instanceIdOnEnterOrLeaveImage4Toolbar = id; - _onEnterOrLeaveImage4Toolbar = func; - }, - onEnterOrLeaveImageCleaner: (id) { - // If _instanceIdOnEnterOrLeaveImage4Toolbar != id - // it means `_onEnterOrLeaveImage4Toolbar` is not set or it has been changed to another toolbar. - if (_instanceIdOnEnterOrLeaveImage4Toolbar == id) { - _instanceIdOnEnterOrLeaveImage4Toolbar = null; - _onEnterOrLeaveImage4Toolbar = null; - } - }, - setRemoteState: setState, - ); - - bodyWidget() { - return Stack( - children: [ - Container( - color: kColorCanvas, - child: getBodyForDesktop(context), - ), - Stack( - children: [ - _ffi.ffiModel.pi.isSet.isTrue && - _ffi.ffiModel.waitForFirstImage.isTrue - ? emptyOverlay() - : () { - if (!_ffi.ffiModel.isPeerAndroid) { - return Offstage(); - } else { - return Obx(() => Offstage( - offstage: _ffi.dialogManager - .mobileActionsOverlayVisible.isFalse, - child: Overlay(initialEntries: [ - makeMobileActionsOverlayEntry( - () => _ffi.dialogManager - .setMobileActionsOverlayVisible(false), - ffi: _ffi, - ) - ]), - )); - } - }(), - // Use Overlay to enable rebuild every time on menu button click. - _ffi.ffiModel.pi.isSet.isTrue - ? Overlay( - initialEntries: [OverlayEntry(builder: remoteToolbar)]) - : remoteToolbar(context), - _ffi.ffiModel.pi.isSet.isFalse ? emptyOverlay() : Offstage(), - ], - ), - ], - ); - } - - return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: Obx(() { - final imageReady = _ffi.ffiModel.pi.isSet.isTrue && - _ffi.ffiModel.waitForFirstImage.isFalse; - if (imageReady) { - // If the privacy mode(disable physical displays) is switched, - // we should not dismiss the dialog immediately. - if (DateTime.now().difference(togglePrivacyModeTime) > - const Duration(milliseconds: 3000)) { - // `dismissAll()` is to ensure that the state is clean. - // It's ok to call dismissAll() here. - _ffi.dialogManager.dismissAll(); - // Recreate the block state to refresh the state. - _blockableOverlayState = BlockableOverlayState(); - _blockableOverlayState.applyFfi(_ffi); - } - // Block the whole `bodyWidget()` when dialog shows. - return BlockableOverlay( - underlying: bodyWidget(), - state: _blockableOverlayState, - ); - } else { - // `_blockableOverlayState` is not recreated here. - // The toolbar's block state won't work properly when reconnecting, but that's okay. - return bodyWidget(); - } - }), - ); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return WillPopScope( - onWillPop: () async { - clientClose(sessionId, _ffi); - return false; - }, - child: MultiProvider(providers: [ - ChangeNotifierProvider.value(value: _ffi.ffiModel), - ChangeNotifierProvider.value(value: _ffi.imageModel), - ChangeNotifierProvider.value(value: _ffi.cursorModel), - ChangeNotifierProvider.value(value: _ffi.canvasModel), - ChangeNotifierProvider.value(value: _ffi.recordingModel), - ], child: buildBody(context))); - } - - void enterView(PointerEnterEvent evt) { - _cursorOverImage.value = true; - _firstEnterImage.value = true; - if (_onEnterOrLeaveImage4Toolbar != null) { - try { - _onEnterOrLeaveImage4Toolbar!(true); - } catch (e) { - // - } - } - // See [onWindowBlur]. - if (!isWindows) { - if (!_rawKeyFocusNode.hasFocus) { - _rawKeyFocusNode.requestFocus(); - } - _ffi.inputModel.enterOrLeave(true); - } - } - - void leaveView(PointerExitEvent evt) { - if (_ffi.ffiModel.keyboard) { - _ffi.inputModel.tryMoveEdgeOnExit(evt.position); - } - - _cursorOverImage.value = false; - _firstEnterImage.value = false; - if (_onEnterOrLeaveImage4Toolbar != null) { - try { - _onEnterOrLeaveImage4Toolbar!(false); - } catch (e) { - // - } - } - // See [onWindowBlur]. - if (!isWindows) { - _ffi.inputModel.enterOrLeave(false); - } - } - - Widget _buildRawTouchAndPointerRegion( - Widget child, - PointerEnterEventListener? onEnter, - PointerExitEventListener? onExit, - ) { - return RawTouchGestureDetectorRegion( - child: _buildRawPointerMouseRegion(child, onEnter, onExit), - ffi: _ffi, - isCamera: true, - ); - } - - Widget _buildRawPointerMouseRegion( - Widget child, - PointerEnterEventListener? onEnter, - PointerExitEventListener? onExit, - ) { - return CameraRawPointerMouseRegion( - 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. - // Sometimes the system does not send the necessary focus event to flutter. We should manually - // handle this inconsistent status by setting `_isWindowBlur` to false. So we can - // ensure the grab-key thread is running when our users are clicking the remote canvas. - if (_isWindowBlur) { - debugPrint( - "Unexpected status: onPointerDown is triggered while the remote window is in blur status"); - _isWindowBlur = false; - } - if (!_rawKeyFocusNode.hasFocus) { - _rawKeyFocusNode.requestFocus(); - } - }, - inputModel: _ffi.inputModel, - child: child, - ); - } - - Widget getBodyForDesktop(BuildContext context) { - var paints = [ - MouseRegion(onEnter: (evt) { - if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: false); - }, onExit: (evt) { - if (!isWeb) bind.hostStopSystemKeyPropagate(stopped: true); - }, child: LayoutBuilder(builder: (context, constraints) { - final c = Provider.of(context, listen: false); - Future.delayed(Duration.zero, () => c.updateViewStyle()); - final peerDisplay = CurrentDisplayState.find(widget.id); - return Obx( - () => _ffi.ffiModel.pi.isSet.isFalse - ? Container(color: Colors.transparent) - : Obx(() { - _ffi.textureModel.updateCurrentDisplay(peerDisplay.value); - return ImagePaint( - id: widget.id, - cursorOverImage: _cursorOverImage, - listenerBuilder: (child) => _buildRawTouchAndPointerRegion( - child, enterView, leaveView), - ffi: _ffi, - ); - }), - ); - })) - ]; - - paints.add( - Positioned( - top: 10, - right: 10, - child: _buildRawTouchAndPointerRegion( - QualityMonitor(_ffi.qualityMonitorModel), null, null), - ), - ); - return Stack( - children: paints, - ); - } - - @override - bool get wantKeepAlive => true; -} - -class ImagePaint extends StatefulWidget { - final FFI ffi; - final String id; - final RxBool cursorOverImage; - final Widget Function(Widget)? listenerBuilder; - - ImagePaint( - {Key? key, - required this.ffi, - required this.id, - required this.cursorOverImage, - this.listenerBuilder}) - : super(key: key); - - @override - State createState() => _ImagePaintState(); -} - -class _ImagePaintState extends State { - String get id => widget.id; - RxBool get cursorOverImage => widget.cursorOverImage; - Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder; - - @override - Widget build(BuildContext context) { - final m = Provider.of(context); - var c = Provider.of(context); - final s = c.scale; - - bool isViewOriginal() => c.viewStyle.style == kRemoteViewStyleOriginal; - - if (c.imageOverflow.isTrue && c.scrollStyle != ScrollStyle.scrollauto) { - final paintWidth = c.getDisplayWidth() * s; - final paintHeight = c.getDisplayHeight() * s; - final paintSize = Size(paintWidth, paintHeight); - final paintWidget = - m.useTextureRender || widget.ffi.ffiModel.pi.forceTextureRender - ? _BuildPaintTextureRender( - c, s, Offset.zero, paintSize, isViewOriginal()) - : _buildScrollbarNonTextureRender(m, paintSize, s); - return NotificationListener( - onNotification: (notification) { - c.updateScrollPercent(); - return false; - }, - child: Container( - child: _buildCrossScrollbarFromLayout( - context, - _buildListener(paintWidget), - c.size, - paintSize, - c.scrollHorizontal, - c.scrollVertical, - )), - ); - } else { - if (c.size.width > 0 && c.size.height > 0) { - final paintWidget = - m.useTextureRender || widget.ffi.ffiModel.pi.forceTextureRender - ? _BuildPaintTextureRender( - c, - s, - Offset( - isLinux ? c.x.toInt().toDouble() : c.x, - isLinux ? c.y.toInt().toDouble() : c.y, - ), - c.size, - isViewOriginal()) - : _buildScrollAutoNonTextureRender(m, c, s); - return Container(child: _buildListener(paintWidget)); - } else { - return Container(); - } - } - } - - Widget _buildScrollbarNonTextureRender( - ImageModel m, Size imageSize, double s) { - return CustomPaint( - size: imageSize, - painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), - ); - } - - Widget _buildScrollAutoNonTextureRender( - ImageModel m, CanvasModel c, double s) { - return CustomPaint( - size: Size(c.size.width, c.size.height), - painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s), - ); - } - - Widget _BuildPaintTextureRender( - CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) { - final ffiModel = c.parent.target!.ffiModel; - final displays = ffiModel.pi.getCurDisplays(); - final children = []; - final rect = ffiModel.rect; - if (rect == null) { - return Container(); - } - final curDisplay = ffiModel.pi.currentDisplay; - for (var i = 0; i < displays.length; i++) { - final textureId = widget.ffi.textureModel - .getTextureId(curDisplay == kAllDisplayValue ? i : curDisplay); - if (true) { - // both "textureId.value != -1" and "true" seems ok - children.add(Positioned( - left: (displays[i].x - rect.left) * s + offset.dx, - top: (displays[i].y - rect.top) * s + offset.dy, - width: displays[i].width * s, - height: displays[i].height * s, - child: Obx(() => Texture( - textureId: textureId.value, - filterQuality: - isViewOriginal ? FilterQuality.none : FilterQuality.low, - )), - )); - } - } - return SizedBox( - width: size.width, - height: size.height, - child: Stack(children: children), - ); - } - - MouseCursor _buildCustomCursor(BuildContext context, double scale) { - final cursor = Provider.of(context); - final cache = cursor.cache ?? preDefaultCursor.cache; - return buildCursorOfCache(cursor, scale, cache); - } - - MouseCursor _buildDisabledCursor(BuildContext context, double scale) { - final cursor = Provider.of(context); - final cache = preForbiddenCursor.cache; - return buildCursorOfCache(cursor, scale, cache); - } - - Widget _buildCrossScrollbarFromLayout( - BuildContext context, - Widget child, - Size layoutSize, - Size size, - ScrollController horizontal, - ScrollController vertical, - ) { - var widget = child; - if (layoutSize.width < size.width) { - widget = ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: SingleChildScrollView( - controller: horizontal, - scrollDirection: Axis.horizontal, - physics: cursorOverImage.isTrue - ? const NeverScrollableScrollPhysics() - : null, - child: widget, - ), - ); - } else { - widget = Row( - children: [ - Container( - width: ((layoutSize.width - size.width) ~/ 2).toDouble(), - ), - widget, - ], - ); - } - if (layoutSize.height < size.height) { - widget = ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: SingleChildScrollView( - controller: vertical, - physics: cursorOverImage.isTrue - ? const NeverScrollableScrollPhysics() - : null, - child: widget, - ), - ); - } else { - widget = Column( - children: [ - Container( - height: ((layoutSize.height - size.height) ~/ 2).toDouble(), - ), - widget, - ], - ); - } - if (layoutSize.width < size.width) { - widget = RawScrollbar( - thickness: kScrollbarThickness, - thumbColor: Colors.grey, - controller: horizontal, - thumbVisibility: false, - trackVisibility: false, - notificationPredicate: layoutSize.height < size.height - ? (notification) => notification.depth == 1 - : defaultScrollNotificationPredicate, - child: widget, - ); - } - if (layoutSize.height < size.height) { - widget = RawScrollbar( - thickness: kScrollbarThickness, - thumbColor: Colors.grey, - controller: vertical, - thumbVisibility: false, - trackVisibility: false, - child: widget, - ); - } - - return Container( - child: widget, - width: layoutSize.width, - height: layoutSize.height, - ); - } - - Widget _buildListener(Widget child) { - if (listenerBuilder != null) { - return listenerBuilder!(child); - } else { - return child; - } - } -} diff --git a/flutter/lib/desktop/pages/view_camera_tab_page.dart b/flutter/lib/desktop/pages/view_camera_tab_page.dart deleted file mode 100644 index 36fa623ff..000000000 --- a/flutter/lib/desktop/pages/view_camera_tab_page.dart +++ /dev/null @@ -1,522 +0,0 @@ -import 'dart:convert'; -import 'dart:async'; -import 'dart:ui' as ui; - -import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/common/shared_state.dart'; -import 'package:flutter_hbb/common/widgets/dialog.dart'; -import 'package:flutter_hbb/consts.dart'; -import 'package:flutter_hbb/models/input_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:flutter_hbb/desktop/pages/view_camera_page.dart'; -import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; -import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; -import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' - as mod_menu; -import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/utils/multi_window_manager.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:bot_toast/bot_toast.dart'; - -import '../../models/platform_model.dart'; - -class _MenuTheme { - static const Color blueColor = MyTheme.button; - // kMinInteractiveDimension - static const double height = 20.0; - static const double dividerHeight = 12.0; -} - -class ViewCameraTabPage extends StatefulWidget { - final Map params; - - const ViewCameraTabPage({Key? key, required this.params}) : super(key: key); - - @override - State createState() => _ViewCameraTabPageState(params); -} - -class _ViewCameraTabPageState extends State { - final tabController = - Get.put(DesktopTabController(tabType: DesktopTabType.viewCamera)); - final contentKey = UniqueKey(); - static const IconData selectedIcon = Icons.desktop_windows_sharp; - static const IconData unselectedIcon = Icons.desktop_windows_outlined; - - String? peerId; - bool _isScreenRectSet = false; - int? _display; - - var connectionMap = RxList.empty(growable: true); - - _ViewCameraTabPageState(Map params) { - RemoteCountState.init(); - peerId = params['id']; - final sessionId = params['session_id']; - final tabWindowId = params['tab_window_id']; - final display = params['display']; - final displays = params['displays']; - final screenRect = parseParamScreenRect(params); - _isScreenRectSet = screenRect != null; - _display = display as int?; - tryMoveToScreenAndSetFullscreen(screenRect); - if (peerId != null) { - ConnectionTypeState.init(peerId!); - tabController.onSelected = (id) { - final viewCameraPage = tabController.widget(id); - if (viewCameraPage is ViewCameraPage) { - final ffi = viewCameraPage.ffi; - bind.setCurSessionId(sessionId: ffi.sessionId); - } - WindowController.fromWindowId(params['windowId']) - .setTitle(getWindowNameWithId(id)); - UnreadChatCountState.find(id).value = 0; - }; - tabController.add(TabInfo( - key: peerId!, - label: peerId!, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: peerId!, - tabController: tabController, - )) { - return; - } - tabController.closeBy(peerId!); - }, - page: ViewCameraPage( - key: ValueKey(peerId), - id: peerId!, - sessionId: sessionId == null ? null : SessionID(sessionId), - tabWindowId: tabWindowId, - display: display, - displays: displays?.cast(), - password: params['password'], - toolbarState: ToolbarState(), - tabController: tabController, - connToken: params['connToken'], - forceRelay: params['forceRelay'], - isSharedPassword: params['isSharedPassword'], - ), - )); - _update_remote_count(); - } - tabController.onRemoved = (_, id) => onRemoveId(id); - rustDeskWinManager.setMethodHandler(_remoteMethodHandler); - } - - @override - void initState() { - super.initState(); - - if (!_isScreenRectSet) { - Future.delayed(Duration.zero, () { - restoreWindowPosition( - WindowType.ViewCamera, - windowId: windowId(), - peerId: tabController.state.value.tabs.isEmpty - ? null - : tabController.state.value.tabs[0].key, - display: _display, - ); - }); - } - } - - @override - Widget build(BuildContext context) { - final child = Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: DesktopTab( - controller: tabController, - onWindowCloseButton: handleWindowCloseButton, - tail: const AddButton(), - selectedBorderColor: MyTheme.accent, - pageViewBuilder: (pageView) => pageView, - labelGetter: DesktopTab.tablabelGetter, - tabBuilder: (key, icon, label, themeConf) => Obx(() { - final connectionType = ConnectionTypeState.find(key); - if (!connectionType.isValid()) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - label, - ], - ); - } else { - bool secure = - connectionType.secure.value == ConnectionType.strSecure; - bool direct = - connectionType.direct.value == ConnectionType.strDirect; - String msgConn = getConnectionText( - secure, direct, connectionType.stream_type.value); - var msgFingerprint = '${translate('Fingerprint')}:\n'; - var fingerprint = FingerprintState.find(key).value; - if (fingerprint.isEmpty) { - fingerprint = 'N/A'; - } - if (fingerprint.length > 5 * 8) { - var first = fingerprint.substring(0, 39); - var second = fingerprint.substring(40); - msgFingerprint += '$first\n$second'; - } else { - msgFingerprint += fingerprint; - } - - final tab = Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - icon, - Tooltip( - message: '$msgConn\n$msgFingerprint', - child: SvgPicture.asset( - 'assets/${connectionType.secure.value}${connectionType.direct.value}.svg', - width: themeConf.iconSize, - height: themeConf.iconSize, - ).paddingOnly(right: 5), - ), - label, - unreadMessageCountBuilder(UnreadChatCountState.find(key)) - .marginOnly(left: 4), - ], - ); - - return Listener( - onPointerDown: (e) { - if (e.kind != ui.PointerDeviceKind.mouse) { - return; - } - final viewCameraPage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == key) - .page as ViewCameraPage; - if (viewCameraPage.ffi.ffiModel.pi.isSet.isTrue && - e.buttons == 2) { - showRightMenu( - (CancelFunc cancelFunc) { - return _tabMenuBuilder(key, cancelFunc); - }, - target: e.position, - ); - } - }, - child: tab, - ); - } - }), - ), - ); - final tabWidget = isLinux - ? buildVirtualWindowFrame(context, child) - : workaroundWindowBorder( - context, - Obx(() => Container( - decoration: BoxDecoration( - border: Border.all( - color: MyTheme.color(context).border!, - width: stateGlobal.windowBorderWidth.value), - ), - child: child, - ))); - return 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, - enableResizeEdges: subWindowManagerEnableResizeEdges, - windowId: stateGlobal.windowId, - )); - } - - // Note: Some dup code to ../widgets/remote_toolbar - Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) { - final List> menu = []; - const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); - final viewCameraPage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == key) - .page as ViewCameraPage; - final ffi = viewCameraPage.ffi; - final sessionId = ffi.sessionId; - final toolbarState = viewCameraPage.toolbarState; - menu.addAll([ - MenuEntryButton( - childBuilder: (TextStyle? style) => Obx(() => Text( - translate( - toolbarState.hide.isTrue ? 'Show Toolbar' : 'Hide Toolbar'), - style: style, - )), - proc: () { - toolbarState.switchHide(sessionId); - cancelFunc(); - }, - padding: padding, - ), - ]); - - if (tabController.state.value.tabs.length > 1) { - final splitAction = MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Move tab to new window'), - style: style, - ), - proc: () async { - await DesktopMultiWindow.invokeMethod( - kMainWindowId, - kWindowEventMoveTabToNewWindow, - '${windowId()},$key,$sessionId,ViewCamera'); - cancelFunc(); - }, - padding: padding, - ); - menu.insert(1, splitAction); - } - - menu.addAll([ - MenuEntryDivider(), - MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Copy Fingerprint'), - style: style, - ), - proc: () => onCopyFingerprint(FingerprintState.find(key).value), - padding: padding, - dismissOnClicked: true, - dismissCallback: cancelFunc, - ), - MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Close'), - style: style, - ), - proc: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: key, - tabController: tabController, - )) { - return; - } - tabController.closeBy(key); - cancelFunc(); - }, - padding: padding, - ) - ]); - - return mod_menu.PopupMenu( - items: menu - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: _MenuTheme.blueColor, - height: _MenuTheme.height, - dividerHeight: _MenuTheme.dividerHeight, - ))) - .expand((i) => i) - .toList(), - ); - } - - void onRemoveId(String id) async { - if (tabController.state.value.tabs.isEmpty) { - // Keep calling until the window status is hidden. - // - // Workaround for Windows: - // If you click other buttons and close in msgbox within a very short period of time, the close may fail. - // `await WindowController.fromWindowId(windowId()).close();`. - Future loopCloseWindow() async { - int c = 0; - final windowController = WindowController.fromWindowId(windowId()); - while (c < 20 && - tabController.state.value.tabs.isEmpty && - (!await windowController.isHidden())) { - await windowController.close(); - await Future.delayed(Duration(milliseconds: 100)); - c++; - } - } - - loopCloseWindow(); - } - ConnectionTypeState.delete(id); - _update_remote_count(); - } - - int windowId() { - return widget.params["windowId"]; - } - - Future handleWindowCloseButton() async { - final connLength = tabController.length; - if (connLength == 1) { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: tabController.state.value.tabs[0].key, - tabController: tabController, - )) { - return false; - } - } - if (connLength <= 1) { - tabController.clear(); - return true; - } else { - final bool res; - if (!option2bool(kOptionEnableConfirmClosingTabs, - bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { - res = true; - } else { - res = await closeConfirmDialog(); - } - if (res) { - tabController.clear(); - } - return res; - } - } - - _update_remote_count() => - RemoteCountState.find().value = tabController.length; - - Future _remoteMethodHandler(call, fromWindowId) async { - debugPrint( - "[View Camera Page] call ${call.method} with args ${call.arguments} from window $fromWindowId"); - - dynamic returnValue; - // for simplify, just replace connectionId - if (call.method == kWindowEventNewViewCamera) { - final args = jsonDecode(call.arguments); - final id = args['id']; - final sessionId = args['session_id']; - final tabWindowId = args['tab_window_id']; - final display = args['display']; - final displays = args['displays']; - final screenRect = parseParamScreenRect(args); - final prePeerCount = tabController.length; - Future.delayed(Duration.zero, () async { - if (stateGlobal.fullscreen.isTrue) { - await WindowController.fromWindowId(windowId()).setFullscreen(false); - stateGlobal.setFullscreen(false, procWnd: false); - } - await setNewConnectWindowFrame(windowId(), id!, prePeerCount, - WindowType.ViewCamera, display, screenRect); - Future.delayed(Duration(milliseconds: isWindows ? 100 : 0), () async { - await windowOnTop(windowId()); - }); - }); - ConnectionTypeState.init(id); - tabController.add(TabInfo( - key: id, - label: id, - selectedIcon: selectedIcon, - unselectedIcon: unselectedIcon, - onTabCloseButton: () async { - if (await desktopTryShowTabAuditDialogCloseCancelled( - id: id, - tabController: tabController, - )) { - return; - } - tabController.closeBy(id); - }, - page: ViewCameraPage( - key: ValueKey(id), - id: id, - sessionId: sessionId == null ? null : SessionID(sessionId), - tabWindowId: tabWindowId, - display: display, - displays: displays?.cast(), - password: args['password'], - toolbarState: ToolbarState(), - tabController: tabController, - connToken: args['connToken'], - forceRelay: args['forceRelay'], - isSharedPassword: args['isSharedPassword'], - ), - )); - } else if (call.method == kWindowDisableGrabKeyboard) { - // ??? - } else if (call.method == "onDestroy") { - tabController.clear(); - } else if (call.method == kWindowActionRebuild) { - reloadCurrentWindow(); - } else if (call.method == kWindowEventActiveSession) { - final jumpOk = tabController.jumpToByKey(call.arguments); - if (jumpOk) { - windowOnTop(windowId()); - } - return jumpOk; - } else if (call.method == kWindowEventActiveDisplaySession) { - final args = jsonDecode(call.arguments); - final id = args['id']; - final display = args['display']; - final jumpOk = - tabController.jumpToByKeyAndDisplay(id, display, isCamera: true); - if (jumpOk) { - windowOnTop(windowId()); - } - return jumpOk; - } else if (call.method == kWindowEventGetRemoteList) { - return tabController.state.value.tabs - .map((e) => e.key) - .toList() - .join(','); - } else if (call.method == kWindowEventGetSessionIdList) { - return tabController.state.value.tabs - .map((e) => '${e.key},${(e.page as ViewCameraPage).ffi.sessionId}') - .toList() - .join(';'); - } else if (call.method == kWindowEventGetCachedSessionData) { - // Ready to show new window and close old tab. - final args = jsonDecode(call.arguments); - final id = args['id']; - final close = args['close']; - try { - final viewCameraPage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == id) - .page as ViewCameraPage; - returnValue = viewCameraPage.ffi.ffiModel.cachedPeerData.toString(); - } catch (e) { - debugPrint('Failed to get cached session data: $e'); - } - if (close && returnValue != null) { - closeSessionOnDispose[id] = false; - tabController.closeBy(id); - } - } else if (call.method == kWindowEventRemoteWindowCoords) { - final viewCameraPage = - tabController.state.value.selectedTabInfo.page as ViewCameraPage; - final ffi = viewCameraPage.ffi; - final displayRect = ffi.ffiModel.displaysRect(); - if (displayRect != null) { - final wc = WindowController.fromWindowId(windowId()); - Rect? frame; - try { - frame = await wc.getFrame(); - } catch (e) { - debugPrint( - "Failed to get frame of window $windowId, it may be hidden"); - } - if (frame != null) { - ffi.cursorModel.moveLocal(0, 0); - final coords = RemoteWindowCoords( - frame, - CanvasCoords.fromCanvasModel(ffi.canvasModel), - CursorCoords.fromCursorModel(ffi.cursorModel), - displayRect); - returnValue = jsonEncode(coords.toJson()); - } - } - } else if (call.method == kWindowEventSetFullscreen) { - stateGlobal.setFullscreen(call.arguments == 'true'); - } - _update_remote_count(); - return returnValue; - } -} diff --git a/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart b/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart deleted file mode 100644 index f76603337..000000000 --- a/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/pages/file_manager_tab_page.dart'; -import 'package:provider/provider.dart'; - -/// multi-tab file transfer remote screen -class DesktopFileTransferScreen extends StatelessWidget { - final Map params; - - const DesktopFileTransferScreen({Key? key, required this.params}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ChangeNotifierProvider.value(value: gFFI.imageModel), - ChangeNotifierProvider.value(value: gFFI.cursorModel), - ChangeNotifierProvider.value(value: gFFI.canvasModel), - ], - child: Scaffold( - backgroundColor: isLinux ? Colors.transparent : null, - body: FileManagerTabPage( - params: params, - ), - ), - ); - } -} diff --git a/flutter/lib/desktop/screen/desktop_port_forward_screen.dart b/flutter/lib/desktop/screen/desktop_port_forward_screen.dart deleted file mode 100644 index c586a5837..000000000 --- a/flutter/lib/desktop/screen/desktop_port_forward_screen.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/pages/port_forward_tab_page.dart'; -import 'package:provider/provider.dart'; - -/// multi-tab file port forward screen -class DesktopPortForwardScreen extends StatelessWidget { - final Map params; - - const DesktopPortForwardScreen({Key? key, required this.params}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ], - child: Scaffold( - backgroundColor: isLinux ? Colors.transparent : null, - body: PortForwardTabPage( - params: params, - ), - ), - ); - } -} diff --git a/flutter/lib/desktop/screen/desktop_remote_screen.dart b/flutter/lib/desktop/screen/desktop_remote_screen.dart deleted file mode 100644 index e88078eb2..000000000 --- a/flutter/lib/desktop/screen/desktop_remote_screen.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:provider/provider.dart'; - -/// multi-tab desktop remote screen -class DesktopRemoteScreen extends StatelessWidget { - final Map params; - - DesktopRemoteScreen({Key? key, required this.params}) : super(key: key) { - bind.mainInitInputSource(); - stateGlobal.getInputSource(force: true); - } - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ChangeNotifierProvider.value(value: gFFI.imageModel), - ChangeNotifierProvider.value(value: gFFI.cursorModel), - 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: isLinux ? Colors.transparent : null, - body: ConnectionTabPage( - params: params, - ), - )); - } -} diff --git a/flutter/lib/desktop/screen/desktop_terminal_screen.dart b/flutter/lib/desktop/screen/desktop_terminal_screen.dart deleted file mode 100644 index 301489c86..000000000 --- a/flutter/lib/desktop/screen/desktop_terminal_screen.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:provider/provider.dart'; - -import 'package:flutter_hbb/desktop/pages/terminal_tab_page.dart'; - -class DesktopTerminalScreen extends StatelessWidget { - final Map params; - - const DesktopTerminalScreen({Key? key, required this.params}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ], - child: Scaffold( - backgroundColor: isLinux ? Colors.transparent : null, - body: TerminalTabPage( - params: params, - ), - ), - ); - } -} diff --git a/flutter/lib/desktop/screen/desktop_view_camera_screen.dart b/flutter/lib/desktop/screen/desktop_view_camera_screen.dart deleted file mode 100644 index a845b89d0..000000000 --- a/flutter/lib/desktop/screen/desktop_view_camera_screen.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hbb/common.dart'; -import 'package:flutter_hbb/desktop/pages/view_camera_tab_page.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/state_model.dart'; -import 'package:provider/provider.dart'; - -/// multi-tab desktop remote screen -class DesktopViewCameraScreen extends StatelessWidget { - final Map params; - - DesktopViewCameraScreen({Key? key, required this.params}) : super(key: key) { - bind.mainInitInputSource(); - stateGlobal.getInputSource(force: true); - } - - @override - Widget build(BuildContext context) { - return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ChangeNotifierProvider.value(value: gFFI.imageModel), - ChangeNotifierProvider.value(value: gFFI.cursorModel), - 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: isLinux ? Colors.transparent : null, - body: ViewCameraTabPage( - params: params, - ), - )); - } -} diff --git a/flutter/lib/desktop/widgets/button.dart b/flutter/lib/desktop/widgets/button.dart deleted file mode 100644 index 0c09f7c77..000000000 --- a/flutter/lib/desktop/widgets/button.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -import '../../common.dart'; - -class Button extends StatefulWidget { - final GestureTapCallback onTap; - final String text; - final double? textSize; - final double? minWidth; - final bool isOutline; - final double? padding; - final Color? textColor; - final double? radius; - final Color? borderColor; - - Button({ - Key? key, - this.minWidth, - this.isOutline = false, - this.textSize, - this.padding, - this.textColor, - this.radius, - this.borderColor, - required this.onTap, - required this.text, - }) : super(key: key); - - @override - State} + {auth ? "" : } + {auth ? : ""} + + {c.is_file_transfer || c.port_forward ? "" :
{svg_chat}
} + +
+ {c.is_file_transfer || c.port_forward ? "" : } +
+ ); + } + + sendMsg(text) { + if (!text) return; + let { cid, connection } = this; + checkClickTime(function() { + connection.msgs.push({ name: "me", text: text, time: getNowStr()}); + handler.xcall("send_msg",cid, text); + body.componentUpdate(); + }); + } + + ["on click at icon.keyboard"](e) { + let { cid, connection } = this; + checkClickTime(function() { + connection.keyboard = !connection.keyboard; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "keyboard", connection.keyboard); + }); + } + + ["on click at icon.clipboard"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.clipboard = !connection.clipboard; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "clipboard", connection.clipboard); + }); + } + + ["on click at icon.audio"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.audio = !connection.audio; + body.componentUpdate(); + handler.xcall("switch_permission",cid, "audio", connection.audio); + }); + } + + ["on click at button#accept"]() { + let { cid, connection } = this; + checkClickTime(function() { + connection.authorized = true; + body.componentUpdate(); + handler.xcall("authorize",cid); + setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,30); + }); + } + + ["on click at button#dismiss"]() { + let cid = this.cid; + checkClickTime(function() { + handler.close(cid); // TEST + }); + } + + ["on click at button#disconnect"]() { + let cid = this.cid; + checkClickTime(function() { + handler.close(cid); // TEST + }); + } + ["on click at div.chaticon"]() { + checkClickTime(function() { + show_chat = !show_chat; + adaptSize(); + }); + } +} + +$("body").content(); + +var header; + +class Header extends Element { + this() { + header = this; + } + + render() { + let me = this; + let conn = connections[body.cur]; + if (conn && conn.unreaded > 0) { + let el = this.select("#unreaded" + conn.id); // TODO select + if (el) el.style.setProperty("display","inline-block"); + setTimeout(function() { + conn.unreaded = 0; + let el = this.select("#unreaded" + conn.id); // TODO + if (el) el.style.setProperty("display","none"); + },300); + } + let tabs = connections.map((c, i)=> this.renderTab(c, i)); + return (
+ {tabs} +
+
+ < + > +
+
); + } + + renderTab(c, i) { + let cur = body.cur; + return (
+ {c.name} + {c.unreaded > 0 ? {c.unreaded} : ""} +
); + } + + update_cur(idx) { + checkClickTime(function(){ + body.cur = idx; + update(); + setTimeout(adjustHeader,1); + }); + } + + ["on click at div.tab"] (_, me) { + let idx = me.index; + if (idx == body.cur) return; + this.update_cur(idx); + } + + ["on click at span.left-arrow"]() { + let cur = body.cur; + if (cur == 0) return; + this.update_cur(cur - 1); + } + + ["on click at span.right-arrow"]() { + let cur = body.cur; + if (cur == connections.length - 1) return; + this.update_cur(cur + 1); + } +} + +if (is_osx) { + $("header").content(
); + $("header").attributes["role"] = "window-caption"; // TODO +} else { + $("div.window-toolbar").content(
); + setWindowButontsAndIcon(true); +} + +function checkClickTime(callback) { + callback(); +} + +function adaptSize() { + $("div.right-panel").style.setProperty("display",show_chat ? "block" : "none"); + let el = $("div.chaticon"); + if (el) el.classList.toggle("active", show_chat); + let [x, y, w, h] = view.state.box("rectw", "border", "screen"); + if (show_chat && w < 600) { + view.move(x - (600 - w), y, 600, h); + } else if (!show_chat && w > 450) { + view.move(x + (w - 300), y, 300, h); + } +} + +function update() { + header.componentUpdate(); + body.componentUpdate(); +} + +function bring_to_top(idx=-1) { + if (view.state == Window.WINDOW_HIDDEN || view.state == Window.WINDOW_MINIMIZED) { + if (is_linux) { + view.focus = $("body"); + } else { + view.state = Window.WINDOW_SHOWN; + } + if (idx >= 0) body.cur = idx; + } else { + view.isTopmost = true; // TEST + view.isTopmost = false; // TEST + } +} + +handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio) { + let conn; + connections.map(function(c) { + if (c.id == id) conn = c; + }); + if (conn) { + conn.authorized = authorized; + update(); + return; + } + if (!name) name = "NA"; + connections.push({ + id: id, is_file_transfer: is_file_transfer, peer_id: peer_id, + port_forward: port_forward, + name: name, authorized: authorized, time: new Date(), + keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0, + audio: audio, + }); + body.cur = connections.length - 1; + bring_to_top(); + update(); + setTimeout(adjustHeader,1); + if (authorized) { + setTimeout(()=>view.state = Window.WINDOW_MINIMIZED,3000); + } +} + +handler.removeConnection = function(id) { + let i = -1; + connections.map(function(c, idx) { + if (c.id == id) i = idx; + }); + connections.splice(i, 1); + if (connections.length == 0) { + handler.xcall("exit"); + } else { + if (body.cur >= i && body.cur > 0) body.cur -= 1; + update(); + } +} + +handler.newMessage = function(id, text) { + let idx = -1; + connections.map(function(c, i) { + if (c.id == id) idx = i; + }); + let conn = connections[idx]; + if (!conn) return; + conn.msgs.push({name: conn.name, text: text, time: getNowStr()}); + bring_to_top(idx); + if (idx == body.cur) show_chat = true; + conn.unreaded += 1; + update(); +} + +handler.awake = function() { + view.state = Window.WINDOW_SHOWN; + view.focus = $("body"); +} + +// TEST +// view << event statechange { +// adjustBorder(); +// } +view.on("statechange",()=>{ + adjustBorder(); +}) + +document.on("ready",()=>{ + adjustBorder(); + let [sw, sh] = view.screenBox("workarea", "dimension"); + let w = 300; + let h = 400; + view.move(sw - w, 0, w, h); +}) + +document.on("unloadequest",(evt)=>{ + view.state = Window.WINDOW_HIDDEN; + console.log("cm unloadequest") + evt.preventDefault(); // prevent unloading TEST +}) + +function getElaspsed(time) { + // let now = new Date(); + // let seconds = Date.diff(time, now, #seconds); + // let hours = seconds / 3600; + // let days = hours / 24; + // hours = hours % 24; + // let minutes = seconds % 3600 / 60; + // seconds = seconds % 60; + // let out = String.printf("%02d:%02d:%02d", hours, minutes, seconds); + // if (days > 0) { + // out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out); + // } + let out = "TIME TODO" + new Date(); // TODO + return out; +} + +// updateTime +setInterval(function() { + let el = $("#time"); + if (el) { + let c = connections[body.cur]; + if (c) { + el.text = getElaspsed(c.time); + } + } +},1000); + + +function adjustHeader() { + let hw = $("header").state.box("width"); + let tabswrapper = $("div.tabs-wrapper"); + let tabs = $("div.tabs"); + let arrows = $("div.tab-arrows"); + if (!arrows) return; + let n = connections.length; + let wtab = 80; + let max = hw - 98; + let need_width = n * wtab + 2; // include border of active tab + if (need_width < max) { + arrows.style.setProperty("display","none"); + tabs.style.setProperty("width",need_width); + tabs.style.setProperty("margin-left",0); + tabswrapper.style.setProperty("width",need_width); + } else { + let margin = (body.cur + 1) * wtab - max + 30; + if (margin < 0) margin = 0; + arrows.style.setProperty("display","block"); + tabs.style.setProperty("width",(max - 20 + margin) + 'px'); + tabs.style.setProperty("margin-left",-margin + 'px'); + tabswrapper.style.setProperty("width",(max + 10) + 'px'); + } +} + +document.onsizechange = ()=>{ + console.log("cm onsizechange"); + adjustHeader(); +} + +// handler.addConnection(0, false, 0, "", "test1", true, false, false, false); +// handler.addConnection(1, false, 0, "", "test2--------", true, false, false, false); +// handler.addConnection(2, false, 0, "", "test3", true, false, false, false); +// handler.newMessage(0, 'h'); diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 4a68a571d..7873ea14c 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -1,198 +1,456 @@ -#[cfg(target_os = "linux")] -use crate::ipc::start_pa; -use crate::ui_cm_interface::{start_ipc, ConnectionManager, InvokeUiCM}; - -use hbb_common::{allow_err, log}; +use crate::ipc::{self, new_listener, Connection, Data}; +use hbb_common::{ + allow_err, + config::{Config, ICON}, + fs, log, + message_proto::*, + protobuf::Message as _, + tokio::{self, sync::mpsc, task::spawn_blocking}, +}; use sciter::{make_args, Element, Value, HELEMENT}; -use std::sync::Mutex; -use std::{ops::Deref, sync::Arc}; +use std::{ + collections::HashMap, + ops::Deref, + sync::{Arc, RwLock}, +}; -lazy_static::lazy_static! { - pub static ref HIDE_CM: Arc> = Arc::new(Mutex::new(false)); +pub struct ConnectionManagerInner { + root: Option, + senders: HashMap>, } -#[derive(Clone, Default)] -pub struct SciterHandler { - pub element: Arc>>, -} +#[derive(Clone)] +pub struct ConnectionManager(Arc>); -impl InvokeUiCM for SciterHandler { - fn add_connection(&self, client: &crate::ui_cm_interface::Client) { - self.call( - "addConnection", - &make_args!( - client.id, - client.is_file_transfer, - client.is_view_camera, - client.is_terminal, - client.port_forward.clone(), - client.peer_id.clone(), - client.name.clone(), - client.avatar.clone(), - client.authorized, - client.keyboard, - client.clipboard, - client.audio, - client.file, - client.restart, - client.recording, - client.block_input, - client.privacy_mode - ), - ); - } - - fn remove_connection(&self, id: i32, close: bool) { - self.call("removeConnection", &make_args!(id, close)); - if crate::ui_cm_interface::get_clients_length().eq(&0) { - crate::platform::quit_gui(); - } - } - - fn new_message(&self, id: i32, text: String) { - self.call("newMessage", &make_args!(id, text)); - } - - fn change_theme(&self, dark: String) { - self.call("changeTheme", &make_args!(dark)); - } - - fn change_language(&self) { - self.call("changeLanguage", &make_args!()); - } - - fn show_elevation(&self, show: bool) { - self.call("showElevation", &make_args!(show)); - } - - fn update_voice_call_state(&self, client: &crate::ui_cm_interface::Client) { - self.call( - "updateVoiceCallState", - &make_args!(client.id, client.in_voice_call, client.incoming_voice_call), - ); - } - - fn file_transfer_log(&self, _action: &str, _log: &str) {} -} - -impl SciterHandler { - #[inline] - fn call(&self, func: &str, args: &[Value]) { - if let Some(e) = self.element.lock().unwrap().as_ref() { - allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..])); - } - } -} - -pub struct SciterConnectionManager(ConnectionManager); - -impl Deref for SciterConnectionManager { - type Target = ConnectionManager; +impl Deref for ConnectionManager { + type Target = Arc>; fn deref(&self) -> &Self::Target { &self.0 } } -impl SciterConnectionManager { +impl ConnectionManager { pub fn new() -> Self { #[cfg(target_os = "linux")] std::thread::spawn(start_pa); - let cm = ConnectionManager { - ui_handler: SciterHandler::default(), + let inner = ConnectionManagerInner { + root: None, + senders: HashMap::new(), }; + let cm = Self(Arc::new(RwLock::new(inner))); + #[cfg(target_os = "macos")] + { + let cloned = cm.clone(); + *super::macos::SHOULD_OPEN_UNTITLED_FILE_CALLBACK + .lock() + .unwrap() = Some(Box::new(move || { + cloned.call("awake", &make_args!()); + })); + } let cloned = cm.clone(); std::thread::spawn(move || start_ipc(cloned)); - SciterConnectionManager(cm) + cm } fn get_icon(&mut self) -> String { - super::get_icon() + ICON.to_owned() } - fn check_click_time(&mut self, id: i32) { - crate::ui_cm_interface::check_click_time(id); + #[inline] + fn call(&self, func: &str, args: &[Value]) { + let r = self.read().unwrap(); + if let Some(ref e) = r.root { + allow_err!(e.call_method(func, args)); + } } - fn get_click_time(&self) -> f64 { - crate::ui_cm_interface::get_click_time() as _ + fn add_connection( + &self, + id: i32, + is_file_transfer: bool, + port_forward: String, + peer_id: String, + name: String, + authorized: bool, + keyboard: bool, + clipboard: bool, + audio: bool, + tx: mpsc::UnboundedSender, + ) { + self.call( + "addConnection", + &make_args!( + id, + is_file_transfer, + port_forward, + peer_id, + name, + authorized, + keyboard, + clipboard, + audio + ), + ); // TODO + self.write().unwrap().senders.insert(id, tx); + } + + fn remove_connection(&self, id: i32) { + self.write().unwrap().senders.remove(&id); + self.call("removeConnection", &make_args!(id)); // TODO + } + + async fn handle_data( + &self, + id: i32, + data: Data, + write_jobs: &mut Vec, + conn: &mut Connection, + ) { + match data { + Data::ChatMessage { text } => { + self.call("newMessage", &make_args!(id, text)); + } + Data::FS(v) => match v { + ipc::FS::ReadDir { + dir, + include_hidden, + } => { + Self::read_dir(&dir, include_hidden, conn).await; + } + ipc::FS::RemoveDir { + path, + id, + recursive, + } => { + Self::remove_dir(path, id, recursive, conn).await; + } + ipc::FS::RemoveFile { path, id, file_num } => { + Self::remove_file(path, id, file_num, conn).await; + } + ipc::FS::CreateDir { path, id } => { + Self::create_dir(path, id, conn).await; + } + ipc::FS::NewWrite { + path, + id, + mut files, + } => { + write_jobs.push(fs::TransferJob::new_write( + id, + path, + files + .drain(..) + .map(|f| FileEntry { + name: f.0, + modified_time: f.1, + ..Default::default() + }) + .collect(), + )); + } + ipc::FS::CancelWrite { id } => { + if let Some(job) = fs::get_job(id, write_jobs) { + job.remove_download_file(); + fs::remove_job(id, write_jobs); + } + } + ipc::FS::WriteDone { id, file_num } => { + if let Some(job) = fs::get_job(id, write_jobs) { + job.modify_time(); + Self::send(fs::new_done(id, file_num), conn).await; + fs::remove_job(id, write_jobs); + } + } + ipc::FS::WriteBlock { + id, + file_num, + data, + compressed, + } => { + if let Some(job) = fs::get_job(id, write_jobs) { + if let Err(err) = job + .write(FileTransferBlock { + id, + file_num, + data, + compressed, + ..Default::default() + }) + .await + { + Self::send(fs::new_error(id, err, file_num), conn).await; + } + } + } + }, + _ => {} + } + } + + async fn read_dir(dir: &str, include_hidden: bool, conn: &mut Connection) { + let path = { + if dir.is_empty() { + Config::get_home() + } else { + fs::get_path(dir) + } + }; + if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await { + let mut msg_out = Message::new(); + let mut file_response = FileResponse::new(); + file_response.set_dir(fd); + msg_out.set_file_response(file_response); + Self::send(msg_out, conn).await; + } + } + + async fn handle_result( + res: std::result::Result, S>, + id: i32, + file_num: i32, + conn: &mut Connection, + ) { + match res { + Err(err) => { + Self::send(fs::new_error(id, err, file_num), conn).await; + } + Ok(Err(err)) => { + Self::send(fs::new_error(id, err, file_num), conn).await; + } + Ok(Ok(())) => { + Self::send(fs::new_done(id, file_num), conn).await; + } + } + } + + async fn remove_file(path: String, id: i32, file_num: i32, conn: &mut Connection) { + Self::handle_result( + spawn_blocking(move || fs::remove_file(&path)).await, + id, + file_num, + conn, + ) + .await; + } + + async fn create_dir(path: String, id: i32, conn: &mut Connection) { + Self::handle_result( + spawn_blocking(move || fs::create_dir(&path)).await, + id, + 0, + conn, + ) + .await; + } + + async fn remove_dir(path: String, id: i32, recursive: bool, conn: &mut Connection) { + let path = fs::get_path(&path); + Self::handle_result( + spawn_blocking(move || { + if recursive { + fs::remove_all_empty_dir(&path) + } else { + std::fs::remove_dir(&path).map_err(|err| err.into()) + } + }) + .await, + id, + 0, + conn, + ) + .await; + } + + async fn send(msg: Message, conn: &mut Connection) { + match msg.write_to_bytes() { + Ok(bytes) => allow_err!(conn.send(&Data::RawMessage(bytes)).await), + err => allow_err!(err), + } } fn switch_permission(&self, id: i32, name: String, enabled: bool) { - crate::ui_cm_interface::switch_permission(id, name, enabled); + let lock = self.read().unwrap(); + if let Some(s) = lock.senders.get(&id) { + allow_err!(s.send(Data::SwitchPermission { name, enabled })); + } } fn close(&self, id: i32) { - crate::ui_cm_interface::close(id); - } - - fn remove_disconnected_connection(&self, id: i32) { - crate::ui_cm_interface::remove(id); - } - - fn quit(&self) { - crate::platform::quit_gui(); - } - - fn authorize(&self, id: i32) { - crate::ui_cm_interface::authorize(id); + let lock = self.read().unwrap(); + if let Some(s) = lock.senders.get(&id) { + allow_err!(s.send(Data::Close)); + } } fn send_msg(&self, id: i32, text: String) { - crate::ui_cm_interface::send_chat(id, text); + let lock = self.read().unwrap(); + if let Some(s) = lock.senders.get(&id) { + allow_err!(s.send(Data::ChatMessage { text })); + } + } + + fn authorize(&self, id: i32) { + let lock = self.read().unwrap(); + if let Some(s) = lock.senders.get(&id) { + allow_err!(s.send(Data::Authorize)); + } + } + + fn exit(&self) { + std::process::exit(0); } fn t(&self, name: String) -> String { crate::client::translate(name) } - - fn can_elevate(&self) -> bool { - crate::ui_cm_interface::can_elevate() - } - - fn elevate_portable(&self, id: i32) { - crate::ui_cm_interface::elevate_portable(id); - } - - fn get_option(&self, key: String) -> String { - crate::ui_interface::get_option(key) - } - - fn get_builtin_option(&self, key: String) -> String { - crate::ui_interface::get_builtin_option(&key) - } - - fn hide_cm(&self) -> bool { - *crate::ui::cm::HIDE_CM.lock().unwrap() - } - - fn get_supported_privacy_mode_impls(&self) -> String { - serde_json::to_string(&crate::privacy_mode::get_supported_privacy_mode_impl()) - .unwrap_or_default() - } } -impl sciter::EventHandler for SciterConnectionManager { +impl sciter::EventHandler for ConnectionManager { fn attached(&mut self, root: HELEMENT) { - *self.ui_handler.element.lock().unwrap() = Some(Element::from(root)); + self.write().unwrap().root = Some(Element::from(root)); } sciter::dispatch_script_call! { fn t(String); - fn check_click_time(i32); - fn get_click_time(); fn get_icon(); fn close(i32); - fn remove_disconnected_connection(i32); - fn quit(); fn authorize(i32); fn switch_permission(i32, String, bool); fn send_msg(i32, String); - fn can_elevate(); - fn elevate_portable(i32); - fn get_option(String); - fn get_builtin_option(String); - fn hide_cm(); - fn get_supported_privacy_mode_impls(); + fn exit(); + } +} + +#[tokio::main(flavor = "current_thread")] +async fn start_ipc(cm: ConnectionManager) { + match new_listener("_cm").await { + Ok(mut incoming) => { + while let Some(result) = incoming.next().await { + match result { + Ok(stream) => { + let mut stream = Connection::new(stream); + let cm = cm.clone(); + tokio::spawn(async move { + let mut conn_id: i32 = 0; + let (tx, mut rx) = mpsc::unbounded_channel::(); + let mut write_jobs: Vec = Vec::new(); + loop { + tokio::select! { + res = stream.next() => { + match res { + Err(err) => { + log::info!("cm ipc connection closed: {}", err); + break; + } + Ok(Some(data)) => { + match data { + Data::Login{id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio} => { + conn_id = id; + cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, tx.clone()); + } + _ => { + cm.handle_data(conn_id, data, &mut write_jobs, &mut stream).await; + } + } + } + _ => {} + } + } + Some(data) = rx.recv() => { + allow_err!(stream.send(&data).await); + } + } + } + cm.remove_connection(conn_id); + }); + } + Err(err) => { + log::error!("Couldn't get cm client: {:?}", err); + } + } + } + } + Err(err) => { + log::error!("Failed to start cm ipc server: {}", err); + } + } + std::process::exit(-1); +} + +#[cfg(target_os = "linux")] +#[tokio::main(flavor = "current_thread")] +async fn start_pa() { + use hbb_common::config::APP_NAME; + use libpulse_binding as pulse; + use libpulse_simple_binding as psimple; + match new_listener("_pa").await { + Ok(mut incoming) => { + loop { + if let Some(result) = incoming.next().await { + match result { + Ok(stream) => { + let mut stream = Connection::new(stream); + let mut device: String = "".to_owned(); + if let Some(Ok(Some(Data::Config((_, Some(x)))))) = + stream.next_timeout2(1000).await + { + device = x; + } + if device == "Mute" { + break; + } + if !device.is_empty() { + device = crate::platform::linux::get_pa_source_name(&device); + } + if device.is_empty() { + device = crate::platform::linux::get_pa_monitor(); + } + if device.is_empty() { + break; + } + let spec = pulse::sample::Spec { + format: pulse::sample::Format::F32le, + channels: 2, + rate: crate::platform::linux::PA_SAMPLE_RATE, + }; + log::info!("pa monitor: {:?}", device); + // systemctl --user status pulseaudio.service + let mut buf: Vec = vec![0; 480 * 4]; + match psimple::Simple::new( + None, // Use the default server + APP_NAME, // Our application’s name + pulse::stream::Direction::Record, // We want a record stream + Some(&device), // Use the default device + APP_NAME, // Description of our stream + &spec, // Our sample format + None, // Use default channel map + None, // Use default buffering attributes + ) { + Ok(s) => loop { + if let Some(Err(_)) = stream.next_timeout2(1).await { + break; + } + if let Ok(_) = s.read(&mut buf) { + allow_err!( + stream.send(&Data::RawMessage(buf.clone())).await + ); + } + }, + Err(err) => { + log::error!("Could not create simple pulse: {}", err); + } + } + } + Err(err) => { + log::error!("Couldn't get pa client: {:?}", err); + } + } + } + } + } + Err(err) => { + log::error!("Failed to start pa ipc server: {}", err); + } } } diff --git a/src/ui/cm.tis b/src/ui/cm.tis deleted file mode 100644 index f306e9032..000000000 --- a/src/ui/cm.tis +++ /dev/null @@ -1,598 +0,0 @@ -view.windowFrame = is_osx ? #extended : #solid; - -var body; -var connections = []; -var show_chat = false; -var show_elevation = true; -var is_privacy_mode_supported = handler.get_supported_privacy_mode_impls() != '[]'; -var allow_perm_change_in_accept_window = - handler.get_builtin_option('enable-perm-change-in-accept-window') != 'N'; -var svg_elevate = ; - -var hide_cm = undefined; -function setWindowState(state) { - if (hide_cm == undefined) hide_cm = handler.hide_cm(); - if (hide_cm) return; - view.windowState = state; -} - -class Body: Reactor.Component -{ - this var cur = 0; - - function this() { - body = this; - } - - function render() { - if (connections.length == 0) - return
- Waiting for new connection ... -
; - var c = connections[this.cur]; - this.connection = c; - this.cid = c.id; - var auth = c.authorized; - var me = this; - var callback = function(msg) { - me.sendMsg(msg); - }; - var right_style = show_chat ? "" : "display: none"; - var permissions_locked = !allow_perm_change_in_accept_window; - var disconnected = c.disconnected; - var show_elevation_btn = handler.can_elevate() && show_elevation && !c.is_file_transfer && !c.is_view_camera && !c.is_terminal && c.port_forward.length == 0; - var show_accept_btn = handler.get_option('approve-mode') != 'password'; - // below size:* is a workaround for Linux, it already set in css, but not work, shit sciter - return
-
-
- {c.avatar ? - : -
- {c.name[0].toUpperCase()} -
} -
-
{c.name}
-
({c.peer_id})
-
{auth - ? {disconnected ? translate('Disconnected') : translate('Connected')}{" "}{getElapsed(c.time, c.now)} - : {translate('Request access to your device')}{"..."}} -
-
-
-
- {c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" :
{translate('Permissions')}
} - {c.is_file_transfer || c.is_terminal || c.port_forward || disconnected ? "" :
-
-
-
-
-
-
-
-
-
-
- } - {c.is_file_transfer ?
{translate('Transfer file')}
: ""} - {c.is_view_camera ?
{translate('View camera')}
: ""} - {c.is_terminal ?
{translate('Terminal')}
: ""} - {c.port_forward ?
Port Forwarding: {c.port_forward}
: ""} -
-
- {!auth && !disconnected && show_elevation_btn && show_accept_btn ? : "" } - {auth && !disconnected && show_elevation_btn ? : "" } -
- {!auth && show_accept_btn ? : "" } - {!auth ? : "" } -
- {auth && !disconnected ? : "" } - {auth && disconnected ? : "" } -
- {c.is_file_transfer || c.is_terminal || c.port_forward ? "" :
{svg_chat}
} -
-
- {c.is_file_transfer || c.is_terminal || c.port_forward ? "" : } -
-
; - } - - function sendMsg(text) { - if (!text) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.msgs.push({ name: "me", text: text, time: getNowStr()}); - handler.send_msg(cid, text); - body.update(); - }); - } - - event click $(icon.keyboard) (e) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.keyboard = !connection.keyboard; - body.update(); - handler.switch_permission(cid, "keyboard", connection.keyboard); - }); - } - - event click $(icon.clipboard) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.clipboard = !connection.clipboard; - body.update(); - handler.switch_permission(cid, "clipboard", connection.clipboard); - }); - } - - event click $(icon.audio) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.audio = !connection.audio; - body.update(); - handler.switch_permission(cid, "audio", connection.audio); - }); - } - - event click $(icon.file) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.file = !connection.file; - body.update(); - handler.switch_permission(cid, "file", connection.file); - }); - } - - event click $(icon.restart) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.restart = !connection.restart; - body.update(); - handler.switch_permission(cid, "restart", connection.restart); - }); - } - - event click $(icon.recording) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.recording = !connection.recording; - body.update(); - handler.switch_permission(cid, "recording", connection.recording); - }); - } - - event click $(icon.block_input) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.block_input = !connection.block_input; - body.update(); - handler.switch_permission(cid, "block_input", connection.block_input); - }); - } - - event click $(icon.privacy_mode) { - if (!allow_perm_change_in_accept_window) return; - var { cid, connection } = this; - checkClickTime(function() { - connection.privacy_mode = !connection.privacy_mode; - body.update(); - handler.switch_permission(cid, "privacy_mode", connection.privacy_mode); - }); - } - - event click $(button#accept) { - var { cid, connection } = this; - checkClickTime(function() { - connection.authorized = true; - body.update(); - handler.authorize(cid); - self.timer(30ms, function() { - setWindowState(View.WINDOW_MINIMIZED); - }); - }); - } - - event click $(button#elevate_accept) { - var { cid, connection } = this; - checkClickTime(function() { - connection.authorized = true; - show_elevation = false; - body.update(); - handler.elevate_portable(cid); - handler.authorize(cid); - self.timer(30ms, function() { - setWindowState(View.WINDOW_MINIMIZED); - }); - }); - } - - event click $(button#elevate) { - var { cid, connection } = this; - checkClickTime(function() { - show_elevation = false; - body.update(); - handler.elevate_portable(cid); - self.timer(30ms, function() { - setWindowState(View.WINDOW_MINIMIZED); - }); - }); - } - - event click $(button#dismiss) { - var cid = this.cid; - checkClickTime(function() { - handler.close(cid); - }); - } - - event click $(button#disconnect) { - var cid = this.cid; - checkClickTime(function() { - handler.close(cid); - }); - } - - event click $(button#close) { - var cid = this.cid; - if (this.cur >= 0 && this.cur < connections.length){ - handler.remove_disconnected_connection(cid); - connections.splice(this.cur, 1); - if (connections.length > 0) { - if (this.cur > 0) - this.cur -= 1; - else - this.cur = connections.length - 1; - header.update(); - body.update(); - } else { - handler.quit(); - } - } - - } -} - -$(body).content(); - -var header; - -class Header: Reactor.Component -{ - function this() { - header = this; - } - - function render() { - var me = this; - var conn = connections[body.cur]; - if (conn && conn.unreaded > 0) {; - var el = me.select("#unreaded" + conn.id); - if (el) el.style.set { - display: "inline-block", - }; - self.timer(300ms, function() { - conn.unreaded = 0; - var el = me.select("#unreaded" + conn.id); - if (el) el.style.set { - display: "none", - }; - }); - } - var tabs = connections.map(function(c, i) { return me.renderTab(c, i) }); - return
- {tabs} -
-
- < - > -
-
; - } - - function renderTab(c, i) { - var cur = body.cur; - return
- {c.name} - {c.unreaded > 0 ? {c.unreaded} : ""} -
; - } - - function update_cur(idx) { - checkClickTime(function() { - body.cur = idx; - update(); - self.timer(1ms, adjustHeader); - }); - } - - event click $(div.tab) (_, me) { - var idx = me.index; - if (idx == body.cur) return; - this.update_cur(idx); - } - - event click $(span#left-arrow) { - var cur = body.cur; - if (cur == 0) return; - this.update_cur(cur - 1); - } - - event click $(span#right-arrow) { - var cur = body.cur; - if (cur == connections.length - 1) return; - this.update_cur(cur + 1); - } -} - -if (is_osx) { - $(header).content(
); - $(header).attributes["role"] = "window-caption"; -} else { - $(div.window-toolbar).content(
); - setWindowButontsAndIcon(true); -} - -event click $(div.chaticon) { - checkClickTime(function() { - show_chat = !show_chat; - adaptSizeForChat(); - if (show_chat) { - view.focus = $(.outline-focus); - } - }); -} - -function checkClickTime(callback) { - var click_callback_time = getTime(); - handler.check_click_time(body.cid); - self.timer(120ms, function() { - var d = click_callback_time - handler.get_click_time(); - if (d > 120) - callback(); - }); -} - -function adaptSizeForChat() { - $(div.right-panel).style.set { - display: show_chat ? "block" : "none", - }; - var (x, y, w, h) = view.box(#rectw, #border, #screen); - if (show_chat && w < scaleIt(600)) { - view.move(x - (scaleIt(600) - w), y, scaleIt(600), h); - } else if (!show_chat && w > scaleIt(450)) { - view.move(x + (w - scaleIt(300)), y, scaleIt(300), h); - } -} - -function update() { - header.update(); - body.update(); -} - -function bring_to_top(idx=-1) { - if (view.windowState == View.WINDOW_HIDDEN || view.windowState == View.WINDOW_MINIMIZED) { - if (is_linux) { - view.focus = self; - } else { - setWindowState(View.WINDOW_SHOWN); - } - if (idx >= 0) body.cur = idx; - } else { - view.windowTopmost = true; - view.windowTopmost = false; - } -} - -handler.addConnection = function(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, privacy_mode) { - stdout.println("new connection #" + id + ": " + peer_id); - var conn; - connections.map(function(c) { - if (c.id == id) conn = c; - }); - if (conn) { - conn.authorized = authorized; - conn.privacy_mode = privacy_mode; - update(); - return; - } - var idx = -1; - connections.map(function(c, i) { - if (c.disconnected && c.peer_id == peer_id) idx = i; - }); - if (!name) name = "NA"; - conn = { - id: id, is_file_transfer: is_file_transfer, is_view_camera: is_view_camera, is_terminal: is_terminal, peer_id: peer_id, - port_forward: port_forward, - avatar: avatar, - name: name, authorized: authorized, time: new Date(), now: new Date(), - keyboard: keyboard, clipboard: clipboard, msgs: [], unreaded: 0, - audio: audio, file: file, restart: restart, recording: recording, - block_input:block_input, privacy_mode:privacy_mode, - disconnected: false - }; - if (idx < 0) { - connections.push(conn); - body.cur = connections.length - 1; - } else { - connections[idx] = conn; - body.cur = idx; - } - bring_to_top(); - update(); - self.timer(1ms, adjustHeader); - if (authorized) { - self.timer(3s, function() { - setWindowState(View.WINDOW_MINIMIZED); - }); - } -} - -handler.removeConnection = function(id, close) { - var i = -1; - connections.map(function(c, idx) { - if (c.id == id) i = idx; - }); - if (i < 0) return; - if (close) { - connections.splice(i, 1); - } else { - var conn = connections[i]; - conn.disconnected = true; - } - if (connections.length > 0) { - if (body.cur >= i && body.cur > 0 && close) body.cur -= 1; - update(); - } -} - -handler.newMessage = function(id, text) { - var idx = -1; - connections.map(function(c, i) { - if (c.id == id) idx = i; - }); - var conn = connections[idx]; - if (!conn) return; - conn.msgs.push({name: conn.name, text: text, time: getNowStr()}); - bring_to_top(idx); - if (idx == body.cur) { - var shouldAdapt = !show_chat; - show_chat = true; - if (shouldAdapt) adaptSizeForChat(); - } - conn.unreaded += 1; - update(); -} - -handler.showElevation = function(show) { - if (show != show_elevation) { - show_elevation = show; - update(); - } -} - -view << event statechange { - adjustBorder(); -} - -function self.ready() { - adjustBorder(); - var (sw, sh) = view.screenBox(#workarea, #dimension); - var w = scaleIt(300); - var h = scaleIt(400); - view.move(sw - w, 0, w, h); -} - -function getElapsed(time, now) { - var seconds = Date.diff(time, now, #seconds); - var hours = seconds / 3600; - var days = hours / 24; - hours = hours % 24; - var minutes = seconds % 3600 / 60; - seconds = seconds % 60; - var out = String.printf("%02d:%02d:%02d", hours, minutes, seconds); - if (days > 0) { - out = String.printf("%d day%s %s", days, days > 1 ? "s" : "", out); - } - return out; -} - -var ui_status_cache = ["", ""]; -function check_update_ui() { - self.timer(1s, function() { - var approve_mode = handler.get_option('approve-mode'); - var allow_perm_change = handler.get_builtin_option('enable-perm-change-in-accept-window'); - var changed = false; - if (ui_status_cache[0] != approve_mode) { - ui_status_cache[0] = approve_mode; - changed = true; - } - if (ui_status_cache[1] != allow_perm_change) { - ui_status_cache[1] = allow_perm_change; - allow_perm_change_in_accept_window = allow_perm_change != 'N'; - changed = true; - } - if (changed) update(); - check_update_ui(); - }); -} -check_update_ui(); - -function updateTime() { - self.timer(1s, function() { - var now = new Date(); - connections.map(function(c) { - if (!c.authorized) c.time = now; - if (!c.disconnected) c.now = now; - }); - var el = $(#time); - if (el) { - var c = connections[body.cur]; - if (c && c.authorized && !c.disconnected) { - el.text = getElapsed(c.time, c.now); - } - } - updateTime(); - }); -} - -updateTime(); - -var tm0 = getTime(); - -function self.closing() { - if (connections.length == 0 && getTime() - tm0 > 30000) return true; - setWindowState(View.WINDOW_HIDDEN); - return false; -} - - -function adjustHeader() { - var hw = $(header).box(#width) / scaleFactor; - var tabswrapper = $(div.tabs-wrapper); - var tabs = $(div.tabs); - var arrows = $(div.tab-arrows); - if (!arrows) return; - var n = connections.length; - var wtab = 80; - var max = hw - 98; - var need_width = n * wtab + scaleIt(2); // include border of active tab - if (need_width < max) { - arrows.style.set { - display: "none", - }; - tabs.style.set { - width: need_width, - margin-left: 0, - }; - tabswrapper.style.set { - width: need_width, - }; - } else { - var margin = (body.cur + 1) * wtab - max + 30; - if (margin < 0) margin = 0; - arrows.style.set { - display: "block", - }; - tabs.style.set { - width: (max - 20 + margin) + 'px', - margin-left: -margin + 'px' - }; - tabswrapper.style.set { - width: (max + 10) + 'px', - }; - } -} - -view.on("size", adjustHeader); - -// handler.addConnection(0, false, false, 0, "", "test1", true, false, false, true, true); -// handler.addConnection(1, false, false, 0, "", "test2--------", true, false, false, false, false); -// handler.addConnection(2, false, false, 0, "", "test3", true, false, false, false, false); -// handler.newMessage(0, 'h'); diff --git a/src/ui/common.css b/src/ui/common.css index 16dd6ca9f..7e7eb6bf1 100644 --- a/src/ui/common.css +++ b/src/ui/common.css @@ -9,7 +9,6 @@ html { var(placeholder): #aaa; var(lighter-text): #888; var(light-text): #666; - var(menu-hover): #D7E4F2; var(dark-red): #A72145; var(dark-yellow): #FBC732; var(dark-blue): #2E2459; @@ -19,20 +18,6 @@ html { var(light-green): #D4EAB7; var(dark-green): #5CB85C; var(blood-red): #F82600; - var(gray-bg-osx): rgba(238, 238, 238, 0.75); -} - -html.darktheme { - var(bg): #252525; - var(gray-bg): #141414; - var(menu-hover): #2D3033; - var(border): #555; - - var(text): white; - var(light-text): #999; - var(lighter-text): #777; - var(placeholder): #555; - var(gray-bg-osx): rgba(37, 37, 37, 0.75); } body { @@ -58,7 +43,7 @@ button[type=checkbox], button[type=checkbox]:active { button.outline { border: color(border) solid 1px; - background: transparent; + background: transparent; color: color(text); } @@ -72,28 +57,13 @@ button.button:hover, button.outline:hover { border-color: color(hover-border); } -button:disabled, -button:disabled:hover { - opacity: 0.3; -} - -button.link { - background: none !important; - border: none; - padding: 0 !important; - color: color(button); - text-decoration: underline; - cursor: pointer; -} - input[type=text], input[type=password], input[type=number] { width: *; font-size: 1.5em; border-color: color(border); border-radius: 0; - color: color(text); + color: black; padding-left: 0.5em; - background: color(bg); } input:empty { @@ -104,15 +74,6 @@ input.outline-focus:focus { outline: color(button) solid 3px; } -textarea { - background: color(bg); - color: color(text); -} - -textarea:empty { - color: color(placeholder); -} - @set my-scrollbar { .prev { display:none; } @@ -122,21 +83,22 @@ textarea:empty { .base:disabled { background: transparent; } .slider:hover { background: grey; } .slider:active { background: grey; } - .base { size: 16px; } + .base { size: 16px; } .corner { background: white; } } @mixin ELLIPSIS { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - overflow-x: hidden; } .ellipsis { - @ELLIPSIS; + text-overflow: ellipsis; + white-space: nowrap; } -div.password svg:not(.checkmark) { +div.password svg { padding-left: 1em; size: 16px; color: #ddd; @@ -149,10 +111,6 @@ div.password input { font-size: 1.2em; } -div.username input { - font-size: 1.2em; -} - svg { background: none; } @@ -196,7 +154,7 @@ header div.window-icon icon { header caption { size: *; -} +} @media platform != "OSX" { button.window { @@ -252,24 +210,24 @@ header caption { } } -div.chatbox { +div.msgbox { size: *; } -div.chatbox div.send svg { +div.msgbox div.send svg { size: 16px; } -div.chatbox div.send span:active { +div.msgbox div.send span:active { opacity: 0.5; } -div.chatbox div.send span { +div.msgbox div.send span { display: inline-block; padding: 6px; } -div.chatbox .msgs { +div.msgbox .msgs { border: none; size: *; border-bottom: color(border) 1px solid; @@ -279,26 +237,28 @@ div.chatbox .msgs { padding: 1em; } -div.chatbox div.send { +div.msgbox div.send { flow: horizontal; height: 30px; padding: 5px; } -div.chatbox div.send input { +div.msgbox div.send input { height: 20px !important; } -div.chatbox div.name { +div.msgbox div.name { color: color(dark-green); } -div.chatbox div.right-side div { +div.msgbox div.right-side div { text-align: right; } -div.chatbox div.text { +div.msgbox div.text { margin-top: 0.5em; + word-wrap: break-word; + word-break: break-all; } @media platform != "OSX" { @@ -330,10 +290,6 @@ progress { background: transparent; } -menu { - background: color(bg); -} - menu div.separator { height: 1px; width: *; @@ -343,7 +299,6 @@ menu div.separator { } menu li { - color: color(text); position: relative; } @@ -351,7 +306,7 @@ menu li span { display: none; } -menu li.selected span:nth-child(1) { +menu li.selected span { display: inline-block; position: absolute; left: -10px; @@ -367,126 +322,7 @@ menu li.selected span:nth-child(1) { opacity: 0.5; } -menu li:hover { - background: color(menu-hover); - color: color(text); -} - -menu li.line-through, menu li.line-through :hover { +menu li.line-through { text-decoration-line: line-through; color: red; } - -#tags { - size: *; - padding: 0.5em; - overflow-y: scroll-indicator; - border-spacing: 0.5em; - flow: horizontal-flow; -} - -#tags span { - background: color(gray-bg); - display: inline-block; - border-radius: 6px; - padding: 3px 0.5em; - word-wrap: normal; -} - -#tags span.active { - background: color(button); - border-color: color(button); - color: white; -} - -#tags span:hover { - border-color: color(hover-border); -} - -div#msgbox .msgbox-icon svg { - size: 80px; - background: white; - -} - -div#msgbox .form { - border-spacing: 0.5em; -} - -div#msgbox .caption { - @ELLIPSIS; - height: 2em; - line-height: 2em; - text-align: center; - color: color(bg); - font-weight: bold; -} - -div#msgbox .form .text { - @ELLIPSIS; -} - -div#msgbox button.button { - margin-left: 1.6em; -} - -div#msgbox div.password { - position: relative; -} - -div#msgbox div.password svg { - position: absolute; - right: 0.25em; - top: 0.25em; - padding: 0.5em; - color: color(text); -} - -div#msgbox div.set-password > div { - flow: horizontal; -} - -div#msgbox div.set-password > div > span { - width: 30%; - line-height: 2em; -} - -div#msgbox div.set-password div.password { - width: *; -} - -div#msgbox div.set-password div > input { - width: *; -} - -div#msgbox div.set-password input { - font-size: 1em; -} - -.wrap-text { - width: *; - word-wrap: break-word; - overflow-wrap: break-word; - white-space: normal; - height: auto; - overflow: hidden; -} - -div#msgbox #error { - color: red; -} - -div.user-session .title { - font-size: 1.2em; - margin-bottom: 2em; -} - -div.user-session select { - width: 98%; - height: 2em; - border-radius: 0.5em; - border: color(border) solid 1px; - background: color(bg); - color: color(text); - padding-left: 0.5em; -} diff --git a/src/ui/common.js b/src/ui/common.js new file mode 100644 index 000000000..240fdc591 --- /dev/null +++ b/src/ui/common.js @@ -0,0 +1,379 @@ + +export const view = Window.this; +export const handler = document.$("#handler") || view; + +try { view.windowIcon = document.url(handler.xcall("get_icon")); } catch (e) { } + +export const OS = view.mediaVar("platform"); +export const is_osx = OS == "OSX"; +export const is_win = OS == "Windows"; +export const is_linux = OS == "Linux"; + +view.mediaVar("is_osx", is_osx); +view.mediaVar("not_osx", !is_osx); +handler.is_port_forward = false; +handler.is_file_transfer = false; +export var is_xfce = false; +try { is_xfce = handler.xcall("is_xfce"); } catch (e) { } + + +export function translate(name) { + try { + return handler.xcall("t", name); + } catch (_) { + return name; + } +} + +export function hashCode(str) { + let hash = 160 << 16 + 114 << 8 + 91; + for (let i = 0; i < str.length; i += 1) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return hash % 16777216; +} + +export function intToRGB(i, a = 1) { + return `rgba(${((i >> 16) & 0xFF)}, ${((i >> 8) & 0x7F)},${(i & 0xFF)},${a})`; +} + +export function string2RGB(s, a = 1) { + return intToRGB(hashCode(s), a); +} + +export function getTime() { + return new Date().valueOf(); +} + +export function platformSvg(platform, color) { + platform = (platform || "").toLowerCase(); + if (platform == "linux") { + return ( + + + + + ); + } + if (platform == "mac os") { + return ( + + ); + } + return ( + + ); +} + +export function centerize(w, h) { + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); + if (w > sw) w = sw; + if (h > sh) h = sh; + const x = (sx + sw - w) / 2; + const y = (sy + sh - h) / 2; + view.move(x, y, w, h); +} + +// TODO CSS +export function setWindowButontsAndIcon(only_min = false) { + if (only_min) { + document.$("div.window-buttons").content( +
+ +
+ ); + } else { + document.$("div.window-buttons").content(
+ + + +
); + } + document.$("div.window-icon>icon").style.setProperty( + "background-image", `url(${handler.xcall("get_icon")})`, + ); +} + +export function adjustBorder() { + if (is_osx) { + let headerStyle = document.$("header").style; + if (view.state == Window.WINDOW_FULL_SCREEN) { + headerStyle.setProperty("display", "none",); + } else { + headerStyle.setProperty("display", "block",); + headerStyle.setProperty("padding", "0",); + } + return; + } + if (view.state == Window.WINDOW_MAXIMIZED) { + document.style.setProperty("border", "window-frame-width solid transparent"); + } else if (view.state == Window.WINDOW_FULL_SCREEN) { + document.style.setProperty("border", "none"); + } else { + document.style.setProperty("border", "black solid 1px"); + } + let el = document.$("button#maximize"); + if (el) el.classList.toggle("restore", view.state == Window.WINDOW_MAXIMIZED); + el = document.$("span#fullscreen"); + if (el) el.classList.toggle("active", view.state == Window.WINDOW_FULL_SCREEN); +} + +export const svg_checkmark = (); +export const svg_edit = ( + +); +export const svg_eye = ( + + +); +export const svg_send = ( + +); +export const svg_chat = ( + +); + +export function scrollToBottom(el) { + // TEST .box() + let y = el.state.box("height", "content") - el.state.box("height", "client"); + el.scrollTo(0, y); +} + +export function getNowStr() { + let now = new Date(); + return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second); +} + +/******************** start of chatbox ****************************************/ +export class ChatBox extends Element { + msgs = []; + callback; + + this(props) { + if (props) { + this.msgs = props.msgs || []; + this.callback = props.callback; + } + } + + renderMsg(msg) { + let cls = msg.name == "me" ? "right-side msg" : "left-side msg"; + return ( +
+ {msg.name == "me" ? +
{msg.time + " "} me
: +
{msg.name} {" " + msg.time}
+ } +
{msg.text}
+
+ ); + } + + render() { + let msgs = this.msgs.map((msg) => this.renderMsg(msg)); + setTimeout(() => { + scrollToBottom(this.msgs); + }, 1); + // TODO @{this.msgs} in TIS: + return (
+ + {msgs} + +
+ + {svg_send} +
+
); + } + + send() { + let el = this.$("input"); + let value = (el.value || "").trim(); + el.value = ""; + if (!value) return; + if (this.callback) this.callback(value); + } + + ["on keydown at input"](evt) { + // TODO is shortcutKey useless? + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + this.send(); + } + } + } + + ["on click at div.send span"](evt) { + this.send(); + view.focus = this.$("input"); + } +} +/******************** end of chatbox ****************************************/ + +/******************** start of msgbox ****************************************/ +var remember_password = false; +var msgbox_params; +function getMsgboxParams() { + return msgbox_params; +} + +// tmp workaround https://sciter.com/forums/topic/menu-not-be-hidden-when-open-dialog-on-linux/ +export function msgbox(type, title, text, callback = null, height = 180, width = 500, retry = 0, contentStyle = "") { + if (is_linux) { // fix menu not hidden issue + setTimeout(() => msgbox_(type, title, text, callback, height, width, retry, contentStyle), 1); + } else { + msgbox_(type, title, text, callback, height, width, retry, contentStyle); + } +} + +function msgbox_(type, title, text, callback, height, width, retry, contentStyle) { + let has_msgbox = msgbox_params != null; + if (!has_msgbox && !type) return; + let remember = false; + try { + remember = handler.xcall("get_remember"); + } catch (e) { } + msgbox_params = { + remember: remember, type: type, text: text, title: title, + getParams: getMsgboxParams, + callback: callback, translate: translate, + retry: retry, contentStyle: contentStyle, + }; + if (has_msgbox) return; + let dialog = { + client: true, + parameters: msgbox_params, + width: width + (is_xfce ? 50 : 0), + height: height + (is_xfce ? 50 : 0), + }; + let html = handler.xcall("get_msgbox"); + if (html) dialog.html = html; + else dialog.url = document.url("msgbox.html"); + let res = view.modal(dialog); + msgbox_params = null; + console.log(`msgbox return, type: ${type}, res: ${res}`); + if (type.indexOf("custom") >= 0) { + // + } else if (!res) { + if (!handler.is_port_forward) view.close(); + } else if (res == "!alive") { + // do nothing + } else if (res.type == "input-password") { + if (!handler.is_port_forward) msgbox("connecting", "Connecting...", "Logging in..."); + handler.login(res.password, res.remember); + if (!is_port_forward) msgbox("connecting", "Connecting...", "Logging in..."); + } else if (res.reconnect) { + if (!handler.is_port_forward) connecting(); + handler.reconnect(); + } +} + +export function connecting() { + handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait."); +} + +handler.msgbox = function (type, title, text, retry = 0) { + setTimeout(() => msgbox(type, title, text, null, 180, 500, retry), 30); +} + +var reconnectTimeout = 1; +handler.msgbox_retry = function (type, title, text, hasRetry) { + handler.msgbox(type, title, text, hasRetry ? reconnectTimeout : 0); + if (hasRetry) { + reconnectTimeout *= 2; + } else { + reconnectTimeout = 1; + } +} +/******************** end of msgbox ****************************************/ +// TODO Progress +// function Progress() +// { +// var _val; +// var pos = -0.25; + +// function step() { +// if( _val !== undefined ) { this.refresh(); return false; } +// pos += 0.02; +// if( pos > 1.25) +// pos = -0.25; +// this.refresh(); +// return true; +// } + +// function paintNoValue(gfx) +// { +// var (w,h) = this.box(#dimension,#inner); +// var x = pos * w; +// w = w * 0.25; +// gfx.fillColor( this.style#color ) +// .pushLayer(#inner-box) +// .rectangle(x,0,w,h) +// .popLayer(); +// return true; +// } + +// this[#value] = property(v) { +// get return _val; +// set { +// _val = undefined; +// pos = -0.25; +// this.paintContent = paintNoValue; +// this.animate(step); +// this.refresh(); +// } +// } + +// this.value = ""; +// } + +const svg_eye_cross = ( + + +); + +export class PasswordComponent extends Element { + visible = false; + value = ''; + name = 'password'; + + constructor(props) { + if (props && props.value) { + this.value = props.value; + } + if (props && props.name) { + this.name = props.name; + } + } + + render() { + return ( +
+ + {this.visible ? svg_eye_cross : svg_eye} +
); + } + + ["on click at svg"](svg) { + let el = this.$("input"); + let value = el.value; + // TODO selectionStart/selectionEnd run ok,but always return 0 + let start = el.xcall("selectionStart") || 0; + let end = el.xcall("selectionEnd"); + this.componentUpdate({ visible: !this.visible }); + setTimeout(() => { + let el = this.$("input"); + view.focus = el; + el.value = value; + el.xcall("setSelection", start, end); + }, 30) + } +} + +export function isReasonableSize(r) { + let x = r[0]; + let y = r[1]; + return !(x < -3200 || x > 3200 || y < -3200 || y > 3200); +} + diff --git a/src/ui/common.tis b/src/ui/common.tis deleted file mode 100644 index 240799059..000000000 --- a/src/ui/common.tis +++ /dev/null @@ -1,482 +0,0 @@ -include "sciter:reactor.tis"; - -var handler = $(#handler) || view; -try { view.windowIcon = self.url(handler.get_icon()); } catch(e) {} -var OS = view.mediaVar("platform"); -var is_osx = OS == "OSX"; -var is_win = OS == "Windows"; -var is_linux = OS == "Linux"; -var is_file_transfer; -var is_xfce = false; -try { is_xfce = handler.is_xfce(); } catch(e) {} - -function isEnterKey(evt) { - return (evt.keyCode == Event.VK_ENTER || - (is_osx && evt.keyCode == 0x4C) || - (is_linux && evt.keyCode == 65421)); -} - -function getScaleFactor() { - if (!is_win) return 1; - var s = self.toPixels(10000dip) / 10000.; - return s < 0.000001 ? 1 : s; -} -var scaleFactor = getScaleFactor(); -view << event resolutionchange { - scaleFactor = getScaleFactor(); -} -function scaleIt(x) { - return (x * scaleFactor).toInteger(); -} -stdout.println("scaleFactor", scaleFactor); - -function translate(name) { - try { - return handler.t(name); - } catch(_) { - return name; - } -} - -function hashCode(str) { - var hash = 160 << 16 + 114 << 8 + 91; - for (var i = 0; i < str.length; i += 1) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - return hash % 16777216; -} - -function intToRGB(i, a = 1) { - return 'rgba(' + ((i >> 16) & 0xFF) + ', ' + ((i >> 8) & 0x7F) - + ',' + (i & 0xFF) + ',' + a + ')'; -} - -function string2RGB(s, a = 1) { - return intToRGB(hashCode(s), a); -} - -function getTime() { - var now = new Date(); - return now.valueOf(); -} - -function platformSvg(platform, color) { - platform = (platform || "").toLowerCase(); - if (platform == "linux") { - return - - - - - ; - } - if (platform == "mac os") { - return - - ; - } - if (platform == "android") { - return ; - } - return - - ; -} - -function centerize(w, h) { - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - if (w > sw) w = sw; - if (h > sh) h = sh; - var x = (sx + sw - w) / 2; - var y = (sy + sh - h) / 2; - view.move(x, y, w, h); -} - -function setWindowButontsAndIcon(only_min=false) { - if (only_min) { - $(div.window-buttons).content(
-
-
); - } else { - $(div.window-buttons).content(
-
-
-
-
); - } - $(div.window-icon>icon).style.set { - "background-image": "url('" + handler.get_icon() + "')", - }; -} - -function adjustBorder() { - if (is_osx) { - if (view.windowState == View.WINDOW_FULL_SCREEN) { - $(header).style.set { - display: "none", - }; - } else { - $(header).style.set { - display: "block", - padding: "0", - }; - } - return; - } - if (view.windowState == view.WINDOW_MAXIMIZED) { - self.style.set { - border: "window-frame-width solid transparent", - }; - } else if (view.windowState == view.WINDOW_FULL_SCREEN) { - self.style.set { - border: "none", - }; - } else { - self.style.set { - border: "black solid 1px", - }; - } - var el = $(button#maximize); - if (el) el.attributes.toggleClass("restore", view.windowState == View.WINDOW_MAXIMIZED); - el = $(span#fullscreen); - if (el) el.attributes.toggleClass("active", view.windowState == View.WINDOW_FULL_SCREEN); -} - -var svg_checkmark = ; -var svg_edit = - -; -var svg_eye = - - -; -var svg_send = - -; -var svg_chat = - -; -var svg_keyboard = ; - -function scrollToBottom(el) { - var y = el.box(#height, #content) - el.box(#height, #client); - el.scrollTo(0, y); -} - -function getNowStr() { - var now = new Date(); - return String.printf("%02d:%02d:%02d", now.hour, now.minute, now.second); -} - -/******************** start of chatbox ****************************************/ -class ChatBox: Reactor.Component { - this var msgs = []; - this var callback; - - function this(params) { - if (params) { - this.msgs = params.msgs || []; - this.callback = params.callback; - } - } - - function renderMsg(msg) { - var cls = msg.name == "me" ? "right-side msg" : "left-side msg"; - return
- {msg.name == "me" ? -
{msg.time + " "} me
: -
{msg.name} {" " + msg.time}
- } -
{msg.text}
-
; - } - - function render() { - var me = this; - var msgs = this.msgs.map(function(msg) { return me.renderMsg(msg); }); - self.timer(1ms, function() { - scrollToBottom(me.msgs); - }); - return
- - {msgs} - -
- - {svg_send} -
-
; - } - - function send() { - var el = this.$(input); - var value = (el.value || "").trim(); - el.value = ""; - if (!value) return; - if (this.callback) this.callback(value); - } - - event keydown $(input) (evt) { - if (!evt.shortcutKey) { - if (isEnterKey(evt)) { - this.send(); - } - } - } - - event click $(div.send span) { - this.send(); - view.focus = $(input); - } -} -/******************** end of chatbox ****************************************/ - -/******************** start of msgbox ****************************************/ -var remember_password = false; -var last_msgbox_tag = ""; -function msgbox(type, title, content, link="", callback=null, height=180, width=500, hasRetry=false, contentStyle="") { - $(body).scrollTo(0, 0); - if (!type) { - closeMsgbox(); - return; - } - var remember = false; - try { remember = handler.get_remember(); } catch(e) {} - var autoLogin = false; - try { autoLogin = handler.get_option("auto-login") != ''; } catch(e) {} - width += is_xfce ? 50 : 0; - height += is_xfce ? 50 : 0; - - if (type.indexOf("input-password") >= 0) { - callback = function (res) { - if (!res) { - view.close(); - return; - } - handler.login("", "", res.password, res.remember); - if (!is_port_forward) { - // Specially handling file transfer for no permission hanging issue (including 60ms - // timer in setPermission. - // For wrong password input hanging issue, we can not use handler.msgbox. - // But how about wrong password for file transfer? - if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in..."); - else msgbox("connecting", "Connecting...", "Logging in..."); - } - }; - } else if (type.indexOf("input-2fa") >= 0) { - callback = function (res) { - if (!res) { - view.close(); - return; - } - handler.send2fa(res.code, res.trust_this_device || false); - msgbox("connecting", "Connecting...", "Logging in..."); - }; - } else if (type == "session-login" || type == "session-re-login") { - callback = function (res) { - if (!res) { - view.close(); - return; - } - handler.login(res.osusername, res.ospassword, "", false); - if (!is_port_forward) { - if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in..."); - else msgbox("connecting", "Connecting...", "Logging in..."); - } - }; - } else if (type.indexOf("session-login") >= 0) { - callback = function (res) { - if (!res) { - view.close(); - return; - } - handler.login(res.osusername, res.ospassword, res.password, res.remember); - if (!is_port_forward) { - if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in..."); - else msgbox("connecting", "Connecting...", "Logging in..."); - } - }; - } else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) { - callback = function() { view.close(); } - } else if (type == 'wait-remote-accept-nook') { - callback = function (res) { - if (!res) { - view.close(); - return; - } - }; - } - last_msgbox_tag = type + "-" + title + "-" + content + "-" + link; - $(#msgbox).content(); -} - -function connecting() { - handler.msgbox("connecting", "Connecting...", "Connection in progress. Please wait."); -} - -handler.msgbox = function(type, title, text, link = "", hasRetry=false) { - // crash somehow (when input wrong password), even with small time, for example, 1ms - self.timer(60ms, function() { msgbox(type, title, text, link, null, 180, 500, hasRetry); }); -} - -handler.cancel_msgbox = function(tag) { - if (last_msgbox_tag == tag) { - closeMsgbox(); - } -} - -var reconnectTimeout = 1000; -handler.msgbox_retry = function(type, title, text, link, hasRetry) { - handler.msgbox(type, title, text, link, hasRetry); - if (hasRetry) { - self.timer(0, retryConnect); - self.timer(reconnectTimeout, retryConnect); - reconnectTimeout *= 2; - } else { - reconnectTimeout = 1000; - } -} - -function retryConnect(cancelTimer=false) { - if (cancelTimer) self.timer(0, retryConnect); - if (!is_port_forward) connecting(); - handler.reconnect(false); -} -/******************** end of msgbox ****************************************/ - -function Progress() -{ - var _val; - var pos = -0.25; - - function step() { - if( _val !== undefined ) { this.refresh(); return false; } - pos += 0.02; - if( pos > 1.25) - pos = -0.25; - this.refresh(); - return true; - } - - function paintNoValue(gfx) - { - var (w,h) = this.box(#dimension,#inner); - var x = pos * w; - w = w * 0.25; - gfx.fillColor( this.style#color ) - .pushLayer(#inner-box) - .rectangle(x,0,w,h) - .popLayer(); - return true; - } - - this[#value] = property(v) { - get return _val; - set { - _val = undefined; - pos = -0.25; - this.paintContent = paintNoValue; - this.animate(step); - this.refresh(); - } - } - - this.value = ""; -} - -var svg_eye_cross = - - -; - -class PasswordComponent: Reactor.Component { - this var visible = false; - this var value = ''; - this var name = 'password'; - - function this(params) { - if (params && params.value) { - this.value = params.value; - } - if (params && params.name) { - this.name = params.name; - } - } - - function render() { - return
- - {this.visible ? svg_eye_cross : svg_eye} -
; - } - - event click $(svg) { - var el = this.$(input); - var value = el.value; - var start = el.xcall(#selectionStart) || 0; - var end = el.xcall(#selectionEnd); - this.update({ visible: !this.visible }); - var me = this; - self.timer(30ms, function() { - var el = me.$(input); - view.focus = el; - el.value = value; - el.xcall(#setSelection, start, end); - }); - } -} - -// type: #post, #get, #delete, #put -function httpRequest(url, type, params, _onSuccess, _onError, headers="") { - if (type != #post) { - stderr.println("only post ok"); - } - handler.post_request(url, JSON.stringify(params), headers); - function check_status() { - var status = handler.get_async_job_status(); - if (status == " ") self.timer(0.1s, check_status); - else { - try { - var data = JSON.parse(status || "{}"); - _onSuccess(data); - } catch (e) { - _onError(status, 0); - } - } - } - check_status(); -} - -function isReasonableSize(r) { - var x = r[0]; - var y = r[1]; - var n = scaleIt(3200); - return !(x < -n || x > n || y < -n || y > n); -} - -function awake() { - view.windowState = View.WINDOW_SHOWN; - view.focus = self; -} - -class MultipleSessionComponent extends Reactor.Component { - this var sessions = []; - this var messageText = translate("Please select the session you want to connect to"); - - function this(params) { - if (params && params.sessions) { - this.sessions = params.sessions; - } - } - - function render() { - return
-
{this.messageText}
- -
; - } -} \ No newline at end of file diff --git a/src/ui/file_transfer.css b/src/ui/file_transfer.css index 7fd4ac7a8..d1e1a4072 100644 --- a/src/ui/file_transfer.css +++ b/src/ui/file_transfer.css @@ -12,22 +12,22 @@ div#file-transfer { } table -{ +{ font: system; border: 1px solid color(border); flow: table-fixed; prototype: Grid; size: *; padding:0; - border-spacing: 0; + border-spacing: 0; overflow-x: auto; overflow-y: hidden; } - -table > thead { + +table > thead { behavior: column-resizer; border-bottom: color(border) solid 1px; -} +} table > tbody { behavior: select-multiple; @@ -41,20 +41,20 @@ table th { } table th -{ +{ padding: 4px; foreground-repeat: no-repeat; foreground-position: 50% 3px auto auto; border-left: color(border) solid 1px; -} +} -table th.sortable[sort=asc] -{ +table th.sortable[sort=asc] +{ foreground-image: url(stock:arrow-down); -} +} table th.sortable[sort=desc] -{ +{ foreground-image: url(stock:arrow-up); } @@ -81,10 +81,10 @@ table.has_current thead th:current { table tr:nth-child(odd) { background-color: white; } /* each odd row */ table tr:nth-child(even) { background-color: #F4F5F6; } /* each even row */ -table.has_current tr:current /* current row */ -{ - background-color: color(accent); -} +table.has_current tr:current /* current row */ +{ + background-color: color(accent); +} table.has_current tbody tr:checked { @@ -95,9 +95,9 @@ table.has_current tbody tr:checked td { color: highlighttext; } -table td -{ - padding: 4px; +table td +{ + padding: 4px; text-align: left; font-size: 1em; height: 1.4em; @@ -124,11 +124,11 @@ table td:nth-child(4) { section { size: *; margin: 1em; - border-spacing: 0.5em; + border-spacing: 0.5em; } table td:nth-child(1) { - foreground-repeat: no-repeat; + foreground-repeat: no-repeat; foreground-position: 50% 50% } @@ -160,11 +160,11 @@ div.toolbar > div.button:hover { div.toolbar > div.send { flow: horizontal; - border-spacing: 0.5em; + border-spacing: 0.5em; } div.remote > div.send svg { - transform: scale(-1, 1); + transform: scale(-1, 1); } div.navbar { @@ -207,7 +207,7 @@ table.job-table tr td { padding: 0.5em 1em; border-bottom: color(border) 1px solid; flow: horizontal; - border-spacing: 1em; + border-spacing: 1em; height: 3em; overflow-x: hidden; } @@ -217,11 +217,7 @@ table.job-table tr svg { } table.job-table tr.is_remote svg { - transform: scale(-1, 1); -} - -table.job-table tr.is_remote div.svg_continue svg { - transform: scale(1, 1); + transform: scale(-1, 1); } table.job-table tr td div.text { @@ -246,7 +242,7 @@ table#port-forward thead tr th { table#port-forward tr td { height: 3em; - text-align: left; + text-align: left; } table#port-forward input[type=text], table#port-forward input[type=number] { diff --git a/src/ui/file_transfer.js b/src/ui/file_transfer.js new file mode 100644 index 000000000..70d312239 --- /dev/null +++ b/src/ui/file_transfer.js @@ -0,0 +1,663 @@ +import { handler,svg_send,translate,msgbox } from "./common.js"; +import {$} from "@sciter"; + +var remote_home_dir; + +const svg_add_folder = ( + + +); +const svg_trash = ( + + + +); +export const svg_arrow = ( + +); +const svg_home = ( + +); +const svg_refresh = ( + +); +export const svg_cancel = (); +const svg_computer = ( + + + +); + +// TODO +function getSize(type, size) { + if (!size) { + if (type <= 3) return ""; + return "0B"; + } + size = size.toFloat(); + var toFixed = function(size) { + size = (size * 100).toInteger(); + var a = (size / 100).toInteger(); + if (size % 100 == 0) return a; + if (size % 10 == 0) return a + '.' + (size % 10); + var b = size % 100; + if (b < 10) b = '0' + b; + return a + '.' + b; + } + if (size < 1024) return size.toInteger() + "B"; + if (size < 1024 * 1024) return toFixed(size / 1024) + "K"; + if (size < 1024 * 1024 * 1024) return toFixed(size / (1024 * 1024)) + "M"; + return toFixed(size / (1024 * 1024 * 1024)) + "G"; +} + +function getParentPath(is_remote, path) { + let sep = handler.xcall("get_path_sep",is_remote); + let res = path.lastIndexOf(sep); + if (res <= 0) return "/"; + return path.substr(0, res); +} + +function getFileName(is_remote, path) { + let sep = handler.xcall("get_path_sep",is_remote); + let res = path.lastIndexOf(sep); + return path.substr(res + 1); +} + +function getExt(name) { + if (name.indexOf(".") == 0) { + return ""; + } + let i = name.lastIndexOf("."); + if (i > 0) return name.substr(i + 1); + return ""; +} + +var jobIdCounter = 1; + +class JobTable extends Element { + jobs = []; + job_map = {}; + + render() { + let rows = this.jobs.map((job, i)=>this.renderRow(job, i)); + return (
+ + {rows} + +
); + } + + ["on click at svg.cancel"](_, me) { + let job = this.jobs[me.parentElement.parentElement.index]; + let id = job.id; + handler.xcall("cancel_job",id); + delete this.job_map[id]; + let i = -1; + this.jobs.map(function(job, idx) { + if (job.id == id) i = idx; + }); + this.jobs.splice(i, 1); + this.componentUpdate(); + let is_remote = job.is_remote; + if (job.type != "del-dir") is_remote = !is_remote; + refreshDir(is_remote); + } + + send(path, is_remote) { + let to; + let show_hidden; + if (is_remote) { + to = file_transfer.local_folder_view.fd.path; // NULL + show_hidden = file_transfer.remote_folder_view.show_hidden; + } else { + to = file_transfer.remote_folder_view.fd.path; + show_hidden = file_transfer.local_folder_view.show_hidden; + } + if (!to) return; + to += handler.xcall("get_path_sep",!is_remote) + getFileName(is_remote, path); + let id = jobIdCounter; + jobIdCounter += 1; + this.jobs.push({ type: "transfer", + id: id, path: path, to: to, + include_hidden: show_hidden, + is_remote: is_remote }); + this.job_map[id] = this.jobs[this.jobs.length - 1]; + handler.xcall("send_files",id, path, to, show_hidden, is_remote); + this.componentUpdate(); + } + + addDelDir(path, is_remote) { + let id = jobIdCounter; + jobIdCounter += 1; + this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote }); + this.job_map[id] = this.jobs[this.jobs.length - 1]; + handler.xcall("remove_dir_all",id, path, is_remote); + this.componentUpdate(); + } + + getSvg(job) { + if (job.type == "transfer") { + return svg_send; + } else if (job.type == "del-dir") { + return svg_trash; + } + } + + getStatus(job) { + if (!job.entries) return translate("Waiting"); + let i = job.file_num + 1; + let n = job.num_entries || job.entries.length; + if (i > n) i = n; + let res = i + ' / ' + n + " " + translate("files"); + if (job.total_size > 0) { + let s = getSize(0, job.finished_size); + if (s) s += " / "; + res += ", " + s + getSize(0, job.total_size); + } + // below has problem if some file skipped + let percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger(); + if (job.finished) percent = '100'; + if (percent) res += ", " + percent + "%"; + if (job.finished) res = translate("Finished") + " " + res; + if (job.speed) res += ", " + getSize(0, job.speed) + "/s"; + return res; + } + + updateJob(job) { + let el = this.$("div#s" + job.id); // TODO TEST + console.log("updateJob el",el); + if (el) el.text = this.getStatus(job); + } + + updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) { + let job = this.job_map[id]; + if (!job) return; + if (file_num < job.file_num) return; + job.file_num = file_num; + let n = job.num_entries || job.entries.length; + job.finished = job.file_num >= n - 1 || err == "cancel"; + job.finished_size = finished_size; + job.speed = speed || 0; + this.updateJob(job); + if (job.type == "del-dir") { + if (job.finished) { + if (!err) { + handler.xcall("remove_dir",job.id, job.path, job.is_remote); + refreshDir(job.is_remote); + } + } else if (!job.no_confirm) { + handler.xcall("confirm_delete_files",id, job.file_num + 1); + } + } else if (job.finished || file_num == -1) { + refreshDir(!job.is_remote); + } + } + + renderRow(job, i) { + svg = this.getSvg(job); + return ( + {svg} +
+
{job.path}
+
{this.getStatus(job)}
+
+ {svg_cancel} + ); + } +} + +class FolderView extends Element { + fd = {}; + history = []; + show_hidden = false; + select_dir; + + sep() { + return handler.xcall("get_path_sep",this.is_remote); + } + + this(params) { + this.is_remote = params.is_remote; + if (this.is_remote) { + this.show_hidden = !!handler.xcall("get_option","remote_show_hidden"); + } else { + this.show_hidden = !!handler.xcall("get_option","local_show_hidden"); + } + if (!this.is_remote) { + let dir = handler.xcall("get_option","local_dir"); + if (dir) { + this.fd = handler.xcall("read_dir",dir, this.show_hidden); + if (this.fd) return; + } + this.fd = handler.xcall("read_dir",handler.xcall("get_home_dir"), this.show_hidden); + } + } + + // sort predicate + foldersFirst(a, b) { + if (a.type <= 3 && b.type > 3) return -1; + if (a.type > 3 && b.type <= 3) return +1; + if (a.name == b.name) return 0; + return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase()); // TODO lexicalCompare + } + + render() + { + return (
+ {this.renderTitle()} + {this.renderNavBar()} + {this.renderOpBar()} + {this.renderTable()} +
); + } + + renderTitle() { + return (
+ {svg_computer} +
{platformSvg(handler.xcall("get_platform",this.is_remote), "white")}
+
{translate(this.is_remote ? "Remote Computer" : "Local Computer")}
+
) + } + + renderNavBar() { + return ; + } + + // TODO + componentDidMount(){ + this.select_dir = this.$("select.select-dir") + } + + renderSelect() { + return (); + } + + renderOpBar() { + if (this.is_remote) { + return (
+
{svg_send}{translate('Receive')}
+
+
{svg_add_folder}
+
{svg_trash}
+
); + } + return (
+
{svg_add_folder}
+
{svg_trash}
+
+
{translate('Send')}{svg_send}
+
); + } + + get_updated() { + this.table.sortRows(false); // TODO sortRows + if (this.fd && this.fd.path) this.select_dir.value = this.fd.path; + } + + renderTable() { + let fd = this.fd; + let entries = fd.entries || []; + let table = this.table; + if (!table || !table.sortBy) { + entries.sort(this.foldersFirst); // TODO sort function + } + let path = fd.path; + if (path != "/" && path) { + entries = [{ name: "..", type: 1 }].concat(entries); + } + let rows = entries.map(e=>this.renderRow(e)); + let id = (this.is_remote ? "remote" : "local") + "-folder-view"; + //@{} return ( + + return (
+ + + + + {rows} + + + +
  • {svg_checkmark}{translate('Show Hidden Files')}
  • +
    +
    +
    {translate('Name')}{translate('Modified')}{translate('Size')}
    ); + } + + joinPath(name) { + let path = this.fd.path; + if (path == "/") { + if (this.sep() == "/") return this.sep() + name; + else return name; + } + return path + (path[path.length - 1] == this.sep() ? "" : this.sep()) + name; + } + + attached() { + this.table.onRowDoubleClick = (row)=>{ + let type = row[0].attributes["type"]; + if (type > 3) return; + let name = row[1].text; + let path = name == ".." ? getParentPath(this.is_remote, this.fd.path) : this.joinPath(name); + this.goto(path, true); + } + this.get_updated(); + } + + goto(path, push) { + if (!path) return; + if (this.sep() == "\\" && path.length == 2) { // windows drive + path += "\\"; + } + if (push) this.pushHistory(); + if (this.is_remote) { + handler.xcall("read_remote_dir",path, this.show_hidden); + } else { + var fd = handler.xcall("read_dir",path, this.show_hidden); + this.refresh({ fd: fd }); + } + } + + refresh(data) { + if (!data.fd || !data.fd.path) return; + if (this.is_remote && !remote_home_dir) { + remote_home_dir = data.fd.path; + } + this.componentUpdate(data); + setTimeout(()=>this.get_updated(),1); + } + + renderRow(entry) { + let path; + if (this.is_remote) { + path = handler.xcall("get_icon_path",entry.type, getExt(entry.name)); + } else { + path = this.joinPath(entry.name); + } + let tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0; // TODO toFloat() + return ( + + {entry.name} + {tm || ""} + {getSize(entry.type, entry.size)} + ); + } + + ["on click at #switch-hidden"]() { + this.show_hidden = !this.show_hidden; + this.refreshDir(); + } + + ["on click at .goup"]() { + let path = this.fd.path; + if (!path || path == "/") return; + path = getParentPath(this.is_remote, path); + this.goto(path, true); + } + + ["on click at .goback"] () { + let path = this.history.pop(); + if (!path) return; + this.goto(path, false); + } + + ["on click at .trash"]() { + let rows = this.getCurrentRows(); + if (!rows || rows.length == 0) return; + + let delete_dirs = new Array(); + + for (let i = 0; i < rows.length; ++i) { + let row = rows[i]; + + let path = row[0]; + let type = row[1]; + + let new_history = []; + for (let j = 0; j < this.history.length; ++j) { + let h = this.history[j]; + if ((h + this.sep()).indexOf(path + this.sep()) == -1) new_history.push(h); + } + this.history = new_history; + if (type == 1) { + delete_dirs.push(path); + } else { + confirmDelete(path, this.is_remote); + } + } + for (let i = 0; i < delete_dirs.length; ++i) { + file_transfer.job_table.addDelDir(delete_dirs[i], this.is_remote); + } + } + + ["on click at .add-folder"]() { + let me = this; + msgbox("custom", translate("Create Folder"), "
    \ +
    " + translate("Please enter the folder name") + ":
    \ +
    \ +
    ", function(res=null) { + if (!res) return; + if (!res.name) return; + let name = res.name.trim(); + if (!name) return; + if (name.indexOf(me.sep()) >= 0) { + handler.msgbox("custom-error", "Create Folder", "Invalid folder name"); + return; + } + let path = me.joinPath(name); + handler.xcall("create_dir",jobIdCounter, path, me.is_remote); + create_dir_jobs[jobIdCounter] = { is_remote: me.is_remote, path: path }; + jobIdCounter += 1; + }); + } + + refreshDir() { + this.goto(this.fd.path, false); + } + + ["on click at .refresh"]() { + this.refreshDir(); + } + + ["on click at .home"]() { + let path = this.is_remote ? remote_home_dir : handler.xcall("get_home_dir"); + if (!path) return; + if (path == this.fd.path) return; + this.goto(path, true); + } + + getCurrentRow() { + let row = this.table.getCurrentRow(); // TEST getCurrentRow + if (!row) return; + let name = row[1].text; + if (!name || name == "..") return; + let type = row[0].attributes["type"]; + return [this.joinPath(name), type]; + } + + getCurrentRows() { + let rows = this.table.getCurrentRows(); + if (!rows || rows.length== 0) return; + + let records = new Array(); + + for (let i = 0; i < rows.length; ++i) { + let name = rows[i][1].text; + if (!name || name == "..") continue; + + let type = rows[i][0].attributes["type"]; + records.push([this.joinPath(name), type]); + } + return records; + } + + ["on click at .send"]() { + let rows = this.getCurrentRows(); + if (!rows || rows.length == 0) return; + for (let i = 0; i < rows.length; ++i) { + file_transfer.job_table.send(rows[i][0], this.is_remote); + } + } + + ["on change at .select-dir"](_, el) { + var x = getTime() - last_key_time; // TODO getTime + if (x < 1000) return; + if (this.fd.path != el.value) { + this.goto(el.value, true); + } + } + + ["on keydown at .select-dir"](evt, me) { + if (evt.code == "KeyRETURN") { // TODO TEST mac + this.goto(me.value, true); + } + } + + pushHistory() { + let path = this.fd.path; + if (!path) return; + if (path != this.history[this.history.length - 1]) this.history.push(path); + } +} + +var file_transfer; + +class FileTransfer extends Element { + this() { + file_transfer = this; + } + // TODO @{} + // + // + // + + render() { + return (
    + + + +
    ); + } +} + +export function initializeFileTransfer() +{ + $("#file-transfer-wrapper").content(); + $("#video-wrapper").style.setProperty("visibility","hidden"); + $("#video-wrapper").style.setProperty("position","absolute"); + $("#file-transfer-wrapper").style.setProperty("display","block"); +} + +handler.updateFolderFiles = function(fd) { + fd.entries = fd.entries || []; + if (fd.id > 0) { + let jt = file_transfer.job_table; + let job = jt.job_map[fd.id]; + if (job) { + job.file_num = -1; + job.total_size = fd.total_size; + job.entries = fd.entries; + job.num_entries = fd.num_entries; + file_transfer.job_table.updateJobStatus(job.id); + } + } else { + file_transfer.remote_folder_view.refresh({ fd: fd }); + } +} + +handler.jobProgress = function(id, file_num, speed, finished_size) { + file_transfer.job_table.updateJobStatus(id, file_num, null, speed, finished_size); +} + +handler.jobDone = function(id, file_num = -1) { + let job = deleting_single_file_jobs[id] || create_dir_jobs[id]; + if (job) { + refreshDir(job.is_remote); + return; + } + file_transfer.job_table.updateJobStatus(id, file_num); +} + +handler.jobError = function(id, err, file_num = -1) { + var job = deleting_single_file_jobs[id]; + if (job) { + msgbox("custom-error", "Delete File", err); + return; + } + job = create_dir_jobs[id]; + if (job) { + msgbox("custom-error", "Create Folder", err); + return; + } + if (file_num < 0) { + handler.msgbox("custom-error", "Failed", err); + } + file_transfer.job_table.updateJobStatus(id, file_num, err); +} + +function refreshDir(is_remote) { + if (is_remote) file_transfer.remote_folder_view.refreshDir(); + else file_transfer.local_folder_view.refreshDir(); +} + +var deleting_single_file_jobs = {}; +var create_dir_jobs = {} + +function confirmDelete(path, is_remote) { + msgbox("custom-skip", "Confirm Delete", "
    \ +
    " + translate('Are you sure you want to delete this file?') + "
    \ + " + path + "
    \ +
    ", function(res=null) { + if (res) { + handler.xcall("remove_file",jobIdCounter, path, 0, is_remote); + deleting_single_file_jobs[jobIdCounter] = { is_remote: is_remote, path: path }; + jobIdCounter += 1; + } + }); +} + +handler.confirmDeleteFiles = function(id, i, name) { + var jt = file_transfer.job_table; + var job = jt.job_map[id]; + if (!job) return; + var n = job.num_entries; + if (i >= n) return; + var file_path = job.path; + if (name) file_path += handler.xcall("get_path_sep",job.is_remote) + name; + msgbox("custom-skip", "Confirm Delete", "
    \ +
    " + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".
    \ +
    " + translate('Are you sure you want to delete this file?') + "
    \ + " + name + "
    \ +
    " + translate('Do this for all conflicts') + "
    \ +
    ", function(res=null) { + if (!res) { + jt.updateJobStatus(id, i - 1, "cancel"); + } else if (res.skip) { + if (res.remember) jt.updateJobStatus(id, i, "cancel"); + else handler.jobDone(id, i); + } else { + job.no_confirm = res.remember; + if (job.no_confirm) handler.set_no_confirm(id); + handler.xcall("remove_file",id, file_path, i, job.is_remote); + } + }); +} + +export function save_file_transfer_close_state() { + var local_dir = file_transfer.local_folder_view.fd.path || ""; + var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : ""; + var remote_dir = file_transfer.remote_folder_view.fd.path || ""; + var remote_show_hidden = file_transfer.remote_folder_view.show_hidden ? "Y" : ""; + handler.xcall("save_close_state","local_dir", local_dir); + handler.xcall("save_close_state","local_show_hidden", local_show_hidden); + handler.xcall("save_close_state","remote_dir", remote_dir); + handler.xcall("save_close_state","remote_show_hidden", remote_show_hidden); +} diff --git a/src/ui/file_transfer.tis b/src/ui/file_transfer.tis deleted file mode 100644 index 1090c018d..000000000 --- a/src/ui/file_transfer.tis +++ /dev/null @@ -1,819 +0,0 @@ -var remote_home_dir; - -var svg_add_folder = - - -; -var svg_trash = - - - -; -var svg_arrow = - -; -var svg_home = - -; -var svg_refresh = - -; -var svg_cancel = ; -var svg_continue = ; -var svg_computer = - - - -; - -function getSize(type, size) { - if (!size) { - if (type <= 3) return ""; - return "0B"; - } - size = size.toFloat(); - var toFixed = function(size) { - size = (size * 100).toInteger(); - var a = (size / 100).toInteger(); - if (size % 100 == 0) return a; - if (size % 10 == 0) return a + '.' + (size % 10); - var b = size % 100; - if (b < 10) b = '0' + b; - return a + '.' + b; - } - if (size < 1024) return size.toInteger() + "B"; - if (size < 1024 * 1024) return toFixed(size / 1024) + "K"; - if (size < 1024 * 1024 * 1024) return toFixed(size / (1024 * 1024)) + "M"; - return toFixed(size / (1024 * 1024 * 1024)) + "G"; -} - -function getParentPath(is_remote, path) { - var sep = handler.get_path_sep(is_remote); - var res = path.lastIndexOf(sep); - if (res <= 0) return "/"; - return path.substr(0, res); -} - -function getFileName(is_remote, path) { - var sep = handler.get_path_sep(is_remote); - var res = path.lastIndexOf(sep); - return path.substr(res + 1); -} - -function getExt(name) { - if (name.indexOf(".") == 0) { - return ""; - } - var i = name.lastIndexOf("."); - if (i > 0) return name.substr(i + 1); - return ""; -} - -class JobTable: Reactor.Component { - this var jobs = []; - this var job_map = {}; - - function render() { - var me = this; - var rows = this.jobs.map(function(job, i) { return me.renderRow(job, i); }); - return
    - - {rows} - -
    ; - } - - event click $(svg.cancel) (_, me) { - var job = this.jobs[me.parent.parent.index]; - var id = job.id; - handler.cancel_job(id); - delete this.job_map[id]; - var i = -1; - this.jobs.map(function(job, idx) { - if (job.id == id) i = idx; - }); - this.jobs.splice(i, 1); - this.update(); - var is_remote = job.is_remote; - if (job.type != "del-dir") is_remote = !is_remote; - refreshDir(is_remote); - } - - event click $(svg.continue) (_, me) { - var job = this.jobs[me.parent.parent.parent.index]; - var id = job.id; - this.continueJob(id); - this.update(); - } - - function clearAllJobs() { - this.jobs = []; - this.job_map = {}; - this.update(); - } - - function send(path, is_remote) { - var to; - var show_hidden; - if (is_remote) { - to = file_transfer.local_folder_view.fd.path; - show_hidden = file_transfer.remote_folder_view.show_hidden; - } else { - to = file_transfer.remote_folder_view.fd.path; - show_hidden = file_transfer.local_folder_view.show_hidden; - } - if (!to) return; - to += handler.get_path_sep(!is_remote) + getFileName(is_remote, path); - var id = handler.get_next_job_id(); - this.jobs.push({ type: "transfer", - id: id, path: path, to: to, - include_hidden: show_hidden, - is_remote: is_remote, - is_last: false - }); - this.job_map[id] = this.jobs[this.jobs.length - 1]; - handler.send_files(id, 0, path, to, 0, show_hidden, is_remote); - var self = this; - self.timer(30ms, function() { self.update(); }); - } - - function addJob(id, path, to, file_num, show_hidden, is_remote, auto_start) { - var job = { type: "transfer", - id: id, path: path, to: to, - include_hidden: show_hidden, - is_remote: is_remote, is_last: true, file_num: file_num }; - this.jobs.push(job); - this.job_map[id] = this.jobs[this.jobs.length - 1]; - handler.update_next_job_id(id + 1); - handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote); - if (auto_start) { - this.continueJob(id); - this.update(); - } - stdout.println(JSON.stringify(job)); - } - - function continueJob(id) { - var job = this.job_map[id]; - if (job == null || !job.is_last){ - return; - } - job.is_last = false; - handler.resume_job(job.id, job.is_remote); - } - - function addDelDir(path, is_remote) { - var id = handler.get_next_job_id(); - this.jobs.push({ type: "del-dir", id: id, path: path, is_remote: is_remote }); - this.job_map[id] = this.jobs[this.jobs.length - 1]; - this.update(); - } - - function addDelFile(path, is_remote) { - var id = handler.get_next_job_id(); - this.jobs.push({ type: "del-file", id: id, path: path, is_remote: is_remote }); - this.job_map[id] = this.jobs[this.jobs.length - 1]; - this.update(); - } - - function confirmDeletePolling(is_remote) { - for(var i=0;i n) i = n; - var res = i + ' / ' + n + " " + translate("files"); - if (job.total_size > 0) { - var s = getSize(0, job.finished_size); - if (s) s += " / "; - res += ", " + s + getSize(0, job.total_size); - } - // below has problem if some file skipped - var percent = job.total_size == 0 ? 100 : (100. * job.finished_size / job.total_size).toInteger(); // (100. * i / (n || 1)).toInteger(); - if (job.finished) percent = '100'; - if (percent) res += ", " + percent + "%"; - if (job.finished) { - if (job.err == "skipped") { - res = translate("Skipped") + " " + res; - } else { - res = translate("Finished") + " " + res; - } - } - if (job.speed) res += ", " + getSize(0, job.speed) + "/s"; - return res; - } - - function updateJob(job) { - var el = this.select("div[id=s" + job.id + "]"); - if (el) el.text = this.getStatus(job); - } - - function updateJobStatus(id, file_num = -1, err = null, speed = null, finished_size = 0) { - var job = this.job_map[id]; - if (job.type == "del-file"){ - job.finished = true; - job.err = err; - refreshDir(job.is_remote); - this.updateJob(job); - return; - } - if (!job) return; - if (file_num < job.file_num) return; - job.file_num = file_num; - var n = job.num_entries || job.entries.length; - job.finished = job.file_num >= n - 1 || err == "cancel" || err == "skipped"; - job.finished_size = finished_size; - job.speed = speed || 0; - job.err = err; - this.updateJob(job); - if (job.type == "del-dir") { - if (job.finished) { - if (!err) { - handler.remove_dir(job.id, job.path, job.is_remote); - refreshDir(job.is_remote); - // Use the job's is_remote; local variable `is_remote` is undefined in this scope. - if (job.is_remote) file_transfer.remote_folder_view.table.resetCurrent(); - else file_transfer.local_folder_view.table.resetCurrent(); - } - } else if (!job.no_confirm) { - handler.confirm_delete_files(id, job.file_num + 1); - } - } else if (job.finished || file_num == -1) { - refreshDir(!job.is_remote); - } - } - - function renderRow(job, i) { - var svg = this.getSvg(job); - return - {svg} -
    -
    {job.path}
    -
    {this.getStatus(job)}
    -
    -
    - {svg_continue} -
    - {svg_cancel} - ; - } -} - -class FolderView : Reactor.Component { - this var fd = {}; - this var history = []; - this var show_hidden = false; - - function sep() { - return handler.get_path_sep(this.is_remote); - } - - function this(params) { - this.is_remote = params.is_remote; - if (this.is_remote) { - this.show_hidden = !!handler.get_option("remote_show_hidden"); - } else { - this.show_hidden = !!handler.get_option("local_show_hidden"); - } - if (!this.is_remote) { - var dir = handler.get_option("local_dir"); - if (dir) { - this.fd = handler.read_dir(dir, this.show_hidden); - if (this.fd) return; - } - this.fd = handler.read_dir(handler.get_home_dir(), this.show_hidden); - } - } - - // sort predicate - function foldersFirst(a, b) { - if (a.type <= 3 && b.type > 3) return -1; - if (a.type > 3 && b.type <= 3) return +1; - if (a.name == b.name) return 0; - return a.name.toLowerCase().lexicalCompare(b.name.toLowerCase()); - } - - function render() - { - return
    - {this.renderTitle()} - {this.renderNavBar()} - {this.renderOpBar()} - {this.renderTable()} -
    ; - } - - function renderTitle() { - return
    - {svg_computer} -
    {platformSvg(handler.get_platform(this.is_remote), "white")}
    -
    {translate(this.is_remote ? "Remote Computer" : "Local Computer")}
    -
    - } - - function renderNavBar() { - return
    -
    {svg_home}
    -
    {svg_arrow}
    -
    {svg_arrow}
    - {this.renderSelect()} -
    {svg_refresh}
    -
    ; - } - - function renderSelect() { - return ; - } - - function renderOpBar() { - if (this.is_remote) { - return
    -
    {svg_send}{translate('Receive')}
    -
    -
    {svg_add_folder}
    -
    {svg_trash}
    -
    ; - } - return
    -
    {svg_add_folder}
    -
    {svg_trash}
    -
    -
    {translate('Send')}{svg_send}
    -
    ; - } - - function get_updated() { - this.table.sortRows(false); - if (this.fd && this.fd.path) this.select_dir.value = this.fd.path; - } - - function renderTable() { - var fd = this.fd; - var entries = fd.entries || []; - var table = this.table; - if (!table || !table.sortBy) { - entries.sort(this.foldersFirst); - } - var me = this; - var path = fd.path; - if (path != "/" && path) { - entries = [{ name: "..", type: 1 }].concat(entries); - } - var rows = entries.map(function(e) { return me.renderRow(e); }); - var id = (this.is_remote ? "remote" : "local") + "-folder-view"; - return - - - - - {rows} - - - -
  • {svg_checkmark}{translate('Show Hidden Files')}
  • - -
    -
    {translate('Name')}{translate('Modified')}{translate('Size')}
    ; - } - - function joinPath(name) { - var path = this.fd.path; - if (path == "/") { - if (this.sep() == "/") return this.sep() + name; - else return name; - } - return path + (path[path.length - 1] == this.sep() ? "" : this.sep()) + name; - } - - function attached() { - var me = this; - this.table.onRowDoubleClick = function (row) { - var type = row[0].attributes["type"]; - if (type > 3) return; - var name = row[1].text; - var path = name == ".." ? getParentPath(me.is_remote, me.fd.path) : me.joinPath(name); - me.table.resetCurrent(); - me.goto(path, true); - } - this.get_updated(); - } - - function goto(path, push) { - if (!path) return; - if (this.sep() == "\\" && path.length == 2) { // windows drive - path += "\\"; - } - if (push) this.pushHistory(); - if (this.is_remote) { - handler.read_remote_dir(path, this.show_hidden); - } else { - var fd = handler.read_dir(path, this.show_hidden); - this.refresh({ fd: fd }); - } - } - - function refresh(data) { - if (!data.fd || !data.fd.path) return; - if (this.is_remote && !remote_home_dir) { - remote_home_dir = data.fd.path; - } - this.update(data); - var me = this; - self.timer(1ms, function() { me.get_updated(); }); - } - - function renderRow(entry) { - var path; - if (this.is_remote) { - path = handler.get_icon_path(entry.type, getExt(entry.name)); - } else { - path = this.joinPath(entry.name); - } - var tm = entry.time ? new Date(entry.time.toFloat() * 1000.).toLocaleString() : 0; - return - - {entry.name} - {tm || ""} - {getSize(entry.type, entry.size)} - ; - } - - event click $(#switch-hidden) { - this.show_hidden = !this.show_hidden; - this.refreshDir(); - } - - event click $(.goup) () { - var path = this.fd.path; - if (!path || path == "/") return; - path = getParentPath(this.is_remote, path); - this.goto(path, true); - } - - event click $(.goback) () { - var path = this.history.pop(); - if (!path) return; - this.goto(path, false); - } - - event click $(.trash) () { - var rows = this.getCurrentRows(); - if (!rows || rows.length == 0) return; - - var delete_dirs = new Array(); - - for (var i = 0; i < rows.length; ++i) { - var row = rows[i]; - - var path = row[0]; - var type = row[1]; - - var new_history = []; - for (var j = 0; j < this.history.length; ++j) { - var h = this.history[j]; - if ((h + this.sep()).indexOf(path + this.sep()) == -1) new_history.push(h); - } - this.history = new_history; - if (type == 1) { - file_transfer.job_table.addDelDir(path, this.is_remote); - } else { - file_transfer.job_table.addDelFile(path, this.is_remote); - } - } - file_transfer.job_table.confirmDeletePolling(this.is_remote); - } - - event click $(.add-folder) () { - var me = this; - msgbox("custom", translate("Create Folder"), "
    \ -
    " + translate("Please enter the folder name") + ":
    \ -
    \ -
    ", "", function(res=null) { - if (!res) return; - if (!res.name) return; - var name = res.name.trim(); - if (!name) return; - if (name.indexOf(me.sep()) >= 0) { - handler.msgbox("custom-error", "Create Folder", "Invalid folder name"); - return; - } - var path = me.joinPath(name); - var id = handler.get_next_job_id(); - handler.create_dir(id, path, me.is_remote); - create_dir_jobs[id] = { is_remote: me.is_remote, path: path }; - }); - } - - function refreshDir() { - this.goto(this.fd.path, false); - } - - event click $(.refresh) () { - this.refreshDir(); - } - - event click $(.home) () { - var path = this.is_remote ? remote_home_dir : handler.get_home_dir(); - if (!path) return; - if (path == this.fd.path) return; - this.goto(path, true); - } - - function getCurrentRow() { - var row = this.table.getCurrentRow(); - if (!row) return; - var name = row[1].text; - if (!name || name == "..") return; - var type = row[0].attributes["type"]; - return [this.joinPath(name), type]; - } - - function getCurrentRows() { - var rows = this.table.getCurrentRows(); - if (!rows || rows.length== 0) return; - - var records = new Array(); - - for (var i = 0; i < rows.length; ++i) { - var name = rows[i][1].text; - if (!name || name == "..") continue; - - var type = rows[i][0].attributes["type"]; - records.push([this.joinPath(name), type]); - } - return records; - } - - event click $(.send) () { - var rows = this.getCurrentRows(); - if (!rows || rows.length == 0) return; - for (var i = 0; i < rows.length; ++i) { - file_transfer.job_table.send(rows[i][0], this.is_remote); - } - } - - event change $(.select-dir) (_, el) { - var x = getTime() - last_key_time; - if (x < 1000) return; - if (this.fd.path != el.value) { - this.goto(el.value, true); - } - } - - event keydown $(.select-dir) (evt, me) { - if (isEnterKey(evt)) { - this.goto(me.value, true); - } - } - - function pushHistory() { - var path = this.fd.path; - if (!path) return; - if (path != this.history[this.history.length - 1]) this.history.push(path); - } -} - -var file_transfer; - -class FileTransfer: Reactor.Component { - function this() { - file_transfer = this; - } - - function render() { - return
    - - - -
    ; - } -} - -function initializeFileTransfer() -{ - $(#file-transfer-wrapper).content(); - $(#video-wrapper).style.set { visibility: "hidden", position: "absolute" }; - $(#file-transfer-wrapper).style.set { display: "block" }; -} - -handler.updateFolderFiles = function(fd) { - // stdout.println("update folder files: " + JSON.stringify(fd)); - fd.entries = fd.entries || []; - if (fd.id > 0) { - var jt = file_transfer.job_table; - var job = jt.job_map[fd.id]; - if (job) { - job.file_num = -1; - job.total_size = fd.total_size; - job.entries = fd.entries; - job.num_entries = fd.num_entries; - file_transfer.job_table.updateJobStatus(job.id); - } - } else { - file_transfer.remote_folder_view.refresh({ fd: fd }); - } -} - -handler.jobProgress = function(id, file_num, speed, finished_size) { - file_transfer.job_table.updateJobStatus(id, file_num, null, speed, finished_size); -} - -handler.jobDone = function(id, file_num = -1) { - var job = create_dir_jobs[id]; - if (job) { - refreshDir(job.is_remote); - return; - } - file_transfer.job_table.updateJobStatus(id, file_num); -} - -handler.jobError = function(id, err, file_num = -1) { - var job = deleting_single_file_jobs[id]; - if (job) { - msgbox("custom-error", "Delete File", err); - return; - } - job = create_dir_jobs[id]; - if (job) { - msgbox("custom-error", "Create Folder", err); - return; - } - if (file_num < 0) { - handler.msgbox("custom-error", "Failed", err); - } - file_transfer.job_table.updateJobStatus(id, file_num, err); -} - -handler.clearAllJobs = function() { - file_transfer.job_table.clearAllJobs(); -} - -handler.addJob = function (id, path, to, file_num, show_hidden, is_remote, auto_start) { // load last job - // stdout.println("restore job: " + is_remote); - file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote,auto_start); -} - -handler.updateTransferList = function () { - file_transfer.job_table.update(); -} - -function refreshDir(is_remote) { - if (is_remote) file_transfer.remote_folder_view.refreshDir(); - else file_transfer.local_folder_view.refreshDir(); -} - -var deleting_single_file_jobs = {}; -var create_dir_jobs = {} - -function confirmDelete(id ,path, is_remote) { - msgbox("custom-skip", "Confirm Delete", "
    \ -
    " + translate('Are you sure you want to delete this file?') + "
    \ - " + path + "
    \ -
    ", "", function(res=null) { - if (!res) { - file_transfer.job_table.updateJobStatus(id, -1, "cancel"); - file_transfer.job_table.cancelDeletePolling(); - } else if (res.skip) { - file_transfer.job_table.updateJobStatus(id, -1, "cancel"); - file_transfer.job_table.confirmDeletePolling(is_remote); - } else { - handler.remove_file(id, path, 0, is_remote); - if (is_remote) file_transfer.remote_folder_view.table.resetCurrent(); - else file_transfer.local_folder_view.table.resetCurrent(); - deleting_single_file_jobs[id] = { is_remote: is_remote, path: path }; - file_transfer.job_table.confirmDeletePolling(is_remote); - } - }); -} - -handler.confirmDeleteFiles = function(id, i, name) { - var jt = file_transfer.job_table; - var job = jt.job_map[id]; - if (!job) return; - var n = job.num_entries; - if (i >= n) return; - var file_path = job.path; - if (name) file_path += handler.get_path_sep(job.is_remote) + name; - msgbox("custom-skip", "Confirm Delete", "
    \ -
    " + translate('Deleting') + " #" + (i + 1) + " / " + n + " " + translate('files') + ".
    \ -
    " + translate('Are you sure you want to delete this file?') + "
    \ - " + file_path + "
    \ -
    " + translate('Do this for all conflicts') + "
    \ -
    ", "", function(res=null) { - if (!res) { - jt.updateJobStatus(id, i - 1, "cancel"); - file_transfer.job_table.cancelDeletePolling(); - } else if (res.skip) { - if (res.remember){ - jt.updateJobStatus(id, i, "cancel"); - } else{ - handler.jobDone(id, i); - } - file_transfer.job_table.confirmDeletePolling(job.is_remote); - } else { - job.no_confirm = res.remember; - if (job.no_confirm){ - handler.set_no_confirm(id); - file_transfer.job_table.confirmDeletePolling(job.is_remote); - } - handler.remove_file(id, file_path, i, job.is_remote); - } - if(i+1 >= n){ - file_transfer.job_table.confirmDeletePolling(job.is_remote); - } - }); -} - -handler.overrideFileConfirm = function(id, file_num, to, is_upload, is_identical) { - var jt = file_transfer.job_table; - var identical_msg = is_identical ? translate("identical_file_tip"): ""; - msgbox("custom-skip", "Confirm Write Strategy", "
    \ -
    " + translate('Overwrite') + " " + translate('files') + ".
    \ -
    " + translate('This file exists, skip or overwrite this file?') + "
    \ - " + to + "
    \ -
    " + identical_msg + "
    \ -
    " + translate('Do this for all conflicts') + "
    \ -
    ", "", function(res=null) { - if (!res) { - jt.updateJobStatus(id, -1, "cancel"); - handler.cancel_job(id); - } else if (res.skip) { - if (res.remember){ - handler.set_write_override(id,file_num,false,true, is_upload); // - } else { - handler.set_write_override(id,file_num,false,false,is_upload); // - } - } else { - if (res.remember){ - handler.set_write_override(id,file_num,true,true,is_upload); // - } else { - handler.set_write_override(id,file_num,true,false,is_upload); // - } - } - }); -} - -function save_file_transfer_close_state() { - var local_dir = file_transfer.local_folder_view.fd.path || ""; - var local_show_hidden = file_transfer.local_folder_view.show_hidden ? "Y" : ""; - var remote_dir = file_transfer.remote_folder_view.fd.path || ""; - var remote_show_hidden = file_transfer.remote_folder_view.show_hidden ? "Y" : ""; - handler.save_close_state("local_dir", local_dir); - handler.save_close_state("local_show_hidden", local_show_hidden); - handler.save_close_state("remote_dir", remote_dir); - handler.save_close_state("remote_show_hidden", remote_show_hidden); -} diff --git a/src/ui/grid.tis b/src/ui/grid.js similarity index 93% rename from src/ui/grid.tis rename to src/ui/grid.js index 6560521ca..f33b4ef35 100644 --- a/src/ui/grid.tis +++ b/src/ui/grid.js @@ -1,12 +1,3 @@ -var last_key_time = 0; -var keymap = {}; -for (var (k, v) in Event) { - k = k + "" - if (k[0] == "V" && k[1] == "K") { - keymap[v] = k; - } -} - class Grid: Behavior { const TABLE_HEADER_CLICK = 0x81; const TABLE_ROW_CLICK = 0x82; @@ -43,15 +34,6 @@ class Grid: Behavior { { return this.$(thead>:current); // return current cell in header row } - - function resetCurrent() { - var rows = this.getCurrentRows(); - for (var i = 0; i < rows.length; ++i) { - var row = rows[i]; - row.state.current = false; - row.state.checked = false; - } - } function setCurrentRow(row, reason = #by_code, doubleClick = false) { @@ -62,10 +44,8 @@ class Grid: Behavior { { if (prev === row && !doubleClick) return; // already here, nothing to do. prev.state.current = false; // drop state flag - prev.state.checked = false; // drop state flag } row.state.current = true; - row.state.checked = true; row.scrollToView(); if (doubleClick) @@ -164,7 +144,7 @@ class Grid: Behavior { function onKey(evt) { - last_key_time = getTime(); + if (evt.type != Event.KEY_DOWN) return false; @@ -250,7 +230,8 @@ class Grid: Behavior { idx += 1; } } - if (isEnterKey(evt)) { + if (evt.keyCode == Event.VK_ENTER || + (view.mediaVar("platform") == "OSX" && evt.keyCode == 0x4C)) { this.onRowDoubleClick(this.getCurrentRow()); } return false; diff --git a/src/ui/header.css b/src/ui/header.css index 8fe408612..e444c1f56 100644 --- a/src/ui/header.css +++ b/src/ui/header.css @@ -1,14 +1,10 @@ -header div { - word-wrap: normal; -} - header #screens { background: white; border: #A9A9A9 1px solid; height: 22px; border-radius: 4px; flow: horizontal; - border-spacing: 0.5em; + border-spacing: 0.5em; padding-right: 1em; position: relative; } @@ -40,8 +36,7 @@ header #secure svg { } header .remote-id { - width: 80px; - @ELLIPSIS; + width: *; padding-left: 30px; padding-right: 4em; margin: * 0; @@ -94,4 +89,3 @@ span#fullscreen.active { button:disabled { opacity: 0.3; } - diff --git a/src/ui/header.js b/src/ui/header.js new file mode 100644 index 000000000..c1dcb94a3 --- /dev/null +++ b/src/ui/header.js @@ -0,0 +1,411 @@ +import { handler,view,setWindowButontsAndIcon,translate,msgbox,adjustBorder,is_osx,is_xfce,svg_chat,svg_checkmark, is_linux } from "./common.js"; +import {$,$$} from "@sciter"; +import { adaptDisplay, audio_enabled, clipboard_enabled, keyboard_enabled } from "./remote.js"; +var pi = handler.xcall("get_default_pi"); // peer information + +var chat_msgs = []; + +const svg_fullscreen = ( + +); +const svg_action = (); +const svg_display = ( + +); +const svg_secure = ( + +); +const svg_insecure = (); +const svg_insecure_relay = (); +const svg_secure_relay = (); + +var cur_window_state = view.state; + + +if (is_linux) { + // check_state_change; + setInterval(() => { + if (view.state != cur_window_state) { + stateChanged(); + } + }, 30); +} else { + view.on("statechange",()=>{ + stateChanged(); + }) +} + +function get_id() { + return handler.xcall("get_option","alias") || handler.xcall("get_id") +} + +function stateChanged() { + console.log('state changed from ' + cur_window_state + ' -> ' + view.state); + cur_window_state = view.state; + adjustBorder(); + adaptDisplay(); + if (cur_window_state != Window.WINDOW_MINIMIZED) { + view.focus = handler; // to make focus away from restore/maximize button, so that enter key work + } + let fs = view.state == Window.WINDOW_FULL_SCREEN; + let el = $("#fullscreen"); + if (el) el.classList.toggle("active", fs); + el = $("#maximize"); + if (el) { + el.state.disabled = fs; // TODO TEST + } + if (fs) { + $("header").style.setProperty("display","none"); + } +} + +export var header; +var old_window_state = Window.WINDOW_SHOWN; +var input_blocked; + +class Header extends Element { + this() { + header = this; + } + + render() { + let icon_conn; + let title_conn; + if (this.secure_connection && this.direct_connection) { + icon_conn = svg_secure; + title_conn = translate("Direct and encrypted connection"); + } else if (this.secure_connection && !this.direct_connection) { + icon_conn = svg_secure_relay; + title_conn = translate("Relayed and encrypted connection"); + } else if (!this.secure_connection && this.direct_connection) { + icon_conn = svg_insecure; + title_conn = translate("Direct and unencrypted connection"); + } else { + icon_conn = svg_insecure_relay; + title_conn = translate("Relayed and unencrypted connection"); + } + let title = get_id(); + if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")"; + if ((pi.displays || []).length == 0) { + return (
    {title}
    ); + } + let screens = pi.displays.map(function(d, i) { + return
    + {i+1} +
    ; + }); + updateWindowToolbarPosition(); + let style = "flow:horizontal;"; + if (is_osx) style += "margin:*"; + setTimeout(toggleMenuState,1); + + return (
    + {is_osx || is_xfce ? "" : {svg_fullscreen}} +
    + {icon_conn} +
    {get_id()}
    +
    {screens}
    + {this.renderGlobalScreens()} +
    + {svg_chat} + {svg_action} + {svg_display} + {this.renderDisplayPop()} + {this.renderActionPop()} +
    ); + } + + renderDisplayPop() { + return ( + + + + ); + } + + renderActionPop() { + return ( + +
  • {translate('Transfer File')}
  • +
  • {translate('TCP Tunneling')}
  • +
    + {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ?
  • {translate('Insert')} Ctrl + Alt + Del
  • : ""} +
    + {keyboard_enabled ?
  • {translate('Insert Lock')}
  • : ""} + {false && pi.platform == "Windows" ?
  • Block user input
  • : ""} + {handler.xcall("support_refresh") ?
  • {translate('Refresh')}
  • : ""} +
    +
    ); + } + + renderGlobalScreens() { + if (pi.displays.length < 3) return ""; + let x0 = 9999999; + let y0 = 9999999; + let x = -9999999; + let y = -9999999; + pi.displays.map(function(d, i) { + if (d.x < x0) x0 = d.x; + if (d.y < y0) y0 = d.y; + let dx = d.x + d.width; + if (dx > x) x = dx; + let dy = d.y + d.height; + if (dy > y) y = dy; + }); + let w = x - x0; + let h = y - y0; + let scale = 16. / h; + let screens = pi.displays.map(function(d, i) { + let min_wh = d.width > d.height ? d.height : d.width; + let fs = min_wh * 0.9 * scale; + let style = "width:" + (d.width * scale) + "px;" + + "height:" + (d.height * scale) + "px;" + + "left:" + ((d.x - x0) * scale) + "px;" + + "top:" + ((d.y - y0) * scale) + "px;" + + "font-size:" + fs + "px;"; + if (is_osx) { + style += "line-height:" + fs + "px;"; + } + return
    {i+1}
    ; + }); + + let style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;"; + return
    + {screens} +
    ; + } + + ["on click at #fullscreen"](_, el) { + if (view.state == Window.WINDOW_FULL_SCREEN) { + if (old_window_state == Window.WINDOW_MAXIMIZED) { + view.state = Window.WINDOW_SHOWN; + } + view.state = old_window_state; + } else { + old_window_state = view.state; + if (view.state == Window.WINDOW_MAXIMIZED) { + view.state = Window.WINDOW_SHOWN; + } + view.state = Window.WINDOW_FULL_SCREEN; + if (is_linux) { setTimeout(()=>view.state = Window.WINDOW_FULL_SCREEN,150); } + } + } + + ["on click at #chat"]() { + startChat(); + } + + ["on click at #action"](_, me) { + let menu = $("menu#action-options"); + me.popup(menu); + } + + ["on click at #display"](_, me) { + let menu = $("menu#display-options"); + me.popup(menu); + } + + ["on click at #screen"](_, me) { + if (pi.current_display == me.index) return; + handler.xcall("switch_display",me.index); + } + + ["on click at #transfer-file"]() { + handler.xcall("transfer_file"); + } + + ["on click at #tunnel"] () { + handler.xcall("tunnel"); + } + + ["on click at #ctrl-alt-del"]() { + handler.xcall("ctrl_alt_del"); + } + + ["on click at #lock-screen"]() { + handler.xcall("lock_screen"); + } + + ["on click at #refresh"] () { + handler.xcall("refresh_video"); + } + + ["on click at #block-input"] (_,me) { + if (!input_blocked) { + handler.xcall("toggle_option","block-input"); + input_blocked = true; + me.text = "Unblock user input"; // TEST + } else { + handler.xcall("toggle_option","unblock-input"); + input_blocked = false; + me.text = "Block user input"; + } + } + + ["on click at menu#display-options>li"] (_, me) { + if (me.id == "custom") { + handle_custom_image_quality(); + } else if (me.attributes.hasClass("toggle-option")) { + handler.toggle_option(me.id); + toggleMenuState(); + } else if (!me.attributes.hasClass("selected")) { + let type = me.attributes["type"]; + if (type == "image-quality") { + handler.xcall("save_image_quality",me.id); + } else if (type == "view-style") { + handler.xcall("save_view_style",me.id); + adaptDisplay(); + } + toggleMenuState(); + } + } +} + +function handle_custom_image_quality() { + let tmp = handler.xcall("get_custom_image_quality"); + let bitrate0 = tmp[0] || 50; + let quantizer0 = tmp.length > 1 ? tmp[1] : 100; + msgbox("custom", "Custom Image Quality", "
    \ +
    x% bitrate
    \ +
    x% quantizer
    \ +
    ", function(res=null) { + if (!res) return; + if (!res.bitrate) return; + handler.xcall("save_custom_image_quality",res.bitrate, res.quantizer); + toggleMenuState(); + }); +} + +function toggleMenuState() { + let values = []; + let q = handler.xcall("get_image_quality"); + if (!q) q = "balanced"; + values.push(q); + let s = handler.xcall("get_view_style"); + if (!s) s = "original"; + values.push(s); + for (let el of $$("menu#display-options>li")) { + el.classList.toggle("selected", values.indexOf(el.id) >= 0); + } + for (let id of ["show-remote-cursor", "disable-audio", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) { + let el = $('#' + id); // TEST + if (el) { + el.classList.toggle("selected", handler.xcall("get_toggle_option",id)); + } + } +} + +if (is_osx) { + $("header").content(
    ); + $("header").attributes["role"] = "window-caption"; // TODO +} else { + if (handler.is_file_transfer || handler.is_port_forward) { + $("caption").content(
    ); + } else { + $("div.window-toolbar").content(
    ); + } + setWindowButontsAndIcon(); +} + +if (!(handler.is_file_transfer || handler.is_port_forward)) { + $("header").style.setProperty("height","32px"); + if (!is_osx) { + $("div.window-icon").style.setProperty("size","32px"); + } +} + +handler.updatePi = function(v) { + pi = v; + header.componentUpdate(); + if (handler.is_port_forward) { + view.state = Window.WINDOW_MINIMIZED; + } +} + +handler.switchDisplay = function(i) { + pi.current_display = i; + header.componentUpdate(); +} + +function updateWindowToolbarPosition() { + if (is_osx) return; + setTimeout(function() { + let el = $("div.window-toolbar"); + let w1 = el.state.box("width", "border"); // TEST + let w2 = $("header").state.box("width", "border"); + let x = (w2 - w1) / 2; + el.style.setProperty("left",x + "px"); + el.style.setProperty("display","block") + },1); +} + +view.onsizechange = function() { + // ensure size is done, so add timer + setTimeout(function() { + updateWindowToolbarPosition(); + adaptDisplay(); + },1); +}; + +handler.newMessage = function(text) { + chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()}); + startChat(); +} + +function sendMsg(text) { + chat_msgs.push({text: text, name: "me", time: getNowStr()}); + handler.xcall("send_chat",text); + if (chatbox) chatbox.refresh(); +} + +var chatbox; +function startChat() { + if (chatbox) { + chatbox.state = Window.WINDOW_SHOWN; // TODO TEST el.state + chatbox.refresh(); // TODO el.refresh + return; + } + let icon = handler.xcall("get_icon"); + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); // TEST + let w = 300; + let h = 400; + let x = (sx + sw - w) / 2; + let y = sy + 80; + let params = { + type: Window.FRAME_WINDOW, + x: x, + y: y, + width: w, + height: h, + client: true, + parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon }, + caption: get_id(), + }; + let html = handler.xcall("get_chatbox"); + if (html) params.html = html; + else params.url = document.url("chatbox.html"); + chatbox = view.window(params); // TEST +} + +handler.setConnectionType = function(secured, direct) { + // TEST + header.componentUpdate({ + secure_connection: secured, + direct_connection: direct, + }); +} diff --git a/src/ui/header.tis b/src/ui/header.tis deleted file mode 100644 index 40ccbcbf2..000000000 --- a/src/ui/header.tis +++ /dev/null @@ -1,722 +0,0 @@ -var pi = handler.get_default_pi(); // peer information -var chat_msgs = []; - -var svg_fullscreen = - -; -var svg_action = ; -var svg_display = - -; -var svg_secure = - -; -var svg_insecure = ; -var svg_insecure_relay = ; -var svg_secure_relay = ; -var svg_recording_off = ; -var svg_recording_on = ; - -var cur_window_state = view.windowState; -function check_state_change() { - if (view.windowState != cur_window_state) { - stateChanged(); - } - self.timer(30ms, check_state_change); -} - -if (is_linux) { - check_state_change(); -} else { - view << event statechange { - stateChanged(); - } -} - -function get_id() { - return handler.get_option('alias') || handler.get_id() -} - -function stateChanged() { - stdout.println('state changed from ' + cur_window_state + ' -> ' + view.windowState); - cur_window_state = view.windowState; - adjustBorder(); - adaptDisplay(); - if (cur_window_state != View.WINDOW_MINIMIZED) { - view.focus = handler; // to make focus away from restore/maximize button, so that enter key work - } - var fs = view.windowState == View.WINDOW_FULL_SCREEN; - var el = $(#fullscreen); - if (el) el.attributes.toggleClass("active", fs); - el = $(#maximize); - if (el) { - el.state.disabled = fs; - } - if (fs) { - $(header).style.set { - display: "none", - }; - } -} - -var header; -var old_window_state = View.WINDOW_SHOWN; - -var is_edit_os_password; -class EditOsPassword: Reactor.Component { - function render() { - return {svg_edit}; - } - - function onMouse(evt) { - if (evt.type == Event.MOUSE_DOWN) { - is_edit_os_password = true; - editOSPassword(); - } - } -} - -function editOSPassword(login=false) { - var p0 = handler.get_option('os-password'); - msgbox("custom-os-password", 'OS Password', p0, "", function(res=null) { - if (!res) return; - var a0 = handler.get_option('auto-login') != ''; - var p = (res.password || '').trim(); - var a = res.autoLogin || false; - if (p == p0 && a == a0) return; - if (p != p0) handler.set_option('os-password', p); - if (a != a0) handler.set_option('auto-login', a ? 'Y' : ''); - if (p && login) { - handler.input_os_password(p, true); - } - }); -} - -var recording = false; - -class Header: Reactor.Component { - this var conn_note = ""; - - function this() { - header = this; - } - - function render() { - var icon_conn; - var title_conn; - if (this.secure_connection && this.direct_connection) { - icon_conn = svg_secure; - title_conn = translate("Direct and encrypted connection"); - } else if (this.secure_connection && !this.direct_connection) { - icon_conn = svg_secure_relay; - title_conn = translate("Relayed and encrypted connection"); - } else if (!this.secure_connection && this.direct_connection) { - icon_conn = svg_insecure; - title_conn = translate("Direct and unencrypted connection"); - } else { - icon_conn = svg_insecure_relay; - title_conn = translate("Relayed and unencrypted connection"); - } - var stream_type = this.stream_type; - if (stream_type == "Relay") { - stream_type = "TCP"; - } - if (stream_type) { - title_conn += " (" + stream_type + ")"; - } - var title = get_id(); - if (pi.hostname) title += "(" + pi.username + "@" + pi.hostname + ")"; - if ((pi.displays || []).length == 0) { - return
    {title}
    ; - } - var screens = pi.displays.map(function(d, i) { - return
    - {i+1} -
    ; - }); - updateWindowToolbarPosition(); - var style = "flow:horizontal;"; - if (is_osx) style += "margin:*"; - self.timer(1ms, updatePrivacyMode); - self.timer(1ms, toggleMenuState); - return
    - {is_osx || is_xfce ? "" : {svg_fullscreen}} -
    - {icon_conn} -
    {get_id()}
    -
    {screens}
    - {this.renderGlobalScreens()} -
    - {svg_chat} - {svg_action} - {svg_display} - {svg_keyboard} - {recording_enabled ? {recording ? svg_recording_on : svg_recording_off} : ""} - {this.renderKeyboardPop()} - {this.renderDisplayPop()} - {this.renderActionPop()} -
    ; - } - - function renderKeyboardPop(){ - const is_map_mode_supported = handler.is_keyboard_mode_supported("map"); - const is_translate_mode_supported = handler.is_keyboard_mode_supported("translate"); - return - -
  • {svg_checkmark}{translate('Legacy mode')}
  • - { is_map_mode_supported &&
  • {svg_checkmark}{translate('Map mode')}
  • } - { is_translate_mode_supported &&
  • {svg_checkmark}{translate('Translate mode')}
  • } - -
    ; - } - - function renderDisplayPop() { - var codecs = handler.alternative_codecs(); - var show_codec = codecs[0] || codecs[1] || codecs[2] || codecs[3]; - - var cursor_embedded = false; - if ((pi.displays || []).length > 0) { - if (pi.displays.length > pi.current_display) { - cursor_embedded = pi.displays[pi.current_display].cursor_embedded; - } - } - - var is_file_copy_paste_supported = false; - if (handler.version_cmp(pi.version, '1.2.4') < 0) { - is_file_copy_paste_supported = is_win && pi.platform == "Windows"; - } else { - is_file_copy_paste_supported = handler.has_file_clipboard() && pi.platform_additions?.has_file_clipboard; - } - - return - -
  • {translate('Adjust Window')}
  • -
    -
  • {svg_checkmark}{translate('Original')}
  • -
  • {svg_checkmark}{translate('Shrink')}
  • -
  • {svg_checkmark}{translate('Stretch')}
  • -
    -
  • {svg_checkmark}{translate('Good image quality')}
  • -
  • {svg_checkmark}{translate('Balanced')}
  • -
  • {svg_checkmark}{translate('Optimize reaction time')}
  • -
  • {svg_checkmark}{translate('Custom')}
  • - {show_codec ?
    -
    -
  • {svg_checkmark}Auto
  • - {codecs[0] ?
  • {svg_checkmark}VP8
  • : ""} -
  • {svg_checkmark}VP9
  • - {codecs[1] ?
  • {svg_checkmark}AV1
  • : ""} - {codecs[2] ?
  • {svg_checkmark}H264
  • : ""} - {codecs[3] ?
  • {svg_checkmark}H265
  • : ""} -
    : ""} -
    - {!cursor_embedded &&
  • {svg_checkmark}{translate('Show remote cursor')}
  • } - {
  • {svg_checkmark}{translate('Follow remote cursor')}
  • } - {
  • {svg_checkmark}{translate('Follow remote window focus')}
  • } -
  • {svg_checkmark}{translate('Show quality monitor')}
  • - {audio_enabled ?
  • {svg_checkmark}{translate('Mute')}
  • : ""} - {is_file_copy_paste_supported && file_enabled ?
  • {svg_checkmark}{translate('Enable file copy and paste')}
  • : ""} - {keyboard_enabled && clipboard_enabled ?
  • {svg_checkmark}{translate('Disable clipboard')}
  • : ""} - {keyboard_enabled ?
  • {svg_checkmark}{translate('Lock after session end')}
  • : ""} - {(pi.platform == "Windows" || pi.platform == "Mac OS") && (handler.get_toggle_option("privacy-mode") || (keyboard_enabled && privacy_mode_enabled)) ?
  • {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')}
  • : ""} - {handler.version_cmp(pi.version, '1.2.4') >= 0 ?
  • {svg_checkmark}{translate('True color (4:4:4)')}
  • : ""} - - ; - } - - function renderActionPop() { - return - - {keyboard_enabled ?
  • {translate('OS Password')}
  • : ""} -
  • {translate('Transfer file')}
  • -
  • {translate('TCP tunneling')}
  • - {handler.get_audit_server("conn") &&
  • {translate('Note')}
  • } -
    - {keyboard_enabled && (pi.platform == "Linux" || pi.sas_enabled) ?
  • {translate('Insert')} Ctrl + Alt + Del
  • : ""} - {restart_enabled && (pi.platform == "Linux" || pi.platform == "Windows" || pi.platform == "Mac OS") ?
  • {translate('Restart remote device')}
  • : ""} - {keyboard_enabled ?
  • {translate('Insert Lock')}
  • : ""} - {keyboard_enabled && pi.platform == "Windows" && pi.sas_enabled ?
  • {translate("Block user input")}
  • : ""} - {handler.is_screenshot_supported() ?
  • {translate('Take screenshot')}
  • : "" } -
  • {translate('Refresh')}
  • - - ; - } - - function renderGlobalScreens() { - if (pi.displays.length < 3) return ""; - var x0 = 9999999; - var y0 = 9999999; - var x = -9999999; - var y = -9999999; - pi.displays.map(function(d, i) { - if (d.x < x0) x0 = d.x; - if (d.y < y0) y0 = d.y; - var dx = d.x + d.width; - if (dx > x) x = dx; - var dy = d.y + d.height; - if (dy > y) y = dy; - }); - var w = x - x0; - var h = y - y0; - var scale = 16. / h; - var screens = pi.displays.map(function(d, i) { - var min_wh = d.width > d.height ? d.height : d.width; - var fs = min_wh * 0.9 * scale; - var style = "width:" + (d.width * scale) + "px;" + - "height:" + (d.height * scale) + "px;" + - "left:" + ((d.x - x0) * scale) + "px;" + - "top:" + ((d.y - y0) * scale) + "px;" + - "font-size:" + fs + "px;"; - if (is_osx) { - style += "line-height:" + fs + "px;"; - } - return
    {i+1}
    ; - }); - - var style = "width:" + (w * scale) + "px; height:" + (h * scale) + "px;"; - return
    - {screens} -
    ; - } - - event click $(#fullscreen) (_, el) { - if (view.windowState == View.WINDOW_FULL_SCREEN) { - if (old_window_state == View.WINDOW_MAXIMIZED) { - view.windowState = View.WINDOW_SHOWN; - } - view.windowState = old_window_state; - } else { - old_window_state = view.windowState; - if (view.windowState == View.WINDOW_MAXIMIZED) { - view.windowState = View.WINDOW_SHOWN; - } - view.windowState = View.WINDOW_FULL_SCREEN; - if (is_linux) { self.timer(150ms, function() { view.windowState = View.WINDOW_FULL_SCREEN; }); } - } - } - - event click $(#chat) { - startChat(); - } - - event click $(#action) (_, me) { - var menu = $(menu#action-options); - me.popup(menu); - } - - event click $(#display) (_, me) { - var menu = $(menu#display-options); - me.popup(menu); - } - - event click $(#keyboard) (_, me) { - var menu = $(menu#keyboard-options); - me.popup(menu); - } - - event click $(span#recording) (_, me) { - header.update(); - handler.record_screen(!recording) - } - - event click $(#screen) (_, me) { - if (pi.current_display == me.index) return; - handler.switch_display(me.index); - } - - event keyup (evt) { - if((pi.displays || []).length > 0 && evt.keyCode == 220) - { - if (pi.displays.length > pi.current_display) - handler.switch_display(pi.current_display + 1); - else - handler.switch_display(1); - } - } - - event click $(#transfer-file) { - handler.transfer_file(); - } - - event click $(#os-password) (evt) { - if (is_edit_os_password) { - is_edit_os_password = false; - return; - } - var p = handler.get_option('os-password'); - if (!p) editOSPassword(true); - else handler.input_os_password(p, true); - } - - event click $(#tunnel) { - handler.tunnel(); - } - - event click $(#note) { - var self = this; - msgbox("custom", "Note",
    - -
    , "", function(res=null) { - if (!res) return; - if (res.text == null || res.text == undefined) return; - self.conn_note = res.text ?? ""; - handler.send_note(res.text); - }, 280); - } - - event click $(#ctrl-alt-del) { - handler.ctrl_alt_del(); - } - - event click $(#restart_remote_device) { - msgbox( - "restart-confirmation", - translate("Restart remote device"), - translate("Are you sure you want to restart") + " " + pi.username + "@" + pi.hostname + "(" + get_id() + ") ?", - "", - function(res=null) { - if (res != null) handler.restart_remote_device(); - } - ); - } - - event click $(#lock-screen) { - handler.lock_screen(); - } - - event click $(#take-screenshot) { - handler.take_screenshot(pi.current_display, ""); - } - - event click $(#refresh) { - // 0 is just a dummy value. It will be ignored by the handler. - handler.refresh_video(0); - } - - event click $(#block-input) { - if (!input_blocked) { - handler.toggle_option("block-input"); - input_blocked = true; - $(#block-input).text = translate("Unblock user input"); - } else { - handler.toggle_option("unblock-input"); - input_blocked = false; - $(#block-input).text = translate("Block user input"); - } - } - - event click $(menu#display-options li) (_, me) { - if (me.id == "custom") { - handle_custom_image_quality(); - } else if (me.id == "privacy-mode") { - togglePrivacyMode(me.id); - } else if (me.id == "show-quality-monitor") { - toggleQualityMonitor(me.id); - } else if (me.id == "i444") { - toggleI444(me.id); - } else if (me.attributes.hasClass("toggle-option")) { - handler.toggle_option(me.id); - toggleMenuState(); - } else if (!me.attributes.hasClass("selected")) { - var type = me.attributes["type"]; - if (type == "image-quality") { - handler.save_image_quality(me.id); - } else if (type == "view-style") { - handler.save_view_style(me.id); - adaptDisplay(); - } else if (type == "codec-preference") { - handler.set_option("codec-preference", me.id); - handler.update_supported_decodings(); - } - toggleMenuState(); - } - } - - event click $(menu#keyboard-options>li) (_, me) { - if (me.id == "legacy") { - handler.save_keyboard_mode("legacy"); - } else if (me.id == "map") { - handler.save_keyboard_mode("map"); - } else if (me.id == "translate") { - handler.save_keyboard_mode("translate"); - } - toggleMenuState() - } -} - -function handle_custom_image_quality() { - var tmp = handler.get_custom_image_quality(); - var bitrate = (tmp[0] || 50); - var extendedBitrate = bitrate > 100; - var maxRate = extendedBitrate ? 2000 : 100; - msgbox("custom-image-quality", "Custom Image Quality", "
    \ -
    x% Bitrate More
    \ -
    ", "", function(res=null) { - if (!res) return; - if (res.id === "extended-slider") { - var slider = res.parent.$(#bitrate-slider) - slider.slider.max = res.checked ? 2000 : 100; - if (slider.value > slider.slider.max) { - slider.value = slider.slider.max; - } - var buddy = res.parent.$(#bitrate-buddy); - buddy.value = slider.value; - return; - } - if (!res.bitrate) return; - handler.save_custom_image_quality(res.bitrate); - toggleMenuState(); - }); -} - -function toggleMenuState() { - var values = []; - var q = handler.get_image_quality(); - if (!q) q = "balanced"; - values.push(q); - var s = handler.get_view_style(); - if (!s) s = "original"; - values.push(s); - var k = handler.get_keyboard_mode(); - values.push(k); - var c = handler.get_option("codec-preference"); - if (!c) c = "auto"; - values.push(c); - for (var el in $$(menu#display-options li)) { - el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); - } - for (var el in $$(menu#keyboard-options>li)) { - el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); - } - for (var id in ["show-remote-cursor", "follow-remote-cursor", "follow-remote-window", "show-quality-monitor", "disable-audio", "enable-file-copy-paste", "disable-clipboard", "lock-after-session-end", "allow_swap_key", "i444"]) { - var el = self.select('#' + id); - if (el) { - var value = handler.get_toggle_option(id); - el.attributes.toggleClass("selected", value); - } - } -} - -if (is_osx) { - $(header).content(
    ); - $(header).attributes["role"] = "window-caption"; -} else { - if (is_file_transfer || is_port_forward) { - $(caption).content(
    ); - } else { - $(div.window-toolbar).content(
    ); - } - setWindowButontsAndIcon(); -} - -if (!(is_file_transfer || is_port_forward)) { - $(header).style.set { - height: "32px", - }; - if (!is_osx) { - $(div.window-icon).style.set { - size: "32px", - }; - } -} - -handler.updatePi = function(v) { - pi = v; - recording = handler.is_recording(); - header.update(); - if (is_port_forward) { - view.windowState = View.WINDOW_MINIMIZED; - } -} - -handler.updateDisplays = function(v) { - pi.displays = v; - header.update(); - if (is_port_forward) { - view.windowState = View.WINDOW_MINIMIZED; - } -} - -handler.setMultipleWindowsSession = function(sessions) { - // It will be covered by other message box if the timer is not used, - self.timer(1000ms, function() { - msgbox("multiple-sessions-nocancel", translate("Multiple Windows sessions found"), , "", function(res) { - if (res && res.sid) { - handler.set_selected_windows_session_id("" + res.sid); - } - }, 230); - }); -} - -handler.setCurrentDisplay = function(v) { - pi.current_display = v; - handler.switch_display(v); - header.update(); - if (is_port_forward) { - view.windowState = View.WINDOW_MINIMIZED; - } -} - -handler.screenshot = function(msg) { - if (msg) { - msgbox( - "custom-nocancel-nook-hasclose-error", - translate("Take screenshot"), - msg, - "", - function() {} - ); - } else { - msgbox( - "custom-take-screenshot-nocancel-nook", - translate("Take screenshot"), - translate("screenshot-action-tip"), - "", - function() {} - ); - } -} - -function updatePrivacyMode() { - var el = $(li#privacy-mode); - if (el) { - var supported = handler.is_privacy_mode_supported(); - if (!supported) { - // el.attributes.toggleClass("line-through", true); - el.style["display"]="none"; - } else { - var value = handler.get_toggle_option("privacy-mode"); - el.attributes.toggleClass("selected", value); - var el = $(li#block-input); - if (el) { - el.state.disabled = value; - } - } - } -} -handler.updatePrivacyMode = updatePrivacyMode; - -function togglePrivacyMode(privacy_id) { - var supported = handler.is_privacy_mode_supported(); - if (!supported) { - msgbox("nocancel", translate("Privacy mode"), translate("Unsupported"), "", function() { }); - } else { - var privacy_mode_impls = pi.platform_additions?.supported_privacy_mode_impl; - if (privacy_mode_impls == null || privacy_mode_impls == undefined) { - handler.toggle_option(privacy_id); - return; - } - var is_on = handler.get_toggle_option("privacy-mode"); - handler.toggle_privacy_mode("", !is_on); - } -} - -function toggleQualityMonitor(name) { - var show = handler.get_toggle_option(name); - if (show) { - $(#quality-monitor).style.set{ display: "none" }; - } else { - $(#quality-monitor).style.set{ display: "block" }; - } - handler.toggle_option(name); - toggleMenuState(); -} - -function toggleI444(name) { - handler.toggle_option(name); - handler.update_supported_decodings(); - toggleMenuState(); -} - -handler.updateBlockInputState = function(input_blocked) { - if (!input_blocked) { - handler.toggle_option("block-input"); - input_blocked = true; - $(#block-input).text = translate("Unblock user input"); - } else { - handler.toggle_option("unblock-input"); - input_blocked = false; - $(#block-input).text = translate("Block user input"); - } -} - -handler.switchDisplay = function(i) { - pi.current_display = i; - header.update(); -} - -function updateWindowToolbarPosition() { - if (is_osx) return; - self.timer(1ms, function() { - var el = $(div.window-toolbar); - var w1 = el.box(#width, #border); - var w2 = $(header).box(#width, #border); - var x = (w2 - w1) / 2 / scaleFactor; - el.style.set { - left: x + "px", - display: "block", - }; - }); -} - -view.on("size", function() { - // ensure size is done, so add timer - self.timer(1ms, function() { - updateWindowToolbarPosition(); - adaptDisplay(); - }); -}); - -handler.newMessage = function(text) { - chat_msgs.push({text: text, name: pi.username || "", time: getNowStr()}); - startChat(); -} - -function sendMsg(text) { - chat_msgs.push({text: text, name: "me", time: getNowStr()}); - handler.send_chat(text); - if (chatbox) chatbox.refresh(); -} - -var chatbox; -function startChat() { - if (chatbox) { - chatbox.windowState = View.WINDOW_SHOWN; - chatbox.refresh(); - return; - } - var icon = handler.get_icon(); - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - var w = scaleIt(300); - var h = scaleIt(400); - var x = (sx + sw - w) / 2; - var y = sy + scaleIt(80); - var params = { - type: View.FRAME_WINDOW, - x: x, - y: y, - width: w, - height: h, - client: true, - parameters: { msgs: chat_msgs, callback: sendMsg, icon: icon }, - caption: get_id(), - }; - var html = handler.get_chatbox(); - if (html) params.html = html; - else params.url = self.url("chatbox.html"); - chatbox = view.window(params); -} - -handler.setConnectionType = function(secured, direct, stream_type) { - header.update({ - secure_connection: secured, - direct_connection: direct, - stream_type: stream_type, - }); -} - -handler.updateRecordStatus = function(status) { - recording = status; - header.update(); -} diff --git a/src/ui/index.css b/src/ui/index.css index d23e4f038..b5844db37 100644 --- a/src/ui/index.css +++ b/src/ui/index.css @@ -1,5 +1,6 @@ html { background-color: transparent; + var(gray-bg-osx): rgba(238, 238, 238, 0.75); } body { @@ -31,37 +32,11 @@ body { height: *; background: color(bg); border-right: color(border) 1px solid; - position: relative; -} - -#ab .left-pane { - border-radius: 1em; - padding: 1em; -} - -#ab .right-pane { - background: none; -} - -#ab .right-content { - overflow: unset; } .left-pane > div:nth-child(1) { border-spacing: 1em; padding: 20px; - padding-bottom: 60px; /* reserve space for bottom connect-status */ -} - -.left-pane > div.connect-status { - position: absolute; - bottom: 0; - left: 0; - right: 0; -} - -.left-pane div { - word-wrap: break-word; } div.sessions-bar { @@ -75,9 +50,11 @@ div.sessions-bar { div.sessions-tab span { display: inline-block; - padding: 6px 8px; + padding: 6px 12px; cursor: pointer; - @ELLIPSIS; + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; } div.sessions-tab svg { @@ -87,8 +64,8 @@ div.sessions-tab svg { div.sessions-tab span.active { cursor: default; border-radius: 3px; - background: color(bg); - color: color(text); + background: white; + color: black; } div.search-id { @@ -192,6 +169,7 @@ div.connect-status { left: 240px; border-top: color(border) solid 1px; width: 100%; + background: color(gray-bg); padding: 1em; } @@ -252,6 +230,7 @@ div.remote-session .platform .username{ bottom: 38px; font-size: 0.8em; width: 220px; + overflow: hidden; text-align: center; } @@ -262,7 +241,7 @@ div.remote-session .platform svg { } div.remote-session-list { - background: color(bg); + background: white; width: 220px; flow: horizontal; } @@ -293,10 +272,11 @@ div.remote-session-list .name .username { margin-top: 3px; font-size: 0.8em; color: color(lighter-text); + overflow: hidden; } div.remote-session .text { - background: color(bg); + background: white; position: absolute; height: 3em; width: 100%; @@ -324,13 +304,13 @@ svg#menu { } svg#menu:hover { - color: color(text); + color: black; border-radius: 1em; background: color(gray-bg); } svg#edit:hover { - color: color(text); + color: black; } svg#edit { @@ -344,11 +324,9 @@ div.install-me, div.trust-me { background: linear-gradient(left,#e242bc,#f4727c); } -div.trust-me > div:nth-child(1), div.install-me > div:nth-child(1) { font-size: 1.2em; font-weight: bold; - text-align: center; margin-bottom: 0.5em; } @@ -356,8 +334,11 @@ div.install-me > div:nth-child(2) { line-height: 1.4em; } -#install-me.link { - margin-top: 0.5em; +div.trust-me > div:nth-child(1) { + font-size: 1.2em; + text-align: center; + font-weight: bold; + margin-bottom: 0.5em; } div.trust-me > div:nth-child(2) { @@ -365,77 +346,17 @@ div.trust-me > div:nth-child(2) { margin-bottom: 1em; } -div.install-me > div:nth-child(3), div.trust-me > div:nth-child(3) { text-align: center; font-size: 1.5em; font-weight: bold; } -div.trust-me > div:nth-child(4), -div.trust-me > div:nth-child(5) { - margin-top: 0.5em; - text-align: center; -} - -div#myid, div#tags-label { +div#myid { position: relative; } -div#myid svg#menu, div#tags-label svg#menu { +div#myid svg#menu { position: absolute; right: -1em; } - -div#tags-label svg#menu:hover { - background-color: #ddd; -} - -div.remote-session svg#menu { - position: absolute; - right: 0; - top: 0; -} - -.install-me .button { - height: 2em; - line-height: 2em; - text-align: center; - font-weight: bold; - font-size: 1em; - margin-top: 1em; - border-color: white; - border: 1px; - background: none; - color: white; -} - -svg#refresh-password { - display: inline-block; - stroke:#ddd; -} - -svg#refresh-password:hover { - stroke:color(text); -} - -li:disabled, li:disabled:hover { - color: color(lighter-text); - background: color(menu); - opacity: 0.8; -} - -.grey-text { - color: #888 !important; -} - -input.grey-text, -textarea.grey-text { - color: #888 !important; -} - -@media platform == "OSX" { - div.eye-area > input { - font-size: 1em; - } -} \ No newline at end of file diff --git a/src/ui/index.html b/src/ui/index.html index 88c172231..dae2bd221 100644 --- a/src/ui/index.html +++ b/src/ui/index.html @@ -1,16 +1,14 @@ - - + diff --git a/src/ui/index.js b/src/ui/index.js new file mode 100644 index 000000000..80267aa62 --- /dev/null +++ b/src/ui/index.js @@ -0,0 +1,730 @@ +import { is_osx,view,OS,handler,translate,msgbox,is_win,svg_checkmark,svg_edit,isReasonableSize,centerize,svg_eye, PasswordComponent } from "./common"; +import { SearchBar,SessionStyle,SessionList, MultipleSessions } from "./ab.js"; +import {$} from "@sciter"; //TEST $$ import + +if (is_osx) view.blurBehind = "light"; +console.log("current platform:", OS); +console.log("wayland",handler.xcall("is_login_wayland")); +// html min-width, min-height not working on mac, below works for all +view.minSize = [500, 300]; // TODO not work on ubuntu + +export var app; // 注意判空 +var tmp = handler.xcall("get_connect_status"); +var connect_status = tmp[0]; +var service_stopped = false; +var software_update_url = ""; +var key_confirmed = tmp[1]; +var system_error = ""; + +export const svg_menu = + + + +; + +var my_id = ""; +function get_id() { + my_id = handler.xcall("get_id"); + return my_id; +} + +class ConnectStatus extends Element { + render() { + return(
    + + {this.getConnectStatusStr()} + {service_stopped ? {translate('Start Service')} : ""} +
    ); + } + + getConnectStatusStr() { + if (service_stopped) { + return translate("Service is not running"); + } else if (connect_status == -1) { + return translate('not_ready_status'); + } else if (connect_status == 0) { + return translate('connecting_status'); + } + return translate("Ready"); + } + + ["on click at #start-service"]() { + handler.xcall("set_option","stop-service", ""); + } +} + +export function createNewConnect(id, type) { + id = id.replace(/\s/g, ""); + app.remote_id.value = formatId(id); + if (!id) return; + if (id == my_id) { + msgbox("custom-error", "Error", "You cannot connect to your own computer"); + return; + } + handler.xcall("set_remote_id",id); + handler.xcall("new_remote",id, type); +} + +var direct_server; +class DirectServer extends Element { + this() { + direct_server = this; + } + + render() { + var text = translate("Enable Direct IP Access"); + var cls = handler.xcall("get_option", "direct-server") == "Y" ? "selected" : "line-through"; + return
  • {svg_checkmark}{text}
  • ; + } + + onClick() { + handler.xcall("set_option", "direct-server", handler.xcall("get_option", "direct-server") == "Y" ? "" : "Y"); + this.componentUpdate(); + } +} + +var myIdMenu; +var audioInputMenu; +class AudioInputs extends Element { + this() { + audioInputMenu = this; + } + + render() { + // TODO this.show + if (!this.show) return
  • ; + let inputs = handler.xcall("get_sound_inputs"); + if (is_win) inputs = ["System Sound"].concat(inputs); + if (!inputs.length) return
    ; + inputs = ["Mute"].concat(inputs); + setTimeout(()=>this.toggleMenuState(),1); + return (
  • {translate('Audio Input')} + + {inputs.map((name)=>
  • {svg_checkmark}{translate(name)}
  • )} +
    +
  • ); + } + + get_default() { + if (is_win) return "System Sound"; + return ""; + } + + get_value() { + return handler.xcall("get_option","audio-input") || this.get_default(); + } + + toggleMenuState() { + let v = this.get_value(); + for (let el of this.$$("menu#audio-input>li")) { + let selected = el.id == v; + el.classList.toggle("selected", selected); + } + } + + ["on click at menu#audio-input>li"](_, me) { + let v = me.id; + if (v == this.get_value()) return; + if (v == this.get_default()) v = ""; + handler.xcall("set_option","audio-input", v); + this.toggleMenuState(); + } +} + +class MyIdMenu extends Element { + this() { + myIdMenu = this; + } + + render() { + return (
    + {this.renderPop()} + ID{svg_menu} +
    ); + } + + renderPop() { + return ( + +
  • {svg_checkmark}{translate('Enable Keyboard/Mouse')}
  • +
  • {svg_checkmark}{translate('Enable Clipboard')}
  • +
  • {svg_checkmark}{translate('Enable File Transfer')}
  • +
  • {svg_checkmark}{translate('Enable TCP Tunneling')}
  • + +
    +
  • {translate('IP Whitelisting')}
  • +
  • {translate('ID/Relay Server')}
  • +
  • {translate('Socks5 Proxy')}
  • +
    +
  • {svg_checkmark}{translate("Enable Service")}
  • + +
    +
  • {translate('About')} {" "} {handler.xcall("get_app_name")}
  • +
    +
    ); + } + + + ["on click at svg#menu"](_, me) { + + audioInputMenu.componentUpdate({ show: true }); + this.toggleMenuState(); + let menu = this.$("menu#config-options"); + me.popup(menu); + } + + toggleMenuState() { + for (let el of this.$$("menu#config-options>li")) { + if (el.id && el.id.indexOf("enable-") == 0) { + let enabled = handler.xcall("get_option",el.id) != "N"; + console.log(el.id,enabled) + el.classList.toggle("selected", enabled); + el.classList.toggle("line-through", !enabled); + } + } + } + + ["on click at menu#config-options>li"] (_, me) { + if (me.id && me.id.indexOf("enable-") == 0) { + handler.xcall("set_option",me.id, handler.xcall("get_option",me.id) == "N" ? "" : "N"); + } + if (me.id == "whitelist") { + let old_value = handler.xcall("get_option","whitelist").split(",").join("\n"); + msgbox("custom-whitelist", translate("IP Whitelisting"), "
    \ +
    " + translate("whitelist_sep") + "
    \ + \ +
    \ + ", + function(res=null) { + if (!res) return; + let value = (res.text || "").trim(); + if (value) { + let values = value.split(/[\s,;\n]+/g); + for (let ip in values) { + if (!ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { + return translate("Invalid IP") + ": " + ip; + } + } + value = values.join("\n"); + } + if (value == old_value) return; + console.log("whitelist updated"); + handler.xcall("set_option","whitelist", value.replace("\n", ",")); + }, 300); + } else if (me.id == "custom-server") { + let configOptions = handler.xcall("get_options"); + let old_relay = configOptions["relay-server"] || ""; + let old_id = configOptions["custom-rendezvous-server"] || ""; + msgbox("custom-server", "ID/Relay Server", "
    \ +
    " + translate("ID Server") + ":
    \ +
    " + translate("Relay Server") + ":
    \ +
    \ + ", + function(res=null) { + if (!res) return; + let id = (res.id || "").trim(); + let relay = (res.relay || "").trim(); + if (id == old_id && relay == old_relay) return; + if (id) { + let err = handler.xcall("test_if_valid_server",id); + if (err) return translate("ID Server") + ": " + err; + } + if (relay) { + let err = handler.xcall("test_if_valid_server",relay); + if (err) return translate("Relay Server") + ": " + err; + } + configOptions["custom-rendezvous-server"] = id; + configOptions["relay-server"] = relay; + handler.xcall("set_options",configOptions); + }, 240); + } else if (me.id == "socks5-server") { + var socks5 = handler.xcall("get_socks") || {}; + var old_proxy = socks5[0] || ""; + var old_username = socks5[1] || ""; + var old_password = socks5[2] || ""; + msgbox("custom-server", "Socks5 Proxy",
    +
    {translate("Hostname")}
    +
    {translate("Username")}
    +
    {translate("Password")}
    +
    + , function(res=null) { + if (!res) return; + var proxy = (res.proxy || "").trim(); + var username = (res.username || "").trim(); + var password = (res.password || "").trim(); + if (proxy == old_proxy && username == old_username && password == old_password) return; + if (proxy) { + var err = handler.xcall("test_if_valid_server", proxy); + if (err) return translate("Server") + ": " + err; + } + handler.xcall("set_socks", proxy, username, password); + }, 240); + } else if (me.id == "stop-service") { + handler.xcall("set_option","stop-service", service_stopped ? "" : "Y"); + } else if (me.id == "about") { + let name = handler.xcall("get_app_name"); + msgbox("custom-nocancel-nook-hasclose", "About " + name, "
    \ +
    Version: " + handler.xcall("get_version") + " \ + \ + \ +
    Copyright © 2020 CarrieZ Studio \ +
    Author: Carrie \ +

    Made with heart in this chaotic world!

    \ +
    \ +
    ", + function(el) { + if (el && el.attributes) { + handler.xcall("open_url",el.attributes['url']); + }; + }, 400); + } + } +} + +class App extends Element{ + remote_id; + recent_sessions; + connect_status; + this() { + app = this; + } + + componentDidMount(){ + this.remote_id = this.$("#ID"); + this.multipleSessions = this.$("#multipleSessions"); + this.connect_status = this.$("#ConnectStatus"); + } + + render() { + let is_can_screen_recording = handler.xcall("is_can_screen_recording",false); + return(
    + + +
  • {translate('Refresh random password')}
  • +
  • {translate('Set your own password')}
  • +
    +
    +
    +
    +
    {translate('Your Desktop')}
    +
    {translate('desk_tip')}
    +
    + + {key_confirmed ? : translate("Generating ...")} +
    +
    +
    {translate('Password')}
    + +
    +
    + {handler.xcall("is_installed") ? "": } + {handler.xcall("is_installed") && software_update_url ? : ""} + {handler.xcall("is_installed") && !software_update_url && handler.xcall("is_installed_lower_version") ? : ""} + {is_can_screen_recording ? "": } + {is_can_screen_recording && !handler.xcall("is_process_trusted",false) ? : ""} + {system_error ? : ""} + {!system_error && handler.xcall("is_login_wayland") && !handler.xcall("current_is_wayland") ? : ""} + {!system_error && handler.xcall("current_is_wayland") ? : ""} +
    +
    +
    +
    +
    {translate('Control Remote Desktop')}
    + +
    + + +
    +
    + +
    + +
    +
    ); + } + + ["on click at button#connect"](){ + this.newRemote("connect"); + } + + ["on click at button#file-transfer"]() { + this.newRemote("file-transfer"); + } + + ["on keydown"](evt) { + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + var el = $("button#connect"); + view.focus = el; + el.click(); + // simulate button click effect, windows does not have this issue + el.classList.toggle("active", true); + el.timer(300, ()=> el.classList.toggle("active", false)); + } + } + } + + newRemote(type) { + createNewConnect(this.remote_id.value, type); + } +} + +class InstallMe extends Element { + render() { + return (
    + +
    {translate('install_tip')}
    +
    +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("goto_install"); + } +} + +const http = function() { + function makeRequest(httpverb) { + return function( params ) { + params.type = httpverb; + // TODO request + view.request(params); + }; + } + function download(from, to, ...args) { + // TODO #get + let rqp = { type:"get", url: from, toFile: to }; + let fn = 0; + let on = 0; + // TODO p in / p of? + for( let p in args ) + if( p instanceof Function ) + { + switch(++fn) { + case 1: rqp.success = p; break; + case 2: rqp.error = p; break; + case 3: rqp.progress = p; break; + } + } else if( p instanceof Object ) + { + switch(++on) { + case 1: rqp.params = p; break; + case 2: rqp.headers = p; break; + } + } + // TODO request + view.request(rqp); + } + + return { + get: makeRequest("get"), + post: makeRequest("post"), + put: makeRequest("put"), + del: makeRequest("delete"), + download: download + }; + +}(); + +class UpgradeMe extends Element { + render() { + let update_or_download = is_osx ? "download" : "update"; + return (
    +
    {translate('Status')}
    +
    {translate('Your installation is lower version.')}
    + +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("update_me"); + } +} + +class UpdateMe extends Element { + render() { + let update_or_download = "download"; // !is_win ? "download" : "update"; + return (
    +
    {translate('Status')}
    +
    There is a newer version of {handler.xcall("get_app_name")} ({handler.xcall("get_new_version")}) available.
    + +
    +
    ); + } + + ["on click at #install-me"]() { + handler.xcall("open_url","https://rustdesk.com"); + return; + if (!is_win) { + handler.xcall("open_url","https://rustdesk.com"); + return; + } + let url = software_update_url + '.' + handler.xcall("get_software_ext"); + let path = handler.xcall("get_software_store_path"); + let onsuccess = function(md5) { + this.$("#download-percent").content(translate("Installing ...")); + handler.xcall("update_me",path); + }; + let onerror = function(err) { + msgbox("custom-error", "Download Error", "Failed to download"); + }; + let onprogress = function(loaded, total) { + if (!total) total = 5 * 1024 * 1024; + let el = this.$("#download-percent"); + el.style.setProperty("display","block"); + el.content("Downloading %" + (loaded * 100 / total)); + }; + console.log("Downloading " + url + " to " + path); + http.download( + url, + document.url(path), + onsuccess, onerror, onprogress); + } +} + +class SystemError extends Element { + render() { + return (
    +
    {system_error}
    +
    ); + } +} + +class TrustMe extends Element { + render() { + return (
    +
    {translate('Configuration Permissions')}
    +
    {translate('config_acc')}
    + +
    ); + } + + ["on click at #trust-me"] () { + handler.xcall("is_process_trusted",true); + watch_trust(); + } +} + +class CanScreenRecording extends Element { + render() { + return (
    +
    {translate('Configuration Permissions')}
    +
    {translate('config_screen')}
    + +
    ); + } + + ["on click at #screen-recording"]() { + handler.xcall("is_can_screen_recording",true); + watch_trust(); + } +} + +class FixWayland extends Element { + render() { + return (
    +
    {translate('Warning')}
    +
    {translate('Login screen using Wayland is not supported')}
    + +
    ({translate('Reboot required')})
    +
    ); + } + + ["on click at #fix-wayland"] () { + handler.xcall("fix_login_wayland"); + app.componentUpdate(); + } +} + +class ModifyDefaultLogin extends Element { + render() { + return (
    +
    {translate('Warning')}
    +
    {translate('Current Wayland display server is not supported')}
    + +
    ({translate('Reboot required')})
    +
    ); + } + + ["on click at #modify-default-login"]() { + let r = handler.xcall("modify_default_login"); + if (r) { + msgbox("custom-error", "Error", r); + } + app.componentUpdate(); + } +} + +function watch_trust() { + // not use TrustMe::update, because it is buggy + let trusted = handler.xcall("is_process_trusted",false); + let el = $("div.trust-me"); + if (el) { + el.style.setProperty("display", trusted ? "none" : "block"); + } + // if (trusted) return; + // TODO dont have exit? + setTimeout(() => { + watch_trust() + }, 1000); +} + +class PasswordEyeArea extends Element { + render() { + return (
    + + {svg_eye} +
    ); + } + + ["on mouseenter"]() { + this.leaved = false; + setTimeout(()=> { + if (this.leaved) return; + this.$("input").value = handler.xcall("get_password"); + },300); + } + + ["on mouseleave"]() { + this.leaved = true; + this.$("input").value = "******"; + } +} + +class Password extends Element { + render() { + return (
    + + {svg_edit} +
    ); + } + + ["on click at svg#edit"](_,me) { + let menu = $("menu#edit-password-context"); + me.popup(menu); + } + + ["on click at li#refresh-password"] () { + handler.xcall("update_password"); + this.componentUpdate(); + } + + ["on click at li#set-password"] () { + // option .form .set-password ... + msgbox("custom-password", translate("Set Password"), "
    \ +
    " + translate('Password') + ":
    \ +
    " + translate('Confirmation') + ":
    \ +
    \ + ", + function(res=null) { + if (!res) return; + let p0 = (res.password || "").trim(); + let p1 = (res.confirmation || "").trim(); + if (p0.length < 6) { + return translate("Too short, at least 6 characters."); + } + if (p0 != p1) { + return translate("The confirmation is not identical."); + } + handler.xcall("update_password",p0); + this.componentUpdate(); + }); + } +} + +class ID extends Element { + render() { + return ; + } + + // TEST + // https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm + ["on change"]() { + let fid = formatId(this.value); + let d = this.value.length - (this.old_value || "").length; + this.old_value = this.value; + let start = this.xcall("selectionStart") || 0; + let end = this.xcall("selectionEnd"); + if (fid == this.value || d <= 0 || start != end) { + return; + } + // fix Caret position + this.value = fid; + let text_after_caret = this.old_value.substr(start); + let n = fid.length - formatId(text_after_caret).length; + this.xcall("setSelection", n, n); + } +} + +var reg = /^\d+$/; +export function formatId(id) { + id = id.replace(/\s/g, ""); + if (reg.test(id) && id.length > 3) { + let n = id.length; + let a = n % 3 || 3; + let new_id = id.substr(0, a); + for (let i = a; i < n; i += 3) { + new_id += " " + id.substr(i, 3); + } + return new_id; + } + return id; +} + +document.body.content(); + +document.on("ready",()=>{ + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + view.move(r[0], r[1], r[2], r[3]); + } else { + centerize(800, 600); + } + if (!handler.xcall("get_remote_id")) { + view.focus = $("#remote_id"); // TEST + } +}) + +document.on("unloadequest",(evt)=>{ + // evt.preventDefault() // can prevent window close + let [x, y, w, h] = view.box("rectw", "border", "desktop"); + handler.xcall("save_size",x, y, w, h); +}) + +// check connect status +setInterval(() => { + let tmp = !!handler.xcall("get_option","stop-service"); + if (tmp != service_stopped) { + service_stopped = tmp; + app.connect_status.componentUpdate(); + myIdMenu.componentUpdate(); + } + tmp = handler.xcall("get_connect_status"); + if (tmp[0] != connect_status) { + connect_status = tmp[0]; + app.connect_status.componentUpdate(); + } + if (tmp[1] != key_confirmed) { + key_confirmed = tmp[1]; + app.componentUpdate(); + } + if (tmp[2] && tmp[2] != my_id) { + console.log("id updated"); + app.componentUpdate(); + } + tmp = handler.xcall("get_error"); + if (system_error != tmp) { + system_error = tmp; + app.componentUpdate(); + } + tmp = handler.xcall("get_software_update_url"); + if (tmp != software_update_url) { + software_update_url = tmp; + app.componentUpdate(); + } + if (handler.xcall("recent_sessions_updated")) { + console.log("recent sessions updated"); + app.componentUpdate(); + } +}, 1000); diff --git a/src/ui/index.tis b/src/ui/index.tis deleted file mode 100644 index a099b95f9..000000000 --- a/src/ui/index.tis +++ /dev/null @@ -1,1681 +0,0 @@ -if (is_osx) view.windowBlurbehind = #light; -stdout.println("current platform:", OS); -stdout.println("is_xfce: ", is_xfce); - -// See default height in common.tis `msgbox()`. -const msgbox_default_height = 180; -const incoming_only_width = 180; - -const outgoing_only = handler.is_outgoing_only(); -const incoming_only = handler.is_incoming_only(); -const disable_installation = handler.is_disable_installation(); -const disable_account = handler.is_disable_account(); -const disable_settings = handler.is_disable_settings(); -const is_custom_client = handler.is_custom_client(); -const disable_ab = handler.is_disable_ab(); -const hide_server_settings = handler.get_builtin_option("hide-server-settings") == "Y"; -const hide_proxy_settings = handler.get_builtin_option("hide-proxy-settings") == "Y"; -const hide_websocket_settings = handler.get_builtin_option("hide-websocket-settings") == "Y"; -const hide_stop_service = handler.get_builtin_option("hide-stop-service") == "Y"; -const disable_change_permanent_password = handler.get_builtin_option("disable-change-permanent-password") == "Y"; -const disable_change_id = handler.get_builtin_option("disable-change-id") == "Y"; - -// html min-width, min-height not working on mac, below works for all -if (incoming_only) { - view.windowMinSize = (scaleIt(incoming_only_width), scaleIt((handler.is_installed() || disable_installation) ? 300 : 390)); -} else { - view.windowMinSize = (scaleIt(560), scaleIt(300)); -} - -var app; -var tmp = handler.get_connect_status(); -var connect_status = tmp[0]; -var service_stopped = handler.get_option("stop-service") == "Y"; -var disable_udp = handler.get_option("disable-udp") == "Y"; -var using_public_server = handler.using_public_server(); -var software_update_url = ""; -var key_confirmed = tmp[1]; -var system_error = ""; - -const default_option_lang = is_custom_client ? 'default' : ''; -const default_option_yes = is_custom_client ? 'Y' : ''; -const default_option_no = is_custom_client ? 'N' : ''; -const default_option_whitelist = is_custom_client ? ',' : ''; -const default_option_approve_mode = is_custom_client ? 'password-click' : ''; - -const grey_text_style = "color:#888;"; - -var svg_menu = - - - -; -var svg_refresh_password = ; - -var my_id = handler.get_id(); -function get_id() { - my_id = handler.get_id(); - return my_id; -} - -function get_msgbox_width(width=500) { - if (incoming_only) { - var maxw = scaleIt(incoming_only_width); - if (width > maxw) width = maxw; - } - return width; -} - -class ConnectStatus: Reactor.Component { - function render() { - return -
    - - {this.getConnectStatusStr()} - {service_stopped ? {translate('Start service')} : ""} -
    ; - } - - function getConnectStatusStr() { - if (service_stopped) { - return translate("Service is not running"); - } else if (connect_status == -1) { - return translate('not_ready_status'); - } else if (connect_status == 0) { - return translate('connecting_status'); - } - if (!handler.using_public_server()) return translate('Ready'); - return {translate("Ready")}, {translate("setup_server_tip")}; - } - - event click $(#start-service) () { - handler.set_option("stop-service", ""); - } - - event click $(#setup-server) () { - handler.open_url("https://rustdesk.com/blog/id-relay-set/"); - } -} - -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, force_relay); -} - -class ShareRdp: Reactor.Component { - function render() { - var rdp_shared_string = translate("Enable RDP session sharing"); - var cls = handler.is_share_rdp() ? "selected" : "line-through"; - return
  • {svg_checkmark}{rdp_shared_string}
  • ; - } - - function onClick() { - handler.set_share_rdp(!handler.is_share_rdp()); - this.update(); - } -} - -var direct_server; -class DirectServer: Reactor.Component { - function this() { - direct_server = this; - } - - function render() { - var text = translate("Enable direct IP access"); - var enabled = handler.get_option("direct-server") == "Y"; - var cls = enabled ? "selected" : "line-through"; - return
  • {svg_checkmark}{text}{enabled && }
  • ; - } - - function onClick() { - if (is_edit_rdp_port) { - is_edit_rdp_port = false; - return; - } - handler.set_option("direct-server", handler.get_option("direct-server") == "Y" ? default_option_no : "Y"); - this.update(); - } -} - -var myIdMenu; -var audioInputMenu; -class AudioInputs: Reactor.Component { - function this() { - audioInputMenu = this; - } - - function render() { - if (!this.show) return
  • ; - var inputs = handler.get_sound_inputs(); - if (is_win) inputs = ["System Sound"].concat(inputs); - if (!inputs.length) return
  • ; - var me = this; - self.timer(1ms, function() { me.toggleMenuState() }); - return
  • {translate('Audio Input')} - -
  • {svg_checkmark}{translate("Mute")}
  • -
    - {inputs.map(function(name) { - return
  • {svg_checkmark}{translate(name)}
  • ; - })} -
    -
  • ; - } - - function get_default() { - if (is_win) return "System Sound"; - return ""; - } - - function get_value() { - return handler.get_option("audio-input") || this.get_default(); - } - - function toggleMenuState() { - var el = this.$(li#enable-audio); - var enabled = handler.get_option(el.id) != "N"; - el.attributes.toggleClass("selected", !enabled); - var is_opt_fixed = handler.is_option_fixed("enable-audio"); - if (disable_settings || is_opt_fixed) { - el.state.disabled = true; - } - var v = this.get_value(); - for (var el in this.$$(menu#audio-input>li)) { - if (el.id == 'enable-audio') continue; - var selected = el.id == v; - el.attributes.toggleClass("selected", selected); - } - } - - event click $(menu#audio-input>li) (_, me) { - if (me.state.disabled) return; - var v = me.id; - if (v == 'enable-audio') { - handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : default_option_yes); - } else { - if (v == this.get_value()) return; - if (v == this.get_default()) v = ""; - handler.set_option("audio-input", v); - } - this.toggleMenuState(); - } -}; - -class Languages: Reactor.Component { - function render() { - var langs = JSON.parse(handler.get_langs()); - var me = this; - self.timer(1ms, function() { me.toggleMenuState() }); - return
  • {translate('Language')} - -
  • {svg_checkmark}Default
  • -
    - {langs.map(function(lang) { - return
  • {svg_checkmark}{lang[1]}
  • ; - })} -
    -
  • ; - } - - - function toggleMenuState() { - var cur = handler.get_local_option("lang") || "default"; - var is_opt_fixed = handler.is_option_fixed("lang"); - for (var el in this.$$(menu#languages>li)) { - var selected = cur == el.id; - el.attributes.toggleClass("selected", selected); - if (is_opt_fixed) { - el.state.disabled = true; - } - } - } - - event click $(menu#languages>li) (_, me) { - if (me.state.disabled) return; - var v = me.id; - if (v == "default") v = default_option_lang; - handler.set_local_option("lang", v); - app.update(); - this.toggleMenuState(); - } -} - -var enhancementsMenu; -class Enhancements: Reactor.Component { - function this() { - enhancementsMenu = this; - } - - function render() { - var has_hwcodec = handler.has_hwcodec(); - var has_vram = handler.has_vram(); - var support_remove_wallpaper = handler.support_remove_wallpaper(); - var me = this; - self.timer(1ms, function() { me.toggleMenuState() }); - return
  • {translate('Enhancements')} - - {(has_hwcodec || has_vram) ?
  • {svg_checkmark}{translate("Enable hardware codec")}
  • : ""} -
  • {svg_checkmark}{translate("Adaptive bitrate")} (beta)
  • -
  • {translate("Recording")}
  • - {support_remove_wallpaper ?
  • {svg_checkmark}{translate("Remove wallpaper during incoming sessions")}
  • : ""} -
  • {svg_checkmark}{translate("keep-awake-during-incoming-sessions-label")}
  • -
    -
  • ; - } - - function toggleMenuState() { - for (var el in $$(menu#enhancements-menu>li)) { - if (el.id && el.id.indexOf("enable-") == 0) { - var enabled = handler.get_option(el.id) != "N"; - el.attributes.toggleClass("selected", enabled); - var is_opt_fixed = handler.is_option_fixed(el.id); - if (is_opt_fixed) { - el.state.disabled = true; - } - } else if (el.id && el.id.indexOf("allow-") == 0) { - var enabled = handler.get_option(el.id) == "Y"; - el.attributes.toggleClass("selected", enabled); - var is_opt_fixed = handler.is_option_fixed(el.id); - if (is_opt_fixed) { - el.state.disabled = true; - } - } else if (el.id == "keep-awake-during-incoming-sessions") { - var enabled = handler.get_option(el.id) != "N"; - el.attributes.toggleClass("selected", enabled); - var is_opt_fixed = handler.is_option_fixed(el.id); - if (is_opt_fixed) { - el.state.disabled = true; - } - } - } - - } - - event click $(menu#enhancements-menu>li) (_, me) { - if (me.state.disabled) return; - var v = me.id; - if (v.indexOf("enable-") == 0) { - var set_value = handler.get_option(v) != 'N' ? 'N' : default_option_yes; - handler.set_option(v, set_value); - if (v == "enable-hwcodec" && set_value != 'N') { - handler.check_hwcodec(); - } - } else if (v.indexOf("allow-") == 0) { - handler.set_option(v, handler.get_option(v) == 'Y' ? default_option_no : 'Y'); - } else if (v == 'keep-awake-during-incoming-sessions') { - handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : default_option_yes); - } else if (v == 'screen-recording') { - var show_root_dir = is_win && handler.is_installed(); - var user_dir = handler.video_save_directory(false); - var root_dir = show_root_dir ? handler.video_save_directory(true) : ""; - var ts0 = handler.get_option("enable-record-session") != 'N' ? { checked: true } : {}; - var ts1 = handler.get_option("allow-auto-record-incoming") == 'Y' ? { checked: true } : {}; - var ts2 = handler.get_local_option("allow-auto-record-outgoing") == 'Y' ? { checked: true } : {}; - var is_opt_fixed_enable_record = handler.is_option_fixed("enable-record-session"); - var is_opt_fixed_auto_incoming = handler.is_option_fixed("allow-auto-record-incoming"); - var is_opt_fixed_auto_outgoing = handler.is_option_fixed("allow-auto-record-outgoing"); - var is_opt_fixed_video_dir = handler.is_option_fixed("video-save-directory"); - if (is_opt_fixed_enable_record) { ts0.disabled = true; ts0.style = grey_text_style; } - if (is_opt_fixed_auto_incoming) { ts1.disabled = true; ts1.style = grey_text_style; } - if (is_opt_fixed_auto_outgoing) { ts2.disabled = true; ts2.style = grey_text_style; } - msgbox("custom-recording", translate('Recording'), -
    -
    {translate('Enable recording session')}
    -
    {translate('Automatically record incoming sessions')}
    -
    {translate('Automatically record outgoing sessions')}
    -
    - {show_root_dir ?
    {translate("Incoming")}:  {root_dir}
    : ""} -
    {translate(show_root_dir ? "Outgoing" : "Directory")}:  {user_dir}
    - {is_opt_fixed_video_dir ? "" :
    } -
    -
    - , "", function(res=null) { - if (!res) return; - if (!is_opt_fixed_enable_record) handler.set_option("enable-record-session", res.enable_record_session ? default_option_yes : 'N'); - if (!is_opt_fixed_auto_incoming) handler.set_option("allow-auto-record-incoming", res.auto_record_incoming ? 'Y' : default_option_no); - if (!is_opt_fixed_auto_outgoing) handler.set_local_option("allow-auto-record-outgoing", res.auto_record_outgoing ? 'Y' : default_option_no); - if (!is_opt_fixed_video_dir) handler.set_local_option("video-save-directory", $(#folderPath).text); - }, msgbox_default_height, get_msgbox_width()); - } - this.toggleMenuState(); - } -} - -function getUserName() { - try { - return JSON.parse(handler.get_local_option("user_info")).name; - } catch(e) {} - return ''; -} - -function getAccountLabelWithHandle() { - try { - var user = JSON.parse(handler.get_local_option("user_info")); - var username = (user.name || '').trim(); - if (!username) { - return ''; - } - var displayName = (user.display_name || '').trim(); - if (!displayName || displayName == username) { - return username; - } - return displayName + " (@" + username + ")"; - } catch(e) {} - return ''; -} - -// Shared dialog functions -function open_custom_server_dialog() { - var configOptions = handler.get_options(); - var old_relay = configOptions["relay-server"] || ""; - var old_api = configOptions["api-server"] || ""; - var old_id = configOptions["custom-rendezvous-server"] || ""; - var old_key = configOptions["key"] || ""; - msgbox("custom-server", "ID/Relay Server", "
    \ -
    " + translate("ID Server") + ":
    \ -
    " + translate("Relay Server") + ":
    \ -
    " + translate("API Server") + ":
    \ -
    " + translate("Key") + ":
    \ -
    \ - ", "", function(res=null, show_progress) { - if (!res) return; - if (typeof show_progress === 'function') show_progress(); - var id = (res.id || "").trim(); - var relay = (res.relay || "").trim(); - var api = (res.api || "").trim().toLowerCase(); - var key = (res.key || "").trim(); - if (id == old_id && relay == old_relay && key == old_key && api == old_api) return; - if (id) { - var err = handler.test_if_valid_server(id, true); - if (err) { if (typeof show_progress === 'function') show_progress(false, translate("ID Server") + ": " + err); return; } - } - if (relay) { - var err = handler.test_if_valid_server(relay, true); - if (err) { if (typeof show_progress === 'function') show_progress(false, translate("Relay Server") + ": " + err); return; } - } - if (api) { - if (0 != api.indexOf("https://") && 0 != api.indexOf("http://")) { - if (typeof show_progress === 'function') show_progress(false, translate("API Server") + ": " + translate("invalid_http")); - return; - } - } - configOptions["custom-rendezvous-server"] = id; - configOptions["relay-server"] = relay; - configOptions["api-server"] = api; - configOptions["key"] = key; - handler.set_options(configOptions); - if (typeof show_progress === 'function') show_progress(-1); - }, 260, get_msgbox_width()); -} - -function open_whitelist_dialog() { - var is_opt_fixed = handler.is_option_fixed("whitelist"); - var v = handler.get_option("whitelist"); - var old_value = v == default_option_whitelist ? '' : v.split(",").join("\n"); - var type_str = is_opt_fixed ? "custom-whitelist-nook" : "custom-whitelist"; - var readonly_attr = is_opt_fixed ? " readonly=\"readonly\"" : ""; - var grey_class = is_opt_fixed ? " class=\"grey-text\"" : ""; - msgbox(type_str, translate("IP Whitelisting"), "
    \ - " + translate("whitelist_sep") + "
    \ - \ -
    \ - ", "", function(res=null, show_progress) { - if (!res) return; - if (typeof show_progress === 'function') show_progress(); - var value = (res.text || "").trim(); - if (value) { - var values = value.split(/[\s,;\n]+/g); - for (var ip in values) { - if (!ip.match(/^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$/) - && !ip.match(/^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$/)) { - if (typeof show_progress === 'function') show_progress(false, translate("Invalid IP") + ": " + ip); - return; - } - } - value = values.join("\n"); - } - if (value == old_value) return; - if (!value) value = default_option_whitelist; - handler.set_option("whitelist", value.replace("\n", ",")); - if (typeof show_progress === 'function') show_progress(-1); - }, 300, get_msgbox_width()); -} - -function open_proxy_dialog() { - var is_opt_fixed = handler.is_option_fixed("proxy-url"); - var socks5 = handler.get_socks() || {}; - var old_proxy = socks5[0] || ""; - var old_username = socks5[1] || ""; - var old_password = socks5[2] || ""; - var type_str = is_opt_fixed ? "custom-server-nook" : "custom-server"; - var greyStyle = is_opt_fixed ? grey_text_style : ""; - msgbox(type_str, "Socks5/Http(s) Proxy",
    -
    {translate("Server")}:
    -
    {translate("Username")}:
    -
    {translate("Password")}:{ is_opt_fixed ? : }
    -
    - , "", function(res=null, show_progress) { - if (!res) return; - if (typeof show_progress === 'function') show_progress(); - var proxy = (res.proxy || "").trim(); - var username = (res.username || "").trim(); - var password = (res.password || "").trim(); - if (proxy == old_proxy && username == old_username && password == old_password) return; - if (proxy) { - var domain_port = proxy; - var protocol_index = domain_port.indexOf('://'); - if (protocol_index !== -1) { - domain_port = domain_port.substring(protocol_index + 3); - } - var err = handler.test_if_valid_server(domain_port, false); - if (err) { if (typeof show_progress === 'function') show_progress(false, translate("Server") + ": " + err); return; } - } - handler.set_socks(proxy, username, password); - if (typeof show_progress === 'function') show_progress(-1); - }, 240, get_msgbox_width()); -} - -function updateTheme() { - var root_element = self; - if (handler.get_option("allow-darktheme") == "Y") { - // enable dark theme - root_element.attributes.toggleClass("darktheme", true); - } else { - // disable dark theme - root_element.attributes.toggleClass("darktheme", false); - } -} - -class MyIdMenu: Reactor.Component { - function this() { - myIdMenu = this; - } - - function render() { - return
    - {this.renderPop()} - ID{svg_menu} -
    ; - } - - function renderPop() { - var accountLabel = handler.get_local_option("access_token") ? getAccountLabelWithHandle() : ''; - return - - {!disable_settings &&
  • {svg_checkmark}{translate('Enable keyboard/mouse')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable clipboard')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable file transfer')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable camera')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable terminal')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable remote restart')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable TCP tunneling')}
  • } - {!disable_settings && is_win ?
  • {svg_checkmark}{translate('Enable blocking user input')}
  • : ""} - {!disable_settings && (handler.get_supported_privacy_mode_impls() != '[]') &&
  • {svg_checkmark}{translate('Enable privacy mode')}
  • } - {!disable_settings &&
  • {svg_checkmark}{translate('Enable LAN discovery')}
  • } - - - {!disable_settings &&
  • {svg_checkmark}{translate('Enable remote configuration modification')}
  • } - {!disable_settings &&
    } - {!disable_settings && !hide_server_settings &&
  • {translate('ID/Relay Server')}
  • } - {!disable_settings &&
  • {translate('IP Whitelisting')}
  • } - {!disable_settings && !hide_proxy_settings &&
  • {translate('Socks5/Http(s) Proxy')}
  • } - {!disable_settings && !hide_websocket_settings &&
  • {svg_checkmark}{translate('Use WebSocket')}
  • } - {!disable_settings && !using_public_server && !outgoing_only &&
  • {svg_checkmark}{translate('Disable UDP')}
  • } - {!disable_settings && !using_public_server &&
  • {svg_checkmark}{translate('Allow insecure TLS fallback')}
  • } -
    - {(!hide_stop_service || service_stopped) &&
  • {svg_checkmark}{translate("Enable service")}
  • } - {!disable_settings && is_win && handler.is_installed() ? : ""} - {!disable_settings && } - {!disable_settings && false && handler.using_public_server() &&
  • {svg_checkmark}{translate('Always connect via relay')}
  • } - {!disable_change_id && handler.is_ok_change_id() ?
    : ""} - {!disable_account && (accountLabel ? -
  • {translate('Logout')} ({accountLabel})
  • : -
  • {translate('Login')}
  • )} - {!disable_change_id && !disable_settings && handler.is_ok_change_id() && key_confirmed && connect_status > 0 ?
  • {translate('Change ID')}
  • : ""} -
    -
  • {svg_checkmark}{translate('Dark Theme')}
  • - - {disable_installation ? "" :
  • {svg_checkmark}{translate('Auto update')}
  • } -
  • {translate('About')} {" "}{handler.get_app_name()}
  • - - ; - } - - event click $(svg#menu) (_, me) { - this.showSettingMenu(); - } - - function showSettingMenu() { - audioInputMenu.update({ show: true }); - this.toggleMenuState(); - if (direct_server) direct_server.update(); - var menu = this.$(menu#config-options); - this.$(svg#menu).popup(menu); - } - - event click $(li#login) () { - login(); - } - - event click $(li#logout) () { - logout(); - } - - function toggleMenuState() { - for (var el in $$(menu#config-options>li)) { - var id = el.id; - if (!id) continue; - var is_opt_fixed = handler.is_option_fixed(id); - if (id.indexOf("enable-") == 0) { - var enabled = handler.get_option(id) != "N"; - el.attributes.toggleClass("selected", enabled); - el.attributes.toggleClass("line-through", !enabled); - } else if (id.indexOf("allow-") == 0) { - var enabled = handler.get_option(id) == "Y"; - el.attributes.toggleClass("selected", enabled); - el.attributes.toggleClass("line-through", !enabled); - } else if (id == "whitelist") { - // whitelist should be clickable even when fixed (to view the content) - // The dialog will show readonly textarea and no OK button when fixed - continue; - } - if (is_opt_fixed) { - el.state.disabled = true; - } - } - } - - function showAbout() { - var name = handler.get_app_name(); - msgbox("custom-nocancel-nook-hasclose", translate("About") + " " + name, "
    \ -
    Version: " + handler.get_version() + " \ -
    Fingerprint: " + handler.get_fingerprint() + " \ -
    " + translate("Privacy Statement") + "
    \ -
    " + translate("Website") + "
    \ -
    Copyright © 2025 Purslane Ltd.\ -
    " + handler.get_license() + " \ -

    " + translate("Slogan_tip") + "

    \ -
    \ -
    ", "", function(el) { - if (el && el.attributes) { - handler.open_url(el.attributes['url']); - }; - }, 400, get_msgbox_width()); - } - - event click $(menu#config-options>li) (_, me) { - if (me.state.disabled) return; - if (me.id && me.id.indexOf("enable-") == 0) { - handler.set_option(me.id, handler.get_option(me.id) == "N" ? default_option_yes : "N"); - } - if (me.id && me.id.indexOf("allow-") == 0) { - handler.set_option(me.id, handler.get_option(me.id) == "Y" ? default_option_no : "Y"); - } - if (me.id == "whitelist") { - open_whitelist_dialog(); - } else if (me.id == "custom-server") { - open_custom_server_dialog(); - } else if (me.id == "socks5-server") { - open_proxy_dialog(); - } else if (me.id == "disable-udp") { - handler.set_option("disable-udp", handler.get_option("disable-udp") == "Y" ? "N" : "Y"); - } else if (me.id == "stop-service") { - handler.set_option("stop-service", service_stopped ? default_option_no : "Y"); - } else if (me.id == "change-id") { - var id_label_width = incoming_only ? "50px" : "100px"; - var input_width = incoming_only ? (incoming_only_width - 20) + "px" : "250px"; - msgbox("custom-id", translate("Change ID"), "
    \ -
    " + translate('id_change_tip') + "
    \ -
    ID:
    \ -
    \ - ", "", function(res=null, show_progress) { - if (!res) return; - show_progress(); - var id = (res.id || "").trim(); - if (!id) return; - if (id == my_id) return; - handler.change_id(id); - function check_status() { - var status = handler.get_async_job_status(); - if (status == " ") self.timer(0.1s, check_status); - else { - if (status) show_progress(false, translate(status)); - else show_progress(-1); - } - } - check_status(); - return " "; - }, msgbox_default_height, get_msgbox_width()); - } else if (me.id == "allow-darktheme") { - updateTheme(); - } else if (me.id == "about") { - this.showAbout() - } - } -} - -var is_edit_direct_access_port; -class EditDirectAccessPort: Reactor.Component { - function render() { - return {svg_edit}; - } - - function onMouse(evt) { - if (evt.type == Event.MOUSE_DOWN) { - is_edit_direct_access_port = true; - editDirectAccessPort(); - } - } -} - -function editDirectAccessPort() { - var is_opt_fixed = handler.is_option_fixed("direct-access-port"); - var p0 = handler.get_option('direct-access-port'); - var greyStyle = is_opt_fixed ? grey_text_style : ""; - var port = p0 ? : - ; - var type_str = is_opt_fixed ? "custom-direct-access-port-nook" : "custom-direct-access-port"; - msgbox(type_str, translate('Direct IP Access Settings'),
    -
    {translate('Port')}:{port}
    -
    , "", function(res=null) { - if (!res) return; - var p = (res.port || '').trim(); - if (p) { - p = p.toInteger(); - if (!(p > 0)) { - return translate("Invalid port"); - } - p = p + ''; - } - if (p != p0) handler.set_option('direct-access-port', p); - }, msgbox_default_height, get_msgbox_width()); -} - -class App: Reactor.Component -{ - function this() { - app = this; - } - - function render() { - var is_can_screen_recording = handler.is_can_screen_recording(false); - return -
    -
    -
    - {is_custom_client && handler.get_builtin_option("hide-powered-by-me") != "Y" ?
    {translate('powered_by_me')}
    : ""} -
    - {translate('Your Desktop')} - {outgoing_only ? {svg_menu} : ""} -
    -
    {outgoing_only ? translate('outgoing_only_desk_tip') : translate('desk_tip')}
    - {outgoing_only ?
    : ""} - {!outgoing_only &&
    - - {key_confirmed ? : translate("Generating ...")} -
    } - {!outgoing_only && } -
    - {(!is_win || handler.is_installed() || disable_installation) ? "" : } - {software_update_url && !disable_installation ? : ""} - {is_win && handler.is_installed() && !software_update_url && handler.is_installed_lower_version() && !disable_installation ? : ""} - {is_can_screen_recording ? "": } - {is_can_screen_recording && !handler.is_process_trusted(false) ? : ""} - {!service_stopped && is_can_screen_recording && handler.is_process_trusted(false) && handler.is_installed() && !handler.is_installed_daemon(false) ? : ""} - {system_error ? : ""} - {!system_error && handler.is_login_wayland() && !handler.current_is_wayland() ? : ""} - {!system_error && handler.current_is_wayland() ? : ""} - {incoming_only ? : ""} -
    - {!incoming_only &&
    -
    -
    -
    {translate('Control Remote Desktop')}
    - -
    - - -
    -
    - -
    - {!outgoing_only ? : ""} -
    } -
    -
    ; - } - - event click $(button#connect) { - this.newRemote("connect"); - } - - event click $(button#file-transfer) { - this.newRemote("file-transfer"); - } - - function newRemote(type) { - createNewConnect(this.remote_id.value, type); - } -} - -class InstallMe: Reactor.Component { - function render() { - return
    - -
    {translate('install_tip')}
    -
    -
    ; - } - - event click $(#install-me) { - handler.goto_install(); - } -} - -function download(from, to, args..) { - var rqp = { type:#get, url: from, toFile: to }; - var fn = 0; - var on = 0; - for( var p in args ) { - if( p instanceof Function ) { - switch(++fn) { - case 1: rqp.success = p; break; - case 2: rqp.error = p; break; - case 3: rqp.progress = p; break; - } - } else if( p instanceof Object ) { - switch(++on) { - case 1: rqp.params = p; break; - case 2: rqp.headers = p; break; - } - } - } - view.request(rqp); -} - -// current running version is higher than installed -class UpgradeMe: Reactor.Component { - function render() { - var update_or_download = is_osx ? "download" : "update"; - return
    -
    {translate('Status')}
    -
    {translate('Your installation is lower version.')}
    -
    {translate('Click to upgrade')}
    -
    ; - } - - event click $(#install-me) { - handler.update_me(""); - } -} - -class UpdateMe: Reactor.Component { - function render() { - var update_or_download = "download"; // !is_win ? "download" : "update"; - return
    -
    {translate('Status')}
    -
    There is a newer version of {handler.get_app_name()} ({handler.get_new_version()}) available.
    - {is_custom_client - ?
    {translate('Enable \"Auto update\" or contact your administrator for the latest version.')}
    - :
    {translate('Click to ' + update_or_download)}
    } -
    -
    ; - } - - event click $(#install-me) { - handler.open_url("https://rustdesk.com/download"); - return; - if (!is_win) { - handler.open_url("https://rustdesk.com"); - return; - } - var url = software_update_url + '.' + handler.get_software_ext(); - var path = handler.get_software_store_path(); - var onsuccess = function(md5) { - $(#download-percent).content(translate("Installing ...")); - handler.update_me(path); - }; - var onerror = function(err) { - msgbox("custom-error", "Download Error", "Failed to download"); - }; - var onprogress = function(loaded, total) { - if (!total) total = 5 * 1024 * 1024; - var el = $(#download-percent); - el.style.set{display: "block"}; - el.content("Downloading %" + (loaded * 100 / total)); - }; - stdout.println("Downloading " + url + " to " + path); - download( - url, - self.url(path), - onsuccess, onerror, onprogress); - } -} - -class SystemError: Reactor.Component { - function render() { - return
    -
    {system_error}
    -
    ; - } -} - -class TrustMe: Reactor.Component { - function render() { - return
    -
    {translate('Permissions')}
    -
    {translate('config_acc')}
    -
    {translate('Configure')}
    -
    {translate('Help')}
    -
    ; - } - - event click $(#trust-me) { - handler.is_process_trusted(true); - watch_trust(); - } - - event click $(#help-me) { - handler.open_url(translate("doc_mac_permission")); - } -} - -class CanScreenRecording: Reactor.Component { - function render() { - return
    -
    {translate('Permissions')}
    -
    {translate('config_screen')}
    -
    {translate('Configure')}
    -
    {translate('Help')}
    -
    ; - } - - event click $(#screen-recording) { - handler.is_can_screen_recording(true); - watch_screen_recording(); - } - - event click $(#help-me) { - handler.open_url(translate("doc_mac_permission")); - } -} - -class InstallDaemon: Reactor.Component { - function render() { - return
    - -
    {translate('install_daemon_tip')}
    -
    {translate('Install')}
    -
    ; - } - - event click $(#install-me) { - handler.is_installed_daemon(true); - } -} - -class FixWayland: Reactor.Component { - function render() { - return
    -
    {translate('Warning')}
    -
    {translate('Login screen using Wayland is not supported')}
    -
    {translate('Help')}
    -
    ; - } - - event click $(#help-me) { - handler.open_url(translate("doc_fix_wayland")); - } -} - -class ModifyDefaultLogin: Reactor.Component { - function render() { - return
    -
    {translate('Warning')}
    -
    {translate('wayland_experiment_tip')}
    -
    {translate('Help')}
    -
    ; - } - - event click $(#help-me) { - handler.open_url(translate("doc_fix_wayland")); - } -} - -function watch_trust() { - // not use TrustMe::update, because it is buggy - var trusted = handler.is_process_trusted(false); - var el = $(div#trust-me-box); - if (el) { - el.style.set { - display: trusted ? "none" : "block", - }; - } - if (trusted) { - app.update(); - return; - } - self.timer(1s, watch_trust); -} - -function watch_screen_recording() { - var trusted = handler.is_can_screen_recording(false); - var el = $(div#screen-recording-box); - if (el) { - el.style.set { - display: trusted ? "none" : "block", - }; - } - if (trusted) { - app.update(); - return; - } - self.timer(1s, watch_screen_recording); -} - -class PasswordEyeArea : Reactor.Component { - render() { - var method = handler.get_option('verification-method'); - var mode= handler.get_option('approve-mode'); - var hide_one_time = mode == 'click' || method == 'use-permanent-password'; - var value = hide_one_time ? "-" : password_cache[0]; - return -
    - - {hide_one_time ? "" : svg_refresh_password} -
    ; - } - - event click $(svg#refresh-password) (_, me) { - handler.update_temporary_password(); - this.update(); - } -} - -var temporaryPasswordLengthMenu; -class TemporaryPasswordLengthMenu: Reactor.Component { - function this() { - temporaryPasswordLengthMenu = this; - } - - function render() { - if (!this.show) return
  • ; - var me = this; - var method = handler.get_option('verification-method'); - self.timer(1ms, function() { me.toggleMenuState() }); - return
  • {translate("One-time password length")} - -
  • {svg_checkmark}6
  • -
  • {svg_checkmark}8
  • -
  • {svg_checkmark}10
  • -
    -
  • ; - } - - function toggleMenuState() { - var is_opt_fixed = handler.is_option_fixed('temporary-password-length'); - var length = handler.get_option("temporary-password-length"); - var index = ['6', '8', '10'].indexOf(length); - if (index < 0) index = 0; - for (var (i, el) in this.$$(menu#temporary-password-length>li)) { - el.attributes.toggleClass("selected", i == index); - if (is_opt_fixed) { - el.state.disabled = true; - } - } - } - - event click $(menu#temporary-password-length>li) (_, me) { - if (me.state.disabled) return; - var length = me.id.substring('temporary-password-length-'.length); - var old_length = handler.get_option('temporary-password-length'); - if (length != old_length) { - handler.set_option('temporary-password-length', length); - handler.update_temporary_password(); - this.toggleMenuState(); - passwordArea.update(); - } - } -} - -var passwordArea; -class PasswordArea: Reactor.Component { - function this() { - passwordArea = this; - } - - function render() { - var me = this; - self.timer(1ms, function() { me.toggleMenuState() }); - return -
    -
    {translate('One-time Password')}
    -
    - {this.renderPop()} - - {!disable_settings && svg_edit} -
    -
    ; - } - - function renderPop() { - var method = handler.get_option('verification-method'); - var approve_mode= handler.get_option('approve-mode'); - var show_password = approve_mode != 'click'; - var has_local_password = handler.is_local_permanent_password_set(); - return -
  • {svg_checkmark}{translate('Accept sessions via password')}
  • -
  • {svg_checkmark}{translate('Accept sessions via click')}
  • -
  • {svg_checkmark}{translate('Accept sessions via both')}
  • - { !show_password ? '' :
    } - { !show_password ? '' :
  • {svg_checkmark}{translate('Use one-time password')}
  • } - { !show_password ? '' :
  • {svg_checkmark}{translate('Use permanent password')}
  • } - { !show_password ? '' :
  • {svg_checkmark}{translate('Use both passwords')}
  • } - { !show_password ? '' :
    } - { !show_password || disable_change_permanent_password ? '' :
  • {translate('Set permanent password')}
  • } - { !show_password || disable_change_permanent_password ? '' :
  • {translate('Clear permanent password')}
  • } - { !show_password ? '' : } -
    -
  • {svg_checkmark}{translate('enable-2fa-title')}
  • - ; - } - - function toggleMenuState() { - var mode= handler.get_option('approve-mode'); - var mode_id; - if (mode == 'password') - mode_id = 'approve-mode-password'; - else if (mode == 'click') - mode_id = 'approve-mode-click'; - else - mode_id = 'approve-mode-both'; - var pwd_id = handler.get_option('verification-method'); - if (pwd_id != 'use-temporary-password' && pwd_id != 'use-permanent-password') - pwd_id = 'use-both-passwords'; - var has_valid_2fa = handler.has_valid_2fa(); - for (var el in this.$$(menu#edit-password-context>li)) { - if (el.id.indexOf("approve-mode-") == 0) { - el.attributes.toggleClass("selected", el.id == mode_id); - if (handler.is_option_fixed('approve-mode')) { - el.state.disabled = true; - } - } - if (el.id.indexOf("use-") == 0) { - el.attributes.toggleClass("selected", el.id == pwd_id); - if (handler.is_option_fixed('verification-method')) { - el.state.disabled = true; - } - } - if (el.id == "clear-password") { - var has_local_password = handler.is_local_permanent_password_set(); - el.state.disabled = !has_local_password; - } - if (el.id == "tfa") - el.attributes.toggleClass("selected", has_valid_2fa); - } - } - - event click $(svg#edit) (_, me) { - var approve_mode= handler.get_option('approve-mode'); - var show_password = approve_mode != 'click'; - if(show_password && temporaryPasswordLengthMenu) temporaryPasswordLengthMenu.update({show: true }); - var menu = $(menu#edit-password-context); - me.popup(menu); - } - - event click $(li#set-password) { - var me = this; - var has_local_password = handler.is_local_permanent_password_set(); - var permanent_password_set = handler.is_permanent_password_set(); - var password_hidden_tip = translate('password-hidden-tip'); - var preset_password_tip = translate('preset-password-in-use-tip'); - var password_tip = ""; - if (has_local_password) { - password_tip = "
    [!] " + password_hidden_tip + "
    "; - } else if (permanent_password_set) { - password_tip = "
    [!] " + preset_password_tip + "
    "; - } - msgbox("custom-password", translate("Set Password"), "
    \ -
    " + translate('Password') + ":
    \ -
    " + translate('Confirmation') + ":
    \ - " + password_tip + " \ -
    \ - ", "", function(res=null) { - if (!res) return; - var p0 = (res.password || "").trim(); - var p1 = (res.confirmation || "").trim(); - if (p0.length == 0 && p1.length == 0) { - return " "; - } - if (p0.length < 6 && p0.length != 0) { - return translate("Too short, at least 6 characters."); - } - if (p0 != p1) { - return translate("The confirmation is not identical."); - } - handler.set_permanent_password(p0); - me.update(); - }, msgbox_default_height, get_msgbox_width()); - self.timer(30ms, function() { - updateSetPasswordSubmitState(); - }); - } - - event click $(li#clear-password) { - if (this.$(li#clear-password).state.disabled) return; - handler.set_permanent_password(""); - this.update(); - } - - event click $(menu#edit-password-context>li) (_, me) { - if (me.state.disabled) return; - if (me.id.indexOf('use-') == 0) { - handler.set_option('verification-method', me.id); - this.toggleMenuState(); - passwordArea.update(); - } else if (me.id.indexOf('approve-mode') == 0) { - var approve_mode; - if (me.id == 'approve-mode-password') - approve_mode = 'password'; - else if (me.id == 'approve-mode-click') - approve_mode = 'click'; - else - approve_mode = default_option_approve_mode; - handler.set_option('approve-mode', approve_mode); - this.toggleMenuState(); - passwordArea.update(); - } - } - - event click $(li#tfa) { - var me = this; - var has_valid_2fa = handler.has_valid_2fa(); - if (has_valid_2fa) { - handler.set_option('2fa', ''); - me.update(); - } else { - var new2fa = handler.generate2fa(); - var src = handler.generate_2fa_img_src(new2fa); - msgbox("custom-2fa-setting", translate('enable-2fa-title'), -
    -
    {translate('enable-2fa-desc')}
    - -
    -
    - , "", function(res=null) { - if (!res) return; - if (!res.code) return; - if (!handler.verify2fa(res.code)) { - return translate('wrong-2fa-code'); - } - me.update(); - }, 400, get_msgbox_width()); - } - } -} - -var password_cache = ["","","",""]; -function updatePasswordArea() { - self.timer(1s, function() { - var temporary_password = handler.temporary_password(); - var verification_method = handler.get_option('verification-method'); - var temporary_password_length = handler.get_option('temporary-password-length'); - var approve_mode = handler.get_option('approve-mode'); - var update = false; - if (password_cache[0] != temporary_password) { - password_cache[0] = temporary_password; - update = true; - } - if (password_cache[1] != verification_method) { - password_cache[1] = verification_method; - update = true; - } - if (password_cache[2] != temporary_password_length) { - password_cache[2] = temporary_password_length; - update = true; - } - if (password_cache[3] != approve_mode) { - password_cache[3] = approve_mode; - update = true; - } - if (update && passwordArea) passwordArea.update(); - updatePasswordArea(); - }); -} -if (!outgoing_only) updatePasswordArea(); - -function updateSetPasswordSubmitState() { - var dialog = $(#msgbox); - if (!dialog) return; - var password = dialog.$(input[name='password']); - var confirmation = dialog.$(input[name='confirmation']); - var submit = dialog.$(button#submit); - if (!password || !confirmation || !submit) return; - var can_submit = (password.value || "").trim().length > 0 || - (confirmation.value || "").trim().length > 0; - submit.state.disabled = !can_submit; -} - -class ID: Reactor.Component { - function render() { - return ; - } - - // https://github.com/c-smile/sciter-sdk/blob/master/doc/content/sciter/Event.htm - event change { - var fid = formatId(this.value); - var d = this.value.length - (this.old_value || "").length; - this.old_value = this.value; - var start = this.xcall(#selectionStart) || 0; - var end = this.xcall(#selectionEnd); - if (fid == this.value || d <= 0 || start != end) { - return; - } - // fix Caret position - this.value = fid; - var text_after_caret = this.old_value.substr(start); - var n = fid.length - formatId(text_after_caret).length; - this.xcall(#setSelection, n, n); - } -} - -var reg = /^\d+$/; -function formatId(id) { - id = id.replace(/\s/g, ""); - if (reg.test(id) && id.length > 3) { - var n = id.length; - var a = n % 3 || 3; - var new_id = id.substr(0, a); - for (var i = a; i < n; i += 3) { - new_id += " " + id.substr(i, 3); - } - return new_id; - } - return id; -} - -event keydown (evt) { - if (view.focus && view.focus.id != 'remote_id') { - return; - } - if (!evt.shortcutKey) { - if (isEnterKey(evt)) { - var el = $(button#connect); - view.focus = el; - el.sendEvent("click"); - // simulate button click effect, windows does not have this issue - el.attributes.toggleClass("active", true); - self.timer(0.3s, function() { - el.attributes.toggleClass("active", false); - }); - } - } -} - -event keyup $(#msgbox input[name='password']) { - updateSetPasswordSubmitState(); -} - -event keyup $(#msgbox input[name='confirmation']) { - updateSetPasswordSubmitState(); -} - -event change $(#msgbox input[name='password']) { - updateSetPasswordSubmitState(); -} - -event change $(#msgbox input[name='confirmation']) { - updateSetPasswordSubmitState(); -} - -$(body).content(
    ); - -event click $(#powered-by) { - handler.open_url("https://rustdesk.com"); -} - -event click $(#open-settings) (_, me) { - showSettings(); -} - -// Event handlers for outgoing_only mode (when menu items are in main UI, not in MyIdMenu) -event click $(li#custom-server) (_, me) { - if (!outgoing_only) return; - open_custom_server_dialog(); -} - -event click $(li#whitelist) (_, me) { - if (!outgoing_only) return; - open_whitelist_dialog(); -} - -event click $(li#socks5-server) (_, me) { - if (!outgoing_only) return; - open_proxy_dialog(); -} - -event click $(li#login) (_, me) { - if (!outgoing_only) return; - login(); -} - -function self.closing() { - var (x, y, w, h) = view.box(#rectw, #border, #screen); - handler.closing(x, y, w, h); - return true; -} - -function self.ready() { - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - if (r[2] >= sw && r[3] >= sh) { - self.timer(1ms, function() { view.windowState = View.WINDOW_MAXIMIZED; }); - } else { - view.move(r[0], r[1], incoming_only ? scaleIt(incoming_only_width) : r[2], r[3]); - } - } else { - centerize(scaleIt(incoming_only ? incoming_only_width : 800), scaleIt(incoming_only ? 390 : 600)); - } - if (!handler.get_remote_id()) { - view.focus = $(#remote_id); - } - refreshCurrentUser(); - updateTheme(); -} - -function showAbout() { - myIdMenu.showAbout(); -} - -function showSettings() { - if ($(#overlay).style#display == 'block') return; - var menu = myIdMenu.$(menu#config-options); - var anchor = $(#open-settings); - if (!anchor) anchor = myIdMenu.$(svg#menu); - // show immediately at button, then update menu state asynchronously - anchor.popup(menu); - self.timer(1ms, function() { - audioInputMenu.update({ show: true }); - myIdMenu.toggleMenuState(); - if (direct_server) direct_server.update(); - }); -} - -function checkConnectStatus() { - handler.check_mouse_time(); // trigger connection status updater - self.timer(1s, function() { - var tmp = handler.get_option("stop-service") == "Y"; - if (tmp != service_stopped) { - service_stopped = tmp; - app.update(); - } - tmp = handler.using_public_server(); - if (tmp != using_public_server) { - using_public_server = tmp; - app.connect_status.update(); - } - tmp = handler.get_connect_status(); - if (tmp[0] != connect_status) { - connect_status = tmp[0]; - app.connect_status.update(); - myIdMenu.update(); - } - if (tmp[1] != key_confirmed) { - key_confirmed = tmp[1]; - app.update(); - } - if (tmp[2] && tmp[2] != my_id) { - stdout.println("id updated"); - app.update(); - } - tmp = handler.get_error(); - if (system_error != tmp) { - system_error = tmp; - app.update(); - } - tmp = handler.get_software_update_url(); - if (tmp != software_update_url) { - software_update_url = tmp; - app.update(); - } - if (handler.recent_sessions_updated()) { - stdout.println("recent sessions updated"); - updateAbPeer(); - app.update(); - } - tmp = handler.get_option("disable-udp") == "Y"; - if (tmp != disable_udp) { - disable_udp = tmp; - app.update(); - } - check_if_overlay(); - checkConnectStatus(); - }); -} - -var enter = false; -function self.onMouse(evt) { - switch(evt.type) { - case Event.MOUSE_ENTER: - enter = true; - check_if_overlay(); - break; - case Event.MOUSE_LEAVE: - $(#overlay).style#display = 'none'; - enter = false; - break; - } -} - -function check_if_overlay() { - var enabled; - var is_enabled_by_control_permissions = handler.is_remote_modify_enabled_by_control_permissions(); - if (is_enabled_by_control_permissions == "true") { - enabled = true; - } else if (is_enabled_by_control_permissions == "false") { - enabled = false; - } else { - enabled = handler.get_option('allow-remote-config-modification') == 'Y'; - } - if (!enabled) { - var time0 = getTime(); - handler.check_mouse_time(); - self.timer(120ms, function() { - if (!enter) return; - var d = time0 - handler.get_mouse_time(); - if (d < 120) $(#overlay).style#display = 'block'; - }); - } -} - -checkConnectStatus(); - -function set_local_user_info(user) { - var user_info = {name: user.name}; - if (user.display_name) { - user_info.display_name = user.display_name; - } - if (user.avatar) { - user_info.avatar = user.avatar; - } - if (user.status) { - user_info.status = user.status; - } - handler.set_local_option("user_info", JSON.stringify(user_info)); -} - -function login() { - var name0 = getUserName(); - var pass0 = ''; - msgbox("custom-login", translate('Login'),
    -
    {translate('Username')}:
    -
    {translate('Password')}:
    -
    , "", function(res=null, show_progress) { - if (!res) return; - show_progress(); - var name = (res.username || '').trim(); - if (!name) { - show_progress(false, translate("Username missed")); - return " "; - } - var pass = (res.password || '').trim(); - if (!pass) { - show_progress(false, translate("Password missed")); - return " "; - } - abLoading = true; - var url = handler.get_api_server(); - httpRequest(url + "/api/login", #post, {username: name, password: pass, id: my_id, uuid: handler.get_uuid(), type: 'account', deviceInfo: getDeviceInfo()}, function(data) { - if (data.error) { - abLoading = false; - var err = translate(data.error); - show_progress(false, err); - return; - } - if (data.type == 'email_check') { - abLoading = false; - show_progress(-1); - on_2fa_check(data); - return; - } - handler.set_local_option("access_token", data.access_token); - set_local_user_info(data.user); - show_progress(-1); - myIdMenu.update(); - getAb(); - }, function(err, status) { - abLoading = false; - err = translate(err); - if (url.indexOf('rustdesk') < 0) err = url + ', ' + err; - show_progress(false, err); - }); - return " "; - }, msgbox_default_height, get_msgbox_width()); -} - -function on_2fa_check(last_msg) { - const isEmailCheck = !last_msg.tfa_type || last_msg.tfa_type == 'email_check'; - const secret = last_msg.secret; - const emailHint = last_msg.user.email; - - msgbox("custom-2fa-verification-code", translate('Verification code'),
    - { isEmailCheck &&
    {translate('Email')}:{emailHint}
    } -
    {translate(isEmailCheck ? 'Verification code' : '2FA code')}:
    - { isEmailCheck &&
    {translate('verification_tip')}
    } -
    , "", - function(res=null, show_progress) { - if (!res) return; - show_progress(); - var code = (res.verification_code || '').trim(); - if (!code || code.length < 6) { - show_progress(false, translate("Too short, at least 6 characters.")); - return " "; - } - abLoading = true; - var url = handler.get_api_server(); - const loginData = { - username: last_msg.user.name, - id: my_id, - uuid: handler.get_uuid(), - type: 'email_code', - verificationCode: code, - tfaCode: isEmailCheck ? '' : code, - secret: secret, - deviceInfo: getDeviceInfo() - }; - httpRequest(url + "/api/login", #post, loginData, - function(data) { - if (data.error) { - abLoading = false; - show_progress(false, data.error); - return; - } - handler.set_local_option("access_token", data.access_token); - set_local_user_info(data.user); - show_progress(-1); - myIdMenu.update(); - getAb(); - }, - function(err, status) { - abLoading = false; - err = translate(err); - if (url.indexOf('rustdesk') < 0) err = url + ', ' + err; - show_progress(false, err); - } - ); - return " "; - }, - msgbox_default_height, - get_msgbox_width() - ); -} - -function reset_token() { - handler.set_local_option("access_token", ""); - handler.set_local_option("user_info", ""); - handler.set_local_option("selected-tags", ""); - myIdMenu.update(); - resetAb(); - if (abComponent) { - abComponent.update(); - } -} - -function logout() { - var url = handler.get_api_server(); - httpRequest(url + "/api/logout", #post, {id: my_id, uuid: handler.get_uuid()}, function(data) { - }, function(err, status) { - msgbox("custom-error", translate('Error'), err); - }, getHttpHeaders()); - reset_token(); -} - -function refreshCurrentUser() { - var token = handler.get_local_option("access_token"); - if (!token) { return; } - abLoading = true; - abError = ""; - app.update(); - httpRequest(handler.get_api_server() + "/api/currentUser", #post, {id: my_id, uuid: handler.get_uuid()}, function(data) { - if (data.error) { - if (data.error == 'Invalid token') { - reset_token(); - } - handleAbError(data.error); - return; - } - if (!handler.verify_login(data.verifier, token)) { - handleAbError("Please update your self-hosting server Pro to latest version"); - return; - } - set_local_user_info(data); - myIdMenu.update(); - getAb(); - }, function(err, status) { - if (status == 401 || status == 400) { - reset_token(); - } - handleAbError(err); - }, getHttpHeaders()); -} - -function getHttpHeaders() { - return "Authorization: Bearer " + handler.get_local_option("access_token"); -} - -function getDeviceInfo() { - return JSON.parse(handler.get_login_device_info()); -} diff --git a/src/ui/install.html b/src/ui/install.html index bd9653e97..c86861ba0 100644 --- a/src/ui/install.html +++ b/src/ui/install.html @@ -5,7 +5,7 @@ div.content { size: *; background: white; - padding:2em 8em; + padding:2em 10em; border-spacing: 1em; } input { diff --git a/src/ui/install.js b/src/ui/install.js new file mode 100644 index 000000000..59c104354 --- /dev/null +++ b/src/ui/install.js @@ -0,0 +1,45 @@ +function self.ready() { + centerize(800, 600); +} + +class Install: Reactor.Component { + function render() { + return
    +
    {translate('Installation')}
    +
    {translate('Installation Path')} {": "}
    +
    {translate('Create start menu shortcuts')}
    +
    {translate('Create desktop icon')}
    +
    {translate('End-user license agreement')}
    +
    {translate('agreement_tip')}
    +
    +
    + + + +
    +
    ; + } + + event click $(#cancel) { + view.close(); + } + + event click $(#aggrement) { + view.open_url("http://rustdesk.com/privacy"); + } + + event click $(#submit) { + for (var el in $$(button)) el.state.disabled = true; + $(progress).style.set{ display: "inline-block" }; + var args = ""; + if ($(#startmenu).value) { + args += "startmenu "; + } + if ($(#desktopicon).value) { + args += "desktopicon "; + } + view.install_me(args); + } +} + +$(body).content(); diff --git a/src/ui/install.tis b/src/ui/install.tis deleted file mode 100644 index fad407123..000000000 --- a/src/ui/install.tis +++ /dev/null @@ -1,70 +0,0 @@ -function self.ready() { - centerize(scaleIt(800), scaleIt(600)); -} - -var install_path = ""; - -class Install: Reactor.Component { - function render() { - const install_options = JSON.parse(view.install_options()); - const desktop_icon = { checked: install_options?.DESKTOPSHORTCUTS == '0' ? false : true }; - const startmenu_shortcuts = { checked: install_options?.STARTMENUSHORTCUTS == '0' ? false : true }; - return
    -
    {translate('Installation')}
    -
    {translate('Installation Path')} {": "} - -
    -
    {translate('Create start menu shortcuts')}
    -
    {translate('Create desktop icon')}
    -
    {translate('End-user license agreement')}
    -
    {translate('agreement_tip')}
    -
    -
    - - - - {handler.show_run_without_install() && } -
    -
    ; - } - - event click $(#cancel) { - view.close(); - } - - event click $(#run-without-install) { - handler.run_without_install(); - } - - event click $(#path) { - install_path = view.selectFolder() || ""; - if (install_path) { - install_path = install_path.urlUnescape(); - install_path = install_path.replace("file://", "").replace("/", "\\"); - if (install_path[install_path.length - 1] != "\\") install_path += "\\"; - install_path += handler.get_app_name(); - $(#path_input).value = install_path; - } - } - - event click $(#agreement) { - view.open_url("http://rustdesk.com/privacy"); - } - - event click $(#submit) { - for (var el in $$(button)) el.state.disabled = true; - $(progress).style.set{ display: "inline-block" }; - var args = ""; - if ($(#startmenu).value) { - args += "startmenu "; - } - if ($(#desktopicon).value) { - args += "desktopicon "; - } - view.install_me(args, install_path); - } -} - -$(body).content(); diff --git a/src/ui/macos.rs b/src/ui/macos.rs new file mode 100644 index 000000000..b722f3875 --- /dev/null +++ b/src/ui/macos.rs @@ -0,0 +1,145 @@ +#[cfg(target_os = "macos")] +use cocoa::{ + appkit::{NSApp, NSApplication, NSMenu, NSMenuItem}, + base::{id, nil, YES}, + foundation::{NSAutoreleasePool, NSString}, +}; +use objc::{ + class, + declare::ClassDecl, + msg_send, + runtime::{Object, Sel, BOOL}, + sel, sel_impl, +}; +use std::{ + ffi::c_void, + sync::{Arc, Mutex}, +}; + +static APP_HANDLER_IVAR: &str = "GoDeskAppHandler"; + +lazy_static::lazy_static! { + pub static ref SHOULD_OPEN_UNTITLED_FILE_CALLBACK: Arc>>> = Default::default(); +} + +trait AppHandler { + fn command(&mut self, cmd: u32); +} + +struct DelegateState { + handler: Option>, +} + +impl DelegateState { + fn command(&mut self, command: u32) { + if command == 0 { + unsafe { + let () = msg_send!(NSApp(), terminate: nil); + } + } else if let Some(inner) = self.handler.as_mut() { + inner.command(command) + } + } +} + +// 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"); + decl.add_ivar::<*mut c_void>(APP_HANDLER_IVAR); + + decl.add_method( + sel!(applicationDidFinishLaunching:), + application_did_finish_launching as extern "C" fn(&mut Object, Sel, id), + ); + + decl.add_method( + sel!(applicationShouldOpenUntitledFile:), + application_should_handle_open_untitled_file as extern "C" fn(&mut Object, Sel, id) -> BOOL, + ); + + decl.add_method( + sel!(handleMenuItem:), + handle_menu_item as extern "C" fn(&mut Object, Sel, id), + ); + let decl = decl.register(); + let delegate: id = msg_send![decl, alloc]; + let () = msg_send![delegate, init]; + let state = DelegateState { handler }; + let handler_ptr = Box::into_raw(Box::new(state)); + (*delegate).set_ivar(APP_HANDLER_IVAR, handler_ptr as *mut c_void); + let () = msg_send![NSApp(), setDelegate: delegate]; +} + +extern "C" fn application_did_finish_launching(_this: &mut Object, _: Sel, _notification: id) { + unsafe { + let () = msg_send![NSApp(), activateIgnoringOtherApps: YES]; + } +} + +extern "C" fn application_should_handle_open_untitled_file( + _this: &mut Object, + _: Sel, + _sender: id, +) -> BOOL { + if let Some(callback) = SHOULD_OPEN_UNTITLED_FILE_CALLBACK.lock().unwrap().as_ref() { + callback(); + } + YES +} + +/// This handles menu items in the case that all windows are closed. +extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { + unsafe { + let tag: isize = msg_send![item, tag]; + if tag == 0 { + let inner: *mut c_void = *this.get_ivar(APP_HANDLER_IVAR); + let inner = &mut *(inner as *mut DelegateState); + (*inner).command(tag as u32); + } else if tag == 1 { + crate::run_me(Vec::::new()).ok(); + } + } +} + +pub fn make_menubar() { + unsafe { + let _pool = NSAutoreleasePool::new(nil); + set_delegate(None); + let menubar = NSMenu::new(nil).autorelease(); + let app_menu_item = NSMenuItem::new(nil).autorelease(); + menubar.addItem_(app_menu_item); + let app_menu = NSMenu::new(nil).autorelease(); + let quit_title = + NSString::alloc(nil).init_str(&format!("Quit {}", hbb_common::config::APP_NAME)); + let quit_action = sel!(handleMenuItem:); + let quit_key = NSString::alloc(nil).init_str("q"); + let quit_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key) + .autorelease(); + let () = msg_send![quit_item, setTag: 0]; + /* + if !enabled { + let () = msg_send![quit_item, setEnabled: NO]; + } + + if selected { + let () = msg_send![quit_item, setState: 1_isize]; + } + let () = msg_send![item, setTag: id as isize]; + */ + app_menu.addItem_(quit_item); + if std::env::args().len() > 1 { + let new_title = NSString::alloc(nil).init_str("New Window"); + let new_action = sel!(handleMenuItem:); + let new_key = NSString::alloc(nil).init_str("n"); + let new_item = NSMenuItem::alloc(nil) + .initWithTitle_action_keyEquivalent_(new_title, new_action, new_key) + .autorelease(); + let () = msg_send![new_item, setTag: 1]; + app_menu.addItem_(new_item); + } + app_menu_item.setSubmenu_(app_menu); + NSApp().setMainMenu_(menubar); + } +} diff --git a/src/ui/msgbox.html b/src/ui/msgbox.html new file mode 100644 index 000000000..e8b9d02a7 --- /dev/null +++ b/src/ui/msgbox.html @@ -0,0 +1,70 @@ + + + + + + + + + diff --git a/src/ui/msgbox.js b/src/ui/msgbox.js new file mode 100644 index 000000000..04f53254d --- /dev/null +++ b/src/ui/msgbox.js @@ -0,0 +1,253 @@ +import { PasswordComponent } from "./common"; +import {$,$$} from "@sciter"; //TEST $$ import +const view = Window.this; +var type, title, text, getParams, remember, retry, callback, contentStyle; +var my_translate; // TEST +function updateParams(params) { + type = params.type; + title = params.title; + text = params.text; + getParams = params.getParams; + remember = params.remember; + callback = params.callback; + my_translate = params.translate; + retry = params.retry; + contentStyle = params.contentStyle; + + try { text = translate_text(text); } catch (e) {} + if (retry > 0) { + setTimeout(()=>view.close({ reconnect: true }),retry * 1000);// TEST + } +} + +function translate_text(text) { + if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) { + let fds = text.split(': '); + for (let i = 0; i < fds.length; ++i) { + fds[i] = my_translate(fds[i]); + } + text = fds.join(': '); + } + return text; +} + +var params = view.parameters; // TEST +updateParams(params); + +var body; + +class Body extends Element { + this() { + body = this; + } + + getIcon(color) { + if (type == "input-password") { + return ; + } + if (type == "connecting") { + return ; + } + if (type == "success") { + return ; + } + if (type.indexOf("error") >= 0 || type == "re-input-password") { + return ; + } + return ; + } + + getInputPasswordContent() { + var ts = remember ? { checked: true } : {}; + //
    {my_translate('Remember password')}
    + return
    +
    {my_translate('Please enter your password')}
    + +
    +
    ; + } + + getContent() { + if (type == "input-password") { + return this.getInputPasswordContent(); + } + return text; + } + + getColor() { + if (type == "input-password") { + return "#AD448E"; + } + if (type == "success") { + return "#32bea6"; + } + if (type.indexOf("error") >= 0 || type == "re-input-password") { + return "#e04f5f"; + } + return "#2C8CFF"; + } + + hasSkip() { + return type.indexOf("skip") >= 0; + } + + render() { + let color = this.getColor(); + let icon = this.getIcon(color); + let content = this.getContent(); + let hasCancel = type.indexOf("error") < 0 && type != "success" && type.indexOf("nocancel") < 0; + let hasOk = type != "connecting" && type.indexOf("nook") < 0; + let hasClose = type.indexOf("hasclose") >= 0; + let show_progress = type == "connecting"; + document.body.style.setProperty("border",(color + " solid 1px")); + setTimeout(()=>this.$("#content").content(my_translate(content)),1); + return ( +
    +
    + {my_translate(title)} +
    +
    +
    + {icon &&
    {icon}
    } +
    +
    +
    + + + {hasCancel || retry ? : ""} + {this.hasSkip() ? : ""} + {hasOk || retry ? : ""} + {hasClose ? : ""} +
    +
    +
    ); + } + + // TEST + ["on click at .custom-event"](_, me) { + if (callback) callback(me); + } + + ["on click at button#cancel"]() { + view.close(); + if (callback) callback(null); + } + + ["on click at button#skip"]() { + let values = getValues(); + values.skip = true; + view.close(values); + if (callback) callback(values); + } + + ["on click at button#submit"](){ + if (type == "error") { + if (retry) { + view.close({ reconnect: true }); + } else { + view.close(); + if (callback) callback(null); + } + return; + } + if (type == "re-input-password") { + type = "input-password"; + body.componentUpdate(); + set_outline_focus(); + return; + } + var values = getValues(); + if (callback) { + var err = callback(values, show_progress); + if (err && !err.trim()) { + return; + } + if (err) { + show_progress(false, err); + return; + } + } + view.close(values); + } + ["on keydown"] (evt) { // TEST + if (!evt.shortcutKey) { + // TODO TEST Windows/Mac + if (evt.code == "KeyRETURN") { + submit(); + } + if (evt.code == "KeyESCAPE") { + cancel(); + } + } + } +} + +function show_progress(show=1, err="") { + if (show == -1) { + view.close() + return; + } + $("#progress").style.setProperty("display",show ? "inline-block" : "none"); + $("#error").text = err; +} + +function submit() { + if ($("button#submit")) { + $("button#submit").click(); // TEST + } +} + +function cancel() { + if ($("button#cancel")) { + $("button#cancel").click(); + } +} + +function getValues() { + let values = { type: type }; + for (let el of $$(".form input")) { + values[el.getAttribute("name")] = el.value; + } + for (let el of $$(".form textarea")) { + values[el.getAttribute("name")] = el.value; + } + for (let el of $$(".form button")) { + values[el.getAttribute("name")] = el.value; + } + if (type == "input-password") { + values.password = (values.password || "").trim(); + if (!values.password) { + return; + } + } + return values; +} + +function set_outline_focus() { + setTimeout(function() { + let el = $(".outline-focus"); + if (el) view.focus = el; + else { + el = $("#submit"); + if (el) view.focus = el; + } + },30); +} + +set_outline_focus(); + +// checkParams +setInterval(function() { + let tmp = getParams(); + if (!tmp || !tmp.type) { + view.close("!alive"); + return; + } else if (tmp != params) { + params = tmp; + updateParams(params); + body.componentUpdate(); + set_outline_focus(); + } +},30); + +document.body.content(); \ No newline at end of file diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis deleted file mode 100644 index 6e6b6a62f..000000000 --- a/src/ui/msgbox.tis +++ /dev/null @@ -1,390 +0,0 @@ -function translate_text(text) { - if (text.indexOf('Failed') == 0 && text.indexOf(': ') > 0) { - var fds = text.split(': '); - for (var i = 0; i < fds.length; ++i) { - fds[i] = translate(fds[i]); - } - text = fds.join(': '); - } else { - var fds = text.split(' '); - if (fds.length > 1 && fds[0].slice(-4) === '_tip') { - fds[0] = translate(fds[0]); - var rest = text.substring(fds[0].length + 1); - text = fds[0] + ' ' + translate(rest); - } else { - text = translate(text); - } - } - return text; -} - -var msgboxTimerFunc = function() {} -function closeMsgbox() { - self.timer(0, msgboxTimerFunc); - $(#msgbox).content(); -} - -class MsgboxComponent: Reactor.Component { - function this(params) { - this.width = params.width; - this.height = params.height; - this.type = params.type; - this.title = params.title; - this.content = params.content; - this.link = params.link; - this.remember = params.remember; - this.callback = params.callback; - this.hasRetry = params.hasRetry; - this.autoLogin = params.autoLogin; - this.contentStyle = params.contentStyle; - try { this.content = translate_text(this.content); } catch (e) {} - } - - function getIcon(color) { - if (this.type == "input-password" || this.type == "session-login" || this.type == "session-login-password" || this.type == "input-2fa") { - return ; - } - if (this.type == "connecting") { - return ; - } - if (this.type == "success") { - return ; - } - if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "input-2fa" || this.type == "session-re-login" || this.type == "session-login-re-password") { - return ; - } - return null; - } - - function getInputPasswordContent() { - var ts = this.remember ? { checked: true } : {}; - return
    -
    {translate('Please enter your password')}
    - -
    {translate('Remember password')}
    -
    ; - } - - function get2faContent() { - var enable_trusted_devices = handler.get_enable_trusted_devices(); - return
    -
    {translate('enter-2fa-title')}
    -
    - {enable_trusted_devices ?
    {translate('Trust this device')}
    : ""} -
    ; - } - - function getInputUserPasswordContent() { - return
    -
    {translate("OS Username")}
    -
    -
    {translate("OS Password")}
    - -
    -
    ; - } - - function getXsessionPasswordContent() { - return
    -
    {translate("OS Username")}
    -
    -
    {translate("OS Password")}
    - -
    {translate('Please enter your password')}
    - -
    {translate('Remember password')}
    -
    ; - } - - function getContent() { - if (this.type == "input-password") { - return this.getInputPasswordContent(); - } else if (this.type == "input-2fa") { - return this.get2faContent(); - } else if (this.type == "session-login") { - return this.getInputUserPasswordContent(); - } else if (this.type == "session-login-password") { - return this.getXsessionPasswordContent(); - } else if (this.type == "custom-os-password") { - var ts = this.autoLogin ? { checked: true } : {}; - return
    - -
    {translate('Auto Login')}
    -
    ; - } - return this.content; - } - - function getColor() { - if (this.type == "input-password" || this.type == "input-2fa" || this.type == "custom-os-password" || this.type == "session-login" || this.type == "session-login-password") { - return "#AD448E"; - } - if (this.type == "success") { - return "#32bea6"; - } - if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "session-re-login" || this.type == "session-login-re-password") { - return "#e04f5f"; - } - return "#2C8CFF"; - } - - function hasSkip() { - return this.type.indexOf("skip") >= 0; - } - - function getScreenshotButtons() { - var isScreenshot = this.type.indexOf("take-screenshot") >= 0; - return isScreenshot - ?
    - - - -
    - : ""; - } - - function render() { - this.set_outline_focus(); - var color = this.getColor(); - var icon = this.getIcon(color); - var content = this.getContent(); - var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0 && this.type != "restarting"; - var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0; - var hasLink = this.link != ""; - var hasClose = this.type.indexOf("hasclose") >= 0; - var show_progress = this.type == "connecting"; - var me = this; - self.timer(0, msgboxTimerFunc); - msgboxTimerFunc = function() { - if (typeof content == "string") - me.$(#content).html = translate(content); - else - me.$(#content).content(content); - }; - self.timer(3ms, msgboxTimerFunc); - return (
    -
    -
    -
    - {translate(this.title)} -
    -
    -
    - {icon &&
    {icon}
    } -
    -
    -
    - - - {hasCancel || this.hasRetry ? : ""} - {this.hasSkip() ? : ""} - {hasOk || this.hasRetry ? : ""} - {hasLink ? : ""} - {hasClose ? : ""} - {this.getScreenshotButtons()} -
    -
    -
    -
    ); - } - - event click $(.custom-event) (_, me) { - if (this.callback) this.callback(me); - } - - function submit() { - var submit_btn = this.$(button#submit); - if (submit_btn) { - if (submit_btn.state.disabled) return; - submit_btn.sendEvent("click"); - } - } - - function cancel() { - if (this.$(button#cancel)) { - this.$(button#cancel).sendEvent("click"); - } - } - - event click $(button#cancel) { - this.close(); - if (this.callback) this.callback(null); - } - - event click $(button#skip) { - var values = this.getValues(); - values.skip = true; - if (this.callback) this.callback(values); - if (this.close) this.close(); - } - - event click $(button#jumplink) { - if (this.link.indexOf("http") == 0) { - Sciter.launch(this.link); - } - } - - event click $(button#submit) { - if (this.type == "error") { - if (this.hasRetry) { - retryConnect(true); - return; - } - } - if (this.type == "re-input-password") { - this.type = "input-password"; - this.update(); - return; - } - if (this.type == "session-re-login") { - this.type = "session-login"; - this.update(); - return; - } - if (this.type == "session-login-re-password") { - this.type = "session-login-password"; - this.update(); - return; - } - var values = this.getValues(); - if (this.callback) { - var self = this; - var err = this.callback(values, function(a=1, b='') { self.show_progress(a, b); }); - if (!err) { - if (this.close) this.close(); - return; - } - if (err && err.trim()) this.show_progress(false, err); - } else { - this.close(); - } - } - - event click $(button#screenshotSaveAs) { - this.close(); - - handler.leave(handler.get_keyboard_mode()); - const filter = "Png file (*.png)"; - const defaultExt = "png"; - const initialPath = System.path(#USER_DOCUMENTS, "screenshot"); - const caption = "Save as"; - var url = view.selectFile(#save, filter, defaultExt, initialPath, caption); - handler.enter(handler.get_keyboard_mode()); - if(url) { - var res = handler.handle_screenshot("0:" + URL.toPath(url)); - if (res) { - msgbox("custom-error-nocancel-nook-hasclose", "Take screenshot", res, "", function() {}); - } - } else { - handler.handle_screenshot("2"); - } - } - - event click $(button#screenshotCopyToClip) { - this.close(); - var res = handler.handle_screenshot("1"); - if (res) { - msgbox("custom-error-nocancel-nook-hasclose", "Take screenshot", res, "", function() {}); - } - } - - event click $(button#screenshotCancel) { - this.close(); - handler.handle_screenshot("2"); - } - - event keydown (evt) { - if (!evt.shortcutKey) { - if (isEnterKey(evt)) { - this.submit(); - } - if (evt.keyCode == Event.VK_ESCAPE) { - this.cancel(); - } - } - } - - event click $(button#select_directory) { - var folder = view.selectFolder(translate("Change"), $(#folderPath).text); - if (folder) { - if (folder.indexOf("file://") == 0) folder = folder.substring(7); - $(#folderPath).text = folder; - } - } - - function show_progress(show=1, err="") { - if (show == -1) { - this.close() - return; - } - this.$(#progress).style.set { - display: show ? "inline-block" : "none" - }; - this.$(#error).text = err; - } - - function getValues() { - var values = { type: this.type }; - for (var el in this.$$(.form input)) { - values[el.attributes["name"]] = el.value; - } - for (var el in this.$$(.form textarea)) { - values[el.attributes["name"]] = el.value; - } - for (var el in this.$$(.form button)) { - values[el.attributes["name"]] = el.value; - } - if (this.type == "input-password") { - values.password = (values.password || "").trim(); - if (!values.password) { - return; - } - } - if (this.type == "input-2fa") { - values.code = (values.code || "").trim(); - if (!values.code) { - return; - } - } - if (this.type == "session-login") { - values.osusername = (values.osusername || "").trim(); - values.ospassword = (values.ospassword || "").trim(); - if (!values.osusername || !values.ospassword) { - return; - } - } - if (this.type == "session-login-password") { - values.password = (values.password || "").trim(); - values.osusername = (values.osusername || "").trim(); - values.ospassword = (values.ospassword || "").trim(); - if (!values.osusername || !values.ospassword || !values.password) { - return; - } - } - if (this.type == "multiple-sessions-nocancel") { - values.sid = (this.$$(select))[0].value; - } - if (this.type == "remote-printer-selector") { - values.name = (this.$$(select))[0].value; - } - return values; - } - - function set_outline_focus() { - var me = this; - self.timer(30ms, function() { - var el = me.$(.outline-focus); - if (el) view.focus = el; - else { - el = me.$(#submit); - if (el) { - view.focus = el; - } - } - }); - } - - function close() { - closeMsgbox(); - } -} diff --git a/src/ui/port_forward.js b/src/ui/port_forward.js new file mode 100644 index 000000000..c1ab807a2 --- /dev/null +++ b/src/ui/port_forward.js @@ -0,0 +1,81 @@ +import { translate, handler } from "./common.js"; +import { svg_arrow, svg_cancel } from "./file_transfer.js"; + +class PortForward extends Element { + render() { + let args = handler.xcall("get_args"); + let is_rdp = handler.xcall("is_rdp"); + if (is_rdp) { + this.pfs = [["", "", "RDP"]]; + args = ["rdp"]; + } else if (args.length) { + this.pfs = [args]; + } else { + this.pfs = handler.xcall("get_port_forwards"); + } + let pfs = this.pfs.map(function(pf, i) { + return ( + {is_rdp ? : pf[0]} + {args.length ? svg_arrow : ""} + {pf[1] || "localhost"} + {pf[2]} + {args.length ? "" : {svg_cancel}} + ); + }); + return
    + {pfs.length ?
    + {translate('Listening ...')}
    + {translate('not_close_tcp_tip')} +
    : ""} + + + + + + + {args.length ? "" : } + + + + {args.length ? "" : + + + + + + + + } + {pfs} + +
    {translate('Local Port')} + {translate('Remote Host')}{translate('Remote Port')}{translate('Action')}
    {svg_arrow}
    ; + } + + ["on click at #add"] () { + let port = ($("#port").value || "").toInteger() || 0; // TODO toInteger + let remote_host = $("#remote-host").value || ""; + let remote_port = ($("#remote-port").value || "").toInteger() || 0; // TODO toInteger + if (port <= 0 || remote_port <= 0) return; + handler.xcall("add_port_forward",port, remote_host, remote_port); + this.componentUpdate(); + } + + ["on click at #new-rdp"] () { + handler.xcall("new_rdp"); + } + + ["on click at .remove svg"](_, me) { + let pf = this.pfs[me.parentElement.parentElement.index - 1]; + handler.xcall("remove_port_forward",pf[0]); + this.componentUpdate(); + } +} + +export function initializePortForward() +{ + document.$("#file-transfer-wrapper").content(); + document.$("#video-wrapper").style.setProperty("visibility","hidden"); + document.$("#video-wrapper").style.setProperty("position","absolute") + document.$("#file-transfer-wrapper").style.setProperty("display","block"); +} diff --git a/src/ui/port_forward.tis b/src/ui/port_forward.tis deleted file mode 100644 index a30f698c5..000000000 --- a/src/ui/port_forward.tis +++ /dev/null @@ -1,77 +0,0 @@ -class PortForward: Reactor.Component { - function render() { - var args = handler.get_args(); - var is_rdp = handler.is_rdp(); - if (is_rdp) { - this.pfs = [["", "", "RDP"]]; - args = ["rdp"]; - } else if (args.length) { - this.pfs = [args]; - } else { - this.pfs = handler.get_port_forwards(); - } - var pfs = this.pfs.map(function(pf, i) { - return - {is_rdp ? : pf[0]} - {args.length ? svg_arrow : ""} - {pf[1] || "localhost"} - {pf[2]} - {args.length ? "" : {svg_cancel}} - ; - }); - return
    - {pfs.length ?
    - {translate('Listening ...')}
    - {translate('not_close_tcp_tip')} -
    : ""} - - - - - - - {args.length ? "" : } - - - - {args.length ? "" : - - - - - - - - } - {pfs} - -
    {translate('Local Port')} - {translate('Remote Host')}{translate('Remote Port')}{translate('Action')}
    {svg_arrow}
    ; - } - - event click $(#add) () { - var port = ($(#port).value || "").toInteger() || 0; - var remote_host = $(#remote-host).value || ""; - var remote_port = ($(#remote-port).value || "").toInteger() || 0; - if (port <= 0 || remote_port <= 0) return; - handler.add_port_forward(port, remote_host, remote_port); - this.update(); - } - - event click $(#new-rdp) { - handler.new_rdp(); - } - - event click $(.remove svg) (_, me) { - var pf = this.pfs[me.parent.parent.index - 1]; - handler.remove_port_forward(pf[0]); - this.update(); - } -} - -function initializePortForward() -{ - $(#file-transfer-wrapper).content(); - $(#video-wrapper).style.set { visibility: "hidden", position: "absolute" }; - $(#file-transfer-wrapper).style.set { display: "block" }; -} diff --git a/src/ui/printer.tis b/src/ui/printer.tis deleted file mode 100644 index c28482601..000000000 --- a/src/ui/printer.tis +++ /dev/null @@ -1,41 +0,0 @@ -include "sciter:reactor.tis"; - -handler.printerRequest = function(id, path) { - show_printer_selector(id, path); -}; - -function show_printer_selector(id, path) -{ - var names = handler.get_printer_names(); - msgbox("remote-printer-selector", "Incoming Print Job", , "", function(res=null) { - if (res && res.name) { - handler.on_printer_selected(id, path, res.name); - } - }, 180); -} - -class PrinterComponent extends Reactor.Component { - this var names = []; - this var jobTip = translate("print-incoming-job-confirm-tip"); - - function this(params) { - if (params && params.names) { - this.names = params.names; - } - } - - function render() { - return
    -
    {translate("print-incoming-job-confirm-tip")}
    -
    -
    - -
    -
    -
    ; - } -} diff --git a/src/ui/remote.css b/src/ui/remote.css index 71b2c1682..617285e9c 100644 --- a/src/ui/remote.css +++ b/src/ui/remote.css @@ -9,16 +9,6 @@ div#video-wrapper { background: #212121; } -div#quality-monitor { - top: 20px; - right: 20px; - background: #7571719c; - padding: 5px; - min-width: 150px; - color: azure; - border: 0.5px solid azure; -} - video#handler { behavior: native-remote video; size: *; @@ -34,7 +24,7 @@ img#cursor { } .goup { - transform: rotate(90deg); + transform: rotate(90deg); } table#remote-folder-view { @@ -43,4 +33,4 @@ table#remote-folder-view { table#local-folder-view { context-menu: selector(menu#local-folder-view); -} \ No newline at end of file +} diff --git a/src/ui/remote.html b/src/ui/remote.html index 70e909d17..3a740172e 100644 --- a/src/ui/remote.html +++ b/src/ui/remote.html @@ -1,44 +1,40 @@ - - - - + + +
    +
    -
    - - -
    -
    + +
    + +
    +
    +
    + + diff --git a/src/ui/remote.js b/src/ui/remote.js new file mode 100644 index 000000000..d434dffe5 --- /dev/null +++ b/src/ui/remote.js @@ -0,0 +1,470 @@ +import { $ } from "@sciter"; +import { handler,view,isReasonableSize,msgbox,is_osx, is_linux, centerize, connecting } from "./common.js"; +import { initializeFileTransfer, save_file_transfer_close_state } from "./file_transfer.js"; +import { initializePortForward } from "./port_forward.js"; +import { header } from "./header.js"; + +const body = document.body; +var cursor_img = $("img#cursor"); +var last_key_time = 0; +var display_width = 0; +var display_height = 0; +var display_origin_x = 0; +var display_origin_y = 0; +var display_scale = 1; +export var keyboard_enabled = true; // server side +export var clipboard_enabled = true; // server side +export var audio_enabled = true; // server side + +handler.is_port_forward = handler.xcall("is_port_forward"); +handler.is_file_transfer = handler.xcall("is_file_transfer"); + +handler.setDisplay = function(x, y, w, h) { + display_width = w; + display_height = h; + display_origin_x = x; + display_origin_y = y; + adaptDisplay(); +} + +export function adaptDisplay() { + let w = display_width; + let h = display_height; + if (!w || !h) return; + let style = handler.xcall("get_view_style"); + display_scale = 1.; + let [sx, sy, sw, sh] = view.screenBox(view.state == Window.WINDOW_FULL_SCREEN ? "frame" : "workarea", "rectw"); + if (sw >= w && sh > h) { + let hh = $("header").state.box("height", "border"); + let el = $("div#adjust-window"); + if (sh > h + hh && el) { + el.style.setProperty("display","block"); + el = $("li#adjust-window"); + el.style.setProperty("display","block"); + el.on("click",function() { + view.state = Window.WINDOW_SHOWN; + let [x, y] = body.state.box("position", "border", "screen"); // TEST + // extra for border + let extra = 2; + view.move(x, y, w + extra, h + hh + extra); + }) + } + } + if (style != "original") { + let bw = body.state.box("width", "border"); + let bh = body.state.box("height", "border"); + if (view.state == Window.WINDOW_FULL_SCREEN) { + bw = sw; + bh = sh; + } + if (bw > 0 && bh > 0) { + // TEST del toFloat() + let scale_x = bw / w; + let scale_y = bh / h; + let scale = scale_x < scale_y ? scale_x : scale_y; + if ((scale > 1 && style == "stretch") || + (scale < 1 && style == "shrink")) { + display_scale = scale; + w = w * scale; + h = h * scale; + } + } + } + handler.style.setProperty("width",w + "px"); + handler.style.setProperty("height",h + "px") +} + +// https://sciter.com/event-handling/ +// https://sciter.com/docs/content/sciter/Event.htm + +var entered = false; + +// TODO ! +// var keymap = {}; +// for (var (k, v) in Event) { +// k = k + "" +// if (k[0] == "V" && k[1] == "K") { +// keymap[v] = k; +// } +// } + +// VK_ENTER = VK_RETURN +// somehow, handler.onKey and view.onKey not working +// function self.onKey(evt) { +// last_key_time = getTime(); +// if (is_file_transfer || is_port_forward) return false; +// if (!entered) return false; +// if (!keyboard_enabled) return false; +// switch (evt.type) { +// case Event.KEY_DOWN: +// handler.key_down_or_up(1, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// if (is_osx && evt.commandKey) { +// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// } +// break; +// case Event.KEY_UP: +// handler.key_down_or_up(0, keymap[evt.keyCode] || "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// break; +// case Event.KEY_CHAR: +// // the keypress event is fired when the element receives character value. Event.keyCode is a UNICODE code point of the character +// handler.key_down_or_up(2, "", evt.keyCode, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey, evt.extendedKey); +// break; +// default: +// return false; +// } +// return true; +// } + +var wait_window_toolbar = false; +var last_mouse_mask; +var acc_wheel_delta_x = 0; +var acc_wheel_delta_y = 0; +var last_wheel_time = 0; +var inertia_velocity_x = 0; +var inertia_velocity_y = 0; +var acc_wheel_delta_x0 = 0; +var acc_wheel_delta_y0 = 0; +var total_wheel_time = 0; +var wheeling = false; +var dragging = false; + +// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum +function resetWheel() { + acc_wheel_delta_x = 0; + acc_wheel_delta_y = 0; + last_wheel_time = 0; + inertia_velocity_x = 0; + inertia_velocity_y = 0; + acc_wheel_delta_x0 = 0; + acc_wheel_delta_y0 = 0; + total_wheel_time = 0; + wheeling = false; +} + +var INERTIA_ACCELERATION = 30; + +// not good, precision not enough to simulate accelation effect, +// seems have to use pixel based rather line based delta +function accWheel(v, is_x) { + if (wheeling) return; + var abs_v = Math.abs(v); + var max_t = abs_v / INERTIA_ACCELERATION; + for (var t = 0.1; t < max_t; t += 0.1) { + var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger(); + if (d >= 1) { + abs_v -= t * INERTIA_ACCELERATION; + if (v < 0) { + d = -d; + v = -abs_v; + } else { + v = abs_v; + } + handler.xcall("send_mouse",3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false); + accWheel(v, is_x); + break; + } + } +} + +// // TODO +// handler.onMouse(evt) +// { +// if (is_file_transfer || is_port_forward) return false; +// if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) { +// var dy = evt.y - scroll_body.scroll(#top); +// if (dy <= 1) { +// if (!wait_window_toolbar) { +// wait_window_toolbar = true; +// self.timer(300ms, function() { +// if (!wait_window_toolbar) return; +// var extra = 0; +// // workaround for stupid Sciter, without this, click +// // event not triggered on top part of buttons on toolbar +// if (is_osx) extra = 10; +// if (view.windowState == View.WINDOW_FULL_SCREEN) { +// $(header).style.set { +// display: "block", +// padding: (2 * workarea_offset + extra) + "px 0 0 0", +// }; +// } +// wait_window_toolbar = false; +// }); +// } +// } else { +// wait_window_toolbar = false; +// var h = $(header).style; +// if (dy > 20 && h#display != "none") { +// h.set { +// display: "none", +// }; +// } +// } +// } +// if (!got_mouse_control) { +// if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) { +// got_mouse_control = true; +// } else { +// return; +// } +// } +// var mask = 0; +// var wheel_delta_x; +// var wheel_delta_y; +// switch(evt.type) { +// case Event.MOUSE_DOWN: +// mask = 1; +// dragging = true; +// break; +// case Event.MOUSE_UP: +// mask = 2; +// dragging = false; +// break; +// case Event.MOUSE_MOVE: +// if (cursor_img.style#display != "none" && keyboard_enabled) { +// cursor_img.style#display = "none"; +// handler.style#cursor = ''; +// } +// break; +// case Event.MOUSE_WHEEL: +// // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; +// mask = 3; +// { +// var (dx, dy) = evt.wheelDeltas; +// if (dx > 0) dx = 1; +// else if (dx < 0) dx = -1; +// if (dy > 0) dy = 1; +// else if (dy < 0) dy = -1; +// if (Math.abs(dx) > Math.abs(dy)) { +// dy = 0; +// } else { +// dx = 0; +// } +// acc_wheel_delta_x += dx; +// acc_wheel_delta_y += dy; +// wheel_delta_x = acc_wheel_delta_x.toInteger(); +// wheel_delta_y = acc_wheel_delta_y.toInteger(); +// acc_wheel_delta_x -= wheel_delta_x; +// acc_wheel_delta_y -= wheel_delta_y; +// var now = getTime(); +// var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; +// if (dt > 0) { +// var vx = dx / dt; +// var vy = dy / dt; +// if (vx != 0 || vy != 0) { +// inertia_velocity_x = vx; +// inertia_velocity_y = vy; +// } +// } +// acc_wheel_delta_x0 += dx; +// acc_wheel_delta_y0 += dy; +// total_wheel_time += dt; +// if (dx == 0 && dy == 0) { +// wheeling = false; +// if (dt < 0.1 && total_wheel_time > 0) { +// var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y; +// if (v2 > 0) { +// v2 = Math.sqrt(v2); +// inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2; +// accWheel(inertia_velocity_y, false); +// } +// v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x; +// if (v2 > 0) { +// v2 = Math.sqrt(v2); +// inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2; +// accWheel(inertia_velocity_x, true); +// } +// } +// resetWheel(); +// } else { +// wheeling = true; +// } +// last_wheel_time = now; +// if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled; +// } +// break; +// case Event.MOUSE_DCLICK: // seq: down, up, dclick, up +// mask = 1; +// break; +// case Event.MOUSE_ENTER: +// entered = true; +// console.log("enter"); +// return keyboard_enabled; +// case Event.MOUSE_LEAVE: +// entered = false; +// console.log("leave"); +// return keyboard_enabled; +// default: +// return false; +// } +// var x = evt.x; +// var y = evt.y; +// if (mask != 0) { +// // to gain control of the mouse, user must move mouse +// if (cur_x != x || cur_y != y) { +// return keyboard_enabled; +// } +// // save bandwidth +// x = 0; +// y = 0; +// } else { +// cur_local_x = cur_x = x; +// cur_local_y = cur_y = y; +// } +// if (mask != 3) { +// resetWheel(); +// } +// if (!keyboard_enabled) return false; +// x = (x / display_scale).toInteger(); +// y = (y / display_scale).toInteger(); +// // insert down between two up, osx has this behavior for triple click +// if (last_mouse_mask == 2 && mask == 2) { +// handler.send_mouse((evt.buttons << 3) | 1, x + display_origin_x, y + display_origin_y, evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey); +// } +// last_mouse_mask = mask; +// // to-do: altKey, ctrlKey etc +// handler.send_mouse((evt.buttons << 3) | mask, +// mask == 3 ? wheel_delta_x : x + display_origin_x, +// mask == 3 ? wheel_delta_y : y + display_origin_y, +// evt.altKey, +// evt.ctrlKey, evt.shiftKey, evt.commandKey); +// return true; +// }; + +var cur_hotx = 0; +var cur_hoty = 0; +var cur_img = null; +var cur_x = 0; +var cur_y = 0; +var cur_local_x = 0; +var cur_local_y = 0; +var cursors = {}; +var image_binded; + +handler.setCursorData = function(id, hotx, hoty, width, height, colors) { + cur_hotx = hotx; + cur_hoty = hoty; + cursor_img.style.setProperty("width",width + "px"); + cursor_img.style.setProperty("height",height + "px"); + + let img = Image.fromBytes(colors); + if (img) { + image_binded = true; + cursors[id] = [img, hotx, hoty, width, height]; + this.bindImage("in-memory:cursor", img); + if (cursor_img.style.getProperty("display") == 'none') { // TEST getProperty + setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1); // TODO cursor + } + cur_img = img; + } +} + +handler.setCursorId = function(id) { + let img = cursors[id]; + if (img) { + image_binded = true; + cur_hotx = img[1]; + cur_hoty = img[2]; + cursor_img.style.setProperty("width",img[3] + "px"); + cursor_img.style.setProperty("height",img[4] + "px"); + img = img[0]; + this.bindImage("in-memory:cursor", img); + if (cursor_img.style.getProperty("display") == 'none') { + setTimeout(function() { handler.style.cursor(cur_img, cur_hotx, cur_hoty) },1); + } + cur_img = img; + } +} + +var got_mouse_control = true; +handler.setCursorPosition = function(x, y) { + if (!image_binded) return; + got_mouse_control = false; + cur_x = x - display_origin_x; + cur_y = y - display_origin_y; + var x = cur_x - cur_hotx; + var y = cur_y - cur_hoty; + x *= display_scale; + y *= display_scale; + cursor_img.style.setProperty("width",x + "px"); + cursor_img.style.setProperty("width",y + "px"); + if (cursor_img.style.getProperty("display") == 'none') { + handler.style.setProperty("cursor",'none'); + cursor_img.style.setProperty("display","block"); + } +} + +document.on("ready",()=> { + let w = 960; + let h = 640; + if (handler.is_file_transfer || handler.is_port_forward) { + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + view.move(r[0], r[1], r[2], r[3]); + } else { + centerize(w, h); + } + } else { + centerize(w, h); + } + if (!handler.is_port_forward) connecting(); + if (handler.is_file_transfer) initializeFileTransfer(); + if (handler.is_port_forward) initializePortForward(); +}) + +var workarea_offset = 0; +var size_adapted; +handler.adaptSize = function() { + if (size_adapted) return; + size_adapted = true; + let [sx, sy, sw, sh] = view.screenBox("workarea", "rectw"); + let [fx, fy, fw, fh] = view.screenBox("frame", "rectw"); + if (is_osx) workarea_offset = sy; + let r = handler.xcall("get_size"); + if (isReasonableSize(r) && r[2] > 0) { + if (r[2] >= fw && r[3] >= fh && !is_linux) { + view.state = Window.WINDOW_FULL_SCREEN; + console.log("Initialize to full screen"); + } else if (r[2] >= sw && r[3] >= sh) { + view.state = Window.WINDOW_MAXIMIZED; + console.log("Initialize to full screen"); + } else { + view.move(r[0], r[1], r[2], r[3]); + } + } else { + let w = handler.state.box("width", "border") + let h = handler.state.box("height", "border") + if (w >= sw || h >= sh) { + view.state = Window.WINDOW_MAXIMIZED; + return; + } + // extra for border + let extra = 2; + centerize(w + extra, handler.state.box("height", "border") + h + extra); + } +} + +document.on("unloadequest",()=> { + let [x, y, w, h] = body.state.box("rectw", "border", "screen"); + console.log("remote.js unloadequest",x, y, w, h) + if (handler.is_file_transfer) save_file_transfer_close_state(); + if (handler.is_file_transfer || handler.is_port_forward || size_adapted) handler.xcall("save_size",x, y, w, h); +}) + +handler.setPermission = function(name, enabled) { + if (name == "keyboard") keyboard_enabled = enabled; + if (name == "audio") audio_enabled = enabled; + if (name == "clipboard") clipboard_enabled = enabled; + header.componentUpdate(); +} + +// TODO +handler.closeSuccess = function() { + // handler.msgbox("success", "Successful", "Ready to go."); + console.log("remote.js handler.closeSuccess"); + handler.msgbox("", "", ""); +} diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 8b6f01ae0..22be7d038 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -1,9 +1,23 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, - sync::{atomic::AtomicUsize, Arc, Mutex, RwLock}, +use crate::client::*; +use crate::common::{ + self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL, +}; +use enigo::{self, Enigo, KeyboardControllable}; +use hbb_common::{ + allow_err, + config::{self, Config, PeerConfig}, + fs, log, + message_proto::*, + protobuf::Message as _, + rendezvous_proto::ConnType, + sleep, + tokio::{ + self, + sync::mpsc, + time::{self, Duration, Instant, Interval}, + }, + Stream, }; - use sciter::{ dom::{ event::{EventReason, BEHAVIOR_EVENTS, EVENT_GROUPS, PHASE_MASK}, @@ -13,436 +27,64 @@ use sciter::{ video::{video_destination, AssetPtr, COLOR_SPACE}, Value, }; - -use hbb_common::{ - allow_err, fs::TransferJobMeta, log, message_proto::*, rendezvous_proto::ConnType, -}; - -use crate::{ - client::*, - ui_session_interface::{InvokeUiSession, Session}, +use std::{ + collections::HashMap, + ops::Deref, + sync::{Arc, Mutex, RwLock}, }; type Video = AssetPtr; lazy_static::lazy_static! { + static ref ENIGO: Arc> = Arc::new(Mutex::new(Enigo::new())); static ref VIDEO: Arc>> = Default::default(); } -/// SciterHandler -/// * element -/// * close_state for file path when close -#[derive(Clone, Default)] -pub struct SciterHandler { - element: Arc>>, +fn get_key_state(key: enigo::Key) -> bool { + #[cfg(target_os = "macos")] + if key == enigo::Key::NumLock { + return true; + } + ENIGO.lock().unwrap().get_key_state(key) +} + +#[derive(Default)] +pub struct HandlerInner { + element: Option, + sender: Option>, + thread: Option>, close_state: HashMap, } -impl SciterHandler { - #[inline] - fn call(&self, func: &str, args: &[Value]) { - if let Some(ref e) = self.element.lock().unwrap().as_ref() { - allow_err!(e.call_method(func, args)); - } - } - - #[inline] - fn call2(&self, func: &str, args: &[Value]) { - if let Some(ref e) = self.element.lock().unwrap().as_ref() { - allow_err!(e.call_method(func, &super::value_crash_workaround(args)[..])); - } - } - - fn make_displays_array(displays: &Vec) -> Value { - let mut displays_value = Value::array(0); - for d in displays.iter() { - let mut display = Value::map(); - display.set_item("x", d.x); - display.set_item("y", d.y); - display.set_item("width", d.width); - display.set_item("height", d.height); - display.set_item("cursor_embedded", d.cursor_embedded); - displays_value.push(display); - } - displays_value - } - - fn make_platform_additions(data: &str) -> Option { - if let Ok(v2) = serde_json::from_str::>(data) { - let mut value = Value::map(); - for (k, v) in v2 { - match v { - serde_json::Value::String(s) => { - value.set_item(k, s); - } - serde_json::Value::Number(n) => { - if let Some(n) = n.as_i64() { - value.set_item(k, n as i32); - } else if let Some(n) = n.as_f64() { - value.set_item(k, n); - } - } - serde_json::Value::Bool(b) => { - value.set_item(k, b); - } - serde_json::Value::Array(arr) if k == "supported_privacy_mode_impl" => { - let mut impls = Value::array(0); - for item in arr { - if let serde_json::Value::Array(entry) = item { - let impl_key = entry.get(0).and_then(|v| v.as_str()); - let impl_name = entry.get(1).and_then(|v| v.as_str()); - if let (Some(impl_key), Some(impl_name)) = (impl_key, impl_name) { - let mut impl_item = Value::array(0); - impl_item.push(impl_key); - impl_item.push(impl_name); - impls.push(impl_item); - } - } - } - value.set_item(k, impls); - } - _ => { - // ignore for now - } - } - } - if value.len() > 0 { - return Some(value); - } else { - None - } - } else { - None - } - } +#[derive(Clone, Default)] +pub struct Handler { + inner: Arc>, + cmd: String, + id: String, + args: Vec, + lc: Arc>, } -impl InvokeUiSession for SciterHandler { - fn set_cursor_data(&self, cd: CursorData) { - let mut colors = hbb_common::compress::decompress(&cd.colors); - if colors.iter().filter(|x| **x != 0).next().is_none() { - log::info!("Fix transparent"); - // somehow all 0 images shows black rect, here is a workaround - colors[3] = 1; - } - let mut png = Vec::new(); - if let Ok(()) = repng::encode(&mut png, cd.width as _, cd.height as _, &colors) { - self.call( - "setCursorData", - &make_args!( - cd.id.to_string(), - cd.hotx, - cd.hoty, - cd.width, - cd.height, - &png[..] - ), - ); - } - } +impl Deref for Handler { + type Target = Arc>; - fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embedded: bool, scale: f64) { - let scale = if scale <= 0.0 { 1.0 } else { scale }; - self.call("setDisplay", &make_args!(x, y, w, h, cursor_embedded, scale)); - // https://sciter.com/forums/topic/color_spaceiyuv-crash - // Nothing spectacular in decoder – done on CPU side. - // So if you can do BGRA translation on your side – the better. - // BGRA is used as internal image format so it will not require additional transformations. - VIDEO.lock().unwrap().as_mut().map(|v| { - v.stop_streaming().ok(); - let ok = v.start_streaming((w, h), COLOR_SPACE::Rgb32, None); - log::info!("[video] reinitialized: {:?}", ok); - }); - } - - fn update_privacy_mode(&self) { - self.call("updatePrivacyMode", &[]); - } - - fn set_permission(&self, name: &str, value: bool) { - self.call2("setPermission", &make_args!(name, value)); - } - - fn close_success(&self) { - self.call2("closeSuccess", &make_args!()); - } - - fn update_quality_status(&self, status: QualityStatus) { - self.call2( - "updateQualityStatus", - &make_args!( - status.speed.map_or(Value::null(), |it| it.into()), - status - .fps - .iter() - .next() - .map_or(Value::null(), |(_, v)| (*v).into()), - status.delay.map_or(Value::null(), |it| it.into()), - status.target_bitrate.map_or(Value::null(), |it| it.into()), - status - .codec_format - .map_or(Value::null(), |it| it.to_string().into()), - status.chroma.map_or(Value::null(), |it| it.into()) - ), - ); - } - - fn set_cursor_id(&self, id: String) { - self.call("setCursorId", &make_args!(id)); - } - - fn set_cursor_position(&self, cp: CursorPosition) { - self.call("setCursorPosition", &make_args!(cp.x, cp.y)); - } - - fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str) { - self.call( - "setConnectionType", - &make_args!(is_secured, direct, stream_type.to_string()), - ); - } - - fn set_fingerprint(&self, _fingerprint: String) {} - - fn job_error(&self, id: i32, err: String, file_num: i32) { - self.call("jobError", &make_args!(id, err, file_num)); - } - - fn job_done(&self, id: i32, file_num: i32) { - self.call("jobDone", &make_args!(id, file_num)); - } - - fn clear_all_jobs(&self) { - self.call("clearAllJobs", &make_args!()); - } - - fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool) { - let job: Result = serde_json::from_str(job_json); - if let Ok(job) = job { - let path; - let to; - if job.is_remote { - path = job.remote.clone(); - to = job.to.clone(); - } else { - path = job.to.clone(); - to = job.remote.clone(); - } - self.call( - "addJob", - &make_args!( - cnt, - path, - to, - job.file_num, - job.show_hidden, - job.is_remote, - auto_start - ), - ); - } - } - - fn update_folder_files( - &self, - id: i32, - entries: &Vec, - path: String, - _is_local: bool, - only_count: bool, - ) { - let mut m = make_fd(id, entries, only_count); - m.set_item("path", path); - self.call("updateFolderFiles", &make_args!(m)); - } - - fn update_transfer_list(&self) { - self.call("updateTransferList", &make_args!()); - } - - fn confirm_delete_files(&self, id: i32, i: i32, name: String) { - self.call("confirmDeleteFiles", &make_args!(id, i, name)); - } - - fn override_file_confirm( - &self, - id: i32, - file_num: i32, - to: String, - is_upload: bool, - is_identical: bool, - ) { - self.call( - "overrideFileConfirm", - &make_args!(id, file_num, to, is_upload, is_identical), - ); - } - - fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64) { - self.call( - "jobProgress", - &make_args!(id, file_num, speed, finished_size), - ); - } - - fn adapt_size(&self) { - self.call("adaptSize", &make_args!()); - } - - fn on_rgba(&self, _display: usize, rgba: &mut scrap::ImageRgb) { - VIDEO - .lock() - .unwrap() - .as_mut() - .map(|v| v.render_frame(&rgba.raw).ok()); - } - - fn set_peer_info(&self, pi: &PeerInfo) { - let mut pi_sciter = Value::map(); - pi_sciter.set_item("username", pi.username.clone()); - pi_sciter.set_item("hostname", pi.hostname.clone()); - pi_sciter.set_item("platform", pi.platform.clone()); - pi_sciter.set_item("sas_enabled", pi.sas_enabled); - pi_sciter.set_item("displays", Self::make_displays_array(&pi.displays)); - pi_sciter.set_item("current_display", pi.current_display); - pi_sciter.set_item("version", pi.version.clone()); - if let Some(v) = Self::make_platform_additions(&pi.platform_additions) { - pi_sciter.set_item("platform_additions", v); - } - self.call("updatePi", &make_args!(pi_sciter)); - } - - fn set_displays(&self, displays: &Vec) { - self.call( - "updateDisplays", - &make_args!(Self::make_displays_array(displays)), - ); - } - - fn set_platform_additions(&self, _data: &str) { - // Ignore for sciter version. - } - - fn set_current_display(&self, _disp_idx: i32) { - self.call("setCurrentDisplay", &make_args!(_disp_idx)); - } - - fn set_multiple_windows_session(&self, sessions: Vec) { - let mut v = Value::array(0); - let mut sessions = sessions; - for s in sessions.drain(..) { - let mut obj = Value::map(); - obj.set_item("sid", s.sid.to_string()); - obj.set_item("name", s.name); - v.push(obj); - } - self.call("setMultipleWindowsSession", &make_args!(v)); - } - - fn on_connected(&self, conn_type: ConnType) { - match conn_type { - ConnType::DEFAULT_CONN => { - crate::keyboard::client::start_grab_loop(); - } - _ => {} - } - } - - fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) { - self.call2( - "msgbox_retry", - &make_args!(msgtype, title, text, link, retry), - ); - } - - fn cancel_msgbox(&self, tag: &str) { - self.call("cancel_msgbox", &make_args!(tag)); - } - - fn new_message(&self, msg: String) { - self.call("newMessage", &make_args!(msg)); - } - - fn switch_display(&self, display: &SwitchDisplay) { - self.call("switchDisplay", &make_args!(display.display)); - } - - fn update_block_input_state(&self, on: bool) { - self.call("updateBlockInputState", &make_args!(on)); - } - - fn switch_back(&self, _id: &str) {} - - fn portable_service_running(&self, _running: bool) {} - - fn on_voice_call_started(&self) { - self.call("onVoiceCallStart", &make_args!()); - } - - fn on_voice_call_closed(&self, reason: &str) { - self.call("onVoiceCallClosed", &make_args!(reason)); - } - - fn on_voice_call_waiting(&self) { - self.call("onVoiceCallWaiting", &make_args!()); - } - - fn on_voice_call_incoming(&self) { - self.call("onVoiceCallIncoming", &make_args!()); - } - - /// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui. - fn get_rgba(&self, _display: usize) -> *const u8 { - std::ptr::null() - } - - fn next_rgba(&self, _display: usize) {} - - fn update_record_status(&self, start: bool) { - self.call("updateRecordStatus", &make_args!(start)); - } - - fn printer_request(&self, id: i32, path: String) { - self.call("printerRequest", &make_args!(id, path)); - } - - fn handle_screenshot_resp(&self, _sid: String, msg: String) { - self.call("screenshot", &make_args!(msg)); - } - - fn handle_terminal_response(&self, _response: TerminalResponse) { - // Terminal support is not implemented for Sciter UI - // This is a stub implementation to satisfy the trait requirements - } -} - -pub struct SciterSession(Session); - -impl Deref for SciterSession { - type Target = Session; fn deref(&self) -> &Self::Target { - &self.0 + &self.inner } } -impl DerefMut for SciterSession { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl sciter::EventHandler for SciterSession { +impl sciter::EventHandler for Handler { fn get_subscription(&mut self) -> Option { - Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT) + Some(EVENT_GROUPS::HANDLE_METHOD_CALL | EVENT_GROUPS::HANDLE_SCRIPTING_METHOD_CALL | EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT) } fn attached(&mut self, root: HELEMENT) { - *self.element.lock().unwrap() = Some(Element::from(root)); + self.write().unwrap().element = Some(Element::from(root)); } fn detached(&mut self, _root: HELEMENT) { - *self.element.lock().unwrap() = None; - self.sender.write().unwrap().take().map(|sender| { + self.write().unwrap().element = None; + self.write().unwrap().sender.take().map(|sender| { sender.send(Data::Close).ok(); }); } @@ -471,7 +113,7 @@ impl sciter::EventHandler for SciterSession { let site = AssetPtr::adopt(ptr as *mut video_destination); log::debug!("[video] start video"); *VIDEO.lock().unwrap() = Some(site); - self.reconnect(false); + self.reconnect(); } } BEHAVIOR_EVENTS::VIDEO_INITIALIZED => { @@ -482,7 +124,7 @@ impl sciter::EventHandler for SciterSession { let source = Element::from(source); use sciter::dom::ELEMENT_AREAS; let flags = ELEMENT_AREAS::CONTENT_BOX as u32 | ELEMENT_AREAS::SELF_RELATIVE as u32; - let rc = source.get_location(flags).unwrap_or_default(); + let rc = source.get_location(flags).unwrap(); log::debug!( "[video] start video thread on <{}> which is about {:?} pixels", source, @@ -498,36 +140,29 @@ impl sciter::EventHandler for SciterSession { } sciter::dispatch_script_call! { - fn get_audit_server(String); - fn send_note(String); fn is_xfce(); fn get_id(); fn get_default_pi(); fn get_option(String); fn t(String); fn set_option(String, String); - fn input_os_password(String, bool); fn save_close_state(String, String); fn is_file_transfer(); fn is_port_forward(); fn is_rdp(); - fn login(String, String, String, bool); - fn send2fa(String, bool); - fn get_enable_trusted_devices(); + fn login(String, bool); fn new_rdp(); fn send_mouse(i32, i32, i32, bool, bool, bool, bool); - fn enter(String); - fn leave(String); + fn key_down_or_up(bool, String, i32, bool, bool, bool, bool, bool); fn ctrl_alt_del(); fn transfer_file(); fn tunnel(); fn lock_screen(); - fn reconnect(bool); + fn reconnect(); + fn get_msgbox(); fn get_chatbox(); fn get_icon(); fn get_home_dir(); - fn get_next_job_id(); - fn update_next_job_id(i32); fn read_dir(String, bool); fn remove_dir(i32, String, bool); fn create_dir(i32, String, bool); @@ -535,13 +170,11 @@ impl sciter::EventHandler for SciterSession { fn read_remote_dir(String, bool); fn send_chat(String); fn switch_display(i32); - fn remove_dir_all(i32, String, bool, bool); + fn remove_dir_all(i32, String, bool); fn confirm_delete_files(i32, i32); fn set_no_confirm(i32); fn cancel_job(i32); - fn send_files(i32, i32, String, String, i32, bool, bool); - fn add_job(i32, i32, String, String, i32, bool, bool); - fn resume_job(i32, bool); + fn send_files(i32, String, String, bool, bool); fn get_platform(bool); fn get_path_sep(bool); fn get_icon_path(i32, String); @@ -557,72 +190,36 @@ impl sciter::EventHandler for SciterSession { fn get_custom_image_quality(); fn save_view_style(String); fn save_image_quality(String); - fn save_custom_image_quality(i32); - fn refresh_video(i32); - fn record_screen(bool); - fn is_screenshot_supported(); - fn take_screenshot(i32, String); - fn handle_screenshot(String); + fn save_custom_image_quality(i32, i32); + fn refresh_video(); + fn support_refresh(); fn get_toggle_option(String); - fn is_privacy_mode_supported(); fn toggle_option(String); - fn toggle_privacy_mode(String, bool); fn get_remember(); - fn peer_platform(); - fn set_write_override(i32, i32, bool, bool, bool); - fn get_keyboard_mode(); - fn is_keyboard_mode_supported(String); - fn save_keyboard_mode(String); - fn alternative_codecs(); - fn update_supported_decodings(); - fn restart_remote_device(); - fn request_voice_call(); - fn close_voice_call(); - fn version_cmp(String, String); - fn set_selected_windows_session_id(String); - fn is_recording(); - fn has_file_clipboard(); - fn get_printer_names(); - fn on_printer_selected(i32, String, String); } } -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 { - password: password.clone(), +impl Handler { + pub fn new(cmd: String, id: String, args: Vec) -> Self { + let me = Self { + cmd, + id: id.clone(), args, - server_keyboard_enabled: Arc::new(RwLock::new(true)), - server_file_transfer_enabled: Arc::new(RwLock::new(true)), - server_clipboard_enabled: Arc::new(RwLock::new(true)), - reconnect_count: Arc::new(AtomicUsize::new(0)), ..Default::default() }; - - let conn_type = if cmd.eq("--file-transfer") { - ConnType::FILE_TRANSFER - } else if cmd.eq("--view-camera") { - ConnType::VIEW_CAMERA - } else if cmd.eq("--port-forward") { - ConnType::PORT_FORWARD - } else if cmd.eq("--rdp") { - ConnType::RDP - } else { - ConnType::DEFAULT_CONN - }; - - session - .lc + me.lc .write() .unwrap() - .initialize(id, conn_type, None, force_relay, None, None, None); - - Self(session) + .initialize(id, me.is_file_transfer(), me.is_port_forward()); + me } - pub fn inner(&self) -> Session { - self.0.clone() + fn get_view_style(&mut self) -> String { + return self.lc.read().unwrap().view_style.clone(); + } + + fn get_image_quality(&mut self) -> String { + return self.lc.read().unwrap().image_quality.clone(); } fn get_custom_image_quality(&mut self) -> Value { @@ -633,34 +230,74 @@ impl SciterSession { v } - pub fn t(&self, name: String) -> String { + #[inline] + fn save_config(&self, config: PeerConfig) { + self.lc.write().unwrap().save_config(config); + } + + fn save_view_style(&mut self, value: String) { + self.lc.write().unwrap().save_view_style(value); + } + + #[inline] + fn load_config(&self) -> PeerConfig { + load_config(&self.id) + } + + fn toggle_option(&mut self, name: String) { + let msg = self.lc.write().unwrap().toggle_option(name); + if let Some(msg) = msg { + self.send(Data::Message(msg)); + } + } + + fn get_toggle_option(&mut self, name: String) -> bool { + self.lc.read().unwrap().get_toggle_option(&name) + } + + fn refresh_video(&mut self) { + self.send(Data::Message(LoginConfigHandler::refresh())); + } + + fn support_refresh(&self) -> bool { + self.lc.read().unwrap().support_refresh + } + + fn save_custom_image_quality(&mut self, bitrate: i32, quantizer: i32) { + let msg = self + .lc + .write() + .unwrap() + .save_custom_image_quality(bitrate, quantizer); + self.send(Data::Message(msg)); + } + + fn save_image_quality(&mut self, value: String) { + let msg = self.lc.write().unwrap().save_image_quality(value); + if let Some(msg) = msg { + self.send(Data::Message(msg)); + } + } + + fn get_remember(&mut self) -> bool { + self.lc.read().unwrap().remember + } + + fn t(&self, name: String) -> String { crate::client::translate(name) } - pub fn get_icon(&self) -> String { - super::get_icon() - } - - fn alternative_codecs(&self) -> Value { - let (vp8, av1, h264, h265) = self.0.alternative_codecs(); - let mut v = Value::array(0); - v.push(vp8); - v.push(av1); - v.push(h264); - v.push(h265); - v + fn is_xfce(&self) -> bool { + crate::platform::is_xfce() } fn save_size(&mut self, x: i32, y: i32, w: i32, h: i32) { let size = (x, y, w, h); let mut config = self.load_config(); if self.is_file_transfer() { - let close_state = self.close_state.clone(); + let close_state = self.read().unwrap().close_state.clone(); let mut has_change = false; - for (k, mut v) in close_state { - if k == "remote_dir" { - v = self.lc.read().unwrap().get_all_remote_dir(v); - } + for (k, v) in close_state { let v2 = if v.is_empty() { None } else { Some(&v) }; if v2 != config.options.get(&k) { has_change = true; @@ -690,14 +327,6 @@ impl SciterSession { log::info!("size saved"); } - fn set_selected_windows_session_id(&mut self, u_sid: String) { - self.send_selected_session_id(u_sid); - } - - fn has_file_clipboard(&self) -> bool { - cfg!(any(target_os = "windows", feature = "unix-file-copy-paste")) - } - fn get_port_forwards(&mut self) -> Value { let port_forwards = self.lc.read().unwrap().port_forwards.clone(); let mut v = Value::array(0); @@ -719,6 +348,34 @@ impl SciterSession { v } + fn remove_port_forward(&mut self, port: i32) { + let mut config = self.load_config(); + config.port_forwards = config + .port_forwards + .drain(..) + .filter(|x| x.0 != port) + .collect(); + self.save_config(config); + self.send(Data::RemovePortForward(port)); + } + + fn add_port_forward(&mut self, port: i32, remote_host: String, remote_port: i32) { + let mut config = self.load_config(); + if config + .port_forwards + .iter() + .filter(|x| x.0 == port) + .next() + .is_some() + { + return; + } + let pf = (port, remote_host, remote_port); + config.port_forwards.push(pf.clone()); + self.save_config(config); + self.send(Data::AddPortForward(pf)); + } + fn get_size(&mut self) -> Value { let s = if self.is_file_transfer() { self.lc.read().unwrap().size_ft @@ -735,6 +392,10 @@ impl SciterSession { v } + fn get_id(&mut self) -> String { + self.id.clone() + } + fn get_default_pi(&mut self) -> Value { let mut pi = Value::map(); let info = self.lc.read().unwrap().info.clone(); @@ -744,8 +405,292 @@ impl SciterSession { pi } - fn save_close_state(&mut self, k: String, v: String) { - self.close_state.insert(k, v); + fn get_option(&self, k: String) -> String { + self.lc.read().unwrap().get_option(&k) + } + + fn set_option(&self, k: String, v: String) { + self.lc.write().unwrap().set_option(k, v); + } + + fn save_close_state(&self, k: String, v: String) { + self.write().unwrap().close_state.insert(k, v); + } + + fn get_msgbox(&mut self) -> String { + super::get_msgbox() + } + + fn get_chatbox(&mut self) -> String { + #[cfg(feature = "inline")] + return super::inline::get_chatbox(); + #[cfg(not(feature = "inline"))] + return "".to_owned(); + } + + fn get_icon(&mut self) -> String { + config::ICON.to_owned() + } + + fn get_home_dir(&mut self) -> String { + fs::get_home_as_string() + } + + fn read_dir(&mut self, path: String, include_hidden: bool) -> Value { + match fs::read_dir(&fs::get_path(&path), include_hidden) { + Err(_) => Value::null(), + Ok(fd) => { + let mut m = make_fd(0, &fd.entries.to_vec(), false); + m.set_item("path", path); + m + } + } + } + + fn cancel_job(&mut self, id: i32) { + self.send(Data::CancelJob(id)); + } + + fn read_remote_dir(&mut self, path: String, include_hidden: bool) { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_read_dir(ReadDir { + path, + include_hidden, + ..Default::default() + }); + msg_out.set_file_action(file_action); + self.send(Data::Message(msg_out)); + } + + fn send_chat(&mut self, text: String) { + let mut misc = Misc::new(); + misc.set_chat_message(ChatMessage { + text, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + self.send(Data::Message(msg_out)); + } + + fn switch_display(&mut self, display: i32) { + let mut misc = Misc::new(); + misc.set_switch_display(SwitchDisplay { + display, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + self.send(Data::Message(msg_out)); + } + + fn remove_file(&mut self, id: i32, path: String, file_num: i32, is_remote: bool) { + self.send(Data::RemoveFile((id, path, file_num, is_remote))); + } + + fn remove_dir_all(&mut self, id: i32, path: String, is_remote: bool) { + self.send(Data::RemoveDirAll((id, path, is_remote))); + } + + fn confirm_delete_files(&mut self, id: i32, file_num: i32) { + self.send(Data::ConfirmDeleteFiles((id, file_num))); + } + + fn set_no_confirm(&mut self, id: i32) { + self.send(Data::SetNoConfirm(id)); + } + + fn remove_dir(&mut self, id: i32, path: String, is_remote: bool) { + if is_remote { + self.send(Data::RemoveDir((id, path))); + } else { + fs::remove_all_empty_dir(&fs::get_path(&path)).ok(); + } + } + + fn create_dir(&mut self, id: i32, path: String, is_remote: bool) { + self.send(Data::CreateDir((id, path, is_remote))); + } + + fn send_files( + &mut self, + id: i32, + path: String, + to: String, + include_hidden: bool, + is_remote: bool, + ) { + self.send(Data::SendFiles((id, path, to, include_hidden, is_remote))); + } + + fn is_file_transfer(&self) -> bool { + self.cmd == "--file-transfer" + } + + fn is_port_forward(&self) -> bool { + self.cmd == "--port-forward" || self.is_rdp() + } + + fn is_rdp(&self) -> bool { + self.cmd == "--rdp" + } + + fn reconnect(&mut self) { + let cloned = self.clone(); + let mut lock = self.write().unwrap(); + lock.thread.take().map(|t| t.join()); + lock.thread = Some(std::thread::spawn(move || { + io_loop(cloned); + })); + } + + #[inline] + fn peer_platform(&self) -> String { + self.lc.read().unwrap().info.platform.clone() + } + + fn get_platform(&mut self, is_remote: bool) -> String { + if is_remote { + self.peer_platform() + } else { + whoami::platform().to_string() + } + } + + fn get_path_sep(&mut self, is_remote: bool) -> &'static str { + let p = self.get_platform(is_remote); + if &p == "Windows" { + return "\\"; + } else { + return "/"; + } + } + + fn get_icon_path(&mut self, file_type: i32, ext: String) -> String { + let mut path = Config::icon_path(); + if file_type == FileType::DirLink as i32 { + let new_path = path.join("dir_link"); + if !std::fs::metadata(&new_path).is_ok() { + #[cfg(windows)] + allow_err!(std::os::windows::fs::symlink_file(&path, &new_path)); + #[cfg(not(windows))] + allow_err!(std::os::unix::fs::symlink(&path, &new_path)); + } + path = new_path; + } else if file_type == FileType::File as i32 { + if !ext.is_empty() { + path = path.join(format!("file.{}", ext)); + } else { + path = path.join("file"); + } + if !std::fs::metadata(&path).is_ok() { + allow_err!(std::fs::File::create(&path)); + } + } else if file_type == FileType::FileLink as i32 { + let new_path = path.join("file_link"); + if !std::fs::metadata(&new_path).is_ok() { + path = path.join("file"); + if !std::fs::metadata(&path).is_ok() { + allow_err!(std::fs::File::create(&path)); + } + #[cfg(windows)] + allow_err!(std::os::windows::fs::symlink_file(&path, &new_path)); + #[cfg(not(windows))] + allow_err!(std::os::unix::fs::symlink(&path, &new_path)); + } + path = new_path; + } else if file_type == FileType::DirDrive as i32 { + if cfg!(windows) { + path = fs::get_path("C:"); + } else if cfg!(target_os = "macos") { + if let Ok(entries) = fs::get_path("/Volumes/").read_dir() { + for entry in entries { + if let Ok(entry) = entry { + path = entry.path(); + break; + } + } + } + } + } + fs::get_string(&path) + } + + #[inline] + fn send(&mut self, data: Data) { + if let Some(ref sender) = self.read().unwrap().sender { + sender.send(data).ok(); + } + } + + fn login(&mut self, password: String, remember: bool) { + self.send(Data::Login((password, remember))); + } + + fn new_rdp(&mut self) { + self.send(Data::NewRDP); + } + + fn send_mouse( + &mut self, + mask: i32, + x: i32, + y: i32, + alt: bool, + ctrl: bool, + shift: bool, + command: bool, + ) { + let mut msg_out = Message::new(); + let mut mouse_event = MouseEvent { + mask, + x, + y, + ..Default::default() + }; + if alt { + mouse_event.modifiers.push(ControlKey::Alt.into()); + } + if shift { + mouse_event.modifiers.push(ControlKey::Shift.into()); + } + if ctrl { + mouse_event.modifiers.push(ControlKey::Control.into()); + } + if command { + mouse_event.modifiers.push(ControlKey::Meta.into()); + } + msg_out.set_mouse_event(mouse_event); + self.send(Data::Message(msg_out)); + // on macos, ctrl + left = right, up wont emit, so we need to + // emit up myself if peer is not macos + // to-do: how about ctrl + left from win to macos + if cfg!(target_os = "macos") { + let buttons = mask >> 3; + let evt_type = mask & 0x7; + if buttons == 1 && evt_type == 1 && ctrl && self.peer_platform() != "Mac OS" { + self.send_mouse((1 << 3 | 2) as _, x, y, alt, ctrl, shift, command); + } + } + } + + fn set_cursor_data(&mut self, cd: CursorData) { + let colors = hbb_common::compress::decompress(&cd.colors); + let mut png = Vec::new(); + if let Ok(()) = repng::encode(&mut png, cd.width as _, cd.height as _, &colors) { + self.call( + "setCursorData", + &make_args!( + cd.id.to_string(), + cd.hotx, + cd.hoty, + cd.width, + cd.height, + &png[..] + ), + ); + } } fn get_key_event(&self, down_or_up: i32, name: &str, code: i32) -> Option { @@ -856,7 +801,7 @@ impl SciterSession { fn get_char(&mut self, name: String, code: i32) -> String { if let Some(key_event) = self.get_key_event(1, &name, code) { match key_event.union { - Some(key_event::Union::Chr(chr)) => { + Some(key_event::Union::chr(chr)) => { if let Some(chr) = std::char::from_u32(chr as _) { return chr.to_string(); } @@ -867,9 +812,25 @@ impl SciterSession { "".to_owned() } + fn ctrl_alt_del(&mut self) { + if self.peer_platform() == "Windows" { + let del = "CTRL_ALT_DEL".to_owned(); + self.key_down_or_up(1, del, 0, false, false, false, false, false); + } else { + let del = "VK_DELETE".to_owned(); + self.key_down_or_up(1, del.clone(), 0, true, true, false, false, false); + self.key_down_or_up(0, del, 0, true, true, false, false, false); + } + } + + fn lock_screen(&mut self) { + let lock = "LOCK_SCREEN".to_owned(); + self.key_down_or_up(1, lock, 0, false, false, false, false, false); + } + fn transfer_file(&mut self) { let id = self.get_id(); - let args = vec!["--file-transfer", &id, &self.password]; + let args = vec!["--file-transfer", &id]; if let Err(err) = crate::run_me(args) { log::error!("Failed to spawn file transfer: {}", err); } @@ -877,38 +838,796 @@ impl SciterSession { fn tunnel(&mut self) { let id = self.get_id(); - let args = vec!["--port-forward", &id, &self.password]; + let args = vec!["--port-forward", &id]; if let Err(err) = crate::run_me(args) { log::error!("Failed to spawn IP tunneling: {}", err); } } - fn version_cmp(&self, v1: String, v2: String) -> i32 { - (hbb_common::get_version_number(&v1) - hbb_common::get_version_number(&v2)) as i32 - } - - fn get_printer_names(&self) -> Value { - #[cfg(target_os = "windows")] - let printer_names = crate::platform::windows::get_printer_names().unwrap_or_default(); - #[cfg(not(target_os = "windows"))] - let printer_names: Vec = vec![]; - let mut v = Value::array(0); - for name in printer_names { - v.push(name); + fn key_down_or_up( + &mut self, + down_or_up: i32, + name: String, + code: i32, + alt: bool, + ctrl: bool, + shift: bool, + command: bool, + extended: bool, + ) { + if self.peer_platform() == "Windows" { + if ctrl && alt && name == "VK_DELETE" { + self.ctrl_alt_del(); + return; + } + if command && name == "VK_L" { + self.lock_screen(); + return; + } + } + + // extended: e.g. ctrl key on right side, https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event + // not found api of osx and xdo + log::debug!( + "{:?} {} {} {} {} {} {} {} {}", + std::time::SystemTime::now(), + down_or_up, + name, + code, + alt, + ctrl, + shift, + command, + extended, + ); + + let mut name = name; + #[cfg(target_os = "linux")] + if code == 65383 { + // VK_MENU + name = "Apps".to_owned(); + } + + if extended { + match name.as_ref() { + "VK_CONTROL" => name = "RControl".to_owned(), + "VK_MENU" => name = "RAlt".to_owned(), + "VK_SHIFT" => name = "RShift".to_owned(), + _ => {} + } + } + + if let Some(mut key_event) = self.get_key_event(down_or_up, &name, code) { + if alt + && !crate::is_control_key(&key_event, &ControlKey::Alt) + && !crate::is_control_key(&key_event, &ControlKey::RAlt) + { + key_event.modifiers.push(ControlKey::Alt.into()); + } + if shift + && !crate::is_control_key(&key_event, &ControlKey::Shift) + && !crate::is_control_key(&key_event, &ControlKey::RShift) + { + key_event.modifiers.push(ControlKey::Shift.into()); + } + if ctrl + && !crate::is_control_key(&key_event, &ControlKey::Control) + && !crate::is_control_key(&key_event, &ControlKey::RControl) + { + key_event.modifiers.push(ControlKey::Control.into()); + } + if command + && !crate::is_control_key(&key_event, &ControlKey::Meta) + && !crate::is_control_key(&key_event, &ControlKey::RWin) + { + key_event.modifiers.push(ControlKey::Meta.into()); + } + if crate::is_control_key(&key_event, &ControlKey::CapsLock) { + return; + } else if get_key_state(enigo::Key::CapsLock) && common::valid_for_capslock(&key_event) + { + key_event.modifiers.push(ControlKey::CapsLock.into()); + } + if self.peer_platform() != "Mac OS" { + if crate::is_control_key(&key_event, &ControlKey::NumLock) { + return; + } else if get_key_state(enigo::Key::NumLock) + && common::valid_for_numlock(&key_event) + { + key_event.modifiers.push(ControlKey::NumLock.into()); + } + } + if down_or_up == 1 { + key_event.down = true; + } else if down_or_up == 3 { + key_event.press = true; + } + let mut msg_out = Message::new(); + msg_out.set_key_event(key_event); + log::debug!("{:?}", msg_out); + self.send(Data::Message(msg_out)); } - v } - fn on_printer_selected(&self, id: i32, path: String, printer_name: String) { - self.printer_response(id, path, printer_name); + #[inline] + fn set_cursor_id(&mut self, id: String) { + self.call("setCursorId", &make_args!(id)); } - fn handle_screenshot(&self, action: String) -> String { - crate::client::screenshot::handle_screenshot(action) + #[inline] + fn set_cursor_position(&mut self, cd: CursorPosition) { + self.call("setCursorPosition", &make_args!(cd.x, cd.y)); + } + + #[inline] + fn call(&self, func: &str, args: &[Value]) { + let r = self.read().unwrap(); + if let Some(ref e) = r.element { + allow_err!(e.call_method(func, args)); + } + } + + #[inline] + fn set_display(&self, x: i32, y: i32, w: i32, h: i32) { + self.call("setDisplay", &make_args!(x, y, w, h)); } } -pub fn make_fd(id: i32, entries: &Vec, only_count: bool) -> Value { +const MILLI1: Duration = Duration::from_millis(1); + +async fn start_one_port_forward( + handler: Handler, + port: i32, + remote_host: String, + remote_port: i32, + receiver: mpsc::UnboundedReceiver, +) { + handler.lc.write().unwrap().port_forward = (remote_host, remote_port); + if let Err(err) = + crate::port_forward::listen(handler.id.clone(), port, handler.clone(), receiver).await + { + handler.on_error(&format!("Failed to listen on {}: {}", port, err)); + } + log::info!("port forward (:{}) exit", port); +} + +#[tokio::main(flavor = "current_thread")] +async fn io_loop(handler: Handler) { + let (sender, mut receiver) = mpsc::unbounded_channel::(); + handler.write().unwrap().sender = Some(sender.clone()); + if handler.is_port_forward() { + if handler.is_rdp() { + start_one_port_forward(handler, 0, "".to_owned(), 3389, receiver).await; + } else if handler.args.len() == 0 { + let pfs = handler.lc.read().unwrap().port_forwards.clone(); + let mut queues = HashMap::>::new(); + for d in pfs { + sender.send(Data::AddPortForward(d)).ok(); + } + loop { + match receiver.recv().await { + Some(Data::AddPortForward((port, remote_host, remote_port))) => { + if port <= 0 || remote_port <= 0 { + continue; + } + let (sender, receiver) = mpsc::unbounded_channel::(); + queues.insert(port, sender); + let handler = handler.clone(); + tokio::spawn(async move { + start_one_port_forward( + handler, + port, + remote_host, + remote_port, + receiver, + ) + .await; + }); + } + Some(Data::RemovePortForward(port)) => { + if let Some(s) = queues.remove(&port) { + s.send(Data::Close).ok(); + } + } + Some(Data::Close) => { + break; + } + Some(d) => { + for (_, s) in queues.iter() { + s.send(d.clone()).ok(); + } + } + _ => {} + } + } + } else { + let port = handler.args[0].parse::().unwrap_or(0); + if handler.args.len() != 3 + || handler.args[2].parse::().unwrap_or(0) <= 0 + || port <= 0 + { + handler.on_error("Invalid arguments, usage:

    rustdesk --port-forward remote-id listen-port remote-host remote-port"); + } + let remote_host = handler.args[1].clone(); + let remote_port = handler.args[2].parse::().unwrap_or(0); + start_one_port_forward(handler, port, remote_host, remote_port, receiver).await; + } + return; + } + let mut remote = Remote { + handler, + video_handler: VideoHandler::new(), + audio_handler: Default::default(), + receiver, + sender, + old_clipboard: Default::default(), + read_jobs: Vec::new(), + write_jobs: Vec::new(), + remove_jobs: Default::default(), + timer: time::interval(SEC30), + last_update_jobs_status: (Instant::now(), Default::default()), + clipboard: Arc::new(RwLock::new(true)), + keyboard: Arc::new(RwLock::new(true)), + first_frame: false, + }; + remote.io_loop().await; +} + +struct RemoveJob { + files: Vec, + path: String, + sep: &'static str, + is_remote: bool, + no_confirm: bool, + last_update_job_status: Instant, +} + +impl RemoveJob { + fn new(files: Vec, path: String, sep: &'static str, is_remote: bool) -> Self { + Self { + files, + path, + sep, + is_remote, + no_confirm: false, + last_update_job_status: Instant::now(), + } + } +} + +struct Remote { + handler: Handler, + audio_handler: AudioHandler, + video_handler: VideoHandler, + receiver: mpsc::UnboundedReceiver, + sender: mpsc::UnboundedSender, + old_clipboard: Arc>, + read_jobs: Vec, + write_jobs: Vec, + remove_jobs: HashMap, + timer: Interval, + last_update_jobs_status: (Instant, HashMap), + clipboard: Arc>, + keyboard: Arc>, + first_frame: bool, +} + +impl Remote { + async fn io_loop(&mut self) { + let stop_clipboard = self.start_clipboard(); + let mut last_recv_time = Instant::now(); + let conn_type = if self.handler.is_file_transfer() { + ConnType::FILE_TRANSFER + } else { + ConnType::default() + }; + match Client::start(&self.handler.id, conn_type).await { + Ok((mut peer, direct)) => { + self.handler + .call("setConnectionType", &make_args!(peer.is_secured(), direct)); + loop { + tokio::select! { + res = peer.next() => { + if let Some(res) = res { + match res { + Err(err) => { + log::error!("Connection closed: {}", err); + self.handler.msgbox("error", "Connection Error", &err.to_string()); + break; + } + Ok(ref bytes) => { + last_recv_time = Instant::now(); + if !self.handle_msg_from_peer(bytes, &mut peer).await { + break + } + } + } + } else { + log::info!("Reset by the peer"); + self.handler.msgbox("error", "Connection Error", "Reset by the peer"); + break; + } + } + d = self.receiver.recv() => { + if let Some(d) = d { + if !self.handle_msg_from_ui(d, &mut peer).await { + break; + } + } + } + _ = self.timer.tick() => { + if last_recv_time.elapsed() >= SEC30 { + self.handler.msgbox("error", "Connection Error", "Timeout"); + break; + } + if !self.read_jobs.is_empty() { + if let Err(err) = fs::handle_read_jobs(&mut self.read_jobs, &mut peer).await { + self.handler.msgbox("error", "Connection Error", &err.to_string()); + break; + } + self.update_jobs_status(); + } else { + self.timer = time::interval_at(Instant::now() + SEC30, SEC30); + } + } + } + } + log::debug!("Exit io_loop of id={}", self.handler.id); + } + Err(err) => { + self.handler + .msgbox("error", "Connection Error", &err.to_string()); + } + } + if let Some(stop) = stop_clipboard { + stop.send(()).ok(); + } + } + + fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option) { + if let Some(job) = self.remove_jobs.get_mut(&id) { + if job.no_confirm { + let file_num = (file_num + 1) as usize; + if file_num < job.files.len() { + let path = format!("{}{}{}", job.path, job.sep, job.files[file_num].name); + self.sender + .send(Data::RemoveFile((id, path, file_num as i32, job.is_remote))) + .ok(); + let elapsed = job.last_update_job_status.elapsed().as_millis() as i32; + if elapsed >= 1000 { + job.last_update_job_status = Instant::now(); + } else { + return; + } + } else { + self.remove_jobs.remove(&id); + } + } + } + if let Some(err) = err { + self.handler + .call("jobError", &make_args!(id, err, file_num)); + } else { + self.handler.call("jobDone", &make_args!(id, file_num)); + } + } + + fn start_clipboard(&mut self) -> Option> { + if self.handler.is_file_transfer() || self.handler.is_port_forward() { + return None; + } + let (tx, rx) = std::sync::mpsc::channel(); + let old_clipboard = self.old_clipboard.clone(); + let tx_protobuf = self.sender.clone(); + let clipboard = self.clipboard.clone(); + let keyboard = self.keyboard.clone(); + let lc = self.handler.lc.clone(); + match ClipboardContext::new() { + Ok(mut ctx) => { + // ignore clipboard update before service start + check_clipboard(&mut ctx, Some(&old_clipboard)); + std::thread::spawn(move || loop { + std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL)); + match rx.try_recv() { + Ok(_) | Err(std::sync::mpsc::TryRecvError::Disconnected) => { + log::debug!("Exit clipboard service of client"); + break; + } + _ => {} + } + if !*clipboard.read().unwrap() + || !*keyboard.read().unwrap() + || lc.read().unwrap().disable_clipboard + { + continue; + } + if let Some(msg) = check_clipboard(&mut ctx, Some(&old_clipboard)) { + tx_protobuf.send(Data::Message(msg)).ok(); + } + }); + } + Err(err) => { + log::error!("Failed to start clipboard service of client: {}", err); + } + } + Some(tx) + } + + async fn handle_msg_from_ui(&mut self, data: Data, peer: &mut Stream) -> bool { + match data { + Data::Close => { + return false; + } + Data::Login((password, remember)) => { + self.handler + .handle_login_from_ui(password, remember, peer) + .await; + } + Data::Message(msg) => { + allow_err!(peer.send(&msg).await); + } + Data::SendFiles((id, path, to, include_hidden, is_remote)) => { + if is_remote { + log::debug!("New job {}, write to {} from remote {}", id, to, path); + self.write_jobs + .push(fs::TransferJob::new_write(id, to, Vec::new())); + allow_err!(peer.send(&fs::new_send(id, path, include_hidden)).await); + } else { + match fs::TransferJob::new_read(id, path.clone(), include_hidden) { + Err(err) => { + self.handle_job_status(id, -1, Some(err.to_string())); + } + Ok(job) => { + log::debug!( + "New job {}, read {} to remote {}, {} files", + id, + path, + to, + job.files().len() + ); + let m = make_fd(job.id(), job.files(), true); + self.handler.call("updateFolderFiles", &make_args!(m)); + let files = job.files().clone(); + self.read_jobs.push(job); + self.timer = time::interval(MILLI1); + allow_err!(peer.send(&fs::new_receive(id, to, files)).await); + } + } + } + } + Data::SetNoConfirm(id) => { + if let Some(job) = self.remove_jobs.get_mut(&id) { + job.no_confirm = true; + } + } + Data::ConfirmDeleteFiles((id, file_num)) => { + if let Some(job) = self.remove_jobs.get_mut(&id) { + let i = file_num as usize; + if i < job.files.len() { + self.handler.call( + "confirmDeleteFiles", + &make_args!(id, file_num, job.files[i].name.clone()), + ); + } + } + } + Data::RemoveDirAll((id, path, is_remote)) => { + let sep = self.handler.get_path_sep(is_remote); + if is_remote { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_all_files(ReadAllFiles { + id, + path: path.clone(), + include_hidden: true, + ..Default::default() + }); + msg_out.set_file_action(file_action); + allow_err!(peer.send(&msg_out).await); + self.remove_jobs + .insert(id, RemoveJob::new(Vec::new(), path, sep, is_remote)); + } else { + match fs::get_recursive_files(&path, true) { + Ok(entries) => { + let m = make_fd(id, &entries, true); + self.handler.call("updateFolderFiles", &make_args!(m)); + self.remove_jobs + .insert(id, RemoveJob::new(entries, path, sep, is_remote)); + } + Err(err) => { + self.handle_job_status(id, -1, Some(err.to_string())); + } + } + } + } + Data::CancelJob(id) => { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_cancel(FileTransferCancel { + id: id, + ..Default::default() + }); + msg_out.set_file_action(file_action); + allow_err!(peer.send(&msg_out).await); + if let Some(job) = fs::get_job(id, &mut self.write_jobs) { + job.remove_download_file(); + fs::remove_job(id, &mut self.write_jobs); + } + fs::remove_job(id, &mut self.read_jobs); + self.remove_jobs.remove(&id); + } + Data::RemoveDir((id, path)) => { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_remove_dir(FileRemoveDir { + id, + path, + recursive: true, + ..Default::default() + }); + msg_out.set_file_action(file_action); + allow_err!(peer.send(&msg_out).await); + } + Data::RemoveFile((id, path, file_num, is_remote)) => { + if is_remote { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_remove_file(FileRemoveFile { + id, + path, + file_num, + ..Default::default() + }); + msg_out.set_file_action(file_action); + allow_err!(peer.send(&msg_out).await); + } else { + match fs::remove_file(&path) { + Err(err) => { + self.handle_job_status(id, file_num, Some(err.to_string())); + } + Ok(()) => { + self.handle_job_status(id, file_num, None); + } + } + } + } + Data::CreateDir((id, path, is_remote)) => { + if is_remote { + let mut msg_out = Message::new(); + let mut file_action = FileAction::new(); + file_action.set_create(FileDirCreate { + id, + path, + ..Default::default() + }); + msg_out.set_file_action(file_action); + allow_err!(peer.send(&msg_out).await); + } else { + match fs::create_dir(&path) { + Err(err) => { + self.handle_job_status(id, -1, Some(err.to_string())); + } + Ok(()) => { + self.handle_job_status(id, -1, None); + } + } + } + } + _ => {} + } + true + } + + #[inline] + fn update_job_status( + job: &fs::TransferJob, + elapsed: i32, + last_update_jobs_status: &mut (Instant, HashMap), + handler: &mut Handler, + ) { + if elapsed <= 0 { + return; + } + let transferred = job.transferred(); + let last_transferred = { + if let Some(v) = last_update_jobs_status.1.get(&job.id()) { + v.to_owned() + } else { + 0 + } + }; + last_update_jobs_status.1.insert(job.id(), transferred); + let speed = (transferred - last_transferred) as f64 / (elapsed as f64 / 1000.); + let file_num = job.file_num() - 1; + handler.call( + "jobProgress", + &make_args!(job.id(), file_num, speed, job.finished_size() as f64), + ); + } + + fn update_jobs_status(&mut self) { + let elapsed = self.last_update_jobs_status.0.elapsed().as_millis() as i32; + if elapsed >= 1000 { + for job in self.read_jobs.iter() { + Self::update_job_status( + job, + elapsed, + &mut self.last_update_jobs_status, + &mut self.handler, + ); + } + for job in self.write_jobs.iter() { + Self::update_job_status( + job, + elapsed, + &mut self.last_update_jobs_status, + &mut self.handler, + ); + } + self.last_update_jobs_status.0 = Instant::now(); + } + } + + async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool { + if let Ok(msg_in) = Message::parse_from_bytes(&data) { + match msg_in.union { + Some(message::Union::video_frame(vf)) => { + if !self.first_frame { + self.first_frame = true; + self.handler.call("closeSuccess", &make_args!()); + self.handler.call("adaptSize", &make_args!()); + } + if let Some(video_frame::Union::vp9s(vp9s)) = &vf.union { + if let Ok(true) = self.video_handler.handle_vp9s(vp9s) { + VIDEO + .lock() + .unwrap() + .as_mut() + .map(|v| v.render_frame(&self.video_handler.rgb).ok()); + } + } + } + Some(message::Union::hash(hash)) => { + self.handler.handle_hash(hash, peer).await; + } + Some(message::Union::login_response(lr)) => match lr.union { + Some(login_response::Union::error(err)) => { + if !self.handler.handle_login_error(&err) { + return false; + } + } + Some(login_response::Union::peer_info(pi)) => { + self.handler.handle_peer_info(pi); + if !(self.handler.is_file_transfer() + || self.handler.is_port_forward() + || !*self.clipboard.read().unwrap() + || !*self.keyboard.read().unwrap() + || self.handler.lc.read().unwrap().disable_clipboard) + { + let txt = self.old_clipboard.lock().unwrap().clone(); + if !txt.is_empty() { + let msg_out = crate::create_clipboard_msg(txt); + let sender = self.sender.clone(); + tokio::spawn(async move { + // due to clipboard service interval time + sleep(common::CLIPBOARD_INTERVAL as f32 / 1_000.).await; + sender.send(Data::Message(msg_out)).ok(); + }); + } + } + } + _ => {} + }, + Some(message::Union::cursor_data(cd)) => { + self.handler.set_cursor_data(cd); + } + Some(message::Union::cursor_id(id)) => { + self.handler.set_cursor_id(id.to_string()); + } + Some(message::Union::cursor_position(cp)) => { + self.handler.set_cursor_position(cp); + } + Some(message::Union::clipboard(cb)) => { + if !self.handler.lc.read().unwrap().disable_clipboard { + update_clipboard(cb, Some(&self.old_clipboard)); + } + } + Some(message::Union::file_response(fr)) => match fr.union { + Some(file_response::Union::dir(fd)) => { + let entries = fd.entries.to_vec(); + let mut m = make_fd(fd.id, &entries, fd.id > 0); + if fd.id <= 0 { + m.set_item("path", fd.path); + } + self.handler.call("updateFolderFiles", &make_args!(m)); + if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) { + job.set_files(entries); + } else if let Some(job) = self.remove_jobs.get_mut(&fd.id) { + job.files = entries; + } + } + Some(file_response::Union::block(block)) => { + if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) { + if let Err(_err) = job.write(block).await { + // to-do: add "skip" for writing job + } + self.update_jobs_status(); + } + } + Some(file_response::Union::done(d)) => { + if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { + job.modify_time(); + fs::remove_job(d.id, &mut self.write_jobs); + } + self.handle_job_status(d.id, d.file_num, None); + } + Some(file_response::Union::error(e)) => { + self.handle_job_status(e.id, e.file_num, Some(e.error)); + } + _ => {} + }, + Some(message::Union::misc(misc)) => match misc.union { + Some(misc::Union::audio_format(f)) => { + self.audio_handler.handle_format(f); + } + Some(misc::Union::chat_message(c)) => { + self.handler.call("newMessage", &make_args!(c.text)); + } + Some(misc::Union::permission_info(p)) => { + log::info!("Change permission {:?} -> {}", p.permission, p.enabled); + match p.permission.enum_value_or_default() { + Permission::Keyboard => { + self.handler + .call("setPermission", &make_args!("keyboard", p.enabled)); + } + Permission::Clipboard => { + *self.clipboard.write().unwrap() = p.enabled; + self.handler + .call("setPermission", &make_args!("clipboard", p.enabled)); + } + Permission::Audio => { + self.handler + .call("setPermission", &make_args!("audio", p.enabled)); + } + } + } + Some(misc::Union::switch_display(s)) => { + self.handler.call("switchDisplay", &make_args!(s.display)); + self.video_handler.reset(); + if s.width > 0 && s.height > 0 { + VIDEO.lock().unwrap().as_mut().map(|v| { + v.stop_streaming().ok(); + let ok = v.start_streaming( + (s.width, s.height), + COLOR_SPACE::Rgb32, + None, + ); + log::info!("[video] reinitialized: {:?}", ok); + }); + self.handler.set_display(s.x, s.y, s.width, s.height); + } + } + Some(misc::Union::close_reason(c)) => { + self.handler.msgbox("error", "Connection Error", &c); + return false; + } + _ => {} + }, + Some(message::Union::test_delay(t)) => { + self.handler.handle_test_delay(t, peer).await; + } + Some(message::Union::audio_frame(frame)) => { + self.audio_handler + .handle_frame(frame, !self.handler.lc.read().unwrap().disable_audio); + } + _ => {} + } + } + true + } +} + +fn make_fd(id: i32, entries: &Vec, only_count: bool) -> Value { let mut m = Value::map(); m.set_item("id", id); let mut a = Value::array(0); @@ -920,16 +1639,113 @@ pub fn make_fd(id: i32, entries: &Vec, only_count: bool) -> Value { } let mut e = Value::map(); e.set_item("name", entry.name.to_owned()); - let tmp = entry.entry_type.value(); - e.set_item("type", if tmp == 0 { 1 } else { tmp }); + e.set_item("type", entry.entry_type.value()); e.set_item("time", entry.modified_time as f64); e.set_item("size", entry.size as f64); a.push(e); } - if !only_count { + if only_count { + m.set_item("num_entries", entries.len() as i32); + } else { m.set_item("entries", a); } - m.set_item("num_entries", entries.len() as i32); m.set_item("total_size", n as f64); m } + +#[async_trait] +impl Interface for Handler { + fn msgbox(&self, msgtype: &str, title: &str, text: &str) { + let retry = check_if_retry(msgtype, title, text); + self.call("msgbox_retry", &make_args!(msgtype, title, text, retry)); + } + + fn handle_login_error(&mut self, err: &str) -> bool { + self.lc.write().unwrap().handle_login_error(err, self) + } + + fn handle_peer_info(&mut self, pi: PeerInfo) { + let mut pi_sciter = Value::map(); + let username = self.lc.read().unwrap().get_username(&pi); + pi_sciter.set_item("username", username.clone()); + pi_sciter.set_item("hostname", pi.hostname.clone()); + pi_sciter.set_item("platform", pi.platform.clone()); + pi_sciter.set_item("sas_enabled", pi.sas_enabled); + if self.is_file_transfer() { + if pi.username.is_empty() { + self.on_error("No active console user logged on, please connect and logon first."); + return; + } + } else if !self.is_port_forward() { + if pi.displays.is_empty() { + self.lc.write().unwrap().handle_peer_info(username, pi); + self.msgbox("error", "Remote Error", "No Display"); + return; + } + let mut displays = Value::array(0); + for ref d in pi.displays.iter() { + let mut display = Value::map(); + display.set_item("x", d.x); + display.set_item("y", d.y); + display.set_item("width", d.width); + display.set_item("height", d.height); + displays.push(display); + } + pi_sciter.set_item("displays", displays); + let mut current = pi.current_display as usize; + if current >= pi.displays.len() { + current = 0; + } + pi_sciter.set_item("current_display", current as i32); + let current = &pi.displays[current]; + self.set_display(current.x, current.y, current.width, current.height); + // https://sciter.com/forums/topic/color_spaceiyuv-crash + // Nothing spectacular in decoder – done on CPU side. + // So if you can do BGRA translation on your side – the better. + // BGRA is used as internal image format so it will not require additional transformations. + VIDEO.lock().unwrap().as_mut().map(|v| { + let ok = v.start_streaming( + (current.width as _, current.height as _), + COLOR_SPACE::Rgb32, + None, + ); + log::info!("[video] initialized: {:?}", ok); + }); + } + self.lc.write().unwrap().handle_peer_info(username, pi); + self.call("updatePi", &make_args!(pi_sciter)); + if self.is_file_transfer() { + self.call("closeSuccess", &make_args!()); + } else if !self.is_port_forward() { + self.msgbox("success", "Successful", "Connected, waiting for image..."); + } + #[cfg(windows)] + { + let mut path = std::env::temp_dir(); + path.push(&self.id); + let path = path.with_extension(config::APP_NAME.to_lowercase()); + std::fs::File::create(&path).ok(); + if let Some(path) = path.to_str() { + crate::platform::windows::add_recent_document(&path); + } + } + } + + async fn handle_hash(&mut self, hash: Hash, peer: &mut Stream) { + handle_hash(self.lc.clone(), hash, self, peer).await; + } + + async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) { + handle_login_from_ui(self.lc.clone(), password, remember, peer).await; + } + + async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) { + handle_test_delay(t, peer).await; + } +} + +impl Handler { + fn on_error(&self, err: &str) { + self.msgbox("error", "Error", err); + } +} diff --git a/src/ui/remote.tis b/src/ui/remote.tis deleted file mode 100644 index 28fbc3763..000000000 --- a/src/ui/remote.tis +++ /dev/null @@ -1,601 +0,0 @@ -var cursor_img = $(img#cursor); -is_file_transfer = handler.is_file_transfer(); -var is_port_forward = handler.is_port_forward(); -var input_blocked = false; -var display_width = 0; -var display_height = 0; -var display_remote_scale = 1; -var display_origin_x = 0; -var display_origin_y = 0; -var display_cursor_embedded = false; -var display_scale = 1; -// the scale factor is different from `display_scale` if peer platform is Linux (Wayland). -var cursor_scale = 1; -var keyboard_enabled = true; // server side -var clipboard_enabled = true; // server side -var audio_enabled = true; // server side -var file_enabled = true; // server side -var restart_enabled = true; // server side -var recording_enabled = true; // server side -var privacy_mode_enabled = true; // server side -var scroll_body = $(body); -var peer_platform = ""; - -handler.setDisplay = function(x, y, w, h, cursor_embedded, scale) { - display_width = w; - display_height = h; - display_origin_x = x; - display_origin_y = y; - display_cursor_embedded = cursor_embedded; - display_remote_scale = scale; - adaptDisplay(); - if (recording) handler.record_screen(true, 0, w, h); -} - -// in case toolbar not shown correctly -view.windowMinSize = (scaleIt(500), scaleIt(300)); - -function get_peer_platform() { - if (peer_platform == "") { - peer_platform = handler.peer_platform(); - } - return peer_platform; -} - -function isRemoteLinux() { - return get_peer_platform() == "Linux"; -} - -function adaptDisplay() { - var w = display_width; - var h = display_height; - if (!w || !h) return; - var style = handler.get_view_style(); - display_scale = 1.; - cursor_scale = 1.; - var (sx, sy, sw, sh) = view.screenBox(view.windowState == View.WINDOW_FULL_SCREEN ? #frame : #workarea, #rectw); - if (sw >= w && sh > h) { - var hh = $(header).box(#height, #border); - var el = $(div#adjust-window); - if (sh > h + hh && el) { - el.style.set{ display: "block" }; - el = $(li#adjust-window); - el.style.set{ display: "block" }; - el.onClick = function() { - view.windowState == View.WINDOW_SHOWN; - var (x, y) = view.box(#position, #border, #screen); - // extra for border - var extra = is_win ? 4 : 2; - view.move(x, y, (w + extra).toInteger(), (h + hh + extra).toInteger()); - } - } - } - if (style != "original") { - var bw = $(body).box(#width, #border); - var bh = $(body).box(#height, #border); - if (view.windowState == View.WINDOW_FULL_SCREEN) { - bw = sw; - bh = sh; - } - if (bw > 0 && bh > 0) { - var scale_x = bw.toFloat() / w; - var scale_y = bh.toFloat() / h; - var scale = scale_x < scale_y ? scale_x : scale_y; - if ((scale > 1 && style == "stretch") || - (scale < 1 && style == "shrink")) { - display_scale = scale; - w = w * scale; - h = h * scale; - } - } - } - if (isRemoteLinux()) { - cursor_scale = display_scale * display_remote_scale; - } else { - cursor_scale = display_scale; - } - if (cursor_scale <= 0.0001) cursor_scale = 1.; - refreshCursor(); - handler.style.set { - width: w / scaleFactor + "px", - height: h / scaleFactor + "px", - }; -} - -// https://sciter.com/event-handling/ -// https://sciter.com/docs/content/sciter/Event.htm - -var entered = false; -if (!is_file_transfer && !is_port_forward) { - self.onKey = function(evt) { - if (!entered) return false; - // so that arrow key not move scrollbar - return true; - } -} - -var wait_window_toolbar = false; -var last_mouse_mask; -var is_left_down = false; -var acc_wheel_delta_x = 0; -var acc_wheel_delta_y = 0; -var last_wheel_time = 0; -var inertia_velocity_x = 0; -var inertia_velocity_y = 0; -var acc_wheel_delta_x0 = 0; -var acc_wheel_delta_y0 = 0; -var total_wheel_time = 0; -var wheeling = false; -var dragging = false; -var is_mouse_event_triggered = false; - -// https://stackoverflow.com/questions/5833399/calculating-scroll-inertia-momentum -function resetWheel() { - acc_wheel_delta_x = 0; - acc_wheel_delta_y = 0; - last_wheel_time = 0; - inertia_velocity_x = 0; - inertia_velocity_y = 0; - acc_wheel_delta_x0 = 0; - acc_wheel_delta_y0 = 0; - total_wheel_time = 0; - wheeling = false; -} - -var INERTIA_ACCELERATION = 30; -var WHEEL_ACCEL_VELOCITY_THRESHOLD = 5000; -var WHEEL_ACCEL_DT_FAST = 0.04; -var WHEEL_ACCEL_DT_MEDIUM = 0.08; -var WHEEL_ACCEL_VALUE_FAST = 3; -var WHEEL_ACCEL_VALUE_MEDIUM = 2; -// Wheel burst acceleration (empirical tuning). -// Applies only on fast, non-smooth wheel bursts to keep single-step scroll unchanged. -// Sciter uses seconds for dt, so velocity is in delta/sec. - -// not good, precision not enough to simulate acceleration effect, -// seems have to use pixel based rather line based delta -function accWheel(v, is_x) { - if (wheeling) return; - var abs_v = Math.abs(v); - var max_t = abs_v / INERTIA_ACCELERATION; - for (var t = 0.1; t < max_t; t += 0.1) { - var d = Math.round((abs_v - t * INERTIA_ACCELERATION / 2) * t).toInteger(); - if (d >= 1) { - abs_v -= t * INERTIA_ACCELERATION; - if (v < 0) { - d = -d; - v = -abs_v; - } else { - v = abs_v; - } - handler.send_mouse(3, is_x ? d : 0, !is_x ? d : 0, false, false, false, false); - accWheel(v, is_x); - break; - } - } -} - -function handler.onMouse(evt) -{ - is_mouse_event_triggered = true; - if (is_file_transfer || is_port_forward) return false; - if (view.windowState == View.WINDOW_FULL_SCREEN && !dragging) { - var dy = evt.y - scroll_body.scroll(#top); - if (dy <= 1) { - if (!wait_window_toolbar) { - wait_window_toolbar = true; - self.timer(300ms, function() { - if (!wait_window_toolbar) return; - var extra = 0; - // workaround for stupid Sciter, without this, click - // event not triggered on top part of buttons on toolbar - if (is_osx) extra = 10; - if (view.windowState == View.WINDOW_FULL_SCREEN) { - $(header).style.set { - display: "block", - padding: (2 * workarea_offset + extra) + "px 0 0 0", - }; - } - wait_window_toolbar = false; - }); - } - } else { - wait_window_toolbar = false; - var h = $(header).style; - if (dy > 20 && h#display != "none") { - h.set { - display: "none", - }; - } - } - } - if (!got_mouse_control) { - if (Math.abs(evt.x - cur_local_x) > 12 || Math.abs(evt.y - cur_local_y) > 12) { - got_mouse_control = true; - } else { - return; - } - } - var mask = 0; - var wheel_delta_x; - var wheel_delta_y; - switch(evt.type) { - case Event.MOUSE_DOWN: - mask = 1; - dragging = true; - break; - case Event.MOUSE_UP: - mask = 2; - dragging = false; - break; - case Event.MOUSE_MOVE: - if (display_cursor_embedded) { - break; - } - if (cursor_img.style#display != "none" && keyboard_enabled) { - cursor_img.style#display = "none"; - } - if (!keyboard_enabled && !useSystemCursor) { - updateCursor(true); - } - if (keyboard_enabled && useSystemCursor) { - updateCursor(); - } - break; - case Event.MOUSE_WHEEL: - // mouseWheelDistance = 8 * [currentUserDefs floatForKey:@"com.apple.scrollwheel.scaling"]; - mask = 3; - { - var now = getTime(); - var dt = last_wheel_time > 0 ? (now - last_wheel_time) / 1000 : 0; - var (raw_dx, raw_dy) = evt.wheelDeltas; - var dx = 0; - var dy = 0; - var abs_dx = Math.abs(raw_dx); - var abs_dy = Math.abs(raw_dy); - var dominant = abs_dx > abs_dy ? abs_dx : abs_dy; - var is_smooth = dominant < 1; - var accel = 1; - if (!is_smooth && dt > 0 && (is_win || is_linux) && get_peer_platform() == "Mac OS") { - var velocity = dominant / dt; - if (velocity >= WHEEL_ACCEL_VELOCITY_THRESHOLD) { - if (dt < WHEEL_ACCEL_DT_FAST) accel = WHEEL_ACCEL_VALUE_FAST; - else if (dt < WHEEL_ACCEL_DT_MEDIUM) accel = WHEEL_ACCEL_VALUE_MEDIUM; - } - } - if (raw_dx > 0) dx = accel; - else if (raw_dx < 0) dx = -accel; - if (raw_dy > 0) dy = accel; - else if (raw_dy < 0) dy = -accel; - if (abs_dx > abs_dy) { - dy = 0; - } else { - dx = 0; - } - acc_wheel_delta_x += dx; - acc_wheel_delta_y += dy; - wheel_delta_x = acc_wheel_delta_x.toInteger(); - wheel_delta_y = acc_wheel_delta_y.toInteger(); - acc_wheel_delta_x -= wheel_delta_x; - acc_wheel_delta_y -= wheel_delta_y; - if (dt > 0) { - var vx = dx / dt; - var vy = dy / dt; - if (vx != 0 || vy != 0) { - inertia_velocity_x = vx; - inertia_velocity_y = vy; - } - } - acc_wheel_delta_x0 += dx; - acc_wheel_delta_y0 += dy; - total_wheel_time += dt; - if (dx == 0 && dy == 0) { - wheeling = false; - if (dt < 0.1 && total_wheel_time > 0) { - var v2 = (acc_wheel_delta_y0 / total_wheel_time) * inertia_velocity_y; - if (v2 > 0) { - v2 = Math.sqrt(v2); - inertia_velocity_y = inertia_velocity_y < 0 ? -v2 : v2; - accWheel(inertia_velocity_y, false); - } - v2 = (acc_wheel_delta_x0 / total_wheel_time) * inertia_velocity_x; - if (v2 > 0) { - v2 = Math.sqrt(v2); - inertia_velocity_x = inertia_velocity_x < 0 ? -v2 : v2; - accWheel(inertia_velocity_x, true); - } - } - resetWheel(); - } else { - wheeling = true; - } - last_wheel_time = now; - if (wheel_delta_x == 0 && wheel_delta_y == 0) return keyboard_enabled; - } - break; - case Event.MOUSE_DCLICK: // seq: down, up, dclick, up - mask = 1; - break; - case Event.MOUSE_ENTER: - entered = true; - stdout.println("enter"); - handler.enter(handler.get_keyboard_mode()); - last_wheel_time = 0; - return keyboard_enabled; - case Event.MOUSE_LEAVE: - entered = false; - stdout.println("leave"); - handler.leave(handler.get_keyboard_mode()); - last_wheel_time = 0; - if (is_left_down && get_peer_platform() == "Android") { - is_left_down = false; - handler.send_mouse((1 << 3) | 2, 0, 0, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey); - } - return keyboard_enabled; - default: - return false; - } - var x = evt.x; - var y = evt.y; - if (mask != 0) { - // to gain control of the mouse, user must move mouse - if (cur_x != x || cur_y != y) { - return keyboard_enabled; - } - } else { - cur_local_x = cur_x = x; - cur_local_y = cur_y = y; - } - if (mask != 3) { - resetWheel(); - } - if (!keyboard_enabled) return false; - x = (x / cursor_scale).toInteger(); - y = (y / cursor_scale).toInteger(); - // insert down between two up, osx has this behavior for triple click - if (last_mouse_mask == 2 && mask == 2) { - handler.send_mouse((evt.buttons << 3) | 1, 0, 0, evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey); - } - last_mouse_mask = mask; - if (evt.buttons == 1) { - if (mask == 1) { - is_left_down = true; - } else if (mask == 2) { - is_left_down = false; - } - } - // to-do: altKey, ctrlKey etc - handler.send_mouse((evt.buttons << 3) | mask, - mask == 3 ? wheel_delta_x : (mask == 0 ? x + display_origin_x : 0), - mask == 3 ? wheel_delta_y : (mask == 0 ? y + display_origin_y : 0), - evt.altKey, - evt.ctrlKey, evt.shiftKey, evt.commandKey); - return true; -}; - -var cur_id = -1; -var cur_hotx = 0; -var cur_hoty = 0; -var cur_img = null; -var cur_x = 0; -var cur_y = 0; -var cur_local_x = 0; -var cur_local_y = 0; -var cursors = {}; -var image_binded; - -function scaleCursorImage(img) { - var factor = cursor_scale; - if (cursor_img.style#display != 'none') { - factor /= scaleFactor; - } - var w = (img.width * factor).toInteger(); - var h = (img.height * factor).toInteger(); - cursor_img.style.set { - width: w + "px", - height: h + "px", - }; - self.bindImage("in-memory:cursor", img); - if (factor == 1) return img; - function paint(gfx) { - gfx.drawImage(img, 0, 0, w, h); - } - return new Image(w, h, paint); -} - -var useSystemCursor = true; -function updateCursor(system=false) { - stdout.println("Update cursor, system: " + system); - useSystemCursor = system; - if (system) { - handler.style#cursor = undefined; - } else if (cur_img) { - handler.style.cursor(cur_img, (cur_hotx * cursor_scale).toInteger(), (cur_hoty * cursor_scale).toInteger()); - } -} - -function refreshCursor() { - if (display_cursor_embedded) { - cursor_img.style#display = "none"; - return; - } - if (cur_id != -1) { - handler.setCursorId(cur_id); - } -} - -handler.setCursorData = function(id, hotx, hoty, width, height, colors) { - cur_hotx = hotx; - cur_hoty = hoty; - var img = Image.fromBytes(colors); - if (img) { - image_binded = true; - cursors[id] = [img, hotx, hoty, width, height]; - cur_id = id; - img = scaleCursorImage(img); - if (!first_mouse_event_triggered || cursor_img.style#display == 'none') { - self.timer(1ms, updateCursor); - } - cur_img = img; - } -} - -handler.setCursorId = function(id) { - var img = cursors[id]; - if (img) { - cur_id = id; - image_binded = true; - cur_hotx = img[1]; - cur_hoty = img[2]; - img = scaleCursorImage(img[0]); - if (!first_mouse_event_triggered || cursor_img.style#display == 'none') { - self.timer(1ms, updateCursor); - } - cur_img = img; - } -} - -var got_mouse_control = true; -handler.setCursorPosition = function(x, y) { - if (!image_binded) return; - got_mouse_control = false; - cur_x = x - display_origin_x; - cur_y = y - display_origin_y; - var x = cur_x - cur_hotx; - var y = cur_y - cur_hoty; - x *= cursor_scale / scaleFactor; - y *= cursor_scale / scaleFactor; - cursor_img.style.set { - left: x + "px", - top: y + "px", - }; - if (cursor_img.style#display == 'none') { - cursor_img.style#display = "block"; - refreshCursor(); - } -} - -function self.ready() { - var w = scaleIt(960); - var h = scaleIt(640); - if (is_file_transfer || is_port_forward) { - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - view.move(r[0], r[1], r[2], r[3]); - } else { - centerize(w, h); - } - } else { - centerize(w, h); - } - if (!is_port_forward) connecting(); - if (is_file_transfer) initializeFileTransfer(); - if (is_port_forward) initializePortForward(); -} - -var workarea_offset = 0; -var size_adapted; -handler.adaptSize = function() { - if (size_adapted) return; - size_adapted = true; - var (sx, sy, sw, sh) = view.screenBox(#workarea, #rectw); - var (fx, fy, fw, fh) = view.screenBox(#frame, #rectw); - if (is_osx) workarea_offset = sy; - var r = handler.get_size(); - if (isReasonableSize(r) && r[2] > 0) { - if (r[2] >= fw && r[3] >= fh && !is_linux) { - view.windowState = View.WINDOW_FULL_SCREEN; - stdout.println("Initialize to full screen"); - } else if (r[2] >= sw && r[3] >= sh) { - view.windowState = View.WINDOW_MAXIMIZED; - stdout.println("Initialize to full screen"); - } else { - view.move(r[0], r[1], r[2], r[3]); - } - } else { - var w = handler.box(#width, #border) - var h = handler.box(#height, #border) - if (w >= sw || h >= sh) { - view.windowState = View.WINDOW_MAXIMIZED; - return; - } - // extra for border - var extra = 2; - centerize(w + extra, handler.box(#height, #border) + h + extra); - } -} - -function self.closing() { - var (x, y, w, h) = view.box(#rectw, #border, #screen); - if (is_file_transfer) save_file_transfer_close_state(); - if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h); - if (recording) handler.record_screen(false, 0, display_width, display_height); -} - -var qualityMonitor; -var qualityMonitorData = []; - -class QualityMonitor: Reactor.Component -{ - function this() { - qualityMonitor = this; - if (handler.get_toggle_option("show-quality-monitor")) { - $(#quality-monitor).style.set{ display: "block" }; - } - } - - function render() { - return
    -
    - Speed: {qualityMonitorData[0]} -
    -
    - FPS: {qualityMonitorData[1]} -
    -
    - Delay: {qualityMonitorData[2]} ms -
    -
    - Target Bitrate: {qualityMonitorData[3]}kb -
    -
    - Codec: {qualityMonitorData[4]} -
    -
    - Chroma: {qualityMonitorData[5]} -
    -
    ; - } -} - -$(#quality-monitor).content(); -handler.updateQualityStatus = function(speed, fps, delay, bitrate, codec_format, chroma) { - if (speed !== null) qualityMonitorData[0] = speed; - if (fps !== null) qualityMonitorData[1] = fps; - if (delay !== null) qualityMonitorData[2] = qualityMonitorData[1] === 0 ? 0 : delay; - if (bitrate !== null) qualityMonitorData[3] = bitrate; - if (codec_format !== null) qualityMonitorData[4] = codec_format; - if (chroma !== null) qualityMonitorData[5] = chroma; - qualityMonitor.update(); -} - -handler.setPermission = function(name, enabled) { - self.timer(60ms, function() { - if (name == "keyboard") keyboard_enabled = enabled; - if (name == "audio") audio_enabled = enabled; - if (name == "file") file_enabled = enabled; - if (name == "clipboard") clipboard_enabled = enabled; - if (name == "restart") restart_enabled = enabled; - if (name == "recording") recording_enabled = enabled; - if (name == "privacy_mode") privacy_mode_enabled = enabled; - input_blocked = false; - header.update(); - }); -} - -handler.closeSuccess = function() { - // handler.msgbox("success", "Successful", "Ready to go."); - handler.msgbox("", "", ""); -} diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs deleted file mode 100644 index cab0d7f1c..000000000 --- a/src/ui_cm_interface.rs +++ /dev/null @@ -1,1798 +0,0 @@ -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::ipc::Connection; -#[cfg(not(any(target_os = "ios")))] -use crate::ipc::{self, Data}; -#[cfg(target_os = "windows")] -use crate::{clipboard::ClipboardSide, ipc::ClipboardNonFile}; -#[cfg(target_os = "windows")] -use clipboard::ContextSend; -#[cfg(not(any(target_os = "ios")))] -use hbb_common::fs::serialize_transfer_job; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use hbb_common::tokio::sync::mpsc::unbounded_channel; -use hbb_common::{ - allow_err, bail, - config::{ - keys::{OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, OPTION_FILE_TRANSFER_MAX_FILES}, - option2bool, Config, - }, - fs::{self, get_string, is_write_need_confirmation, new_send_confirm, DigestCheckResult}, - log, - message_proto::*, - protobuf::Message as _, - tokio::{ - self, - sync::mpsc::{self, UnboundedSender}, - task::spawn_blocking, - }, - ResultType, -}; -#[cfg(target_os = "windows")] -use hbb_common::{config::keys::*, tokio::sync::Mutex as TokioMutex}; -use serde_derive::Serialize; -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -use std::iter::FromIterator; -#[cfg(not(any(target_os = "ios")))] -use std::path::PathBuf; -#[cfg(target_os = "windows")] -use std::sync::Arc; -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, - sync::{ - atomic::{AtomicI64, Ordering}, - RwLock, - }, -}; - -/// Default maximum number of files allowed per transfer request. -/// Unit: number of files (not bytes). -#[cfg(not(any(target_os = "ios")))] -const DEFAULT_MAX_VALIDATED_FILES: usize = 10_000; - -/// Maximum number of files allowed in a single file transfer request. -/// -/// This limit prevents excessive I/O and memory usage when dealing with -/// large directories. It applies to: -/// - CM-side read jobs (server to client file transfers on Windows) -/// - `AllFiles` recursive directory listing operations -/// - Connection-side read jobs (non-Windows platforms) -/// -/// Unit: number of files (not bytes). -/// Default: 10,000 files. -/// Configured via: `OPTION_FILE_TRANSFER_MAX_FILES` ("file-transfer-max-files") -#[cfg(not(any(target_os = "ios")))] -static MAX_VALIDATED_FILES: std::sync::OnceLock = std::sync::OnceLock::new(); - -/// Get the maximum number of files allowed per transfer request. -/// -/// Initializes the value from configuration (`OPTION_FILE_TRANSFER_MAX_FILES`) -/// on first call. Semantics: -/// - If the option is set to `0`, `DEFAULT_MAX_VALIDATED_FILES` (10,000) is used as a safe upper bound. -/// - If the option is unset, negative, or non-integer, -/// `usize::MAX` is used to represent "no limit" for backward compatibility with older versions -/// that did not enforce any file‑count restriction. -/// (Note: negative values are not valid for `usize` and will cause parsing to fail.) -/// -/// Unit: number of files. -#[cfg(not(any(target_os = "ios")))] -#[inline] -pub fn get_max_validated_files() -> usize { - // If `OPTION_FILE_TRANSFER_MAX_FILES` unset, negative, or non-integer, use - // `usize::MAX` to represent "no limit", maintaining backward compatibility - // with versions that had no file transfer restrictions. - const NO_LIMIT_FILE_COUNT: usize = usize::MAX; - *MAX_VALIDATED_FILES.get_or_init(|| { - let c = crate::get_builtin_option(OPTION_FILE_TRANSFER_MAX_FILES) - .trim() - .parse::() - .unwrap_or(NO_LIMIT_FILE_COUNT); - if c == 0 { - DEFAULT_MAX_VALIDATED_FILES - } else { - c - } - }) -} - -/// Check if file count exceeds the maximum allowed limit. -/// -/// This check is enforced in: -/// - `start_read_job()` for CM-side read jobs -/// - `read_all_files()` for recursive directory listings -/// - `Connection::on_message()` for connection-side read jobs -/// -/// # Arguments -/// * `file_count` - Number of files in the transfer request -/// -/// # Returns -/// * `Ok(())` if within limit -/// * `Err(String)` with error message if limit exceeded -#[cfg(not(any(target_os = "ios")))] -pub fn check_file_count_limit(file_count: usize) -> Result<(), String> { - let max_files = get_max_validated_files(); - if file_count > max_files { - let msg = format!( - "file transfer rejected: too many files ({} files exceeds limit of {}). \ - Adjust '{}' option to increase limit.", - file_count, max_files, OPTION_FILE_TRANSFER_MAX_FILES - ); - log::warn!("{}", msg); - Err(msg) - } else { - Ok(()) - } -} - -#[derive(Serialize, Clone)] -pub struct Client { - pub id: i32, - pub authorized: bool, - pub disconnected: bool, - pub is_file_transfer: bool, - pub is_view_camera: bool, - pub is_terminal: bool, - pub port_forward: String, - pub name: String, - pub avatar: String, - pub peer_id: String, - pub keyboard: bool, - pub clipboard: bool, - pub audio: bool, - pub file: bool, - pub restart: bool, - pub recording: bool, - pub block_input: bool, - pub privacy_mode: bool, - pub from_switch: bool, - pub in_voice_call: bool, - pub incoming_voice_call: bool, - #[serde(skip)] - #[cfg(not(any(target_os = "ios")))] - tx: UnboundedSender, -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -struct IpcTaskRunner { - stream: Connection, - cm: ConnectionManager, - tx: mpsc::UnboundedSender, - rx: mpsc::UnboundedReceiver, - close: bool, - running: bool, - conn_id: i32, - #[cfg(target_os = "windows")] - file_transfer_enabled: bool, - #[cfg(target_os = "windows")] - file_transfer_enabled_peer: bool, - /// Read jobs for CM-side file reading (server to client transfers) - read_jobs: Vec, -} - -lazy_static::lazy_static! { - static ref CLIENTS: RwLock> = Default::default(); -} - -static CLICK_TIME: AtomicI64 = AtomicI64::new(0); - -#[derive(Clone)] -pub struct ConnectionManager { - pub ui_handler: T, -} - -pub trait InvokeUiCM: Send + Clone + 'static + Sized { - fn add_connection(&self, client: &Client); - - fn remove_connection(&self, id: i32, close: bool); - - fn new_message(&self, id: i32, text: String); - - fn change_theme(&self, dark: String); - - fn change_language(&self); - - fn show_elevation(&self, show: bool); - - fn update_voice_call_state(&self, client: &Client); - - fn file_transfer_log(&self, action: &str, log: &str); -} - -impl Deref for ConnectionManager { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.ui_handler - } -} - -impl DerefMut for ConnectionManager { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.ui_handler - } -} - -impl ConnectionManager { - fn add_connection( - &self, - id: i32, - is_file_transfer: bool, - is_view_camera: bool, - is_terminal: bool, - port_forward: String, - peer_id: String, - name: String, - avatar: String, - authorized: bool, - keyboard: bool, - clipboard: bool, - audio: bool, - file: bool, - restart: bool, - recording: bool, - block_input: bool, - privacy_mode: bool, - from_switch: bool, - #[cfg(not(any(target_os = "ios")))] tx: mpsc::UnboundedSender, - ) { - let client = Client { - id, - authorized, - disconnected: false, - is_file_transfer, - is_view_camera, - is_terminal, - port_forward, - name: name.clone(), - avatar, - peer_id: peer_id.clone(), - keyboard, - clipboard, - audio, - file, - restart, - recording, - block_input, - privacy_mode, - from_switch, - #[cfg(not(any(target_os = "ios")))] - tx, - in_voice_call: false, - incoming_voice_call: false, - }; - CLIENTS - .write() - .unwrap() - .retain(|_, c| !(c.disconnected && c.peer_id == client.peer_id)); - CLIENTS.write().unwrap().insert(id, client.clone()); - self.ui_handler.add_connection(&client); - } - - #[inline] - #[cfg(target_os = "windows")] - fn is_authorized(&self, id: i32) -> bool { - CLIENTS - .read() - .unwrap() - .get(&id) - .map(|c| c.authorized) - .unwrap_or(false) - } - - fn remove_connection(&self, id: i32, close: bool) { - if close { - CLIENTS.write().unwrap().remove(&id); - } else { - CLIENTS - .write() - .unwrap() - .get_mut(&id) - .map(|c| c.disconnected = true); - } - - #[cfg(target_os = "windows")] - { - crate::clipboard::try_empty_clipboard_files(ClipboardSide::Host, id); - } - - #[cfg(any(target_os = "android"))] - if CLIENTS - .read() - .unwrap() - .iter() - .filter(|(_k, v)| !v.is_file_transfer && !v.is_terminal) - .next() - .is_none() - { - if let Err(e) = - scrap::android::call_main_service_set_by_name("stop_capture", None, None) - { - log::debug!("stop_capture err:{}", e); - } - } - - self.ui_handler.remove_connection(id, close); - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn show_elevation(&self, show: bool) { - self.ui_handler.show_elevation(show); - } - - #[cfg(not(target_os = "ios"))] - fn voice_call_started(&self, id: i32) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { - client.incoming_voice_call = false; - client.in_voice_call = true; - self.ui_handler.update_voice_call_state(client); - } - } - - #[cfg(not(target_os = "ios"))] - fn voice_call_incoming(&self, id: i32) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { - client.incoming_voice_call = true; - client.in_voice_call = false; - self.ui_handler.update_voice_call_state(client); - } - } - - #[cfg(not(target_os = "ios"))] - fn voice_call_closed(&self, id: i32, _reason: &str) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { - client.incoming_voice_call = false; - client.in_voice_call = false; - self.ui_handler.update_voice_call_state(client); - } - } -} - -#[inline] -#[cfg(not(any(target_os = "ios")))] -pub fn check_click_time(id: i32) { - if let Some(client) = CLIENTS.read().unwrap().get(&id) { - allow_err!(client.tx.send(Data::ClickTime(0))); - }; -} - -#[inline] -pub fn get_click_time() -> i64 { - CLICK_TIME.load(Ordering::SeqCst) -} - -#[inline] -#[cfg(not(any(target_os = "ios")))] -pub fn authorize(id: i32) { - if let Some(client) = CLIENTS.write().unwrap().get_mut(&id) { - client.authorized = true; - allow_err!(client.tx.send(Data::Authorize)); - }; -} - -#[inline] -#[cfg(not(any(target_os = "ios")))] -pub fn close(id: i32) { - if let Some(client) = CLIENTS.read().unwrap().get(&id) { - allow_err!(client.tx.send(Data::Close)); - }; -} - -#[inline] -pub fn remove(id: i32) { - CLIENTS.write().unwrap().remove(&id); -} - -// server mode send chat to peer -#[inline] -#[cfg(not(any(target_os = "ios")))] -pub fn send_chat(id: i32, text: String) { - let clients = CLIENTS.read().unwrap(); - if let Some(client) = clients.get(&id) { - allow_err!(client.tx.send(Data::ChatMessage { text })); - } -} - -#[inline] -#[cfg(not(any(target_os = "ios")))] -pub fn switch_permission(id: i32, name: String, enabled: bool) { - #[cfg(target_os = "android")] - let is_keyboard_permission = name == "keyboard"; - #[cfg(not(target_os = "android"))] - let is_keyboard_permission = false; - if !option2bool( - OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, - &crate::get_builtin_option(OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW), - ) && !is_keyboard_permission - { - log::info!( - "blocked cm switch_permission by policy, conn_id={}, permission={}, enabled={}", - id, - name, - enabled - ); - return; - } - if let Some(client) = CLIENTS.read().unwrap().get(&id) { - allow_err!(client.tx.send(Data::SwitchPermission { name, enabled })); - }; -} - -#[inline] -#[cfg(target_os = "android")] -pub fn switch_permission_all(name: String, enabled: bool) { - if name != "keyboard" - && !option2bool( - OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW, - &crate::get_builtin_option(OPTION_ENABLE_PERM_CHANGE_IN_ACCEPT_WINDOW), - ) - { - log::info!( - "blocked cm switch_permission_all by policy, permission={}, enabled={}", - name, - enabled - ); - return; - } - for (_, client) in CLIENTS.read().unwrap().iter() { - allow_err!(client.tx.send(Data::SwitchPermission { - name: name.clone(), - enabled - })); - } -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn get_clients_state() -> String { - let clients = CLIENTS.read().unwrap(); - let res = Vec::from_iter(clients.values().cloned()); - serde_json::to_string(&res).unwrap_or("".into()) -} - -#[inline] -pub fn get_clients_length() -> usize { - let clients = CLIENTS.read().unwrap(); - clients.len() -} - -#[inline] -#[cfg(target_os = "android")] -pub fn has_active_clients() -> bool { - let clients = CLIENTS.read().unwrap(); - clients.values().any(|c| !c.disconnected) -} - -#[inline] -#[cfg(feature = "flutter")] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn switch_back(id: i32) { - if let Some(client) = CLIENTS.read().unwrap().get(&id) { - allow_err!(client.tx.send(Data::SwitchSidesBack)); - }; -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -impl IpcTaskRunner { - async fn run(&mut self) { - use hbb_common::config::LocalConfig; - use hbb_common::tokio::time::{self, Duration, Instant}; - - const MILLI5: Duration = Duration::from_millis(5); - const SEC30: Duration = Duration::from_secs(30); - - // for tmp use, without real conn id - let mut write_jobs: Vec = Vec::new(); - // File timer for processing read_jobs - let mut file_timer = - crate::rustdesk_interval(time::interval_at(Instant::now() + SEC30, SEC30)); - - #[cfg(target_os = "windows")] - let is_authorized = self.cm.is_authorized(self.conn_id); - - #[cfg(target_os = "windows")] - let rx_clip_holder; - let mut rx_clip; - let _tx_clip; - #[cfg(target_os = "windows")] - if self.conn_id > 0 && is_authorized { - log::debug!("Clipboard is enabled from client peer: type 1"); - let conn_id = self.conn_id; - rx_clip_holder = ( - clipboard::get_rx_cliprdr_server(conn_id), - Some(crate::SimpleCallOnReturn { - b: true, - f: Box::new(move || { - clipboard::remove_channel_by_conn_id(conn_id); - }), - }), - ); - rx_clip = rx_clip_holder.0.lock().await; - } else { - log::debug!("Clipboard is enabled from client peer, actually useless: type 2"); - let rx_clip2; - (_tx_clip, rx_clip2) = unbounded_channel::(); - rx_clip_holder = (Arc::new(TokioMutex::new(rx_clip2)), None); - rx_clip = rx_clip_holder.0.lock().await; - } - #[cfg(not(target_os = "windows"))] - { - (_tx_clip, rx_clip) = unbounded_channel::(); - } - - #[cfg(target_os = "windows")] - { - if ContextSend::is_enabled() { - log::debug!("Clipboard is enabled"); - allow_err!( - self.stream - .send(&Data::ClipboardFile(clipboard::ClipboardFile::MonitorReady)) - .await - ); - } - } - let (tx_log, mut rx_log) = mpsc::unbounded_channel::(); - - self.running = false; - loop { - tokio::select! { - res = self.stream.next() => { - match res { - Err(err) => { - log::info!("cm ipc connection closed: {}", err); - break; - } - Ok(Some(data)) => { - match data { - Data::Login{id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, file_transfer_enabled: _file_transfer_enabled, restart, recording, block_input, privacy_mode, from_switch} => { - log::debug!("conn_id: {}", id); - self.cm.add_connection(id, is_file_transfer, is_view_camera, is_terminal, port_forward, peer_id, name, avatar, authorized, keyboard, clipboard, audio, file, restart, recording, block_input, privacy_mode, from_switch, self.tx.clone()); - self.conn_id = id; - #[cfg(target_os = "windows")] - { - self.file_transfer_enabled = _file_transfer_enabled; - } - self.running = true; - break; - } - Data::Close => { - log::info!("cm ipc connection closed from connection request"); - break; - } - Data::Disconnected => { - self.close = false; - log::info!("cm ipc connection disconnect"); - break; - } - Data::PrivacyModeState((_id, _, _)) => { - #[cfg(windows)] - cm_inner_send(_id, data); - } - Data::ClickTime(ms) => { - CLICK_TIME.store(ms, Ordering::SeqCst); - } - Data::ChatMessage { text } => { - self.cm.new_message(self.conn_id, text); - } - Data::SwitchPermission { name, enabled } => { - // Keep this branch scoped to privacy mode rollback. - // Other CM permission toggles are updated optimistically by the UI itself. - // The backend currently sends SwitchPermission back to CM only when - // privacy-mode turn-off fails and the UI state must be restored. - if name == "privacy_mode" { - let client = { - let mut clients = CLIENTS.write().unwrap(); - clients.get_mut(&self.conn_id).map(|c| { - c.privacy_mode = enabled; - c.clone() - }) - }; - if let Some(client) = client { - // This reuses add_connection(), and cm.tis only selectively updates - // existing rows (authorized/privacy_mode) for this fallback path. - self.cm.ui_handler.add_connection(&client); - } - } - } - Data::FS(mut fs) => { - if let ipc::FS::WriteBlock { id, file_num, data: _, compressed } = fs { - if let Ok(bytes) = self.stream.next_raw().await { - fs = ipc::FS::WriteBlock{id, file_num, data:bytes.into(), compressed}; - handle_fs(fs, &mut write_jobs, &mut self.read_jobs, &self.tx, Some(&tx_log), self.conn_id).await; - } - } else { - handle_fs(fs, &mut write_jobs, &mut self.read_jobs, &self.tx, Some(&tx_log), self.conn_id).await; - } - // Activate fast timer immediately when read jobs exist. - // This ensures new jobs start processing without waiting for the slow 30s timer. - // Deactivation (back to 30s) happens in tick handler when jobs are exhausted. - if !self.read_jobs.is_empty() { - file_timer = crate::rustdesk_interval(time::interval(MILLI5)); - } - let log = fs::serialize_transfer_jobs(&write_jobs); - self.cm.ui_handler.file_transfer_log("transfer", &log); - } - Data::FileTransferLog((action, log)) => { - self.cm.ui_handler.file_transfer_log(&action, &log); - } - #[cfg(target_os = "windows")] - Data::ClipboardFile(_clip) => { - let is_stopping_allowed = _clip.is_beginning_message(); - let is_clipboard_enabled = ContextSend::is_enabled(); - let file_transfer_enabled = self.file_transfer_enabled; - let stop = !is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled); - log::debug!( - "Process clipboard message from client peer, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}", - stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled); - if stop { - ContextSend::set_is_stopped(); - } else { - if !is_authorized { - log::debug!("Clipboard message from client peer, but not authorized"); - continue; - } - let conn_id = self.conn_id; - let _ = ContextSend::proc(|context| -> ResultType<()> { - context.server_clip_file(conn_id, _clip) - .map_err(|e| e.into()) - }); - } - } - Data::ClipboardFileEnabled(_enabled) => { - #[cfg(target_os = "windows")] - { - self.file_transfer_enabled_peer = _enabled; - } - } - Data::Theme(dark) => { - self.cm.change_theme(dark); - } - Data::Language(lang) => { - LocalConfig::set_option("lang".to_owned(), lang); - self.cm.change_language(); - } - Data::DataPortableService(ipc::DataPortableService::CmShowElevation(show)) => { - self.cm.show_elevation(show); - } - Data::StartVoiceCall => { - self.cm.voice_call_started(self.conn_id); - } - Data::VoiceCallIncoming => { - self.cm.voice_call_incoming(self.conn_id); - } - Data::CloseVoiceCall(reason) => { - self.cm.voice_call_closed(self.conn_id, reason.as_str()); - } - #[cfg(target_os = "windows")] - Data::ClipboardNonFile(_) => { - match crate::clipboard::check_clipboard_cm() { - Ok(multi_clipoards) => { - let mut raw_contents = bytes::BytesMut::new(); - let mut main_data = vec![]; - for c in multi_clipoards.clipboards.into_iter() { - let content_len = c.content.len(); - let (content, next_raw) = { - // TODO: find out a better threshold - if content_len > 1024 * 3 { - raw_contents.extend(c.content); - (bytes::Bytes::new(), true) - } else { - (c.content, false) - } - }; - main_data.push(ClipboardNonFile { - compress: c.compress, - content, - content_len, - next_raw, - width: c.width, - height: c.height, - format: c.format.value(), - special_name: c.special_name, - }); - } - allow_err!(self.stream.send(&Data::ClipboardNonFile(Some(("".to_owned(), main_data)))).await); - if !raw_contents.is_empty() { - allow_err!(self.stream.send_raw(raw_contents.into()).await); - } - } - Err(e) => { - log::debug!("Failed to get clipboard content. {}", e); - allow_err!(self.stream.send(&Data::ClipboardNonFile(Some((format!("{}", e), vec![])))).await); - } - } - } - _ => { - - } - } - } - _ => {} - } - } - Some(data) = self.rx.recv() => { - // For FileBlockFromCM, data is sent separately via send_raw (data field has #[serde(skip)]). - // This avoids JSON encoding overhead for large binary data. - // This mirrors the WriteBlock pattern in start_ipc (see rx_to_cm handler). - // - // Note: Empty data (for empty files) is correctly handled. BytesCodec with raw=false - // (the default for IPC connections) adds a length prefix, so send_raw(Bytes::new()) - // sends a 1-byte frame that next_raw() can correctly receive as empty data. - if let Data::FileBlockFromCM { id, file_num, ref data, compressed, conn_id } = data { - // Send metadata first (data field is skipped by serde), then raw data bytes - if let Err(e) = self.stream.send(&Data::FileBlockFromCM { - id, - file_num, - data: bytes::Bytes::new(), // placeholder, skipped by serde - compressed, - conn_id, - }).await { - log::error!("error sending FileBlockFromCM metadata: {}", e); - break; - } - if let Err(e) = self.stream.send_raw(data.clone()).await { - log::error!("error sending FileBlockFromCM data: {}", e); - break; - } - continue; - } - if let Err(e) = self.stream.send(&data).await { - log::error!("error encountered in IPC task, quitting: {}", e); - break; - } - match &data { - Data::SwitchPermission{name: _name, enabled: _enabled} => { - #[cfg(target_os = "windows")] - if _name == "file" { - self.file_transfer_enabled = *_enabled; - } - } - Data::Authorize => { - self.running = true; - break; - } - _ => { - } - } - }, - clip_file = rx_clip.recv() => match clip_file { - Some(_clip) => { - #[cfg(target_os = "windows")] - { - let is_stopping_allowed = _clip.is_stopping_allowed(); - let is_clipboard_enabled = ContextSend::is_enabled(); - let file_transfer_enabled = self.file_transfer_enabled; - let file_transfer_enabled_peer = self.file_transfer_enabled_peer; - let stop = is_stopping_allowed && !(is_clipboard_enabled && file_transfer_enabled && file_transfer_enabled_peer); - log::debug!( - "Process clipboard message from clip, stop: {}, is_stopping_allowed: {}, is_clipboard_enabled: {}, file_transfer_enabled: {}, file_transfer_enabled_peer: {}", - stop, is_stopping_allowed, is_clipboard_enabled, file_transfer_enabled, file_transfer_enabled_peer); - if stop { - ContextSend::set_is_stopped(); - } else { - if _clip.is_beginning_message() && crate::get_builtin_option(OPTION_ONE_WAY_FILE_TRANSFER) == "Y" { - // If one way file transfer is enabled, don't send clipboard file to client - // Don't call `ContextSend::set_is_stopped()`, because it will stop bidirectional file copy&paste. - } else { - allow_err!(self.tx.send(Data::ClipboardFile(_clip))); - } - } - } - } - None => { - // - } - }, - Some(job_log) = rx_log.recv() => { - self.cm.ui_handler.file_transfer_log("transfer", &job_log); - } - _ = file_timer.tick() => { - if !self.read_jobs.is_empty() { - let conn_id = self.conn_id; - if let Err(e) = handle_read_jobs_tick(&mut self.read_jobs, &self.tx, conn_id).await { - log::error!("Error processing read jobs: {}", e); - } - let log = fs::serialize_transfer_jobs(&self.read_jobs); - self.cm.ui_handler.file_transfer_log("transfer", &log); - } else { - file_timer = crate::rustdesk_interval(time::interval_at(Instant::now() + SEC30, SEC30)); - } - } - } - } - } - - async fn ipc_task(stream: Connection, cm: ConnectionManager) { - log::debug!("ipc task begin"); - let (tx, rx) = mpsc::unbounded_channel::(); - let mut task_runner = Self { - stream, - cm, - tx, - rx, - close: true, - running: true, - conn_id: 0, - #[cfg(target_os = "windows")] - file_transfer_enabled: false, - #[cfg(target_os = "windows")] - file_transfer_enabled_peer: false, - read_jobs: Vec::new(), - }; - - while task_runner.running { - task_runner.run().await; - } - if task_runner.conn_id > 0 { - task_runner - .cm - .remove_connection(task_runner.conn_id, task_runner.close); - } - log::debug!("ipc task end"); - } -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[tokio::main(flavor = "current_thread")] -pub async fn start_ipc(cm: ConnectionManager) { - #[cfg(target_os = "windows")] - { - let enabled = crate::Connection::is_permission_enabled_locally(OPTION_ENABLE_FILE_TRANSFER); - let mut lock = crate::ui_interface::IS_FILE_TRANSFER_ENABLED - .lock() - .unwrap(); - ContextSend::enable(enabled); - *lock = Some(enabled); - } - match ipc::new_listener("_cm").await { - Ok(mut incoming) => { - while let Some(result) = incoming.next().await { - match result { - Ok(stream) => { - log::debug!("Got new connection"); - tokio::spawn(IpcTaskRunner::::ipc_task( - Connection::new(stream), - cm.clone(), - )); - } - Err(err) => { - log::error!("Couldn't get cm client: {:?}", err); - } - } - } - } - Err(err) => { - log::error!("Failed to start cm ipc server: {}", err); - } - } - quit_cm(); -} - -#[cfg(target_os = "android")] -#[tokio::main(flavor = "current_thread")] -pub async fn start_listen( - cm: ConnectionManager, - mut rx: mpsc::UnboundedReceiver, - tx: mpsc::UnboundedSender, -) { - let mut current_id = 0; - let mut write_jobs: Vec = Vec::new(); - loop { - match rx.recv().await { - Some(Data::Login { - id, - is_file_transfer, - is_view_camera, - is_terminal, - port_forward, - peer_id, - name, - avatar, - authorized, - keyboard, - clipboard, - audio, - file, - restart, - recording, - block_input, - privacy_mode, - from_switch, - .. - }) => { - current_id = id; - cm.add_connection( - id, - is_file_transfer, - is_view_camera, - is_terminal, - port_forward, - peer_id, - name, - avatar, - authorized, - keyboard, - clipboard, - audio, - file, - restart, - recording, - block_input, - privacy_mode, - from_switch, - tx.clone(), - ); - } - Some(Data::ChatMessage { text }) => { - cm.new_message(current_id, text); - } - Some(Data::FS(fs)) => { - // Android doesn't need CM-side file reading (no need_validate_file_read_access) - let mut read_jobs_placeholder: Vec = Vec::new(); - handle_fs( - fs, - &mut write_jobs, - &mut read_jobs_placeholder, - &tx, - None, - current_id, - ) - .await; - } - Some(Data::Close) => { - break; - } - Some(Data::StartVoiceCall) => { - cm.voice_call_started(current_id); - } - Some(Data::VoiceCallIncoming) => { - cm.voice_call_incoming(current_id); - } - Some(Data::CloseVoiceCall(reason)) => { - cm.voice_call_closed(current_id, reason.as_str()); - } - None => { - break; - } - _ => {} - } - } - cm.remove_connection(current_id, true); -} - -#[cfg(not(any(target_os = "ios")))] -async fn handle_fs( - fs: ipc::FS, - write_jobs: &mut Vec, - read_jobs: &mut Vec, - tx: &UnboundedSender, - tx_log: Option<&UnboundedSender>, - _conn_id: i32, -) { - match fs { - ipc::FS::ReadEmptyDirs { - dir, - include_hidden, - } => { - read_empty_dirs(&dir, include_hidden, tx).await; - } - ipc::FS::ReadDir { - dir, - include_hidden, - } => { - read_dir(&dir, include_hidden, tx).await; - } - ipc::FS::RemoveDir { - path, - id, - recursive, - } => { - remove_dir(path, id, recursive, tx).await; - } - ipc::FS::RemoveFile { path, id, file_num } => { - remove_file(path, id, file_num, tx).await; - } - ipc::FS::CreateDir { path, id } => { - create_dir(path, id, tx).await; - } - ipc::FS::NewWrite { - path, - id, - file_num, - mut files, - overwrite_detection, - total_size, - conn_id, - } => { - // Convert files to FileEntry - let file_entries: Vec = files - .drain(..) - .map(|f| FileEntry { - name: f.0, - modified_time: f.1, - ..Default::default() - }) - .collect(); - - // cm has no show_hidden context - // dummy remote, show_hidden, is_remote - let mut job = fs::TransferJob::new_write( - id, - fs::JobType::Generic, - "".to_string(), - fs::DataSource::FilePath(PathBuf::from(&path)), - file_num, - false, - false, - overwrite_detection, - ); - if let Err(e) = job.set_files(file_entries) { - log::warn!("Reject unsafe transfer file list for {}: {}", path, e); - send_raw(fs::new_error(id, e, file_num), tx); - return; - } - job.total_size = total_size; - job.conn_id = conn_id; - write_jobs.push(job); - } - ipc::FS::CancelWrite { id } => { - if let Some(job) = fs::remove_job(id, write_jobs) { - job.remove_download_file(); - if let Some(tx) = tx_log { - if let Err(e) = tx.send(serialize_transfer_job(&job, false, true, "")) { - log::error!("error sending transfer job log via IPC: {}", e); - } - } - } - } - ipc::FS::WriteDone { id, file_num } => { - if let Some(job) = fs::remove_job(id, write_jobs) { - job.modify_time(); - send_raw(fs::new_done(id, file_num), tx); - tx_log.map(|tx| tx.send(serialize_transfer_job(&job, true, false, ""))); - } - } - ipc::FS::WriteError { id, file_num, err } => { - if let Some(job) = fs::remove_job(id, write_jobs) { - tx_log.map(|tx| tx.send(serialize_transfer_job(&job, false, false, &err))); - send_raw(fs::new_error(job.id(), err, file_num), tx); - } - } - ipc::FS::WriteBlock { - id, - file_num, - data, - compressed, - } => { - if let Some(job) = fs::get_job(id, write_jobs) { - if let Err(err) = job - .write(FileTransferBlock { - id, - file_num, - data, - compressed, - ..Default::default() - }) - .await - { - send_raw(fs::new_error(id, err, file_num), &tx); - } - } - } - ipc::FS::CheckDigest { - id, - file_num, - file_size, - last_modified, - is_upload, - is_resume, - } => { - if let Some(job) = fs::get_job(id, write_jobs) { - let mut req = FileTransferSendConfirmRequest { - id, - file_num, - union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)), - ..Default::default() - }; - let digest = FileTransferDigest { - id, - file_num, - last_modified, - file_size, - ..Default::default() - }; - if let Some(file) = job.files().get(file_num as usize) { - if let fs::DataSource::FilePath(p) = &job.data_source { - let path = get_string(&fs::TransferJob::join(p, &file.name)); - match is_write_need_confirmation(is_resume, &path, &digest) { - Ok(digest_result) => { - job.set_digest(file_size, last_modified); - match digest_result { - DigestCheckResult::IsSame => { - req.set_skip(true); - let msg_out = new_send_confirm(req); - send_raw(msg_out, &tx); - } - DigestCheckResult::NeedConfirm(mut digest) => { - // upload to server, but server has the same file, request - digest.is_upload = is_upload; - let mut msg_out = Message::new(); - let mut fr = FileResponse::new(); - fr.set_digest(digest); - msg_out.set_file_response(fr); - send_raw(msg_out, &tx); - } - DigestCheckResult::NoSuchFile => { - let msg_out = new_send_confirm(req); - send_raw(msg_out, &tx); - } - } - } - Err(err) => { - send_raw(fs::new_error(id, err, file_num), &tx); - } - } - } - } - } - } - ipc::FS::SendConfirm(bytes) => { - if let Ok(r) = FileTransferSendConfirmRequest::parse_from_bytes(&bytes) { - if let Some(job) = fs::get_job(r.id, write_jobs) { - job.confirm(&r).await; - } - } - } - ipc::FS::Rename { id, path, new_name } => { - rename_file(path, new_name, id, tx).await; - } - ipc::FS::ReadFile { - path, - id, - file_num, - include_hidden, - conn_id, - overwrite_detection, - } => { - start_read_job( - path, - file_num, - include_hidden, - id, - conn_id, - overwrite_detection, - read_jobs, - tx, - ) - .await; - } - // Cancel an ongoing read job (file transfer from server to client). - // Note: This only cancels jobs in `read_jobs`. It does NOT cancel `ReadAllFiles` - // operations, which are one-shot directory scans that complete quickly and don't - // have persistent job tracking. - ipc::FS::CancelRead { id, conn_id: _ } => { - if let Some(job) = fs::remove_job(id, read_jobs) { - if let Some(tx) = tx_log { - if let Err(e) = tx.send(serialize_transfer_job(&job, false, true, "")) { - log::error!("error sending transfer job log via IPC: {}", e); - } - } - } - } - ipc::FS::SendConfirmForRead { - id, - file_num: _, - skip, - offset_blk, - conn_id: _, - } => { - if let Some(job) = fs::get_job(id, read_jobs) { - let req = FileTransferSendConfirmRequest { - id, - file_num: job.file_num(), - union: if skip { - Some(file_transfer_send_confirm_request::Union::Skip(true)) - } else { - Some(file_transfer_send_confirm_request::Union::OffsetBlk( - offset_blk, - )) - }, - ..Default::default() - }; - job.confirm(&req).await; - } - } - // Recursively list all files in a directory. - // This is a one-shot operation that cannot be cancelled via CancelRead. - // The operation typically completes quickly as it only reads directory metadata, - // not file contents. File count is limited by `check_file_count_limit()`. - ipc::FS::ReadAllFiles { - path, - id, - include_hidden, - conn_id, - } => { - read_all_files(path, include_hidden, id, conn_id, tx).await; - } - _ => {} - } -} - -/// Start a read job in CM for file transfer from server to client (Windows only). -/// -/// This creates a `TransferJob` using `new_read()`, validates it, and sends the -/// initial file list back to Connection via IPC. -/// -/// NOTE: This is the CM-side equivalent of `create_and_start_read_job()` in -/// `src/server/connection.rs`. On non-Windows platforms, Connection handles -/// read jobs directly. Both use `TransferJob::new_read()` with similar logic. -/// When modifying job creation or validation, ensure both paths stay in sync. -#[cfg(not(any(target_os = "ios")))] -async fn start_read_job( - path: String, - file_num: i32, - include_hidden: bool, - id: i32, - conn_id: i32, - overwrite_detection: bool, - read_jobs: &mut Vec, - tx: &UnboundedSender, -) { - let path_clone = path.clone(); - let result = spawn_blocking(move || -> ResultType { - let data_source = fs::DataSource::FilePath(PathBuf::from(&path)); - fs::TransferJob::new_read( - id, - fs::JobType::Generic, - "".to_string(), - data_source, - file_num, - include_hidden, - true, - overwrite_detection, - ) - }) - .await; - - match result { - Ok(Ok(mut job)) => { - // Optional: enforce file count limit for CM-side jobs to avoid - // excessive I/O. This is applied on the job's file list produced - // by `new_read`, similar to how AllFiles uses the same helper. - if let Err(msg) = check_file_count_limit(job.files().len()) { - if let Err(e) = tx.send(Data::ReadJobInitResult { - id, - file_num, - include_hidden, - conn_id, - result: Err(msg), - }) { - log::error!("error sending ReadJobInitResult via IPC: {}", e); - } - return; - } - - // Build FileDirectory from the job's file list and serialize - let files = job.files().to_owned(); - let mut dir = FileDirectory::new(); - dir.id = id; - dir.path = path_clone.clone(); - dir.entries = files.clone().into(); - - let dir_bytes = match dir.write_to_bytes() { - Ok(bytes) => bytes, - Err(e) => { - if let Err(e) = tx.send(Data::ReadJobInitResult { - id, - file_num, - include_hidden, - conn_id, - result: Err(format!("serialize failed: {}", e)), - }) { - log::error!("error sending ReadJobInitResult via IPC: {}", e); - } - return; - } - }; - - if let Err(e) = tx.send(Data::ReadJobInitResult { - id, - file_num, - include_hidden, - conn_id, - result: Ok(dir_bytes), - }) { - log::error!("error sending ReadJobInitResult via IPC: {}", e); - } - - // Attach connection id so CM can route read blocks back correctly - job.conn_id = conn_id; - read_jobs.push(job); - } - Ok(Err(e)) => { - if let Err(e) = tx.send(Data::ReadJobInitResult { - id, - file_num, - include_hidden, - conn_id, - result: Err(format!("validation failed: {}", e)), - }) { - log::error!("error sending ReadJobInitResult via IPC: {}", e); - } - } - Err(e) => { - if let Err(e) = tx.send(Data::ReadJobInitResult { - id, - file_num, - include_hidden, - conn_id, - result: Err(format!("validation task failed: {}", e)), - }) { - log::error!("error sending ReadJobInitResult via IPC: {}", e); - } - } - } -} - -/// Process read jobs periodically, reading file blocks and sending them via IPC. -/// -/// NOTE: This is the CM-side equivalent of `handle_read_jobs()` in -/// `libs/hbb_common/src/fs.rs`. The logic mirrors that implementation -/// but communicates via IPC instead of direct network stream. -/// When modifying job processing logic, ensure both implementations stay in sync. -#[cfg(not(any(target_os = "ios")))] -async fn handle_read_jobs_tick( - jobs: &mut Vec, - tx: &UnboundedSender, - conn_id: i32, -) -> ResultType<()> { - let mut finished = Vec::new(); - - for job in jobs.iter_mut() { - if job.is_last_job { - continue; - } - - // Initialize data stream if needed (opens file, sends digest for overwrite detection) - if let Err(err) = init_read_job_for_cm(job, tx, conn_id).await { - if let Err(e) = tx.send(Data::FileReadError { - id: job.id, - file_num: job.file_num(), - err: format!("{}", err), - conn_id, - }) { - log::error!("error sending FileReadError via IPC: {}", e); - } - finished.push(job.id); - continue; - } - - // Read a block from the file - match job.read().await { - Err(err) => { - if let Err(e) = tx.send(Data::FileReadError { - id: job.id, - file_num: job.file_num(), - err: format!("{}", err), - conn_id, - }) { - log::error!("error sending FileReadError via IPC: {}", e); - } - // Mark job as finished to prevent infinite retries. - // Connection side will have already removed cm_read_job_ids - // after receiving FileReadError, so continuing would be pointless. - finished.push(job.id); - } - Ok(Some(block)) => { - if let Err(e) = tx.send(Data::FileBlockFromCM { - id: block.id, - file_num: block.file_num, - data: block.data, - compressed: block.compressed, - conn_id, - }) { - log::error!("error sending FileBlockFromCM via IPC: {}", e); - } - } - Ok(None) => { - if job.job_completed() { - finished.push(job.id); - match job.job_error() { - Some(err) => { - if let Err(e) = tx.send(Data::FileReadError { - id: job.id, - file_num: job.file_num(), - err, - conn_id, - }) { - log::error!("error sending FileReadError via IPC: {}", e); - } - } - None => { - if let Err(e) = tx.send(Data::FileReadDone { - id: job.id, - file_num: job.file_num(), - conn_id, - }) { - log::error!("error sending FileReadDone via IPC: {}", e); - } - } - } - } - // else: waiting for confirmation from peer - } - } - // Break to handle jobs one by one. - break; - } - - for id in finished { - let _ = fs::remove_job(id, jobs); - } - - Ok(()) -} - -/// Initialize a read job's data stream and handle digest sending for overwrite detection. -/// -/// NOTE: This is the CM-side equivalent of `TransferJob::init_data_stream()` in -/// `libs/hbb_common/src/fs.rs`. It calls `init_data_stream_for_cm()` and sends -/// digest via IPC instead of direct network stream. -/// When modifying initialization or digest logic, ensure both paths stay in sync. -#[cfg(not(any(target_os = "ios")))] -async fn init_read_job_for_cm( - job: &mut fs::TransferJob, - tx: &UnboundedSender, - conn_id: i32, -) -> ResultType<()> { - // Initialize data stream and get digest info if overwrite detection is needed - match job.init_data_stream_for_cm().await? { - Some((last_modified, file_size)) => { - // Send digest via IPC for overwrite detection - if let Err(e) = tx.send(Data::FileDigestFromCM { - id: job.id, - file_num: job.file_num(), - last_modified, - file_size, - is_resume: job.is_resume, - conn_id, - }) { - log::error!("error sending FileDigestFromCM via IPC: {}", e); - } - } - None => { - // Job done or already initialized, nothing to do - } - } - Ok(()) -} - -#[cfg(not(any(target_os = "ios")))] -async fn read_all_files( - path: String, - include_hidden: bool, - id: i32, - conn_id: i32, - tx: &UnboundedSender, -) { - let path_clone = path.clone(); - let result = spawn_blocking(move || fs::get_recursive_files(&path, include_hidden)).await; - - let result = match result { - Ok(Ok(files)) => { - // Check file count limit to prevent excessive I/O and resource usage - if let Err(msg) = check_file_count_limit(files.len()) { - Err(msg) - } else { - // Serialize FileDirectory to protobuf bytes - let mut fd = FileDirectory::new(); - fd.id = id; - fd.path = path_clone.clone(); - fd.entries = files.into(); - match fd.write_to_bytes() { - Ok(bytes) => Ok(bytes), - Err(e) => Err(format!("serialize failed: {}", e)), - } - } - } - Ok(Err(e)) => Err(format!("{}", e)), - Err(e) => Err(format!("task failed: {}", e)), - }; - - if let Err(e) = tx.send(Data::AllFilesResult { - id, - conn_id, - path: path_clone, - result, - }) { - log::error!("error sending AllFilesResult via IPC: {}", e); - } -} - -#[cfg(not(any(target_os = "ios")))] -async fn read_empty_dirs(dir: &str, include_hidden: bool, tx: &UnboundedSender) { - let path = dir.to_owned(); - let path_clone = dir.to_owned(); - - if let Ok(Ok(fds)) = - spawn_blocking(move || fs::get_empty_dirs_recursive(&path, include_hidden)).await - { - let mut msg_out = Message::new(); - let mut file_response = FileResponse::new(); - file_response.set_empty_dirs(ReadEmptyDirsResponse { - path: path_clone, - empty_dirs: fds, - ..Default::default() - }); - msg_out.set_file_response(file_response); - send_raw(msg_out, tx); - } -} - -#[cfg(not(any(target_os = "ios")))] -async fn read_dir(dir: &str, include_hidden: bool, tx: &UnboundedSender) { - let path = { - if dir.is_empty() { - Config::get_home() - } else { - fs::get_path(dir) - } - }; - if let Ok(Ok(fd)) = spawn_blocking(move || fs::read_dir(&path, include_hidden)).await { - let mut msg_out = Message::new(); - let mut file_response = FileResponse::new(); - file_response.set_dir(fd); - msg_out.set_file_response(file_response); - send_raw(msg_out, tx); - } -} - -#[cfg(not(any(target_os = "ios")))] -async fn handle_result( - res: std::result::Result, S>, - id: i32, - file_num: i32, - tx: &UnboundedSender, -) { - match res { - Err(err) => { - send_raw(fs::new_error(id, err, file_num), tx); - } - Ok(Err(err)) => { - send_raw(fs::new_error(id, err, file_num), tx); - } - Ok(Ok(())) => { - send_raw(fs::new_done(id, file_num), tx); - } - } -} - -#[cfg(not(any(target_os = "ios")))] -async fn remove_file(path: String, id: i32, file_num: i32, tx: &UnboundedSender) { - handle_result( - spawn_blocking(move || fs::remove_file(&path)).await, - id, - file_num, - tx, - ) - .await; -} - -#[cfg(not(any(target_os = "ios")))] -async fn create_dir(path: String, id: i32, tx: &UnboundedSender) { - handle_result( - spawn_blocking(move || fs::create_dir(&path)).await, - id, - 0, - tx, - ) - .await; -} - -#[cfg(not(any(target_os = "ios")))] -async fn rename_file(path: String, new_name: String, id: i32, tx: &UnboundedSender) { - handle_result( - spawn_blocking(move || fs::rename_file(&path, &new_name)).await, - id, - 0, - tx, - ) - .await; -} - -#[cfg(not(any(target_os = "ios")))] -async fn remove_dir(path: String, id: i32, recursive: bool, tx: &UnboundedSender) { - let path = fs::get_path(&path); - handle_result( - spawn_blocking(move || { - if recursive { - fs::remove_all_empty_dir(&path) - } else { - std::fs::remove_dir(&path).map_err(|err| err.into()) - } - }) - .await, - id, - 0, - tx, - ) - .await; -} - -#[cfg(not(any(target_os = "ios")))] -fn send_raw(msg: Message, tx: &UnboundedSender) { - match msg.write_to_bytes() { - Ok(bytes) => { - allow_err!(tx.send(Data::RawMessage(bytes))); - } - err => allow_err!(err), - } -} - -#[cfg(windows)] -fn cm_inner_send(id: i32, data: Data) { - let lock = CLIENTS.read().unwrap(); - if id != 0 { - if let Some(s) = lock.get(&id) { - allow_err!(s.tx.send(data)); - } - } else { - for s in lock.values() { - allow_err!(s.tx.send(data.clone())); - } - } -} - -pub fn can_elevate() -> bool { - #[cfg(windows)] - return !crate::platform::is_installed(); - #[cfg(not(windows))] - return false; -} - -pub fn elevate_portable(_id: i32) { - #[cfg(windows)] - { - let lock = CLIENTS.read().unwrap(); - if let Some(s) = lock.get(&_id) { - allow_err!(s.tx.send(ipc::Data::DataPortableService( - ipc::DataPortableService::RequestStart - ))); - } - } -} - -#[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.read().unwrap().get(&id) { - // Not handled in iOS yet. - #[cfg(not(any(target_os = "ios")))] - allow_err!(client.tx.send(Data::VoiceCallResponse(accept))); - }; -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn close_voice_call(id: i32) { - if let Some(client) = CLIENTS.read().unwrap().get(&id) { - // Not handled in iOS yet. - #[cfg(not(any(target_os = "ios")))] - allow_err!(client.tx.send(Data::CloseVoiceCall("".to_owned()))); - }; -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn quit_cm() { - // in case of std::process::exit not work - log::info!("quit cm"); - CLIENTS.write().unwrap().clear(); - crate::platform::quit_gui(); -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::ipc::Data; - use hbb_common::{ - message_proto::{FileDirectory, Message}, - tokio::{runtime::Runtime, sync::mpsc::unbounded_channel}, - }; - use std::fs; - - #[test] - #[cfg(not(any(target_os = "ios")))] - fn read_all_files_success() { - let rt = Runtime::new().unwrap(); - rt.block_on(async { - let (tx, mut rx) = unbounded_channel(); - let dir = std::env::temp_dir().join("rustdesk_read_all_test"); - let _ = fs::remove_dir_all(&dir); - fs::create_dir_all(&dir).unwrap(); - fs::write(dir.join("test.txt"), b"hello").unwrap(); - - let path_str = dir.to_string_lossy().to_string(); - super::read_all_files(path_str.clone(), false, 1, 2, &tx).await; - - match rx.recv().await.unwrap() { - Data::AllFilesResult { result, .. } => { - let bytes = result.unwrap(); - let fd = FileDirectory::parse_from_bytes(&bytes).unwrap(); - assert!(!fd.entries.is_empty()); - } - _ => panic!("unexpected data"), - } - let _ = fs::remove_dir_all(&dir); - }); - } - - #[test] - #[cfg(not(any(target_os = "ios")))] - fn read_dir_success() { - let rt = Runtime::new().unwrap(); - rt.block_on(async { - let (tx, mut rx) = unbounded_channel(); - let dir = std::env::temp_dir().join("rustdesk_read_dir_test"); - let _ = fs::remove_dir_all(&dir); - fs::create_dir_all(&dir).unwrap(); - - super::read_dir(&dir.to_string_lossy(), false, &tx).await; - - match rx.recv().await.unwrap() { - Data::RawMessage(bytes) => { - let mut msg = Message::new(); - msg.merge_from_bytes(&bytes).unwrap(); - assert!(msg - .file_response() - .dir() - .path - .contains("rustdesk_read_dir_test")); - } - _ => panic!("unexpected data"), - } - let _ = fs::remove_dir_all(&dir); - }); - } - - /// Tests that symlink creation works on this platform. - /// This is a helper to verify the test environment supports symlinks. - #[test] - #[cfg(not(any(target_os = "ios")))] - fn test_symlink_creation_works() { - let base_dir = std::env::temp_dir().join("rustdesk_symlink_test"); - let _ = fs::remove_dir_all(&base_dir); - fs::create_dir_all(&base_dir).unwrap(); - - // Create target file in a subdirectory - let target_dir = base_dir.join("target_dir"); - fs::create_dir_all(&target_dir).unwrap(); - let target_file = target_dir.join("target.txt"); - fs::write(&target_file, b"content").unwrap(); - - // Create symlink in a different directory - let link_dir = base_dir.join("link_dir"); - fs::create_dir_all(&link_dir).unwrap(); - let link_path = link_dir.join("link.txt"); - - #[cfg(unix)] - { - use std::os::unix::fs::symlink; - if symlink(&target_file, &link_path).is_err() { - let _ = fs::remove_dir_all(&base_dir); - return; - } - } - - #[cfg(windows)] - { - use std::os::windows::fs::symlink_file; - if symlink_file(&target_file, &link_path).is_err() { - // Skip if no permission (needs admin or dev mode on Windows) - let _ = fs::remove_dir_all(&base_dir); - return; - } - } - - let _ = fs::remove_dir_all(&base_dir); - } -} diff --git a/src/ui_interface.rs b/src/ui_interface.rs deleted file mode 100644 index 1645b242d..000000000 --- a/src/ui_interface.rs +++ /dev/null @@ -1,1611 +0,0 @@ -#[cfg(any(target_os = "android", target_os = "ios"))] -use hbb_common::password_security; -use hbb_common::{ - allow_err, - bytes::Bytes, - config::{self, keys::*, Config, LocalConfig, PeerConfig, CONNECT_TIMEOUT, RENDEZVOUS_PORT}, - directories_next, - futures::future::join_all, - log, - rendezvous_proto::*, - tokio, -}; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use hbb_common::{ - sleep, - tokio::{sync::mpsc, time}, -}; -use serde_derive::Serialize; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use std::process::Child; -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -use crate::common::SOFTWARE_UPDATE_URL; -#[cfg(feature = "flutter")] -use crate::hbbs_http::account; -#[cfg(not(any(target_os = "ios")))] -use crate::ipc; - -type Message = RendezvousMessage; - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub type Children = Arc)>>; - -#[derive(Clone, Debug, Serialize)] -pub struct UiStatus { - pub status_num: i32, - #[cfg(not(feature = "flutter"))] - pub key_confirmed: bool, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub mouse_time: i64, - #[cfg(not(feature = "flutter"))] - pub id: String, - #[cfg(feature = "flutter")] - pub video_conn_count: usize, -} - -#[derive(Debug, Clone, Serialize)] -pub struct LoginDeviceInfo { - pub os: String, - pub r#type: String, - pub name: String, -} - -lazy_static::lazy_static! { - static ref UI_STATUS : Arc> = Arc::new(Mutex::new(UiStatus{ - status_num: 0, - #[cfg(not(feature = "flutter"))] - key_confirmed: false, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - mouse_time: 0, - #[cfg(not(feature = "flutter"))] - id: "".to_owned(), - #[cfg(feature = "flutter")] - video_conn_count: 0, - })); - static ref ASYNC_JOB_STATUS : Arc> = Default::default(); - static ref ASYNC_HTTP_STATUS : Arc>> = Arc::new(Mutex::new(HashMap::new())); - static ref TEMPORARY_PASSWD : Arc> = Arc::new(Mutex::new("".to_owned())); - static ref IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS : Arc>> = Arc::new(Mutex::new(None)); -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -lazy_static::lazy_static! { - static ref OPTION_SYNCED: Arc> = Default::default(); - static ref OPTIONS : Arc>> = Arc::new(Mutex::new(Config::get_options())); - pub static ref SENDER : Mutex> = Mutex::new(check_connect_status(true)); - static ref CHILDREN : Children = Default::default(); -} - -#[cfg(target_os = "windows")] -lazy_static::lazy_static! { - pub static ref IS_FILE_TRANSFER_ENABLED: Arc>> = Arc::new(Mutex::new(None)); -} - -const INIT_ASYNC_JOB_STATUS: &str = " "; - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn get_id() -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Config::get_id(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return ipc::get_id(); -} - -#[inline] -pub fn goto_install() { - allow_err!(crate::run_me(vec!["--install"])); - std::process::exit(0); -} - -#[inline] -pub fn install_me(_options: String, _path: String, _silent: bool, _debug: bool) { - #[cfg(windows)] - std::thread::spawn(move || { - allow_err!(crate::platform::windows::install_me( - &_options, _path, _silent, _debug - )); - std::process::exit(0); - }); -} - -#[inline] -pub fn update_me(_path: String) { - goto_install(); -} - -#[inline] -pub fn run_without_install() { - crate::run_me(vec!["--noinstall"]).ok(); - std::process::exit(0); -} - -#[inline] -pub fn show_run_without_install() -> bool { - let mut it = std::env::args(); - if let Some(tmp) = it.next() { - if crate::is_setup(&tmp) { - return it.next() == None; - } - } - false -} - -#[inline] -pub fn get_license() -> String { - #[cfg(windows)] - if let Ok(lic) = crate::platform::windows::get_license_from_exe_name() { - #[cfg(feature = "flutter")] - return format!("Key: {}\nHost: {}\nAPI: {}", lic.key, lic.host, lic.api); - // default license format is html formed (sciter) - #[cfg(not(feature = "flutter"))] - return format!( - "
    Key: {}
    Host: {} API: {}", - lic.key, lic.host, lic.api - ); - } - Default::default() -} - -#[inline] -pub fn refresh_options() { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - *OPTIONS.lock().unwrap() = Config::get_options(); - } -} - -#[inline] -pub fn get_option>(key: T) -> String { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let map = OPTIONS.lock().unwrap(); - if let Some(v) = map.get(key.as_ref()) { - v.to_owned() - } else { - "".to_owned() - } - } - #[cfg(any(target_os = "android", target_os = "ios"))] - { - Config::get_option(key.as_ref()) - } -} - -#[inline] -pub fn use_texture_render() -> bool { - #[cfg(target_os = "android")] - return false; - #[cfg(target_os = "ios")] - return false; - - #[cfg(target_os = "macos")] - return cfg!(feature = "flutter") - && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y"; - - #[cfg(target_os = "linux")] - return cfg!(feature = "flutter") - && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"; - - #[cfg(target_os = "windows")] - { - if !cfg!(feature = "flutter") { - return false; - } - // https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 - #[cfg(debug_assertions)] - let default_texture = true; - #[cfg(not(debug_assertions))] - let default_texture = crate::platform::is_win_10_or_greater(); - if default_texture { - LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N" - } else { - return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y"; - } - } -} - -#[inline] -pub fn is_option_fixed(key: &str) -> bool { - config::OVERWRITE_DISPLAY_SETTINGS - .read() - .unwrap() - .contains_key(key) - || config::OVERWRITE_LOCAL_SETTINGS - .read() - .unwrap() - .contains_key(key) - || config::OVERWRITE_SETTINGS.read().unwrap().contains_key(key) -} - -#[inline] -pub fn get_local_option(key: String) -> String { - crate::get_local_option(&key) -} - -#[inline] -#[cfg(feature = "flutter")] -pub fn get_hard_option(key: String) -> String { - config::HARD_SETTINGS - .read() - .unwrap() - .get(&key) - .cloned() - .unwrap_or_default() -} - -#[inline] -pub fn get_builtin_option(key: &str) -> String { - crate::get_builtin_option(key) -} - -#[inline] -pub fn set_local_option(key: String, value: String) { - LocalConfig::set_option(key.clone(), value); -} - -/// Resolve relative avatar path (e.g. "/avatar/xxx") to absolute URL -/// by prepending the API server address. -pub fn resolve_avatar_url(avatar: String) -> String { - let avatar = avatar.trim().to_owned(); - if avatar.starts_with('/') { - let api_server = get_api_server(); - if !api_server.is_empty() { - return format!("{}{}", api_server.trim_end_matches('/'), avatar); - } - } - avatar -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn get_local_flutter_option(key: String) -> String { - LocalConfig::get_flutter_option(&key) -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn set_local_flutter_option(key: String, value: String) { - LocalConfig::set_flutter_option(key, value); -} - -#[cfg(feature = "flutter")] -#[inline] -pub fn get_kb_layout_type() -> String { - LocalConfig::get_kb_layout_type() -} - -#[cfg(feature = "flutter")] -#[inline] -pub fn set_kb_layout_type(kb_layout_type: String) { - LocalConfig::set_kb_layout_type(kb_layout_type); -} - -#[inline] -pub fn peer_has_password(id: String) -> bool { - !PeerConfig::load(&id).password.is_empty() -} - -#[inline] -pub fn forget_password(id: String) { - let mut c = PeerConfig::load(&id); - c.password.clear(); - c.store(&id); -} - -#[inline] -pub fn get_peer_option(id: String, name: String) -> String { - let c = PeerConfig::load(&id); - c.options.get(&name).unwrap_or(&"".to_owned()).to_owned() -} - -#[inline] -#[cfg(feature = "flutter")] -pub fn get_peer_flutter_option(id: String, name: String) -> String { - let c = PeerConfig::load(&id); - c.ui_flutter.get(&name).unwrap_or(&"".to_owned()).to_owned() -} - -#[inline] -#[cfg(feature = "flutter")] -pub fn set_peer_flutter_option(id: String, name: String, value: String) { - let mut c = PeerConfig::load(&id); - if value.is_empty() { - c.ui_flutter.remove(&name); - } else { - c.ui_flutter.insert(name, value); - } - c.store(&id); -} - -#[inline] -pub fn set_peer_option(id: String, name: String, value: String) { - let mut c = PeerConfig::load(&id); - if value.is_empty() { - c.options.remove(&name); - } else { - c.options.insert(name, value); - } - c.store(&id); -} - -#[inline] -pub fn get_options() -> String { - let options = { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - OPTIONS.lock().unwrap() - } - #[cfg(any(target_os = "android", target_os = "ios"))] - { - Config::get_options() - } - }; - let mut m = serde_json::Map::new(); - for (k, v) in options.iter() { - m.insert(k.into(), v.to_owned().into()); - } - serde_json::to_string(&m).unwrap_or_default() -} - -#[inline] -pub fn test_if_valid_server(host: String, test_with_proxy: bool) -> String { - hbb_common::socket_client::test_if_valid_server(&host, test_with_proxy) -} - -#[inline] -#[cfg(feature = "flutter")] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn get_sound_inputs() -> Vec { - let mut a = Vec::new(); - #[cfg(not(target_os = "linux"))] - { - fn get_sound_inputs_() -> Vec { - let mut out = Vec::new(); - use cpal::traits::{DeviceTrait, HostTrait}; - // Do not use `cpal::host_from_id(cpal::HostId::ScreenCaptureKit)` for feature = "screencapturekit" - // Because we explicitly handle the "System Sound" device. - let host = cpal::default_host(); - if let Ok(devices) = host.devices() { - for device in devices { - if device.default_input_config().is_err() { - continue; - } - if let Ok(name) = device.name() { - out.push(name); - } - } - } - out - } - - let inputs = Arc::new(Mutex::new(Vec::new())); - let cloned = inputs.clone(); - // can not call below in UI thread, because conflict with sciter sound com initialization - std::thread::spawn(move || *cloned.lock().unwrap() = get_sound_inputs_()) - .join() - .ok(); - for name in inputs.lock().unwrap().drain(..) { - a.push(name); - } - } - #[cfg(target_os = "linux")] - { - let inputs: Vec = crate::platform::linux::get_pa_sources() - .drain(..) - .map(|x| x.1) - .collect(); - - for name in inputs { - a.push(name); - } - } - a -} - -#[inline] -pub fn set_options(m: HashMap) { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - *OPTIONS.lock().unwrap() = m.clone(); - ipc::set_options(m).ok(); - } - #[cfg(any(target_os = "android", target_os = "ios"))] - Config::set_options(m); -} - -#[inline] -pub fn set_option(key: String, value: String) { - if &key == "stop-service" { - #[cfg(target_os = "macos")] - { - let is_stop = value == "Y"; - if is_stop && crate::platform::uninstall_service(true, false) { - return; - } - } - #[cfg(any(target_os = "windows", target_os = "linux"))] - { - if crate::platform::is_installed() { - if value == "Y" { - if crate::platform::uninstall_service(true, false) { - return; - } - } else { - if crate::platform::install_service() { - return; - } - } - return; - } - } - } else if &key == "audio-input" { - #[cfg(not(target_os = "ios"))] - crate::audio_service::restart(); - } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let mut options = OPTIONS.lock().unwrap(); - if value.is_empty() { - options.remove(&key); - } else { - options.insert(key.clone(), value.clone()); - } - ipc::set_options(options.clone()).ok(); - } - #[cfg(any(target_os = "android", target_os = "ios"))] - { - let _nat = crate::CheckTestNatType::new(); - Config::set_option(key, value); - } -} - -#[inline] -pub fn install_path() -> String { - #[cfg(windows)] - return crate::platform::windows::get_install_info().1; - #[cfg(not(windows))] - return "".to_owned(); -} - -#[inline] -pub fn install_options() -> String { - #[cfg(windows)] - return crate::platform::windows::get_install_options(); - #[cfg(not(windows))] - return "{}".to_owned(); -} - -#[inline] -pub fn get_socks() -> Vec { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let s = ipc::get_socks(); - #[cfg(any(target_os = "android", target_os = "ios"))] - let s = Config::get_socks(); - match s { - None => Vec::new(), - Some(s) => { - let mut v = Vec::new(); - v.push(s.proxy); - v.push(s.username); - v.push(s.password); - v - } - } -} - -#[inline] -pub fn set_socks(proxy: String, username: String, password: String) { - let socks = config::Socks5Server { - proxy, - username, - password, - }; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - ipc::set_socks(socks).ok(); - #[cfg(any(target_os = "android", target_os = "ios"))] - { - let _nat = crate::CheckTestNatType::new(); - if socks.proxy.is_empty() { - Config::set_socks(None); - } else { - Config::set_socks(Some(socks)); - } - log::info!("socks updated"); - } - #[cfg(target_os = "android")] - { - crate::RendezvousMediator::restart(); - } -} - -#[inline] -#[cfg(feature = "flutter")] -pub fn get_proxy_status() -> bool { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return ipc::get_proxy_status(); - - // Currently, only the desktop version has proxy settings. - #[cfg(any(target_os = "android", target_os = "ios"))] - return false; -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[inline] -pub fn is_installed() -> bool { - crate::platform::is_installed() -} - -#[cfg(any(target_os = "android", target_os = "ios"))] -#[inline] -pub fn is_installed() -> bool { - false -} - -#[inline] -pub fn is_share_rdp() -> bool { - #[cfg(windows)] - return crate::platform::windows::is_share_rdp(); - #[cfg(not(windows))] - return false; -} - -#[inline] -pub fn set_share_rdp(_enable: bool) { - #[cfg(windows)] - crate::platform::windows::set_share_rdp(_enable); -} - -#[inline] -pub fn is_installed_lower_version() -> bool { - #[cfg(not(windows))] - return false; - #[cfg(windows)] - { - let b = crate::platform::windows::get_reg("BuildDate"); - return crate::BUILD_DATE.cmp(&b).is_gt(); - } -} - -#[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn get_mouse_time() -> f64 { - UI_STATUS.lock().unwrap().mouse_time as f64 -} - -#[inline] -pub fn check_mouse_time() { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let sender = SENDER.lock().unwrap(); - allow_err!(sender.send(ipc::Data::MouseMoveTime(0))); - } -} - -#[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn get_connect_status() -> UiStatus { - UI_STATUS.lock().unwrap().clone() -} - -#[inline] -pub fn temporary_password() -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return password_security::temporary_password(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return TEMPORARY_PASSWD.lock().unwrap().clone(); -} - -#[inline] -pub fn update_temporary_password() { - #[cfg(any(target_os = "android", target_os = "ios"))] - password_security::update_temporary_password(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - allow_err!(ipc::update_temporary_password()); -} - -#[inline] -pub fn is_permanent_password_set() -> bool { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Config::has_permanent_password(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - let daemon_is_set = ipc::is_permanent_password_set(); - // `daemon_is_set` is authoritative for the return value. Local storage is only used to - // decide whether we should attempt a sync to clear stale user-side state. - let local_storage_is_empty = if daemon_is_set { - true - } else { - let (storage, _) = Config::get_local_permanent_password_storage_and_salt(); - storage.is_empty() - }; - if daemon_is_set || !local_storage_is_empty { - allow_err!(ipc::sync_permanent_password_storage_from_daemon()); - } - daemon_is_set - } -} - -#[inline] -pub fn is_local_permanent_password_set() -> bool { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Config::has_local_permanent_password(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - allow_err!(ipc::sync_permanent_password_storage_from_daemon()); - Config::has_local_permanent_password() - } -} - -pub fn set_permanent_password_with_result(password: String) -> bool { - if config::Config::is_disable_change_permanent_password() { - return false; - } - #[cfg(any(target_os = "android", target_os = "ios"))] - { - config::Config::set_permanent_password(&password); - return true; - } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - match crate::ipc::set_permanent_password_with_ack(password) { - Ok(ok) => ok, - Err(err) => { - log::warn!("Failed to set permanent password via IPC: {err}"); - false - } - } - } -} - -#[inline] -pub fn get_peer(id: String) -> PeerConfig { - PeerConfig::load(&id) -} - -#[inline] -pub fn get_fav() -> Vec { - LocalConfig::get_fav() -} - -#[inline] -pub fn store_fav(fav: Vec) { - LocalConfig::set_fav(fav); -} - -#[inline] -pub fn is_process_trusted(_prompt: bool) -> bool { - #[cfg(target_os = "macos")] - return crate::platform::macos::is_process_trusted(_prompt); - #[cfg(not(target_os = "macos"))] - return true; -} - -#[inline] -pub fn is_can_screen_recording(_prompt: bool) -> bool { - #[cfg(target_os = "macos")] - return crate::platform::macos::is_can_screen_recording(_prompt); - #[cfg(not(target_os = "macos"))] - return true; -} - -#[inline] -pub fn is_installed_daemon(_prompt: bool) -> bool { - #[cfg(target_os = "macos")] - return crate::platform::macos::is_installed_daemon(_prompt); - #[cfg(not(target_os = "macos"))] - return true; -} - -#[inline] -#[cfg(feature = "flutter")] -pub fn is_can_input_monitoring(_prompt: bool) -> bool { - #[cfg(target_os = "macos")] - return crate::platform::macos::is_can_input_monitoring(_prompt); - #[cfg(not(target_os = "macos"))] - return true; -} - -#[inline] -pub fn get_error() -> String { - #[cfg(not(any(feature = "cli")))] - #[cfg(target_os = "linux")] - { - let dtype = crate::platform::linux::get_display_server(); - if crate::platform::linux::DISPLAY_SERVER_WAYLAND == dtype { - return crate::server::wayland::common_get_error(); - } - if dtype != crate::platform::linux::DISPLAY_SERVER_X11 { - return format!( - "{} {}, {}", - crate::client::translate("Unsupported display server".to_owned()), - dtype, - crate::client::translate("x11 expected".to_owned()), - ); - } - } - return "".to_owned(); -} - -#[inline] -pub fn is_login_wayland() -> bool { - #[cfg(target_os = "linux")] - return crate::platform::linux::is_login_wayland(); - #[cfg(not(target_os = "linux"))] - return false; -} - -#[inline] -pub fn current_is_wayland() -> bool { - #[cfg(target_os = "linux")] - return crate::platform::linux::current_is_wayland(); - #[cfg(not(target_os = "linux"))] - return false; -} - -#[inline] -pub fn get_new_version() -> String { - (*SOFTWARE_UPDATE_URL - .lock() - .unwrap() - .rsplit('/') - .next() - .unwrap_or("")) - .to_string() -} - -#[inline] -pub fn get_version() -> String { - crate::VERSION.to_owned() -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn get_app_name() -> String { - crate::get_app_name() -} - -#[cfg(windows)] -#[inline] -pub fn create_shortcut(_id: String) { - crate::platform::windows::create_shortcut(&_id).ok(); -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn discover() { - std::thread::spawn(move || { - allow_err!(crate::lan::discover()); - }); -} - -#[cfg(feature = "flutter")] -pub fn peer_to_map(id: String, p: PeerConfig) -> HashMap<&'static str, String> { - use hbb_common::sodiumoxide::base64; - HashMap::<&str, String>::from_iter([ - ("id", id), - ("username", p.info.username.clone()), - ("hostname", p.info.hostname.clone()), - ("platform", p.info.platform.clone()), - ( - "alias", - p.options.get("alias").unwrap_or(&"".to_owned()).to_owned(), - ), - ( - "hash", - base64::encode(p.password, base64::Variant::Original), - ), - ]) -} - -#[cfg(feature = "flutter")] -pub fn peer_exists(id: &str) -> bool { - PeerConfig::exists(id) -} - -#[inline] -pub fn get_lan_peers() -> Vec> { - config::LanPeers::load() - .peers - .iter() - .map(|peer| { - HashMap::<&str, String>::from_iter([ - ("id", peer.id.clone()), - ("username", peer.username.clone()), - ("hostname", peer.hostname.clone()), - ("platform", peer.platform.clone()), - ]) - }) - .collect() -} - -#[inline] -pub fn remove_discovered(id: String) { - let mut peers = config::LanPeers::load().peers; - peers.retain(|x| x.id != id); - config::LanPeers::store(&peers); -} - -#[inline] -pub fn get_uuid() -> String { - crate::encode64(hbb_common::get_uuid()) -} - -#[inline] -pub fn get_init_async_job_status() -> String { - INIT_ASYNC_JOB_STATUS.to_string() -} - -#[inline] -pub fn reset_async_job_status() { - *ASYNC_JOB_STATUS.lock().unwrap() = get_init_async_job_status(); -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn change_id(id: String) { - reset_async_job_status(); - let old_id = get_id(); - std::thread::spawn(move || { - change_id_shared(id, old_id); - }); -} - -#[inline] -pub fn http_request(url: String, method: String, body: Option, header: String) { - // Respond to concurrent requests for resources - let current_request = ASYNC_HTTP_STATUS.clone(); - current_request - .lock() - .unwrap() - .insert(url.clone(), " ".to_owned()); - std::thread::spawn(move || { - let res = match crate::http_request_sync(url.clone(), method, body, header) { - Err(err) => { - log::error!("{}", err); - err.to_string() - } - Ok(text) => text, - }; - current_request.lock().unwrap().insert(url, res); - }); -} - -#[inline] -pub fn get_async_http_status(url: String) -> Option { - match ASYNC_HTTP_STATUS.lock().unwrap().get(&url) { - None => None, - Some(_str) => Some(_str.to_string()), - } -} - -#[inline] -#[cfg(not(feature = "flutter"))] -pub fn post_request(url: String, body: String, header: String) { - *ASYNC_JOB_STATUS.lock().unwrap() = " ".to_owned(); - std::thread::spawn(move || { - *ASYNC_JOB_STATUS.lock().unwrap() = match crate::post_request_sync(url, body, &header) { - Err(err) => err.to_string(), - Ok(text) => text, - }; - }); -} - -#[inline] -pub fn get_async_job_status() -> String { - ASYNC_JOB_STATUS.lock().unwrap().clone() -} - -#[inline] -pub fn get_langs() -> 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 video_save_directory(root: bool) -> 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() - } - }; - - if root { - // Currently, only installed windows run as root - #[cfg(windows)] - { - let drive = std::env::var("SystemDrive").unwrap_or("C:".to_owned()); - let dir = - std::path::PathBuf::from(format!("{drive}\\ProgramData\\{appname}\\recording",)); - return dir.to_string_lossy().to_string(); - } - } - // Get directory from config file otherwise --server will use the old value from global var. - #[cfg(any(target_os = "linux", target_os = "macos"))] - let dir = LocalConfig::get_option_from_file(OPTION_VIDEO_SAVE_DIRECTORY); - #[cfg(not(any(target_os = "linux", target_os = "macos")))] - let dir = LocalConfig::get_option(OPTION_VIDEO_SAVE_DIRECTORY); - if !dir.is_empty() { - return dir; - } - #[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(format!("/{appname}/ScreenRecord").as_str()); - 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() { - 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") { - "Movies" - } else { - "Videos" - }; - 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(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(); - } - } - Default::default() -} - -#[inline] -pub fn get_api_server() -> String { - crate::get_api_server( - get_option("api-server"), - get_option("custom-rendezvous-server"), - ) -} - -#[inline] -pub fn has_hwcodec() -> bool { - // Has real hardware codec using gpu - (cfg!(feature = "hwcodec") && cfg!(not(target_os = "ios"))) || cfg!(feature = "mediacodec") -} - -#[inline] -pub fn has_vram() -> bool { - cfg!(feature = "vram") -} - -#[cfg(feature = "flutter")] -#[inline] -pub fn supported_hwdecodings() -> (bool, bool) { - let decoding = - scrap::codec::Decoder::supported_decodings(None, use_texture_render(), None, &vec![]); - #[allow(unused_mut)] - let (mut h264, mut h265) = (decoding.ability_h264 > 0, decoding.ability_h265 > 0); - #[cfg(feature = "vram")] - { - // supported_decodings check runtime luid - let vram = scrap::vram::VRamDecoder::possible_available_without_check(); - if vram.0 { - h264 = true; - } - if vram.1 { - h265 = true; - } - } - (h264, h265) -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[inline] -pub fn is_root() -> bool { - crate::platform::is_root() -} - -#[cfg(any(target_os = "android", target_os = "ios"))] -#[inline] -pub fn is_root() -> bool { - false -} - -#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] -#[inline] -pub fn check_super_user_permission() -> bool { - #[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", target_os = "macos")))] - return true; -} - -#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] -pub fn check_zombie() { - let mut deads = Vec::new(); - loop { - let mut lock = CHILDREN.lock().unwrap(); - let mut n = 0; - for (id, c) in lock.1.iter_mut() { - if let Ok(Some(_)) = c.try_wait() { - deads.push(id.clone()); - n += 1; - } - } - for ref id in deads.drain(..) { - lock.1.remove(id); - } - if n > 0 { - lock.0 = true; - } - drop(lock); - std::thread::sleep(std::time::Duration::from_millis(100)); - } -} - -#[inline] -#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] -pub fn recent_sessions_updated() -> bool { - let mut children = CHILDREN.lock().unwrap(); - if children.0 { - children.0 = false; - true - } else { - false - } -} - -#[cfg(not(any(target_os = "android", target_os = "ios", feature = "flutter")))] -pub fn new_remote(id: String, remote_type: String, force_relay: bool) { - let mut lock = CHILDREN.lock().unwrap(); - 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() { - lock.1.remove(&key); - } else { - if remote_type == "rdp" { - allow_err!(c.kill()); - std::thread::sleep(std::time::Duration::from_millis(30)); - c.try_wait().ok(); - lock.1.remove(&key); - } else { - return; - } - } - } - match crate::run_me(args) { - Ok(child) => { - lock.1.insert(key, child); - } - Err(err) => { - log::error!("Failed to spawn remote: {}", err); - } - } -} - -// Make sure `SENDER` is inited here. -#[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn start_option_status_sync() { - let _sender = SENDER.lock().unwrap(); -} - -// not call directly -#[cfg(not(any(target_os = "android", target_os = "ios")))] -fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender { - let (tx, rx) = mpsc::unbounded_channel::(); - std::thread::spawn(move || check_connect_status_(reconnect, rx)); - tx -} - -#[cfg(feature = "flutter")] -pub fn account_auth(op: String, id: String, uuid: String, remember_me: bool) { - account::OidcSession::account_auth(get_api_server(), op, id, uuid, remember_me); -} - -#[cfg(feature = "flutter")] -pub fn account_auth_cancel() { - account::OidcSession::auth_cancel(); -} - -#[cfg(feature = "flutter")] -pub fn account_auth_result() -> String { - serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default() -} - -#[cfg(feature = "flutter")] -pub fn set_user_default_option(key: String, value: String) { - use hbb_common::config::UserDefaultConfig; - UserDefaultConfig::load().set(key, value); -} - -#[cfg(feature = "flutter")] -pub fn get_user_default_option(key: String) -> String { - use hbb_common::config::UserDefaultConfig; - UserDefaultConfig::load().get(&key) -} - -pub fn get_fingerprint() -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - if Config::get_key_confirmed() { - return crate::common::pk_to_fingerprint(Config::get_key_pair().1); - } else { - return "".to_owned(); - } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return ipc::get_fingerprint(); -} - -#[inline] -pub fn get_login_device_info() -> LoginDeviceInfo { - LoginDeviceInfo { - // std::env::consts::OS is better than whoami::platform() here. - os: std::env::consts::OS.to_owned(), - r#type: "client".to_owned(), - name: crate::common::hostname(), - } -} - -#[inline] -pub fn get_login_device_info_json() -> String { - serde_json::to_string(&get_login_device_info()).unwrap_or("{}".to_string()) -} - -// notice: avoiding create ipc connection repeatedly, -// because windows named pipe has serious memory leak issue. -#[cfg(not(any(target_os = "android", target_os = "ios")))] -#[tokio::main(flavor = "current_thread")] -async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver) { - #[cfg(not(feature = "flutter"))] - let mut key_confirmed = false; - let mut rx = rx; - let mut mouse_time = 0; - #[cfg(feature = "flutter")] - let mut video_conn_count = 0; - #[cfg(not(feature = "flutter"))] - let mut id = "".to_owned(); - let is_cm = crate::common::is_cm(); - - loop { - if let Ok(mut c) = ipc::connect(1000, "").await { - let mut timer = crate::rustdesk_interval(time::interval(time::Duration::from_secs(1))); - loop { - tokio::select! { - res = c.next() => { - match res { - Err(err) => { - log::error!("ipc connection closed: {}", err); - if is_cm { - crate::ui_cm_interface::quit_cm(); - } - break; - } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - Ok(Some(ipc::Data::MouseMoveTime(v))) => { - mouse_time = v; - UI_STATUS.lock().unwrap().mouse_time = v; - } - Ok(Some(ipc::Data::Options(Some(v)))) => { - *OPTIONS.lock().unwrap() = v; - *OPTION_SYNCED.lock().unwrap() = true; - } - Ok(Some(ipc::Data::Config((name, Some(value))))) => { - if name == "id" { - #[cfg(not(feature = "flutter"))] - { - id = value; - } - } else if name == "temporary-password" { - *TEMPORARY_PASSWD.lock().unwrap() = value; - } - } - #[cfg(feature = "flutter")] - Ok(Some(ipc::Data::VideoConnCount(Some(n)))) => { - video_conn_count = n; - } - Ok(Some(ipc::Data::OnlineStatus(Some((mut x, _c))))) => { - if x > 0 { - x = 1 - } - #[cfg(not(feature = "flutter"))] - { - key_confirmed = _c; - } - *UI_STATUS.lock().unwrap() = UiStatus { - status_num: x as _, - #[cfg(not(feature = "flutter"))] - key_confirmed: _c, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - mouse_time, - #[cfg(not(feature = "flutter"))] - id: id.clone(), - #[cfg(feature = "flutter")] - video_conn_count, - }; - } - Ok(Some(ipc::Data::ControlPermissionsRemoteModify(v))) => { - *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS.lock().unwrap() = v; - } - #[cfg(target_os = "windows")] - Ok(Some(ipc::Data::FileTransferEnabledState(v))) => { - if let Some(enabled) = v { - let mut lock = IS_FILE_TRANSFER_ENABLED.lock().unwrap(); - if *lock != v { - clipboard::ContextSend::enable(enabled); - *lock = v; - } - } - } - _ => {} - } - } - Some(data) = rx.recv() => { - allow_err!(c.send(&data).await); - } - _ = timer.tick() => { - c.send(&ipc::Data::OnlineStatus(None)).await.ok(); - c.send(&ipc::Data::Options(None)).await.ok(); - c.send(&ipc::Data::Config(("id".to_owned(), None))).await.ok(); - c.send(&ipc::Data::Config(("temporary-password".to_owned(), None))).await.ok(); - #[cfg(feature = "flutter")] - c.send(&ipc::Data::VideoConnCount(None)).await.ok(); - c.send(&ipc::Data::ControlPermissionsRemoteModify(None)).await.ok(); - #[cfg(target_os = "windows")] - c.send(&ipc::Data::FileTransferEnabledState(None)).await.ok(); - } - } - } - } - if !reconnect { - OPTIONS - .lock() - .unwrap() - .insert("ipc-closed".to_owned(), "Y".to_owned()); - break; - } - *UI_STATUS.lock().unwrap() = UiStatus { - status_num: -1, - #[cfg(not(feature = "flutter"))] - key_confirmed, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - mouse_time, - #[cfg(not(feature = "flutter"))] - id: id.clone(), - #[cfg(feature = "flutter")] - video_conn_count, - }; - sleep(1.).await; - } -} - -#[allow(dead_code)] -pub fn option_synced() -> bool { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - OPTION_SYNCED.lock().unwrap().clone() - } - #[cfg(any(target_os = "android", target_os = "ios"))] - { - true - } -} - -#[cfg(any(target_os = "android", feature = "flutter"))] -#[cfg(not(any(target_os = "ios")))] -#[tokio::main(flavor = "current_thread")] -pub(crate) async fn send_to_cm(data: &ipc::Data) { - if let Ok(mut c) = ipc::connect(1000, "_cm").await { - c.send(data).await.ok(); - } -} - -const INVALID_FORMAT: &'static str = "Invalid format"; -const UNKNOWN_ERROR: &'static str = "Unknown error"; - -#[inline] -#[tokio::main(flavor = "current_thread")] -pub async fn change_id_shared(id: String, old_id: String) -> String { - let res = change_id_shared_(id, old_id).await.to_owned(); - *ASYNC_JOB_STATUS.lock().unwrap() = res.clone(); - res -} - -pub async fn change_id_shared_(id: String, old_id: String) -> &'static str { - if !hbb_common::is_valid_custom_id(&id) { - log::debug!( - "debugging invalid id: \"{id}\", len: {}, base64: \"{}\"", - id.len(), - crate::encode64(&id) - ); - let bom = id.trim_start_matches('\u{FEFF}'); - log::debug!("bom: {}", hbb_common::is_valid_custom_id(&bom)); - return INVALID_FORMAT; - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let uuid = Bytes::from( - hbb_common::machine_uid::get() - .unwrap_or("".to_owned()) - .as_bytes() - .to_vec(), - ); - #[cfg(any(target_os = "android", target_os = "ios"))] - let uuid = Bytes::from(hbb_common::get_uuid()); - - if uuid.is_empty() { - log::error!("Failed to change id, uuid is_empty"); - return UNKNOWN_ERROR; - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let rendezvous_servers = crate::ipc::get_rendezvous_servers(1_000).await; - #[cfg(any(target_os = "android", target_os = "ios"))] - let rendezvous_servers = Config::get_rendezvous_servers(); - - let mut futs = Vec::new(); - let err: Arc> = Default::default(); - for rendezvous_server in rendezvous_servers { - let err = err.clone(); - let id = id.to_owned(); - let uuid = uuid.clone(); - let old_id = old_id.clone(); - futs.push(tokio::spawn(async move { - let tmp = check_id(rendezvous_server, old_id, id, uuid).await; - if !tmp.is_empty() { - *err.lock().unwrap() = tmp; - } - })); - } - join_all(futs).await; - let err = *err.lock().unwrap(); - if err.is_empty() { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - crate::ipc::set_config_async("id", id.to_owned()).await.ok(); - #[cfg(any(target_os = "android", target_os = "ios"))] - { - Config::set_key_confirmed(false); - Config::set_id(&id); - } - } - err -} - -async fn check_id( - rendezvous_server: String, - old_id: String, - id: String, - uuid: Bytes, -) -> &'static str { - if let Ok(mut socket) = hbb_common::socket_client::connect_tcp( - crate::check_port(rendezvous_server, RENDEZVOUS_PORT), - CONNECT_TIMEOUT, - ) - .await - { - let mut msg_out = Message::new(); - msg_out.set_register_pk(RegisterPk { - old_id, - id, - uuid, - ..Default::default() - }); - let mut ok = false; - if socket.send(&msg_out).await.is_ok() { - if let Some(msg_in) = - crate::common::get_next_nonkeyexchange_msg(&mut socket, None).await - { - match msg_in.union { - Some(rendezvous_message::Union::RegisterPkResponse(rpr)) => { - match rpr.result.enum_value() { - Ok(register_pk_response::Result::OK) => { - ok = true; - } - Ok(register_pk_response::Result::ID_EXISTS) => { - return "Not available"; - } - Ok(register_pk_response::Result::TOO_FREQUENT) => { - return "Too frequent"; - } - Ok(register_pk_response::Result::NOT_SUPPORT) => { - return "server_not_support"; - } - Ok(register_pk_response::Result::SERVER_ERROR) => { - return "Server error"; - } - Ok(register_pk_response::Result::INVALID_ID_FORMAT) => { - return INVALID_FORMAT; - } - _ => {} - } - } - _ => {} - } - } - } - if !ok { - return UNKNOWN_ERROR; - } - } else { - return "Failed to connect to rendezvous server"; - } - "" -} - -// if it's relay id, return id processed, otherwise return original id -pub fn handle_relay_id(id: &str) -> &str { - if id.ends_with(r"\r") || id.ends_with(r"/r") { - &id[0..id.len() - 2] - } else { - id - } -} - -pub fn support_remove_wallpaper() -> bool { - #[cfg(any(target_os = "windows", target_os = "linux"))] - return crate::platform::WallPaperRemover::support(); - #[cfg(not(any(target_os = "windows", target_os = "linux")))] - return false; -} - -pub fn has_valid_2fa() -> bool { - let raw = get_option("2fa"); - crate::auth_2fa::get_2fa(Some(raw)).is_some() -} - -pub fn generate2fa() -> String { - crate::auth_2fa::generate2fa() -} - -pub fn verify2fa(code: String) -> bool { - let res = crate::auth_2fa::verify2fa(code); - if res { - refresh_options(); - } - res -} - -pub fn has_valid_bot() -> bool { - crate::auth_2fa::TelegramBot::get().map_or(false, |bot| bot.is_some()) -} - -pub fn verify_bot(token: String) -> String { - match crate::auth_2fa::get_chatid_telegram(&token) { - Err(err) => err.to_string(), - Ok(None) => { - "To activate the bot, simply send a message beginning with a forward slash (\"/\") like \"/hello\" to its chat.".to_owned() - } - _ => "".to_owned(), - } -} - -pub fn check_hwcodec() { - #[cfg(feature = "hwcodec")] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - use std::sync::Once; - static ONCE: Once = Once::new(); - - ONCE.call_once(|| { - if crate::platform::is_installed() { - ipc::notify_server_to_check_hwcodec().ok(); - ipc::client_get_hwcodec_config_thread(3); - } else { - scrap::hwcodec::start_check_process(); - } - }) - } -} - -#[cfg(feature = "flutter")] -pub fn get_unlock_pin() -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return String::default(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return ipc::get_unlock_pin(); -} - -#[cfg(feature = "flutter")] -pub fn set_unlock_pin(pin: String) -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return String::default(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - match ipc::set_unlock_pin(pin, true) { - Ok(_) => String::default(), - Err(err) => err.to_string(), - } -} - -#[cfg(feature = "flutter")] -pub fn get_trusted_devices() -> String { - #[cfg(any(target_os = "android", target_os = "ios"))] - return Config::get_trusted_devices_json(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - return ipc::get_trusted_devices(); -} - -#[cfg(feature = "flutter")] -pub fn remove_trusted_devices(json: &str) { - let hwids = serde_json::from_str::>(json).unwrap_or_default(); - #[cfg(any(target_os = "android", target_os = "ios"))] - Config::remove_trusted_devices(&hwids); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - ipc::remove_trusted_devices(hwids); -} - -#[cfg(feature = "flutter")] -pub fn clear_trusted_devices() { - #[cfg(any(target_os = "android", target_os = "ios"))] - Config::clear_trusted_devices(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - ipc::clear_trusted_devices(); -} - -#[cfg(feature = "flutter")] -pub fn max_encrypt_len() -> usize { - hbb_common::config::ENCRYPT_MAX_LEN -} - -pub fn is_remote_modify_enabled_by_control_permissions() -> Option { - *IS_REMOTE_MODIFY_ENABLED_BY_CONTROL_PERMISSIONS - .lock() - .unwrap() -} diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs deleted file mode 100644 index e6c8ac6a2..000000000 --- a/src/ui_session_interface.rs +++ /dev/null @@ -1,2058 +0,0 @@ -use crate::{ - common::{get_supported_keyboard_modes, is_keyboard_mode_supported}, - input::{ - MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_MASK, - MOUSE_TYPE_TRACKPAD, MOUSE_TYPE_UP, MOUSE_TYPE_WHEEL, - }, - ui_interface::use_texture_render, -}; -use async_trait::async_trait; -use bytes::Bytes; -#[cfg(all(target_os = "windows", not(feature = "flutter")))] -use hbb_common::config::keys; -#[cfg(not(feature = "flutter"))] -use hbb_common::fs; -use hbb_common::{ - allow_err, - config::{Config, LocalConfig, PeerConfig}, - get_version_number, log, - message_proto::*, - rendezvous_proto::ConnType, - tokio::{ - self, - sync::mpsc, - time::{Duration as TokioDuration, Instant}, - }, - whoami, Stream, -}; -use rdev::{Event, EventType::*, KeyCode}; -#[cfg(all(feature = "vram", feature = "flutter"))] -use std::ffi::c_void; -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, - str::FromStr, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, RwLock, - }, - time::SystemTime, -}; -use uuid::Uuid; - -use crate::client::io_loop::Remote; -use crate::client::{ - check_if_retry, handle_hash, handle_login_error, handle_login_from_ui, handle_test_delay, - input_os_password, send_mouse, send_pointer_device_event, FileManager, Key, LoginConfigHandler, - QualityStatus, KEY_MAP, -}; -#[cfg(not(any(target_os = "android", target_os = "ios")))] -use crate::common::GrabState; -use crate::keyboard; -use crate::{client::Data, client::Interface}; - -const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15; - -#[derive(Clone, Default)] -pub struct Session { - pub password: String, - pub args: Vec, - pub lc: Arc>, - pub sender: Arc>>>, - pub thread: Arc>>>, - pub ui_handler: T, - pub server_keyboard_enabled: Arc>, - pub server_file_transfer_enabled: Arc>, - pub server_clipboard_enabled: Arc>, - pub last_change_display: Arc>, - pub connection_round_state: Arc>, - pub printer_names: Arc>>, - // Indicate whether the session is reconnected. - // Used to auto start file transfer after reconnection. - pub reconnect_count: Arc, - pub last_audit_note: Arc>, - pub audit_guid: Arc>, -} - -#[derive(Clone)] -pub struct SessionPermissionConfig { - pub lc: Arc>, - pub server_keyboard_enabled: Arc>, - pub server_file_transfer_enabled: Arc>, - pub server_clipboard_enabled: Arc>, -} - -pub struct ChangeDisplayRecord { - time: Instant, - display: i32, - width: i32, - height: i32, -} - -enum ConnectionState { - Connecting, - Connected, - Disconnected, -} - -/// ConnectionRoundState is used to control the reconnecting logic. -pub struct ConnectionRoundState { - round: u32, - state: ConnectionState, -} - -impl ConnectionRoundState { - pub fn new_round(&mut self) -> u32 { - self.round += 1; - self.state = ConnectionState::Connecting; - self.round - } - - pub fn set_connected(&mut self) { - self.state = ConnectionState::Connected; - } - - pub fn is_round_gt(&self, round: u32) -> bool { - if round == u32::MAX && self.round == 0 { - true - } else { - round < self.round - } - } - - pub fn set_disconnected(&mut self, round: u32) -> bool { - if self.is_round_gt(round) { - false - } else { - self.state = ConnectionState::Disconnected; - true - } - } -} - -impl Default for ConnectionRoundState { - fn default() -> Self { - Self { - round: 0, - state: ConnectionState::Connecting, - } - } -} - -impl Default for ChangeDisplayRecord { - fn default() -> Self { - Self { - time: Instant::now() - - TokioDuration::from_secs(CHANGE_RESOLUTION_VALID_TIMEOUT_SECS + 1), - display: 0, - width: 0, - height: 0, - } - } -} - -impl ChangeDisplayRecord { - fn new(display: i32, width: i32, height: i32) -> Self { - Self { - time: Instant::now(), - display, - width, - height, - } - } - - pub fn is_the_same_record(&self, display: i32, width: i32, height: i32) -> bool { - self.time.elapsed().as_secs() < CHANGE_RESOLUTION_VALID_TIMEOUT_SECS - && self.display == display - && self.width == width - && self.height == height - } -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -impl SessionPermissionConfig { - pub fn is_text_clipboard_required(&self) -> bool { - *self.server_clipboard_enabled.read().unwrap() - && *self.server_keyboard_enabled.read().unwrap() - && !self.lc.read().unwrap().disable_clipboard.v - } - - #[cfg(feature = "unix-file-copy-paste")] - pub fn is_file_clipboard_required(&self) -> bool { - *self.server_keyboard_enabled.read().unwrap() - && *self.server_file_transfer_enabled.read().unwrap() - && self.lc.read().unwrap().enable_file_copy_paste.v - } -} - -impl Session { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn get_permission_config(&self) -> SessionPermissionConfig { - SessionPermissionConfig { - lc: self.lc.clone(), - server_keyboard_enabled: self.server_keyboard_enabled.clone(), - server_file_transfer_enabled: self.server_file_transfer_enabled.clone(), - server_clipboard_enabled: self.server_clipboard_enabled.clone(), - } - } - - pub fn is_file_transfer(&self) -> bool { - self.lc - .read() - .unwrap() - .conn_type - .eq(&ConnType::FILE_TRANSFER) - } - - pub fn is_default(&self) -> bool { - self.lc - .read() - .unwrap() - .conn_type - .eq(&ConnType::DEFAULT_CONN) - } - - pub fn is_view_camera(&self) -> bool { - self.lc.read().unwrap().conn_type.eq(&ConnType::VIEW_CAMERA) - } - - pub fn is_terminal(&self) -> bool { - self.lc.read().unwrap().conn_type.eq(&ConnType::TERMINAL) - } - - pub fn is_port_forward(&self) -> bool { - let conn_type = self.lc.read().unwrap().conn_type; - conn_type == ConnType::PORT_FORWARD || conn_type == ConnType::RDP - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn is_rdp(&self) -> bool { - self.lc.read().unwrap().conn_type.eq(&ConnType::RDP) - } - - #[cfg(feature = "flutter")] - pub fn is_multi_ui_session(&self) -> bool { - self.ui_handler.is_multi_ui_session() - } - - pub fn get_view_style(&self) -> String { - self.lc.read().unwrap().view_style.clone() - } - - pub fn get_scroll_style(&self) -> String { - self.lc.read().unwrap().scroll_style.clone() - } - - pub fn get_edge_scroll_edge_thickness(&self) -> i32 { - self.lc.read().unwrap().edge_scroll_edge_thickness - } - - pub fn get_image_quality(&self) -> String { - self.lc.read().unwrap().image_quality.clone() - } - - pub fn get_custom_image_quality(&self) -> Vec { - self.lc.read().unwrap().custom_image_quality.clone() - } - - pub fn get_peer_version(&self) -> i64 { - self.lc.read().unwrap().version.clone() - } - - pub fn get_trackpad_speed(&self) -> i32 { - self.lc.read().unwrap().trackpad_speed - } - - pub fn fallback_keyboard_mode(&self) -> String { - let peer_version = self.get_peer_version(); - let platform = self.peer_platform(); - - let supported_modes = get_supported_keyboard_modes(peer_version, &platform); - if let Some(mode) = supported_modes.first() { - return mode.to_string(); - } else { - if self.get_peer_version() >= get_version_number("1.2.0") { - return KeyboardMode::Map.to_string(); - } else { - return KeyboardMode::Legacy.to_string(); - } - } - } - - // Caution: This function must be called after peer info is received. - pub fn get_keyboard_mode(&self) -> String { - let mode = self.lc.read().unwrap().keyboard_mode.clone(); - let keyboard_mode = KeyboardMode::from_str(&mode); - - // Note: peer_version is 0 before peer info is received. - let peer_version = self.get_peer_version(); - let platform = self.peer_platform(); - - // Saved keyboard mode still exists in this version. - if let Ok(mode) = keyboard_mode { - if is_keyboard_mode_supported(&mode, peer_version, &platform) { - return mode.to_string(); - } - } - self.fallback_keyboard_mode() - } - - pub fn is_keyboard_mode_supported(&self, mode: String) -> bool { - if let Ok(mode) = KeyboardMode::from_str(&mode[..]) { - crate::common::is_keyboard_mode_supported( - &mode, - self.get_peer_version(), - &self.peer_platform(), - ) - } else { - false - } - } - - pub fn save_keyboard_mode(&self, value: String) { - self.lc.write().unwrap().save_keyboard_mode(value); - } - - pub fn get_reverse_mouse_wheel(&self) -> String { - self.lc.read().unwrap().reverse_mouse_wheel.clone() - } - - pub fn get_displays_as_individual_windows(&self) -> String { - self.lc - .read() - .unwrap() - .displays_as_individual_windows - .clone() - } - - pub fn get_use_all_my_displays_for_the_remote_session(&self) -> String { - self.lc - .read() - .unwrap() - .use_all_my_displays_for_the_remote_session - .clone() - } - - pub fn save_reverse_mouse_wheel(&self, value: String) { - self.lc.write().unwrap().save_reverse_mouse_wheel(value); - } - - pub fn save_displays_as_individual_windows(&self, value: String) { - self.lc - .write() - .unwrap() - .save_displays_as_individual_windows(value); - } - - pub fn save_use_all_my_displays_for_the_remote_session(&self, value: String) { - self.lc - .write() - .unwrap() - .save_use_all_my_displays_for_the_remote_session(value); - } - - pub fn save_view_style(&self, value: String) { - self.lc.write().unwrap().save_view_style(value); - } - - pub fn save_scroll_style(&self, value: String) { - self.lc.write().unwrap().save_scroll_style(value); - } - - pub fn save_edge_scroll_edge_thickness(&self, value: i32) { - self.lc - .write() - .unwrap() - .save_edge_scroll_edge_thickness(value); - } - - pub fn save_flutter_option(&self, k: String, v: String) { - self.lc.write().unwrap().save_ui_flutter(k, v); - } - - pub fn get_flutter_option(&self, k: String) -> String { - self.lc.read().unwrap().get_ui_flutter(&k) - } - - pub fn toggle_option(&self, name: String) { - let msg = self.lc.write().unwrap().toggle_option(name.clone()); - #[cfg(all(target_os = "windows", not(feature = "flutter")))] - if name == keys::OPTION_ENABLE_FILE_COPY_PASTE { - self.send(Data::ToggleClipboardFile); - } - if let Some(msg) = msg { - self.send(Data::Message(msg)); - } - } - - pub fn toggle_privacy_mode(&self, impl_key: String, on: bool) { - let mut misc = Misc::new(); - misc.set_toggle_privacy_mode(TogglePrivacyMode { - impl_key, - on, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - - pub fn get_toggle_option(&self, name: String) -> bool { - self.lc.read().unwrap().get_toggle_option(&name) - } - - #[cfg(not(feature = "flutter"))] - pub fn is_privacy_mode_supported(&self) -> bool { - self.lc.read().unwrap().is_privacy_mode_supported() - } - - #[cfg(not(target_os = "ios"))] - pub fn is_text_clipboard_required(&self) -> bool { - *self.server_clipboard_enabled.read().unwrap() - && *self.server_keyboard_enabled.read().unwrap() - && !self.lc.read().unwrap().disable_clipboard.v - } - - #[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))] - pub fn is_file_clipboard_required(&self) -> bool { - *self.server_keyboard_enabled.read().unwrap() - && *self.server_file_transfer_enabled.read().unwrap() - && self.lc.read().unwrap().enable_file_copy_paste.v - } - - #[cfg(feature = "flutter")] - pub fn refresh_video(&self, display: i32) { - if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) { - self.send(Data::Message(LoginConfigHandler::refresh_display( - display as _, - ))); - } else { - self.send(Data::Message(LoginConfigHandler::refresh())); - } - } - - pub fn toggle_virtual_display(&self, index: i32, on: bool) { - let mut misc = Misc::new(); - misc.set_toggle_virtual_display(ToggleVirtualDisplay { - display: index, - on, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - - #[cfg(not(feature = "flutter"))] - pub fn refresh_video(&self, _display: i32) { - self.send(Data::Message(LoginConfigHandler::refresh())); - } - - pub fn record_screen(&self, start: bool) { - self.send(Data::RecordScreen(start)); - } - - pub fn is_screenshot_supported(&self) -> bool { - crate::common::is_support_screenshot_num(self.lc.read().unwrap().version) - } - - pub fn take_screenshot(&self, display: i32, sid: String) { - self.send(Data::TakeScreenshot((display, sid))); - } - - pub fn is_recording(&self) -> bool { - self.lc.read().unwrap().record_state - } - - pub fn save_custom_image_quality(&self, custom_image_quality: i32) { - let msg = self - .lc - .write() - .unwrap() - .save_custom_image_quality(custom_image_quality); - self.send(Data::Message(msg)); - } - - pub fn save_image_quality(&self, value: String) { - let msg = self.lc.write().unwrap().save_image_quality(value.clone()); - if let Some(msg) = msg { - self.send(Data::Message(msg)); - } - if value != "custom" { - let last_auto_fps = self.lc.read().unwrap().last_auto_fps; - if last_auto_fps.unwrap_or(usize::MAX) >= 30 { - // non custom quality use 30 fps - let msg = self.lc.write().unwrap().set_custom_fps(30, false); - self.send(Data::Message(msg)); - } - } - } - - pub fn save_trackpad_speed(&self, trackpad_speed: i32) { - self.lc.write().unwrap().save_trackpad_speed(trackpad_speed); - } - - pub fn set_custom_fps(&self, custom_fps: i32) { - let msg = self.lc.write().unwrap().set_custom_fps(custom_fps, true); - self.send(Data::Message(msg)); - } - - pub fn get_remember(&self) -> bool { - self.lc.read().unwrap().remember - } - - #[cfg(not(feature = "flutter"))] - pub fn set_write_override( - &mut self, - job_id: i32, - file_num: i32, - is_override: bool, - remember: bool, - is_upload: bool, - ) -> bool { - self.send(Data::SetConfirmOverrideFile(( - job_id, - file_num, - is_override, - remember, - is_upload, - ))); - true - } - - pub fn alternative_codecs(&self) -> (bool, bool, bool, bool) { - let luid = self.lc.read().unwrap().adapter_luid; - let mark_unsupported = self.lc.read().unwrap().mark_unsupported.clone(); - let decoder = scrap::codec::Decoder::supported_decodings( - None, - use_texture_render(), - luid, - &mark_unsupported, - ); - let mut vp8 = decoder.ability_vp8 > 0; - let mut av1 = decoder.ability_av1 > 0; - let mut h264 = decoder.ability_h264 > 0; - let mut h265 = decoder.ability_h265 > 0; - let enc = &self.lc.read().unwrap().supported_encoding; - vp8 = vp8 && enc.vp8; - av1 = av1 && enc.av1; - h264 = h264 && enc.h264; - h265 = h265 && enc.h265; - (vp8, av1, h264, h265) - } - - pub fn update_supported_decodings(&self) { - let msg = self.lc.write().unwrap().update_supported_decodings(); - self.send(Data::Message(msg)); - } - - pub fn use_texture_render_changed(&self) { - self.send(Data::ResetDecoder(None)); - self.update_supported_decodings(); - self.send(Data::Message(LoginConfigHandler::refresh())); - } - - pub fn restart_remote_device(&self) { - let mut lc = self.lc.write().unwrap(); - lc.restarting_remote_device = true; - let msg = lc.restart_remote_device(); - self.send(Data::Message(msg)); - } - - #[cfg(all(feature = "flutter", feature = "plugin_framework"))] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn send_plugin_request(&self, request: PluginRequest) { - let mut misc = Misc::new(); - misc.set_plugin_request(request); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - - pub fn get_audit_server(&self, typ: String) -> String { - if LocalConfig::get_option("access_token").is_empty() { - return "".to_owned(); - } - crate::get_audit_server( - Config::get_option("api-server"), - Config::get_option("custom-rendezvous-server"), - typ, - ) - } - - pub fn send_note(&self, note: String) { - let url = self.get_audit_server("conn".to_string()); - let id = self.get_id(); - let session_id = self.lc.read().unwrap().session_id; - *self.last_audit_note.lock().unwrap() = note.clone(); - std::thread::spawn(move || { - send_note(url, id, session_id, note); - }); - } - - #[cfg(not(feature = "flutter"))] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn is_xfce(&self) -> bool { - crate::platform::is_xfce() - } - - pub fn remove_port_forward(&self, port: i32) { - let mut config = self.load_config(); - config.port_forwards = config - .port_forwards - .drain(..) - .filter(|x| x.0 != port) - .collect(); - self.save_config(config); - self.send(Data::RemovePortForward(port)); - } - - pub fn add_port_forward(&self, port: i32, remote_host: String, remote_port: i32) { - let mut config = self.load_config(); - if config - .port_forwards - .iter() - .filter(|x| x.0 == port) - .next() - .is_some() - { - return; - } - let pf = (port, remote_host, remote_port); - config.port_forwards.push(pf.clone()); - self.save_config(config); - self.send(Data::AddPortForward(pf)); - } - - pub fn get_option(&self, k: String) -> String { - if k.eq("remote_dir") { - return self.lc.read().unwrap().get_remote_dir(); - } - self.lc.read().unwrap().get_option(&k) - } - - pub fn set_option(&self, k: String, mut v: String) { - let mut lc = self.lc.write().unwrap(); - if k.eq("remote_dir") { - v = lc.get_all_remote_dir(v); - } - lc.set_option(k, v); - } - - #[inline] - pub fn load_config(&self) -> PeerConfig { - self.lc.read().unwrap().load_config() - } - - #[inline] - pub(super) fn save_config(&self, config: PeerConfig) { - self.lc.write().unwrap().save_config(config); - } - - pub fn is_restarting_remote_device(&self) -> bool { - self.lc.read().unwrap().restarting_remote_device - } - - #[inline] - pub fn peer_platform(&self) -> String { - self.lc.read().unwrap().info.platform.clone() - } - - pub fn get_platform(&self, is_remote: bool) -> String { - if is_remote { - self.peer_platform() - } else { - whoami::platform().to_string() - } - } - - pub fn get_path_sep(&self, is_remote: bool) -> &'static str { - let p = self.get_platform(is_remote); - if &p == crate::PLATFORM_WINDOWS { - return "\\"; - } else { - return "/"; - } - } - - pub fn input_os_password(&self, pass: String, activate: bool) { - input_os_password(pass, activate, self.clone()); - } - - #[cfg(not(feature = "flutter"))] - pub fn get_chatbox(&self) -> String { - #[cfg(feature = "inline")] - return crate::ui::inline::get_chatbox(); - #[cfg(not(feature = "inline"))] - return "".to_owned(); - } - - pub fn swap_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 as _); - 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() as _ - } - _ => { - 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.swap_modifier_key(&mut msg); - let mut msg_out = Message::new(); - msg_out.set_key_event(msg); - self.send(Data::Message(msg_out)); - } - - pub fn send_chat(&self, text: String) { - let mut misc = Misc::new(); - misc.set_chat_message(ChatMessage { - text, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - - // Terminal methods - pub fn open_terminal(&self, terminal_id: i32, rows: u32, cols: u32) { - let mut action = TerminalAction::new(); - action.set_open(OpenTerminal { - terminal_id, - rows, - cols, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_terminal_action(action); - self.send(Data::Message(msg_out)); - } - - pub fn send_terminal_input(&self, terminal_id: i32, data: String) { - let mut action = TerminalAction::new(); - action.set_data(TerminalData { - terminal_id, - data: bytes::Bytes::from(data.into_bytes()), - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_terminal_action(action); - self.send(Data::Message(msg_out)); - } - - pub fn resize_terminal(&self, terminal_id: i32, rows: u32, cols: u32) { - let mut action = TerminalAction::new(); - action.set_resize(ResizeTerminal { - terminal_id, - rows, - cols, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_terminal_action(action); - self.send(Data::Message(msg_out)); - } - - pub fn close_terminal(&self, terminal_id: i32) { - let mut action = TerminalAction::new(); - action.set_close(CloseTerminal { - terminal_id, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_terminal_action(action); - self.send(Data::Message(msg_out)); - } - - pub fn capture_displays(&self, add: Vec, sub: Vec, set: Vec) { - let mut misc = Misc::new(); - misc.set_capture_displays(CaptureDisplays { - add, - sub, - set, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - - pub fn switch_display(&self, display: i32) { - let (w, h) = match self.lc.read().unwrap().get_custom_resolution(display) { - Some((w, h)) => (w, h), - None => (0, 0), - }; - - let mut misc = Misc::new(); - misc.set_switch_display(SwitchDisplay { - display, - width: w, - height: h, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - - if !use_texture_render() { - self.capture_displays(vec![], vec![], vec![display]); - } - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn enter(&self, keyboard_mode: String) { - let session_id = self.lc.read().unwrap().session_id as u128; - keyboard::client::change_grab_status(GrabState::Run, &keyboard_mode, session_id); - } - - #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn leave(&self, keyboard_mode: String) { - let session_id = self.lc.read().unwrap().session_id as u128; - keyboard::client::change_grab_status(GrabState::Wait, &keyboard_mode, session_id); - } - - // flutter only TODO new input - pub fn input_key( - &self, - name: &str, - down: bool, - press: bool, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, - ) { - let chars: Vec = name.chars().collect(); - if chars.len() == 1 { - let key = Key::_Raw(chars[0] as _); - self._input_key(key, down, press, alt, ctrl, shift, command); - } else { - if let Some(key) = KEY_MAP.get(name) { - self._input_key(key.clone(), down, press, alt, ctrl, shift, command); - } - } - } - - pub fn input_string(&self, value: &str) { - let mut key_event = KeyEvent::new(); - key_event.set_seq(value.to_owned()); - let mut msg_out = Message::new(); - msg_out.set_key_event(key_event); - self.send(Data::Message(msg_out)); - } - - #[cfg(any(target_os = "ios"))] - pub fn handle_flutter_raw_key_event( - &self, - _keyboard_mode: &str, - _name: &str, - _platform_code: i32, - _position_code: i32, - _lock_modes: i32, - _down_or_up: bool, - ) { - } - - #[cfg(not(any(target_os = "ios")))] - pub fn handle_flutter_raw_key_event( - &self, - keyboard_mode: &str, - name: &str, - platform_code: i32, - position_code: i32, - lock_modes: i32, - down_or_up: bool, - ) { - if name == "flutter_key" { - self._handle_key_flutter_simulation(keyboard_mode, platform_code, down_or_up); - } else { - self._handle_raw_key_non_flutter_simulation( - keyboard_mode, - platform_code, - position_code, - lock_modes, - down_or_up, - ); - } - } - - #[cfg(not(any(target_os = "ios")))] - fn _handle_raw_key_non_flutter_simulation( - &self, - keyboard_mode: &str, - platform_code: i32, - position_code: i32, - lock_modes: i32, - down_or_up: bool, - ) { - if position_code < 0 || platform_code < 0 { - return; - } - let platform_code: u32 = platform_code as _; - let position_code: KeyCode = position_code as _; - - #[cfg(not(target_os = "windows"))] - let key = rdev::key_from_code(position_code) as rdev::Key; - // Windows requires special handling - #[cfg(target_os = "windows")] - let key = rdev::get_win_key(platform_code, position_code); - - let event_type = if down_or_up { - KeyPress(key) - } else { - KeyRelease(key) - }; - let event = Event { - time: SystemTime::now(), - unicode: None, - platform_code, - position_code: position_code as _, - event_type, - usb_hid: 0, - #[cfg(any(target_os = "windows", target_os = "macos"))] - extra_data: 0, - }; - keyboard::client::process_event_with_session(keyboard_mode, &event, Some(lock_modes), self); - } - - pub fn handle_flutter_key_event( - &self, - keyboard_mode: &str, - character: &str, - usb_hid: i32, - lock_modes: i32, - down_or_up: bool, - ) { - if character == "flutter_key" { - self._handle_key_flutter_simulation(keyboard_mode, usb_hid, down_or_up); - } else { - self._handle_key_non_flutter_simulation( - keyboard_mode, - character, - usb_hid, - lock_modes, - down_or_up, - ); - } - } - - fn _handle_key_flutter_simulation( - &self, - _keyboard_mode: &str, - platform_code: i32, - down_or_up: bool, - ) { - // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L4356 - let ctrl_key = match platform_code { - 0x007f => Some(ControlKey::VolumeMute), - 0x0080 => Some(ControlKey::VolumeUp), - 0x0081 => Some(ControlKey::VolumeDown), - 0x0066 => Some(ControlKey::Power), - _ => None, - }; - let Some(ctrl_key) = ctrl_key else { return }; - let mut key_event = KeyEvent { - mode: KeyboardMode::Translate.into(), - down: down_or_up, - ..Default::default() - }; - key_event.set_control_key(ctrl_key); - self.send_key_event(&key_event); - } - - fn _handle_key_non_flutter_simulation( - &self, - keyboard_mode: &str, - character: &str, - usb_hid: i32, - lock_modes: i32, - down_or_up: bool, - ) { - let key = rdev::usb_hid_key_from_code(usb_hid as _); - - #[cfg(any(target_os = "android", target_os = "ios"))] - let position_code: KeyCode = 0; - #[cfg(any(target_os = "android", target_os = "ios"))] - let platform_code: KeyCode = 0; - - #[cfg(target_os = "windows")] - let platform_code: u32 = rdev::win_code_from_key(key).unwrap_or(0); - #[cfg(target_os = "windows")] - let position_code: KeyCode = rdev::win_scancode_from_key(key).unwrap_or(0) as _; - - #[cfg(not(any(target_os = "windows", target_os = "android", target_os = "ios")))] - let position_code: KeyCode = rdev::code_from_key(key).unwrap_or(0) as _; - #[cfg(not(any( - target_os = "windows", - target_os = "android", - target_os = "ios", - target_os = "linux" - )))] - let platform_code: u32 = position_code as _; - // For translate mode. - // We need to set the platform code (keysym) if is AltGr. - // https://github.com/rustdesk/rustdesk/blob/07cf1b4db5ef2f925efd3b16b87c33ce03c94809/src/keyboard.rs#L1029 - // https://github.com/flutter/flutter/issues/153811 - #[cfg(target_os = "linux")] - let platform_code: u32 = position_code as _; - - let event_type = if down_or_up { - KeyPress(key) - } else { - KeyRelease(key) - }; - let event = Event { - time: SystemTime::now(), - unicode: if character.is_empty() { - None - } else { - Some(rdev::UnicodeInfo { - name: Some(character.to_string()), - unicode: character.encode_utf16().collect(), - // is_dead: is not correct here, because flutter cannot detect deadcode for now. - is_dead: false, - }) - }, - platform_code, - position_code: position_code as _, - event_type, - #[cfg(any(target_os = "android", target_os = "ios"))] - usb_hid: usb_hid as _, - #[cfg(not(any(target_os = "android", target_os = "ios")))] - usb_hid: 0, - #[cfg(any(target_os = "windows", target_os = "macos"))] - extra_data: 0, - }; - keyboard::client::process_event_with_session(keyboard_mode, &event, Some(lock_modes), self); - } - - // flutter only TODO new input - fn _input_key( - &self, - key: Key, - down: bool, - press: bool, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, - ) { - let v = if press { - 3 - } else if down { - 1 - } else { - 0 - }; - let mut key_event = KeyEvent::new(); - match key { - Key::Chr(chr) => { - key_event.set_chr(chr); - } - Key::ControlKey(key) => { - key_event.set_control_key(key.clone()); - } - Key::_Raw(raw) => { - key_event.set_chr(raw); - } - } - - if v == 1 { - key_event.down = true; - } else if v == 3 { - key_event.press = true; - } - keyboard::client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command); - key_event.mode = KeyboardMode::Legacy.into(); - - self.send_key_event(&key_event); - } - - pub fn send_touch_scale(&self, scale: i32, alt: bool, ctrl: bool, shift: bool, command: bool) { - let scale_evt = TouchScaleUpdate { - scale, - ..Default::default() - }; - let mut touch_evt = TouchEvent::new(); - touch_evt.set_scale_update(scale_evt); - let mut evt = PointerDeviceEvent::new(); - evt.set_touch_event(touch_evt); - send_pointer_device_event(evt, alt, ctrl, shift, command, self); - } - - pub fn send_touch_pan_event( - &self, - event: &str, - x: i32, - y: i32, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, - ) { - let mut touch_evt = TouchEvent::new(); - match event { - "pan_start" => { - touch_evt.set_pan_start(TouchPanStart { - x, - y, - ..Default::default() - }); - } - "pan_update" => { - let (x, y) = self.get_scroll_xy((x, y)); - touch_evt.set_pan_update(TouchPanUpdate { - x, - y, - ..Default::default() - }); - } - "pan_end" => { - touch_evt.set_pan_end(TouchPanEnd { - x, - y, - ..Default::default() - }); - } - _ => { - log::warn!("unknown touch pan event: {}", event); - return; - } - }; - let mut evt = PointerDeviceEvent::new(); - evt.set_touch_event(touch_evt); - send_pointer_device_event(evt, alt, ctrl, shift, command, self); - } - - #[inline] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn is_scroll_reverse_mode(&self) -> bool { - self.lc.read().unwrap().reverse_mouse_wheel.eq("Y") - } - - #[inline] - fn get_scroll_xy(&self, xy: (i32, i32)) -> (i32, i32) { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if self.is_scroll_reverse_mode() { - return (-xy.0, -xy.1); - } - xy - } - - pub fn send_mouse( - &self, - mut mask: i32, - x: i32, - y: i32, - alt: bool, - ctrl: bool, - shift: bool, - command: bool, - ) { - #[allow(unused_mut)] - let mut command = command; - #[cfg(windows)] - { - if !command && crate::platform::windows::get_win_key_state() { - command = true; - } - } - - // Compute event type once using MOUSE_TYPE_MASK for reuse - let event_type = mask & MOUSE_TYPE_MASK; - let (x, y) = if event_type == MOUSE_TYPE_WHEEL || event_type == MOUSE_TYPE_TRACKPAD { - self.get_scroll_xy((x, y)) - } else { - (x, y) - }; - - // #[cfg(not(any(target_os = "android", target_os = "ios")))] - let (alt, ctrl, shift, command) = - keyboard::client::get_modifiers_state(alt, ctrl, shift, command); - let is_left = (mask & (MOUSE_BUTTON_LEFT << 3)) > 0; - let is_right = (mask & (MOUSE_BUTTON_RIGHT << 3)) > 0; - if is_left ^ is_right { - let swap_lr = self.get_toggle_option("swap-left-right-mouse".to_string()); - if swap_lr { - if is_left { - mask = (mask & (!(MOUSE_BUTTON_LEFT << 3))) | (MOUSE_BUTTON_RIGHT << 3); - } else { - mask = (mask & (!(MOUSE_BUTTON_RIGHT << 3))) | (MOUSE_BUTTON_LEFT << 3); - } - } - } - - send_mouse(mask, x, y, alt, ctrl, shift, command, self); - // on macos, ctrl + left button down = right button down, up won't emit, so we need to - // emit up myself if peer is not macos - // to-do: how about ctrl + left from win to macos - if cfg!(target_os = "macos") { - let buttons = mask >> 3; - if buttons == MOUSE_BUTTON_LEFT - && event_type == MOUSE_TYPE_DOWN - && ctrl - && self.peer_platform() != "Mac OS" - { - self.send_mouse( - (MOUSE_BUTTON_LEFT << 3 | MOUSE_TYPE_UP) as _, - x, - y, - alt, - ctrl, - shift, - command, - ); - } - } - } - - pub fn reconnect(&self, force_relay: bool) { - // 1. If current session is connecting, do not reconnect. - // 2. If the connection is established, send `Data::Close`. - // 3. If the connection is disconnected, do nothing. - let mut connection_round_state_lock = self.connection_round_state.lock().unwrap(); - if self.thread.lock().unwrap().is_some() { - match connection_round_state_lock.state { - ConnectionState::Connecting => return, - ConnectionState::Connected => self.send(Data::Close), - ConnectionState::Disconnected => {} - } - } - let round = connection_round_state_lock.new_round(); - drop(connection_round_state_lock); - - let cloned = self.clone(); - - // override only if true - if true == force_relay { - self.lc.write().unwrap().force_relay = true; - } - self.lc.write().unwrap().peer_info = None; - self.reconnect_count.fetch_add(1, Ordering::SeqCst); - let mut lock = self.thread.lock().unwrap(); - // No need to join the previous thread, because it will exit automatically. - // And the previous thread will not change important states. - *lock = Some(std::thread::spawn(move || { - io_loop(cloned, round); - })); - } - - #[cfg(not(feature = "flutter"))] - pub fn get_icon_path(&self, file_type: i32, ext: String) -> String { - let mut path = Config::icon_path(); - if file_type == FileType::DirLink as i32 { - let new_path = path.join("dir_link"); - if !std::fs::metadata(&new_path).is_ok() { - #[cfg(windows)] - allow_err!(std::os::windows::fs::symlink_file(&path, &new_path)); - #[cfg(not(windows))] - allow_err!(std::os::unix::fs::symlink(&path, &new_path)); - } - path = new_path; - } else if file_type == FileType::File as i32 { - if !ext.is_empty() { - path = path.join(format!("file.{}", ext)); - } else { - path = path.join("file"); - } - if !std::fs::metadata(&path).is_ok() { - allow_err!(std::fs::File::create(&path)); - } - } else if file_type == FileType::FileLink as i32 { - let new_path = path.join("file_link"); - if !std::fs::metadata(&new_path).is_ok() { - path = path.join("file"); - if !std::fs::metadata(&path).is_ok() { - allow_err!(std::fs::File::create(&path)); - } - #[cfg(windows)] - allow_err!(std::os::windows::fs::symlink_file(&path, &new_path)); - #[cfg(not(windows))] - allow_err!(std::os::unix::fs::symlink(&path, &new_path)); - } - path = new_path; - } else if file_type == FileType::DirDrive as i32 { - if cfg!(windows) { - path = fs::get_path("C:"); - } else if cfg!(target_os = "macos") { - if let Ok(entries) = fs::get_path("/Volumes/").read_dir() { - for entry in entries { - if let Ok(entry) = entry { - path = entry.path(); - break; - } - } - } - } - } - fs::get_string(&path) - } - - pub fn login( - &self, - os_username: String, - os_password: String, - password: String, - remember: bool, - ) { - self.send(Data::Login((os_username, os_password, password, remember))); - } - - pub fn send2fa(&self, code: String, trust_this_device: bool) { - let mut msg_out = Message::new(); - let hwid = if trust_this_device { - crate::get_hwid() - } else { - Bytes::new() - }; - self.lc.write().unwrap().set_option( - "trust-this-device".to_string(), - if trust_this_device { "Y" } else { "" }.to_string(), - ); - msg_out.set_auth_2fa(Auth2FA { - code, - hwid, - ..Default::default() - }); - self.send(Data::Message(msg_out)); - } - - pub fn get_enable_trusted_devices(&self) -> bool { - self.lc.read().unwrap().enable_trusted_devices - } - - pub fn new_rdp(&self) { - self.send(Data::NewRDP); - } - - pub fn close(&self) { - self.send(Data::Close); - } - - fn try_auto_start_job_str(is_reconnected: bool, job_str: &str) -> Option { - if is_reconnected { - let job_str = job_str.trim(); - if let Some(stripped) = job_str.strip_suffix('}') { - format!(r#"{},"auto_start": true}}"#, stripped).into() - } else { - // unreachable in normal cases - log::warn!( - "The last character is not '}}': {}, auto start is ignored on flutter", - job_str - ); - Some(job_str.to_owned()) - } - } else { - None - } - } - - pub fn load_last_jobs(&self) { - self.clear_all_jobs(); - let pc = self.load_config(); - if pc.transfer.write_jobs.is_empty() && pc.transfer.read_jobs.is_empty() { - // no last jobs - return; - } - let reconnect_count_thr = if cfg!(feature = "flutter") { 0 } else { 1 }; - let is_reconnected = self.reconnect_count.load(Ordering::SeqCst) > reconnect_count_thr; - // TODO: can add a confirm dialog - let mut cnt = 1; - for job_str in pc.transfer.read_jobs.iter() { - if !job_str.is_empty() { - self.load_last_job( - cnt, - Self::try_auto_start_job_str(is_reconnected, job_str) - .as_deref() - .unwrap_or(job_str), - is_reconnected, - ); - cnt += 1; - log::info!("restore read_job: {:?}", job_str); - } - } - for job_str in pc.transfer.write_jobs.iter() { - if !job_str.is_empty() { - self.load_last_job( - cnt, - Self::try_auto_start_job_str(is_reconnected, job_str) - .as_deref() - .unwrap_or(job_str), - is_reconnected, - ); - cnt += 1; - log::info!("restore write_job: {:?}", job_str); - } - } - self.update_transfer_list(); - } - - pub fn elevate_direct(&self) { - self.send(Data::ElevateDirect); - } - - pub fn elevate_with_logon(&self, username: String, password: String) { - self.send(Data::ElevateWithLogon(username, password)); - } - - #[cfg(any(target_os = "android", target_os = "ios", not(feature = "flutter")))] - pub fn switch_sides(&self) {} - - #[cfg(feature = "flutter")] - #[cfg(not(any(target_os = "android", target_os = "ios")))] - #[tokio::main(flavor = "current_thread")] - pub async fn switch_sides(&self) { - match crate::ipc::connect(1000, "").await { - Ok(mut conn) => { - if conn - .send(&crate::ipc::Data::SwitchSidesRequest(self.get_id())) - .await - .is_ok() - { - if let Ok(Some(data)) = conn.next_timeout(1000).await { - match data { - crate::ipc::Data::SwitchSidesRequest(str_uuid) => { - if let Ok(uuid) = Uuid::from_str(&str_uuid) { - let mut misc = Misc::new(); - misc.set_switch_sides_request(SwitchSidesRequest { - uuid: Bytes::from(uuid.as_bytes().to_vec()), - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - self.send(Data::Message(msg_out)); - } - } - _ => {} - } - } - } - } - Err(err) => { - log::info!("server not started (will try to start): {}", err); - } - } - } - - fn set_custom_resolution(&self, display: &SwitchDisplay) { - if display.width == display.original_resolution.width - && display.height == display.original_resolution.height - { - self.lc - .write() - .unwrap() - .set_custom_resolution(display.display, None); - } else { - let last_change_display = self.last_change_display.lock().unwrap(); - if last_change_display.display == display.display { - let wh = if last_change_display.is_the_same_record( - display.display, - display.width, - display.height, - ) { - Some((display.width, display.height)) - } else { - // display origin is changed, or some other events. - None - }; - self.lc - .write() - .unwrap() - .set_custom_resolution(display.display, wh); - } - } - } - - #[inline] - pub fn handle_peer_switch_display(&self, display: &SwitchDisplay) { - self.ui_handler.switch_display(display); - self.set_custom_resolution(display); - } - - #[inline] - pub fn change_resolution(&self, display: i32, width: i32, height: i32) { - *self.last_change_display.lock().unwrap() = - ChangeDisplayRecord::new(display, width, height); - self.do_change_resolution(display, width, height); - } - - #[inline] - fn try_change_init_resolution(&self, display: i32) { - let Some((w, h)) = self.lc.read().unwrap().get_custom_resolution(display) else { - return; - }; - self.change_resolution(display, w, h); - } - - fn do_change_resolution(&self, display: i32, width: i32, height: i32) { - let mut misc = Misc::new(); - let resolution = Resolution { - width, - height, - ..Default::default() - }; - if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) { - misc.set_change_display_resolution(DisplayResolution { - display, - resolution: Some(resolution).into(), - ..Default::default() - }); - } else { - misc.set_change_resolution(resolution); - } - let mut msg = Message::new(); - msg.set_misc(misc); - self.send(Data::Message(msg)); - } - - #[inline] - pub fn request_voice_call(&self) { - #[cfg(target_os = "linux")] - std::thread::spawn(crate::ipc::start_pa); - self.send(Data::NewVoiceCall); - } - - #[inline] - pub fn close_voice_call(&self) { - self.send(Data::CloseVoiceCall); - } - - pub fn send_selected_session_id(&self, sid: String) { - if let Ok(sid) = sid.parse::() { - self.lc.write().unwrap().selected_windows_session_id = Some(sid); - let mut misc = Misc::new(); - misc.set_selected_sid(sid); - let mut msg = Message::new(); - msg.set_misc(misc); - self.send(Data::Message(msg)); - let pi = self.lc.read().unwrap().peer_info.clone(); - if let Some(pi) = pi { - if pi.windows_sessions.current_sid == sid { - if self.is_file_transfer() { - if pi.username.is_empty() { - self.on_error( - "No active console user logged on, please connect and logon first.", - ); - } else { - #[cfg(not(feature = "flutter"))] - { - let remote_dir = self.get_option("remote_dir".to_string()); - let show_hidden = - !self.get_option("remote_show_hidden".to_string()).is_empty(); - self.read_remote_dir(remote_dir, show_hidden); - } - } - } else if !self.is_terminal() { - self.msgbox( - "success", - "Successful", - "Connected, waiting for image...", - "", - ); - } - } - } - } else { - log::error!("selected invalid sid: {}", sid); - } - } - - #[inline] - pub fn request_init_msgs(&self, display: usize) { - self.send_message_query(display); - } - - fn send_message_query(&self, display: usize) { - let mut misc = Misc::new(); - misc.set_message_query(MessageQuery { - switch_display: display as _, - ..Default::default() - }); - let mut msg = Message::new(); - msg.set_misc(misc); - self.send(Data::Message(msg)); - } - - pub fn get_conn_token(&self) -> Option { - self.lc.read().unwrap().get_conn_token() - } - - pub fn printer_response(&self, id: i32, path: String, printer_name: String) { - self.printer_names.write().unwrap().insert(id, printer_name); - let to = std::env::temp_dir().join(format!("rustdesk_printer_{id}")); - self.send(Data::SendFiles(( - id, - hbb_common::fs::JobType::Printer, - path, - to.to_string_lossy().to_string(), - 0, - false, - true, - ))); - } -} - -pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { - fn set_cursor_data(&self, cd: CursorData); - fn set_cursor_id(&self, id: String); - fn set_cursor_position(&self, cp: CursorPosition); - fn set_display(&self, x: i32, y: i32, w: i32, h: i32, cursor_embedded: bool, scale: f64); - fn switch_display(&self, display: &SwitchDisplay); - fn set_peer_info(&self, peer_info: &PeerInfo); // flutter - fn set_displays(&self, displays: &Vec); - fn set_platform_additions(&self, data: &str); - fn on_connected(&self, conn_type: ConnType); - fn update_privacy_mode(&self); - fn set_permission(&self, name: &str, value: bool); - fn close_success(&self); - fn update_quality_status(&self, qs: QualityStatus); - fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str); - fn set_fingerprint(&self, fingerprint: String); - fn job_error(&self, id: i32, err: String, file_num: i32); - fn job_done(&self, id: i32, file_num: i32); - fn clear_all_jobs(&self); - fn new_message(&self, msg: String); - fn update_transfer_list(&self); - fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool); - fn update_folder_files( - &self, - id: i32, - entries: &Vec, - path: String, - is_local: bool, - only_count: bool, - ); - fn confirm_delete_files(&self, id: i32, i: i32, name: String); - fn override_file_confirm( - &self, - id: i32, - file_num: i32, - to: String, - is_upload: bool, - is_identical: bool, - ); - fn update_block_input_state(&self, on: bool); - fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64); - fn adapt_size(&self); - fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb); - fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool); - #[cfg(any(target_os = "android", target_os = "ios"))] - fn clipboard(&self, content: String); - fn cancel_msgbox(&self, tag: &str); - fn switch_back(&self, id: &str); - fn portable_service_running(&self, running: bool); - fn on_voice_call_started(&self); - fn on_voice_call_closed(&self, reason: &str); - fn on_voice_call_waiting(&self); - fn on_voice_call_incoming(&self); - fn get_rgba(&self, display: usize) -> *const u8; - fn next_rgba(&self, display: usize); - #[cfg(all(feature = "vram", feature = "flutter"))] - fn on_texture(&self, display: usize, texture: *mut c_void); - fn set_multiple_windows_session(&self, sessions: Vec); - fn set_current_display(&self, disp_idx: i32); - #[cfg(feature = "flutter")] - fn is_multi_ui_session(&self) -> bool; - fn update_record_status(&self, start: bool); - fn update_empty_dirs(&self, _res: ReadEmptyDirsResponse) {} - fn printer_request(&self, id: i32, path: String); - fn handle_screenshot_resp(&self, sid: String, msg: String); - fn handle_terminal_response(&self, response: TerminalResponse); -} - -impl Deref for Session { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.ui_handler - } -} - -impl DerefMut for Session { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.ui_handler - } -} - -impl FileManager for Session {} - -#[async_trait] -impl Interface for Session { - fn get_lch(&self) -> Arc> { - return self.lc.clone(); - } - - fn send(&self, data: Data) { - if let Some(sender) = self.sender.read().unwrap().as_ref() { - sender.send(data).ok(); - } - } - - fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) { - let direct = self.lc.read().unwrap().direct; - let received = self.lc.read().unwrap().received; - let retry_for_relay = direct == Some(true) && !received; - let retry = check_if_retry(msgtype, title, text, retry_for_relay); - self.ui_handler.msgbox(msgtype, title, text, link, retry); - } - - fn handle_login_error(&self, err: &str) -> bool { - handle_login_error(self.lc.clone(), err, self) - } - - fn set_multiple_windows_session(&self, sessions: Vec) { - self.ui_handler.set_multiple_windows_session(sessions); - } - - fn handle_peer_info(&self, mut pi: PeerInfo) { - log::debug!("handle_peer_info :{:?}", pi); - self.lc.write().unwrap().peer_info = Some(pi.clone()); - if pi.current_display as usize >= pi.displays.len() { - pi.current_display = 0; - } - if get_version_number(&pi.version) < get_version_number("1.1.10") { - self.set_permission("restart", false); - } - if self.is_file_transfer() { - if pi.username.is_empty() && pi.windows_sessions.sessions.is_empty() { - self.on_error("No active console user logged on, please connect and logon first."); - return; - } - } else if !self.is_port_forward() && !self.is_terminal() { - if pi.displays.is_empty() { - self.lc.write().unwrap().handle_peer_info(&pi); - self.update_privacy_mode(); - let msg = if self.is_view_camera() { - "No cameras" - } else { - "No displays" - }; - self.msgbox("error", "Error", msg, ""); - return; - } - self.try_change_init_resolution(pi.current_display); - let p = self.lc.read().unwrap().should_auto_login(); - if !p.is_empty() { - input_os_password(p, true, self.clone()); - } - let current = &pi.displays[pi.current_display as usize]; - self.set_display( - current.x, - current.y, - current.width, - current.height, - current.cursor_embedded, - current.scale, - ); - } - self.update_privacy_mode(); - // Clear audit_guid when connection is established successfully - *self.audit_guid.lock().unwrap() = String::new(); - *self.last_audit_note.lock().unwrap() = String::new(); - // Save recent peers, then push event to flutter. So flutter can refresh peer page. - self.lc.write().unwrap().handle_peer_info(&pi); - self.set_peer_info(&pi); - if self.is_file_transfer() { - self.close_success(); - } else if !self.is_port_forward() && !self.is_terminal() { - self.msgbox( - "success", - "Successful", - "Connected, waiting for image...", - "", - ); - } - self.on_connected(self.lc.read().unwrap().conn_type); - #[cfg(windows)] - { - let mut path = std::env::temp_dir(); - path.push(self.get_id()); - let path = path.with_extension(crate::get_app_name().to_lowercase()); - std::fs::File::create(&path).ok(); - if let Some(path) = path.to_str() { - crate::platform::windows::add_recent_document(&path); - } - } - if !pi.windows_sessions.sessions.is_empty() { - let selected = self - .lc - .read() - .unwrap() - .selected_windows_session_id - .to_owned(); - if selected == Some(pi.windows_sessions.current_sid) { - self.send_selected_session_id(pi.windows_sessions.current_sid.to_string()); - } else { - self.set_multiple_windows_session(pi.windows_sessions.sessions.clone()); - } - } - } - - async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) { - handle_hash(self.lc.clone(), pass, hash, self, peer).await; - } - - async fn handle_login_from_ui( - &self, - os_username: String, - os_password: String, - password: String, - remember: bool, - peer: &mut Stream, - ) { - handle_login_from_ui( - self.lc.clone(), - os_username, - os_password, - password, - remember, - peer, - ) - .await; - } - - async fn handle_test_delay(&self, t: TestDelay, peer: &mut Stream) { - if !t.from_client { - self.update_quality_status(QualityStatus { - delay: Some(t.last_delay as _), - target_bitrate: Some(t.target_bitrate as _), - ..Default::default() - }); - 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 { - pub fn lock_screen(&self) { - self.send_key_event(&crate::keyboard::client::event_lock_screen()); - } - pub fn ctrl_alt_del(&self) { - self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del()); - } -} - -#[tokio::main(flavor = "current_thread")] -pub async fn io_loop(handler: Session, round: u32) { - #[cfg(any(target_os = "android", target_os = "ios"))] - let (sender, receiver) = mpsc::unbounded_channel::(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let (sender, mut receiver) = mpsc::unbounded_channel::(); - *handler.sender.write().unwrap() = Some(sender.clone()); - let token = LocalConfig::get_option("access_token"); - let key = crate::get_key(false).await; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if handler.is_port_forward() { - if handler.is_rdp() { - let port = handler - .get_option("rdp_port".to_owned()) - .parse::() - .unwrap_or(3389); - std::env::set_var( - "rdp_username", - handler.get_option("rdp_username".to_owned()), - ); - std::env::set_var( - "rdp_password", - handler.get_option("rdp_password".to_owned()), - ); - log::info!("Remote rdp port: {}", port); - start_one_port_forward(handler, 0, "".to_owned(), port, receiver, &key, &token).await; - } else if handler.args.len() == 0 { - let pfs = handler.lc.read().unwrap().port_forwards.clone(); - let mut queues = HashMap::>::new(); - for d in pfs { - sender.send(Data::AddPortForward(d)).ok(); - } - loop { - match receiver.recv().await { - Some(Data::AddPortForward((port, remote_host, remote_port))) => { - if port <= 0 || remote_port <= 0 { - continue; - } - let (sender, receiver) = mpsc::unbounded_channel::(); - queues.insert(port, sender); - let handler = handler.clone(); - let key = key.clone(); - let token = token.clone(); - tokio::spawn(async move { - start_one_port_forward( - handler, - port, - remote_host, - remote_port, - receiver, - &key, - &token, - ) - .await; - }); - } - Some(Data::RemovePortForward(port)) => { - if let Some(s) = queues.remove(&port) { - s.send(Data::Close).ok(); - } - } - Some(Data::Close) => { - break; - } - Some(d) => { - for (_, s) in queues.iter() { - s.send(d.clone()).ok(); - } - } - _ => {} - } - } - } else { - let port = handler.args[0].parse::().unwrap_or(0); - if handler.args.len() != 3 - || handler.args[2].parse::().unwrap_or(0) <= 0 - || port <= 0 - { - handler.on_error("Invalid arguments, usage:

    rustdesk --port-forward remote-id listen-port remote-host remote-port"); - } - let remote_host = handler.args[1].clone(); - let remote_port = handler.args[2].parse::().unwrap_or(0); - start_one_port_forward( - handler, - port, - remote_host, - remote_port, - receiver, - &key, - &token, - ) - .await; - } - return; - } - let mut remote = Remote::new(handler, receiver, sender); - remote.io_loop(&key, &token, round).await; - let _ = remote.sync_jobs_status_to_local().await; -} - -#[cfg(not(any(target_os = "android", target_os = "ios")))] -async fn start_one_port_forward( - handler: Session, - port: i32, - remote_host: String, - remote_port: i32, - receiver: mpsc::UnboundedReceiver, - key: &str, - token: &str, -) { - if let Err(err) = crate::port_forward::listen( - handler.get_id(), - handler.password.clone(), - port, - handler.clone(), - receiver, - key, - token, - handler.lc.clone(), - remote_host, - remote_port, - ) - .await - { - handler.on_error(&format!("Failed to listen on {}: {}", port, err)); - } - log::info!("port forward (:{}) exit", port); -} - -#[tokio::main(flavor = "current_thread")] -async fn send_note(url: String, id: String, sid: u64, note: String) { - let body = serde_json::json!({ "id": id, "session_id": sid, "note": note }); - allow_err!(crate::post_request(url, body.to_string(), "").await); -} diff --git a/src/updater.rs b/src/updater.rs deleted file mode 100644 index 357f111a7..000000000 --- a/src/updater.rs +++ /dev/null @@ -1,290 +0,0 @@ -use crate::{common::do_check_software_update, hbbs_http::create_http_client_with_url}; -use hbb_common::{bail, config, log, ResultType}; -use std::{ - io::Write, - path::PathBuf, - sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc::{channel, Receiver, Sender}, - Mutex, - }, - time::{Duration, Instant}, -}; - -enum UpdateMsg { - CheckUpdate, - Exit, -} - -lazy_static::lazy_static! { - static ref TX_MSG : Mutex> = Mutex::new(start_auto_update_check()); -} - -static CONTROLLING_SESSION_COUNT: AtomicUsize = AtomicUsize::new(0); - -const DUR_ONE_DAY: Duration = Duration::from_secs(60 * 60 * 24); - -pub fn update_controlling_session_count(count: usize) { - CONTROLLING_SESSION_COUNT.store(count, Ordering::SeqCst); -} - -#[allow(dead_code)] -pub fn start_auto_update() { - let _sender = TX_MSG.lock().unwrap(); -} - -#[allow(dead_code)] -pub fn manually_check_update() -> ResultType<()> { - let sender = TX_MSG.lock().unwrap(); - sender.send(UpdateMsg::CheckUpdate)?; - Ok(()) -} - -#[allow(dead_code)] -pub fn stop_auto_update() { - let sender = TX_MSG.lock().unwrap(); - sender.send(UpdateMsg::Exit).unwrap_or_default(); -} - -#[inline] -fn has_no_active_conns() -> bool { - let conns = crate::Connection::alive_conns(); - conns.is_empty() && has_no_controlling_conns() -} - -#[cfg(any(not(target_os = "windows"), feature = "flutter"))] -fn has_no_controlling_conns() -> bool { - CONTROLLING_SESSION_COUNT.load(Ordering::SeqCst) == 0 -} - -#[cfg(not(any(not(target_os = "windows"), feature = "flutter")))] -fn has_no_controlling_conns() -> bool { - let app_exe = format!("{}.exe", crate::get_app_name().to_lowercase()); - for arg in [ - "--connect", - "--play", - "--file-transfer", - "--view-camera", - "--port-forward", - "--rdp", - ] { - if !crate::platform::get_pids_of_process_with_first_arg(&app_exe, arg).is_empty() { - return false; - } - } - true -} - -fn start_auto_update_check() -> Sender { - let (tx, rx) = channel(); - std::thread::spawn(move || start_auto_update_check_(rx)); - return tx; -} - -fn start_auto_update_check_(rx_msg: Receiver) { - std::thread::sleep(Duration::from_secs(30)); - if let Err(e) = check_update(false) { - log::error!("Error checking for updates: {}", e); - } - - const MIN_INTERVAL: Duration = Duration::from_secs(60 * 10); - const RETRY_INTERVAL: Duration = Duration::from_secs(60 * 30); - let mut last_check_time = Instant::now(); - let mut check_interval = DUR_ONE_DAY; - loop { - let recv_res = rx_msg.recv_timeout(check_interval); - match &recv_res { - Ok(UpdateMsg::CheckUpdate) | Err(_) => { - if last_check_time.elapsed() < MIN_INTERVAL { - // log::debug!("Update check skipped due to minimum interval."); - continue; - } - // Don't check update if there are alive connections. - if !has_no_active_conns() { - check_interval = RETRY_INTERVAL; - continue; - } - if let Err(e) = check_update(matches!(recv_res, Ok(UpdateMsg::CheckUpdate))) { - log::error!("Error checking for updates: {}", e); - check_interval = RETRY_INTERVAL; - } else { - last_check_time = Instant::now(); - check_interval = DUR_ONE_DAY; - } - } - Ok(UpdateMsg::Exit) => break, - } - } -} - -fn check_update(manually: bool) -> ResultType<()> { - #[cfg(target_os = "windows")] - let update_msi = crate::platform::is_msi_installed()? && !crate::is_custom_client(); - if !(manually || config::Config::get_bool_option(config::keys::OPTION_ALLOW_AUTO_UPDATE)) { - return Ok(()); - } - if do_check_software_update().is_err() { - // ignore - return Ok(()); - } - - let update_url = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone(); - if update_url.is_empty() { - log::debug!("No update available."); - } else { - let download_url = update_url.replace("tag", "download"); - let version = download_url.split('/').last().unwrap_or_default(); - #[cfg(target_os = "windows")] - let download_url = if cfg!(feature = "flutter") { - format!( - "{}/rustdesk-{}-x86_64.{}", - download_url, - version, - if update_msi { "msi" } else { "exe" } - ) - } else { - format!("{}/rustdesk-{}-x86-sciter.exe", download_url, version) - }; - log::debug!("New version available: {}", &version); - let client = create_http_client_with_url(&download_url); - let Some(file_path) = get_download_file_from_url(&download_url) else { - bail!("Failed to get the file path from the URL: {}", download_url); - }; - let mut is_file_exists = false; - if file_path.exists() { - // Check if the file size is the same as the server file size - // If the file size is the same, we don't need to download it again. - let file_size = std::fs::metadata(&file_path)?.len(); - let response = client.head(&download_url).send()?; - if !response.status().is_success() { - bail!("Failed to get the file size: {}", response.status()); - } - let total_size = response - .headers() - .get(reqwest::header::CONTENT_LENGTH) - .and_then(|ct_len| ct_len.to_str().ok()) - .and_then(|ct_len| ct_len.parse::().ok()); - let Some(total_size) = total_size else { - bail!("Failed to get content length"); - }; - if file_size == total_size { - is_file_exists = true; - } else { - std::fs::remove_file(&file_path)?; - } - } - if !is_file_exists { - let response = client.get(&download_url).send()?; - if !response.status().is_success() { - bail!( - "Failed to download the new version file: {}", - response.status() - ); - } - let file_data = response.bytes()?; - let mut file = std::fs::File::create(&file_path)?; - file.write_all(&file_data)?; - } - // We have checked if the `conns` is empty before, but we need to check again. - // No need to care about the downloaded file here, because it's rare case that the `conns` are empty - // before the download, but not empty after the download. - if has_no_active_conns() { - #[cfg(target_os = "windows")] - update_new_version(update_msi, &version, &file_path); - } - } - Ok(()) -} - -#[cfg(target_os = "windows")] -fn update_new_version(update_msi: bool, version: &str, file_path: &PathBuf) { - log::debug!( - "New version is downloaded, update begin, update msi: {update_msi}, version: {version}, file: {:?}", - file_path.to_str() - ); - if let Some(p) = file_path.to_str() { - if let Some(session_id) = crate::platform::get_current_process_session_id() { - if update_msi { - match crate::platform::update_me_msi(p, true) { - Ok(_) => { - log::debug!("New version \"{}\" updated.", version); - } - Err(e) => { - log::error!( - "Failed to install the new msi version \"{}\": {}", - version, - e - ); - std::fs::remove_file(&file_path).ok(); - } - } - } else { - let custom_client_staging_dir = if crate::is_custom_client() { - let custom_client_staging_dir = - crate::platform::get_custom_client_staging_dir(); - if let Err(e) = crate::platform::handle_custom_client_staging_dir_before_update( - &custom_client_staging_dir, - ) { - log::error!( - "Failed to handle custom client staging dir before update: {}", - e - ); - std::fs::remove_file(&file_path).ok(); - return; - } - Some(custom_client_staging_dir) - } else { - // Clean up any residual staging directory from previous custom client - let staging_dir = crate::platform::get_custom_client_staging_dir(); - hbb_common::allow_err!(crate::platform::remove_custom_client_staging_dir( - &staging_dir - )); - None - }; - let update_launched = match crate::platform::launch_privileged_process( - session_id, - &format!("{} --update", p), - ) { - Ok(h) => { - if h.is_null() { - log::error!("Failed to update to the new version: {}", version); - false - } else { - log::debug!("New version \"{}\" is launched.", version); - true - } - } - Err(e) => { - log::error!("Failed to run the new version: {}", e); - false - } - }; - if !update_launched { - if let Some(dir) = custom_client_staging_dir { - hbb_common::allow_err!(crate::platform::remove_custom_client_staging_dir( - &dir - )); - } - std::fs::remove_file(&file_path).ok(); - } - } - } else { - log::error!( - "Failed to get the current process session id, Error {}", - std::io::Error::last_os_error() - ); - std::fs::remove_file(&file_path).ok(); - } - } else { - // unreachable!() - log::error!( - "Failed to convert the file path to string: {}", - file_path.display() - ); - } -} - -pub fn get_download_file_from_url(url: &str) -> Option { - let filename = url.split('/').last()?; - Some(std::env::temp_dir().join(filename)) -} diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs deleted file mode 100644 index f0645e4bf..000000000 --- a/src/virtual_display_manager.rs +++ /dev/null @@ -1,925 +0,0 @@ -use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType}; - -// This string is defined here. -// https://github.com/rustdesk-org/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 -pub const RUSTDESK_IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; -pub const AMYUNI_IDD_DEVICE_STRING: &'static str = "USB Mobile Monitor Virtual Display\0"; - -const IDD_IMPL: &str = IDD_IMPL_AMYUNI; -const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd"; -const IDD_IMPL_AMYUNI: &str = "amyuni_idd"; -const IDD_PLUG_OUT_ALL_INDEX: i32 = -1; - -pub fn is_amyuni_idd() -> bool { - IDD_IMPL == IDD_IMPL_AMYUNI -} - -pub fn get_cur_device_string() -> &'static str { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => RUSTDESK_IDD_DEVICE_STRING, - IDD_IMPL_AMYUNI => AMYUNI_IDD_DEVICE_STRING, - _ => "", - } -} - -pub fn is_virtual_display_supported() -> bool { - #[cfg(target_os = "windows")] - { - is_windows_version_or_greater(10, 0, 19041, 0, 0) - } - #[cfg(not(target_os = "windows"))] - { - false - } -} - -pub fn plug_in_headless() -> ResultType<()> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_headless(), - IDD_IMPL_AMYUNI => amyuni_idd::plug_in_headless(), - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub fn get_platform_additions() -> serde_json::Map { - let mut map = serde_json::Map::new(); - if !crate::platform::windows::is_self_service_running() { - return map; - } - map.insert("idd_impl".into(), serde_json::json!(IDD_IMPL)); - match IDD_IMPL { - IDD_IMPL_RUSTDESK => { - let virtual_displays = rustdesk_idd::get_virtual_displays(); - if !virtual_displays.is_empty() { - map.insert( - "rustdesk_virtual_displays".into(), - serde_json::json!(virtual_displays), - ); - } - } - IDD_IMPL_AMYUNI => { - let c = amyuni_idd::get_monitor_count(); - if c > 0 { - map.insert("amyuni_virtual_displays".into(), serde_json::json!(c)); - } - } - _ => {} - } - map -} - -#[inline] -pub fn plug_in_monitor(idx: u32, modes: Vec) -> ResultType<()> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_index_modes(idx, modes), - IDD_IMPL_AMYUNI => amyuni_idd::plug_in_monitor(), - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub fn plug_out_monitor(index: i32, force_all: bool, force_one: bool) -> ResultType<()> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => { - let indices = if index == IDD_PLUG_OUT_ALL_INDEX { - rustdesk_idd::get_virtual_displays() - } else { - vec![index as _] - }; - rustdesk_idd::plug_out_peer_request(&indices) - } - IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index, force_all, force_one), - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub fn plug_in_peer_request(modes: Vec>) -> ResultType> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_peer_request(modes), - IDD_IMPL_AMYUNI => { - amyuni_idd::plug_in_monitor()?; - Ok(vec![0]) - } - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub fn plug_out_monitor_indices( - indices: &[u32], - force_all: bool, - force_one: bool, -) -> ResultType<()> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices), - IDD_IMPL_AMYUNI => { - for _idx in indices.iter() { - amyuni_idd::plug_out_monitor(0, force_all, force_one)?; - } - Ok(()) - } - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub fn reset_all() -> ResultType<()> { - match IDD_IMPL { - IDD_IMPL_RUSTDESK => rustdesk_idd::reset_all(), - IDD_IMPL_AMYUNI => amyuni_idd::reset_all(), - _ => bail!("Unsupported virtual display implementation."), - } -} - -pub mod rustdesk_idd { - use super::windows; - use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; - use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, Mutex}, - }; - - // virtual display index range: 0 - 2 are reserved for headless and other special uses. - const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; - const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; - const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; - - lazy_static::lazy_static! { - static ref VIRTUAL_DISPLAY_MANAGER: Arc> = - Arc::new(Mutex::new(VirtualDisplayManager::default())); - } - - #[derive(Default)] - struct VirtualDisplayManager { - headless_index_name: Option<(u32, String)>, - peer_index_name: HashMap, - is_driver_installed: bool, - } - - impl VirtualDisplayManager { - fn prepare_driver(&mut self) -> ResultType<()> { - if !self.is_driver_installed { - self.install_update_driver()?; - } - Ok(()) - } - - fn install_update_driver(&mut self) -> ResultType<()> { - if let Err(e) = virtual_display::create_device() { - if !e.to_string().contains("Device is already created") { - bail!("Create device failed {}", e); - } - } - // Reboot is not required for this case. - let mut _reboot_required = false; - virtual_display::install_update_driver(&mut _reboot_required)?; - self.is_driver_installed = true; - Ok(()) - } - - fn plug_in_monitor(index: u32, modes: &[virtual_display::MonitorMode]) -> ResultType<()> { - if let Err(e) = virtual_display::plug_in_monitor(index) { - bail!("Plug in monitor failed {}", e); - } - if let Err(e) = virtual_display::update_monitor_modes(index, &modes) { - log::error!("Update monitor modes failed {}", e); - } - Ok(()) - } - } - - pub fn install_update_driver() -> ResultType<()> { - VIRTUAL_DISPLAY_MANAGER - .lock() - .unwrap() - .install_update_driver() - } - - #[inline] - fn get_device_names() -> Vec { - windows::get_device_names(Some(super::RUSTDESK_IDD_DEVICE_STRING)) - } - - pub fn plug_in_headless() -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; - let modes = [virtual_display::MonitorMode { - width: 1920, - height: 1080, - sync: 60, - }]; - let device_names = get_device_names().into_iter().collect(); - VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?; - let device_name = get_new_device_name(&device_names); - manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name)); - Ok(()) - } - - pub fn plug_out_headless() -> bool { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((index, _)) = manager.headless_index_name.take() { - if let Err(e) = virtual_display::plug_out_monitor(index) { - log::error!("Plug out monitor failed {}", e); - } - true - } else { - false - } - } - - fn get_new_device_name(device_names: &HashSet) -> String { - for _ in 0..3 { - let device_names_af: HashSet = get_device_names().into_iter().collect(); - let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); - if diff_names.len() == 1 { - return diff_names[0].clone(); - } else if diff_names.len() > 1 { - log::error!( - "Failed to get diff device names after plugin virtual display, more than one diff names: {:?}", - &diff_names - ); - return "".to_string(); - } - // Sleep is needed here to wait for the virtual display to be ready. - std::thread::sleep(std::time::Duration::from_millis(50)); - } - log::error!("Failed to get diff device names after plugin virtual display",); - "".to_string() - } - - pub fn get_virtual_displays() -> Vec { - VIRTUAL_DISPLAY_MANAGER - .lock() - .unwrap() - .peer_index_name - .keys() - .cloned() - .collect() - } - - pub fn plug_in_index_modes( - idx: u32, - mut modes: Vec, - ) -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; - if !manager.peer_index_name.contains_key(&idx) { - let device_names = get_device_names().into_iter().collect(); - if modes.is_empty() { - modes.push(virtual_display::MonitorMode { - width: 1920, - height: 1080, - sync: 60, - }); - } - match VirtualDisplayManager::plug_in_monitor(idx, modes.as_slice()) { - Ok(_) => { - let device_name = get_new_device_name(&device_names); - manager.peer_index_name.insert(idx, device_name); - } - Err(e) => { - log::error!("Plug in monitor failed {}", e); - } - } - } - Ok(()) - } - - pub fn reset_all() -> ResultType<()> { - if super::is_virtual_display_supported() { - return Ok(()); - } - - if let Err(e) = plug_out_peer_request(&get_virtual_displays()) { - log::error!("Failed to plug out virtual displays: {}", e); - } - let _ = plug_out_headless(); - Ok(()) - } - - pub fn plug_in_peer_request( - modes: Vec>, - ) -> ResultType> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - manager.prepare_driver()?; - - let mut indices: Vec = Vec::new(); - for m in modes.iter() { - for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { - if !manager.peer_index_name.contains_key(&idx) { - let device_names = get_device_names().into_iter().collect(); - match VirtualDisplayManager::plug_in_monitor(idx, m) { - Ok(_) => { - let device_name = get_new_device_name(&device_names); - manager.peer_index_name.insert(idx, device_name); - indices.push(idx); - } - Err(e) => { - log::error!("Plug in monitor failed {}", e); - } - } - break; - } - } - } - - Ok(indices) - } - - pub fn plug_out_peer_request(indices: &[u32]) -> ResultType<()> { - let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - for idx in indices.iter() { - if manager.peer_index_name.contains_key(idx) { - allow_err!(virtual_display::plug_out_monitor(*idx)); - manager.peer_index_name.remove(idx); - } - } - Ok(()) - } - - pub fn is_virtual_display(name: &str) -> bool { - let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((_, device_name)) = &lock.headless_index_name { - if windows::is_device_name(device_name, name) { - return true; - } - } - for (_, v) in lock.peer_index_name.iter() { - if windows::is_device_name(v, name) { - return true; - } - } - false - } - - fn change_resolution(index: u32, w: u32, h: u32) -> bool { - let modes = [virtual_display::MonitorMode { - width: w, - height: h, - sync: 60, - }]; - match virtual_display::update_monitor_modes(index, &modes) { - Ok(_) => true, - Err(e) => { - log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e); - false - } - } - } - - pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option { - let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); - if let Some((index, device_name)) = &lock.headless_index_name { - if windows::is_device_name(device_name, name) { - return Some(change_resolution(*index, w, h)); - } - } - - for (k, v) in lock.peer_index_name.iter() { - if windows::is_device_name(v, name) { - return Some(change_resolution(*k, w, h)); - } - } - None - } -} - -pub mod amyuni_idd { - use super::windows; - use crate::platform::{reg_display_settings, win_device}; - use hbb_common::{bail, lazy_static, log, tokio::time::Instant, ResultType}; - use std::{ - ptr::null_mut, - sync::{atomic, Arc, Mutex}, - time::Duration, - }; - use winapi::{ - shared::{guiddef::GUID, winerror::ERROR_NO_MORE_ITEMS}, - um::shellapi::ShellExecuteA, - }; - - const INF_PATH: &str = r#"usbmmidd_v2\usbmmIdd.inf"#; - const INTERFACE_GUID: GUID = GUID { - Data1: 0xb5ffd75f, - Data2: 0xda40, - Data3: 0x4353, - Data4: [0x8f, 0xf8, 0xb6, 0xda, 0xf6, 0xf1, 0xd8, 0xca], - }; - const HARDWARE_ID: &str = "usbmmidd"; - const PLUG_MONITOR_IO_CONTROL_CDOE: u32 = 2307084; - const INSTALLER_EXE_FILE: &str = "deviceinstaller64.exe"; - - lazy_static::lazy_static! { - static ref LOCK: Arc> = Default::default(); - static ref LAST_PLUG_IN_HEADLESS_TIME: Arc>> = Arc::new(Mutex::new(None)); - } - const VIRTUAL_DISPLAY_MAX_COUNT: usize = 4; - // The count of virtual displays plugged in. - // This count is not accurate, because: - // 1. The virtual display driver may also be controlled by other processes. - // 2. RustDesk may crash and restart, but the virtual displays are kept. - // - // to-do: Maybe a better way is to add an option asking the user if plug out all virtual displays on disconnect. - static VIRTUAL_DISPLAY_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0); - - fn get_deviceinstaller64_work_dir() -> ResultType>> { - let cur_exe = std::env::current_exe()?; - let Some(cur_dir) = cur_exe.parent() else { - bail!("Cannot get parent of current exe file."); - }; - let work_dir = cur_dir.join("usbmmidd_v2"); - if !work_dir.exists() { - return Ok(None); - } - let exe_path = work_dir.join(INSTALLER_EXE_FILE); - if !exe_path.exists() { - return Ok(None); - } - - let Some(work_dir) = work_dir.to_str() else { - bail!("Cannot convert work_dir to string."); - }; - let mut work_dir2 = work_dir.as_bytes().to_vec(); - work_dir2.push(0); - Ok(Some(work_dir2)) - } - - pub fn uninstall_driver() -> ResultType<()> { - if let Ok(Some(work_dir)) = get_deviceinstaller64_work_dir() { - if crate::platform::windows::is_x64() { - log::info!("Uninstalling driver by deviceinstaller64.exe"); - install_if_x86_on_x64(&work_dir, "remove usbmmidd")?; - // Sleep some time to wait for the driver to be uninstalled. - std::thread::sleep(Duration::from_secs(2)); - return Ok(()); - } - } - - log::info!("Uninstalling driver by SetupAPI"); - let mut reboot_required = false; - let _ = unsafe { win_device::uninstall_driver(HARDWARE_ID, &mut reboot_required)? }; - Ok(()) - } - - // SetupDiCallClassInstaller() will always fail if current_exe() is built as x86 and running on x64. - // So we need to call another x64 version exe to install and uninstall the driver. - fn install_if_x86_on_x64(work_dir: &[u8], args: &str) -> ResultType<()> { - const SW_HIDE: i32 = 0; - let mut args = args.bytes().collect::>(); - args.push(0); - let mut exe_file = INSTALLER_EXE_FILE.bytes().collect::>(); - exe_file.push(0); - let hi = unsafe { - ShellExecuteA( - null_mut(), - "open\0".as_ptr() as _, - exe_file.as_ptr() as _, - args.as_ptr() as _, - work_dir.as_ptr() as _, - SW_HIDE, - ) as i32 - }; - if hi <= 32 { - log::error!("Failed to run deviceinstaller: {}", hi); - bail!("Failed to run deviceinstaller.") - } - Ok(()) - } - - // If the driver is installed by "deviceinstaller64.exe", the driver will be installed asynchronously. - // The caller must wait some time before using the driver. - fn check_install_driver(is_async: &mut bool) -> ResultType<()> { - let _l = LOCK.lock().unwrap(); - let drivers = windows::get_display_drivers(); - if drivers - .iter() - .any(|(s, c)| s == super::AMYUNI_IDD_DEVICE_STRING && *c == 0) - { - *is_async = false; - return Ok(()); - } - - if let Ok(Some(work_dir)) = get_deviceinstaller64_work_dir() { - if crate::platform::windows::is_x64() { - log::info!("Installing driver by deviceinstaller64.exe"); - install_if_x86_on_x64(&work_dir, "install usbmmidd.inf usbmmidd")?; - *is_async = true; - return Ok(()); - } - } - - let exe_file = std::env::current_exe()?; - let Some(cur_dir) = exe_file.parent() else { - bail!("Cannot get parent of current exe file"); - }; - let inf_path = cur_dir.join(INF_PATH); - if !inf_path.exists() { - bail!("Driver inf file not found."); - } - let inf_path = inf_path.to_string_lossy().to_string(); - - log::info!("Installing driver by SetupAPI"); - let mut reboot_required = false; - let _ = - unsafe { win_device::install_driver(&inf_path, HARDWARE_ID, &mut reboot_required)? }; - *is_async = false; - Ok(()) - } - - pub fn reset_all() -> ResultType<()> { - let _ = crate::privacy_mode::turn_off_privacy(0, None); - let _ = plug_out_monitor(super::IDD_PLUG_OUT_ALL_INDEX, true, false); - *LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap() = None; - Ok(()) - } - - #[inline] - fn plug_monitor_( - add: bool, - wait_timeout: Option, - ) -> Result<(), win_device::DeviceError> { - let cmd = if add { 0x10 } else { 0x00 }; - let cmd = [cmd, 0x00, 0x00, 0x00]; - let now = Instant::now(); - let c1 = get_monitor_count(); - unsafe { - win_device::device_io_control(&INTERFACE_GUID, PLUG_MONITOR_IO_CONTROL_CDOE, &cmd, 0)?; - } - if let Some(wait_timeout) = wait_timeout { - while now.elapsed() < wait_timeout { - if get_monitor_count() != c1 { - break; - } - std::thread::sleep(Duration::from_millis(30)); - } - } - // No need to consider concurrency here. - if add { - // If the monitor is plugged in, increase the count. - // Though there's already a check of `VIRTUAL_DISPLAY_MAX_COUNT`, it's still better to check here for double ensure. - if VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::SeqCst) < VIRTUAL_DISPLAY_MAX_COUNT { - VIRTUAL_DISPLAY_COUNT.fetch_add(1, atomic::Ordering::SeqCst); - } - } else { - if VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::SeqCst) > 0 { - VIRTUAL_DISPLAY_COUNT.fetch_sub(1, atomic::Ordering::SeqCst); - } - } - Ok(()) - } - - // `std::thread::sleep()` with a timeout is acceptable here. - // Because user can wait for a while to plug in a monitor. - fn plug_in_monitor_( - add: bool, - is_driver_async_installed: bool, - wait_timeout: Option, - ) -> ResultType<()> { - let timeout = Duration::from_secs(3); - let now = Instant::now(); - let reg_connectivity_old = reg_display_settings::read_reg_connectivity(); - loop { - match plug_monitor_(add, wait_timeout) { - Ok(_) => { - break; - } - Err(e) => { - if is_driver_async_installed { - if let win_device::DeviceError::WinApiLastErr(_, e2) = &e { - if e2.raw_os_error() == Some(ERROR_NO_MORE_ITEMS as _) { - if now.elapsed() < timeout { - std::thread::sleep(Duration::from_millis(100)); - continue; - } - } - } - } - return Err(e.into()); - } - } - } - // Workaround for the issue that we can't set the default the resolution. - if let Ok(old_connectivity_old) = reg_connectivity_old { - std::thread::spawn(move || { - try_reset_resolution_on_first_plug_in(old_connectivity_old.len(), 1920, 1080); - }); - } - - Ok(()) - } - - fn try_reset_resolution_on_first_plug_in( - old_connectivity_len: usize, - width: usize, - height: usize, - ) { - for _ in 0..10 { - std::thread::sleep(Duration::from_millis(300)); - if let Ok(reg_connectivity_new) = reg_display_settings::read_reg_connectivity() { - if reg_connectivity_new.len() != old_connectivity_len { - for name in - windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)).iter() - { - crate::platform::change_resolution(&name, width, height).ok(); - } - break; - } - } - } - } - - pub fn plug_in_headless() -> ResultType<()> { - let mut tm = LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap(); - if let Some(tm) = &mut *tm { - if tm.elapsed() < Duration::from_secs(3) { - bail!("Plugging in too frequently."); - } - } - *tm = Some(Instant::now()); - drop(tm); - - let mut is_async = false; - if let Err(e) = check_install_driver(&mut is_async) { - log::error!("Failed to install driver: {}", e); - bail!("Failed to install driver."); - } - - plug_in_monitor_(true, is_async, Some(Duration::from_millis(3_000))) - } - - pub fn plug_in_monitor() -> ResultType<()> { - let mut is_async = false; - if let Err(e) = check_install_driver(&mut is_async) { - log::error!("Failed to install driver: {}", e); - bail!("Failed to install driver."); - } - - if get_monitor_count() == VIRTUAL_DISPLAY_MAX_COUNT { - bail!("There are already {VIRTUAL_DISPLAY_MAX_COUNT} monitors plugged in."); - } - - plug_in_monitor_(true, is_async, None) - } - - // `index` the display index to plug out. -1 means plug out all. - // `force_all` is used to forcibly plug out all virtual displays. - // `force_one` is used to forcibly plug out one virtual display managed by other processes - // if there're no virtual displays managed by RustDesk. - pub fn plug_out_monitor(index: i32, force_all: bool, force_one: bool) -> ResultType<()> { - let plug_out_all = index == super::IDD_PLUG_OUT_ALL_INDEX; - // If `plug_out_all and force_all` is true, forcibly plug out all virtual displays. - // Though the driver may be controlled by other processes, - // we still forcibly plug out all virtual displays. - // - // 1. RustDesk plug in 2 virtual displays. (RustDesk) - // 2. Other process plug out all virtual displays. (User manually) - // 3. Other process plug in 1 virtual display. (User manually) - // 4. RustDesk plug out all virtual displays in this call. (RustDesk disconnect) - // - // This is not a normal scenario, RustDesk will plug out virtual display unexpectedly. - let mut plug_in_count = VIRTUAL_DISPLAY_COUNT.load(atomic::Ordering::Relaxed); - let amyuni_count = get_monitor_count(); - if !plug_out_all { - if plug_in_count == 0 && amyuni_count > 0 { - if force_one { - plug_in_count = 1; - } else { - bail!("The virtual display is managed by other processes."); - } - } - } else { - // Ignore the message if trying to plug out all virtual displays. - } - - let all_count = windows::get_device_names(None).len(); - let mut to_plug_out_count = match all_count { - 0 => return Ok(()), - 1 => { - if plug_in_count == 0 { - bail!("No virtual displays to plug out.") - } else { - if force_all { - 1 - } else { - bail!("This only virtual display cannot be plugged out.") - } - } - } - _ => { - if all_count == plug_in_count { - if force_all { - all_count - } else { - all_count - 1 - } - } else { - plug_in_count - } - } - }; - if to_plug_out_count != 0 && !plug_out_all { - to_plug_out_count = 1; - } - - for _i in 0..to_plug_out_count { - let _ = plug_monitor_(false, None); - } - Ok(()) - } - - #[inline] - pub fn get_monitor_count() -> usize { - windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)).len() - } - - #[inline] - pub fn is_my_display(name: &str) -> bool { - windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)) - .iter() - .any(|s| windows::is_device_name(s, name)) - } -} - -mod windows { - use std::ptr::null_mut; - use winapi::{ - shared::{ - devguid::GUID_DEVCLASS_DISPLAY, - minwindef::{DWORD, FALSE}, - ntdef::ULONG, - }, - um::{ - cfgmgr32::{CM_Get_DevNode_Status, CR_SUCCESS}, - cguid::GUID_NULL, - setupapi::{ - SetupDiEnumDeviceInfo, SetupDiGetClassDevsW, SetupDiGetDeviceRegistryPropertyW, - SP_DEVINFO_DATA, - }, - wingdi::{ - DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER, - }, - winnt::HANDLE, - winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS}, - }, - }; - - const DIGCF_PRESENT: DWORD = 0x00000002; - const SPDRP_DEVICEDESC: DWORD = 0x00000000; - const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; - - #[inline] - pub(super) fn is_device_name(device_name: &str, name: &str) -> bool { - if name.len() == device_name.len() { - name == device_name - } else if name.len() > device_name.len() { - false - } else { - &device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0 - } - } - - pub(super) fn get_device_names(device_string: Option<&str>) -> Vec { - let mut device_names = Vec::new(); - let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; - dd.cb = std::mem::size_of::() as DWORD; - let mut i_dev_num = 0; - loop { - let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) }; - if result == 0 { - break; - } - i_dev_num += 1; - - if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) - || (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0 - { - continue; - } - - let mut dm: DEVMODEW = unsafe { std::mem::zeroed() }; - dm.dmSize = std::mem::size_of::() as _; - dm.dmDriverExtra = 0; - let ok = unsafe { - EnumDisplaySettingsExW( - dd.DeviceName.as_ptr(), - ENUM_CURRENT_SETTINGS, - &mut dm as _, - 0, - ) - }; - if ok == FALSE { - continue; - } - if dm.dmPelsHeight == 0 || dm.dmPelsWidth == 0 { - continue; - } - - if let (Ok(device_name), Ok(ds)) = ( - String::from_utf16(&dd.DeviceName), - String::from_utf16(&dd.DeviceString), - ) { - if let Some(s) = device_string { - if ds.len() >= s.len() && &ds[..s.len()] == s { - device_names.push(device_name); - } - } else { - device_names.push(device_name); - } - } - } - device_names - } - - pub(super) fn get_display_drivers() -> Vec<(String, u32)> { - let mut display_drivers: Vec<(String, u32)> = Vec::new(); - - let device_info_set = unsafe { - SetupDiGetClassDevsW( - &GUID_DEVCLASS_DISPLAY, - null_mut(), - null_mut(), - DIGCF_PRESENT, - ) - }; - - if device_info_set == INVALID_HANDLE_VALUE { - println!( - "Failed to get device information set. Error: {}", - std::io::Error::last_os_error() - ); - return display_drivers; - } - - let mut device_info_data = SP_DEVINFO_DATA { - cbSize: std::mem::size_of::() as u32, - ClassGuid: GUID_NULL, - DevInst: 0, - Reserved: 0, - }; - - let mut device_index = 0; - loop { - let result = unsafe { - SetupDiEnumDeviceInfo(device_info_set, device_index, &mut device_info_data) - }; - if result == 0 { - break; - } - - let mut data_type: DWORD = 0; - let mut required_size: DWORD = 0; - - // Get the required buffer size for the driver description - let mut buffer; - unsafe { - SetupDiGetDeviceRegistryPropertyW( - device_info_set, - &mut device_info_data, - SPDRP_DEVICEDESC, - &mut data_type, - null_mut(), - 0, - &mut required_size, - ); - - buffer = vec![0; required_size as usize / 2]; - SetupDiGetDeviceRegistryPropertyW( - device_info_set, - &mut device_info_data, - SPDRP_DEVICEDESC, - &mut data_type, - buffer.as_mut_ptr() as *mut u8, - required_size, - null_mut(), - ); - } - - let Ok(driver_description) = String::from_utf16(&buffer) else { - println!("Failed to convert driver description to string"); - device_index += 1; - continue; - }; - - let mut status: ULONG = 0; - let mut problem_number: ULONG = 0; - // Get the device status and problem number - let config_ret = unsafe { - CM_Get_DevNode_Status( - &mut status, - &mut problem_number, - device_info_data.DevInst, - 0, - ) - }; - if config_ret != CR_SUCCESS { - println!( - "Failed to get device status. Error: {}", - std::io::Error::last_os_error() - ); - device_index += 1; - continue; - } - display_drivers.push((driver_description, problem_number)); - device_index += 1; - } - - display_drivers - } -} diff --git a/src/whiteboard/client.rs b/src/whiteboard/client.rs deleted file mode 100644 index 0d816ba27..000000000 --- a/src/whiteboard/client.rs +++ /dev/null @@ -1,258 +0,0 @@ -use super::{Cursor, CustomEvent}; -use crate::{ - ipc::{self, Data}, - CHILD_PROCESS, -}; -use hbb_common::{ - allow_err, - anyhow::anyhow, - bail, log, sleep, - tokio::{ - self, - sync::mpsc::{unbounded_channel, UnboundedSender}, - time::interval_at, - }, - ResultType, -}; -use lazy_static::lazy_static; -use std::{collections::HashMap, sync::RwLock, time::Instant}; - -lazy_static! { - static ref TX_WHITEBOARD: RwLock>> = - RwLock::new(None); - static ref CONNS: RwLock> = Default::default(); -} - -struct Conn { - last_cursor_pos: (f32, f32), // For click ripple - last_cursor_evt: LastCursorEvent, -} - -struct LastCursorEvent { - evt: Option, - tm: Instant, - c: usize, -} - -#[inline] -pub fn get_key_cursor(conn_id: i32) -> String { - format!("{}-cursor", conn_id) -} - -pub fn register_whiteboard(k: String) { - std::thread::spawn(|| { - allow_err!(start_whiteboard_()); - }); - let mut conns = CONNS.write().unwrap(); - if !conns.contains_key(&k) { - conns.insert( - k, - Conn { - last_cursor_pos: (0.0, 0.0), - last_cursor_evt: LastCursorEvent { - evt: None, - tm: Instant::now(), - c: 0, - }, - }, - ); - } -} - -pub fn unregister_whiteboard(k: String) { - let mut conns = CONNS.write().unwrap(); - conns.remove(&k); - let is_conns_empty = conns.is_empty(); - drop(conns); - - TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| { - allow_err!(tx.send((k, CustomEvent::Clear))); - }); - if is_conns_empty { - std::thread::spawn(|| { - let mut whiteboard = TX_WHITEBOARD.write().unwrap(); - whiteboard.as_ref().map(|tx| { - allow_err!(tx.send(("".to_string(), CustomEvent::Exit))); - // Simple sleep to wait the whiteboard process exiting. - std::thread::sleep(std::time::Duration::from_millis(3_00)); - }); - whiteboard.take(); - }); - } -} - -pub fn update_whiteboard(k: String, e: CustomEvent) { - let mut conns = CONNS.write().unwrap(); - let Some(conn) = conns.get_mut(&k) else { - return; - }; - match &e { - CustomEvent::Cursor(cursor) => { - conn.last_cursor_evt.c += 1; - conn.last_cursor_evt.tm = Instant::now(); - if cursor.btns == 0 { - // Send one movement event every 4. - if conn.last_cursor_evt.c > 3 { - conn.last_cursor_evt.c = 0; - conn.last_cursor_evt.evt = None; - tx_send_event(conn, k, e); - } else { - conn.last_cursor_evt.evt = Some(e); - } - } else { - if let Some(evt) = conn.last_cursor_evt.evt.take() { - tx_send_event(conn, k.clone(), evt); - conn.last_cursor_evt.c = 0; - } - let click_evt = CustomEvent::Cursor(Cursor { - x: conn.last_cursor_pos.0, - y: conn.last_cursor_pos.1, - argb: cursor.argb, - btns: cursor.btns, - text: cursor.text.clone(), - }); - tx_send_event(conn, k, click_evt); - } - } - _ => { - tx_send_event(conn, k, e); - } - } -} - -#[inline] -fn tx_send_event(conn: &mut Conn, k: String, event: CustomEvent) { - if let CustomEvent::Cursor(cursor) = &event { - if cursor.btns == 0 { - conn.last_cursor_pos = (cursor.x, cursor.y); - } - } - - TX_WHITEBOARD.read().unwrap().as_ref().map(|tx| { - allow_err!(tx.send((k, event))); - }); -} - -#[tokio::main(flavor = "current_thread")] -async fn start_whiteboard_() -> ResultType<()> { - let mut tx_whiteboard = TX_WHITEBOARD.write().unwrap(); - if tx_whiteboard.is_some() { - log::warn!("Whiteboard already started"); - return Ok(()); - } - - loop { - if !crate::platform::is_prelogin() { - break; - } - sleep(1.).await; - } - let mut stream = None; - if let Ok(s) = ipc::connect(1000, "_whiteboard").await { - stream = Some(s); - } else { - #[allow(unused_mut)] - #[allow(unused_assignments)] - let mut args = vec!["--whiteboard"]; - #[allow(unused_mut)] - #[cfg(target_os = "linux")] - let mut user = None; - - let run_done; - if crate::platform::is_root() { - let mut res = Ok(None); - for _ in 0..10 { - #[cfg(not(any(target_os = "linux")))] - { - log::debug!("Start whiteboard"); - res = crate::platform::run_as_user(args.clone()); - } - #[cfg(target_os = "linux")] - { - log::debug!("Start whiteboard"); - res = crate::platform::run_as_user( - args.clone(), - user.clone(), - None::<(&str, &str)>, - ); - } - if res.is_ok() { - break; - } - log::error!("Failed to run whiteboard: {res:?}"); - sleep(1.).await; - } - if let Some(task) = res? { - CHILD_PROCESS.lock().unwrap().push(task); - } - run_done = true; - } else { - run_done = false; - } - if !run_done { - log::debug!("Start whiteboard"); - CHILD_PROCESS.lock().unwrap().push(crate::run_me(args)?); - } - for _ in 0..20 { - sleep(0.3).await; - if let Ok(s) = ipc::connect(1000, "_whiteboard").await { - stream = Some(s); - break; - } - } - if stream.is_none() { - bail!("Failed to connect to connection manager"); - } - } - - let mut stream = stream.ok_or(anyhow!("none stream"))?; - let (tx, mut rx) = unbounded_channel(); - tx_whiteboard.replace(tx); - drop(tx_whiteboard); - let _call_on_ret = crate::common::SimpleCallOnReturn { - b: true, - f: Box::new(move || { - let _ = TX_WHITEBOARD.write().unwrap().take(); - }), - }; - - let dur = tokio::time::Duration::from_millis(300); - let mut timer = interval_at(tokio::time::Instant::now() + dur, dur); - timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { - tokio::select! { - res = rx.recv() => { - match res { - Some(data) => { - if matches!(data.1, CustomEvent::Exit) { - break; - } else { - allow_err!(stream.send(&Data::Whiteboard(data)).await); - timer.reset(); - } - } - None => { - bail!("expected"); - } - } - }, - _ = timer.tick() => { - let mut conns = CONNS.write().unwrap(); - for (k, conn) in conns.iter_mut() { - if conn.last_cursor_evt.tm.elapsed().as_millis() > 300 { - if let Some(evt) = conn.last_cursor_evt.evt.take() { - allow_err!(stream.send(&Data::Whiteboard((k.clone(), evt))).await); - conn.last_cursor_evt.c = 0; - } - } - } - } - } - } - allow_err!( - stream - .send(&Data::Whiteboard(("".to_string(), CustomEvent::Exit))) - .await - ); - Ok(()) -} diff --git a/src/whiteboard/linux.rs b/src/whiteboard/linux.rs deleted file mode 100644 index 686a0d0b1..000000000 --- a/src/whiteboard/linux.rs +++ /dev/null @@ -1,463 +0,0 @@ -use super::{ - server::{Ripple, EVENT_PROXY}, - win_linux::{create_font_face, draw_text}, - Cursor, CustomEvent, -}; -use hbb_common::{bail, log, tokio::sync::mpsc::unbounded_channel, ResultType}; -use softbuffer::{Context, Surface}; -use std::{ - collections::HashMap, - ffi::{c_int, c_short, c_ulong, c_ushort}, - num::NonZeroU32, - sync::Arc, - time::Instant, -}; -use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Stroke, Transform}; -use ttf_parser::Face; -use winit::raw_window_handle::{ - DisplayHandle, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, -}; -use winit::{ - application::ApplicationHandler, - dpi::{PhysicalPosition, PhysicalSize}, - event::WindowEvent, - event_loop::{ActiveEventLoop, EventLoop}, - platform::x11::{WindowAttributesExtX11, WindowType}, - window::{Window, WindowId, WindowLevel}, -}; - -enum _XDisplay {} -type Display = _XDisplay; - -type XID = c_ulong; -type XserverRegion = XID; - -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(C)] -pub struct XRectangle { - pub x: c_short, - pub y: c_short, - pub width: c_ushort, - pub height: c_ushort, -} - -#[link(name = "Xfixes")] -extern "C" { - fn XFixesCreateRegion( - dpy: *mut Display, - rectangles: *mut XRectangle, - nrectangles: c_int, - ) -> XserverRegion; - fn XFixesDestroyRegion(dpy: *mut Display, region: XserverRegion) -> (); - fn XFixesSetWindowShapeRegion( - dpy: *mut Display, - win: XID, - shape_kind: c_int, - x_off: c_int, - y_off: c_int, - region: XserverRegion, - ) -> (); -} - -const SHAPE_INPUT: std::ffi::c_int = 2; - -fn get_display_from_xwayland() -> Option { - if let Ok(output) = crate::platform::run_cmds("pgrep -a Xwayland") { - // 1410 /usr/bin/Xwayland :1 -auth /run/user/1000/xauth_RoDZey -listenfd 8 -listenfd 9 -displayfd 76 -wm 78 -rootless -enable-ei-portal - if output.contains("Xwayland") { - if let Some(display) = output.split_whitespace().nth(2) { - if display.starts_with(':') { - return Some(display.to_string()); - } - } - } - } - None -} - -fn preset_env() -> bool { - if crate::platform::is_x11() { - return true; - } - if let Some(display) = get_display_from_xwayland() { - // https://github.com/rust-windowing/winit/blob/f6893a4390dfe6118ce4b33458d458fd3efd3025/src/event_loop.rs#L99 - // It is acceptable to modify global environment variables here because this process is an isolated, - // dedicated "whiteboard" process. - std::env::set_var("DISPLAY", display); - std::env::remove_var("WAYLAND_DISPLAY"); - return true; - } - false -} - -pub fn is_supported() -> bool { - crate::platform::is_x11() || get_display_from_xwayland().is_some() -} - -pub fn run() { - if !preset_env() { - return; - } - - let event_loop = match EventLoop::<(String, CustomEvent)>::with_user_event().build() { - Ok(el) => el, - Err(e) => { - log::error!("Failed to create event loop: {}", e); - return; - } - }; - - let event_loop_proxy = event_loop.create_proxy(); - EVENT_PROXY.write().unwrap().replace(event_loop_proxy); - - let (tx_exit, rx_exit) = unbounded_channel(); - std::thread::spawn(move || { - super::server::start_ipc(rx_exit); - }); - - let mut app = match WhiteboardApplication::new(&event_loop) { - Ok(app) => app, - Err(e) => { - log::error!("Failed to create whiteboard application: {}", e); - tx_exit.send(()).ok(); - return; - } - }; - - if let Err(e) = event_loop.run_app(&mut app) { - log::error!("Failed to run app: {}", e); - tx_exit.send(()).ok(); - return; - } -} - -struct WindowState { - window: Arc, - // NOTE: This surface must be dropped before the `Window`. - surface: Surface, Arc>, - ripples: Vec, - last_cursors: HashMap, -} - -struct WhiteboardApplication { - windows: Vec, - // Drawing context. - // - // With OpenGL it could be EGLDisplay. - context: Option>>, - face: Option>, - close_requested: bool, -} - -impl WhiteboardApplication { - fn new(event_loop: &EventLoop) -> ResultType { - // https://github.com/rust-windowing/winit/blob/f6893a4390dfe6118ce4b33458d458fd3efd3025/examples/window.rs#L91 - // SAFETY: we drop the context right before the event loop is stopped, thus making it safe. - let context = match Context::new(unsafe { - std::mem::transmute::, DisplayHandle<'static>>( - event_loop.display_handle()?, - ) - }) { - Ok(ctx) => Some(ctx), - Err(e) => { - bail!("Failed to create context: {}", e); - } - }; - let face = match create_font_face() { - Ok(face) => Some(face), - Err(err) => { - log::error!("Failed to create font face: {}", err); - None - } - }; - Ok(Self { - windows: Vec::new(), - context, - face, - close_requested: false, - }) - } -} - -impl ApplicationHandler<(String, CustomEvent)> for WhiteboardApplication { - fn user_event(&mut self, _event_loop: &ActiveEventLoop, (k, evt): (String, CustomEvent)) { - match evt { - CustomEvent::Cursor(cursor) => { - if let Some(state) = self.windows.first_mut() { - if cursor.btns != 0 { - state.ripples.push(Ripple { - x: cursor.x, - y: cursor.y, - start_time: Instant::now(), - }); - } - state.last_cursors.insert(k, cursor); - state.window.request_redraw(); - } - } - CustomEvent::Exit => { - self.close_requested = true; - } - _ => {} - } - } - - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let (x, y, w, h) = match super::server::get_displays_rect() { - Ok(r) => r, - Err(err) => { - log::error!("Failed to get displays rect: {}", err); - self.close_requested = true; - return; - } - }; - - let window_attributes = Window::default_attributes() - .with_title("RustDesk whiteboard") - .with_inner_size(PhysicalSize::new(w, h)) - .with_position(PhysicalPosition::new(x, y)) - .with_decorations(false) - .with_transparent(true) - .with_window_level(WindowLevel::AlwaysOnTop) - .with_x11_window_type(vec![WindowType::Dock]) - .with_override_redirect(true); - - let window = match event_loop.create_window(window_attributes) { - Ok(window) => Arc::new(window), - Err(e) => { - log::error!("Failed to create window: {}", e); - self.close_requested = true; - return; - } - }; - - let display = match window.display_handle() { - Ok(d) => d, - Err(e) => { - log::error!("Failed to get display handle: {}", e); - self.close_requested = true; - return; - } - }; - let rwh = match window.window_handle() { - Ok(w) => w, - Err(e) => { - log::error!("Failed to get window handle: {}", e); - self.close_requested = true; - return; - } - }; - - // Both the following block and `window.set_cursor_hittest(false)` in `draw()` are necessary to ensure cursor events are properly passed through the window. - // These issues may be related to winit X11 handling. - // https://github.com/rust-windowing/winit/issues/3509 - // https://github.com/rust-windowing/winit/issues/4120 - // If either block is removed, cursor events may not be passed through as expected. - // If you update winit, please revisit this workaround. - match (rwh.as_raw(), display.as_raw()) { - (RawWindowHandle::Xlib(xlib_window), RawDisplayHandle::Xlib(xlib_display)) => { - unsafe { - let xwindow = xlib_window.window; - if let Some(display_ptr) = xlib_display.display { - let xdisplay = display_ptr.as_ptr() as *mut Display; - // Mouse event passthrough - let empty_region = XFixesCreateRegion(xdisplay, std::ptr::null_mut(), 0); - if empty_region == 0 { - log::error!("XFixesCreateRegion failed: returned null region"); - } else { - XFixesSetWindowShapeRegion( - xdisplay, - xwindow, - SHAPE_INPUT, - 0, - 0, - empty_region, - ); - XFixesDestroyRegion(xdisplay, empty_region); - } - } - } - } - _ => { - log::error!("Unsupported windowing system for shape extension"); - self.close_requested = true; - return; - } - } - - let Some(ctx) = self.context.as_ref() else { - // unreachable - self.close_requested = true; - return; - }; - - let surface = match Surface::new(ctx, window.clone()) { - Ok(s) => s, - Err(e) => { - log::error!("Failed to create surface: {}", e); - self.close_requested = true; - return; - } - }; - - let state = WindowState { - window, - surface, - ripples: Vec::new(), - last_cursors: HashMap::new(), - }; - - self.windows.push(state); - } - - fn window_event( - &mut self, - _event_loop: &ActiveEventLoop, - window_id: WindowId, - event: WindowEvent, - ) { - match event { - WindowEvent::CloseRequested => { - self.close_requested = true; - } - WindowEvent::RedrawRequested => { - let Some(state) = self.windows.iter_mut().find(|w| w.window.id() == window_id) - else { - log::error!("No window found for id: {:?}", window_id); - return; - }; - if let Err(err) = state.draw(&self.face) { - log::error!("Failed to draw window: {}", err); - } - } - _ => (), - } - } - - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - if !self.close_requested { - for state in self.windows.iter() { - state.window.request_redraw(); - } - } else { - event_loop.exit(); - } - } - - fn exiting(&mut self, _event_loop: &ActiveEventLoop) { - // We must drop the context here. - self.context = None; - } -} - -impl WindowState { - fn draw(&mut self, face: &Option>) -> ResultType<()> { - let (width, height) = { - let size = self.window.inner_size(); - (size.width, size.height) - }; - - let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height)) else { - bail!("Invalid window size, {width}x{height}") - }; - if let Err(e) = self.surface.resize(width, height) { - bail!("Failed to resize surface: {}", e); - } - - let mut buffer = match self.surface.buffer_mut() { - Ok(buf) => buf, - Err(e) => { - bail!("Failed to get buffer: {}", e); - } - }; - - let Some(mut pixmap) = PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut buffer), - width.get(), - height.get(), - ) else { - bail!("Failed to create pixmap from buffer"); - }; - pixmap.fill(Color::TRANSPARENT); - - Ripple::retain_active(&mut self.ripples); - for ripple in &self.ripples { - let (radius, alpha) = ripple.get_radius_alpha(); - - let mut ripple_paint = Paint::default(); - // Note: The real color is bgra here. - ripple_paint.set_color_rgba8(64, 64, 255, (alpha * 128.0) as u8); - ripple_paint.anti_alias = true; - - let mut ripple_pb = PathBuilder::new(); - ripple_pb.push_circle(ripple.x, ripple.y, radius); - if let Some(path) = ripple_pb.finish() { - pixmap.fill_path( - &path, - &ripple_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - } - } - - for cursor in self.last_cursors.values() { - let (x, y) = (cursor.x, cursor.y); - let size = 1.5f32; - - let mut pb = PathBuilder::new(); - pb.move_to(x, y); - pb.line_to(x, y + 16.0 * size); - pb.line_to(x + 4.0 * size, y + 13.0 * size); - pb.line_to(x + 7.0 * size, y + 20.0 * size); - pb.line_to(x + 9.0 * size, y + 19.0 * size); - pb.line_to(x + 6.0 * size, y + 12.0 * size); - pb.line_to(x + 11.0 * size, y + 12.0 * size); - pb.close(); - - if let Some(path) = pb.finish() { - let mut arrow_paint = Paint::default(); - let rgba = super::argb_to_rgba(cursor.argb); - arrow_paint.set_color_rgba8(rgba.2, rgba.1, rgba.0, rgba.3); - arrow_paint.anti_alias = true; - pixmap.fill_path( - &path, - &arrow_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - - let mut black_paint = Paint::default(); - black_paint.set_color_rgba8(0, 0, 0, 255); - black_paint.anti_alias = true; - let mut stroke = Stroke::default(); - stroke.width = 1.0f32; - pixmap.stroke_path(&path, &black_paint, &stroke, Transform::identity(), None); - - face.as_ref().map(|face| { - draw_text( - &mut pixmap, - face, - &cursor.text, - x + 24.0 * size, - y + 24.0 * size, - &arrow_paint, - 14.0f32, - ); - }); - } - } - - self.window.pre_present_notify(); - - if let Err(e) = buffer.present() { - log::error!("Failed to present buffer: {}", e); - } - - self.window.set_cursor_hittest(false).ok(); - - Ok(()) - } -} diff --git a/src/whiteboard/macos.rs b/src/whiteboard/macos.rs deleted file mode 100644 index d1c28b57c..000000000 --- a/src/whiteboard/macos.rs +++ /dev/null @@ -1,323 +0,0 @@ -use super::{server::EVENT_PROXY, Cursor, CustomEvent, Ripple}; -use core_graphics::context::CGContextRef; -use foreign_types::ForeignTypeRef; -use hbb_common::{bail, log, ResultType}; -use objc::{class, msg_send, runtime::Object, sel, sel_impl}; -use piet::{ - kurbo::{BezPath, Point}, - FontFamily, RenderContext, Text, TextLayout, TextLayoutBuilder, -}; -use piet_coregraphics::{CoreGraphicsContext, CoreGraphicsTextLayout}; -use std::{collections::HashMap, sync::Arc, time::Instant}; -use tao::{ - dpi::{LogicalSize, PhysicalPosition}, - event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopBuilder}, - platform::macos::MonitorHandleExtMacOS, - rwh_06::{HasWindowHandle, RawWindowHandle}, - window::{Window, WindowBuilder, WindowId}, -}; - -const MAXIMUM_WINDOW_LEVEL: i64 = 2147483647; -const CURSOR_TEXT_FONT_SIZE: f64 = 14.0; -const CURSOR_TEXT_OFFSET: f64 = 20.0; - -struct WindowState { - window: Arc, - logical_size: LogicalSize, - outer_position: PhysicalPosition, - // A simple workaround to the (logical) cursor position. - display_origin: (f64, f64), -} - -struct CursorInfo { - window_id: WindowId, - text_key: (String, u32), - cursor: Cursor, -} - -fn set_window_properties(window: &Arc) -> ResultType<()> { - let handle = window.window_handle()?; - if let RawWindowHandle::AppKit(appkit_handle) = handle.as_raw() { - unsafe { - let ns_view = appkit_handle.ns_view.as_ptr() as *mut Object; - if ns_view.is_null() { - bail!("Ns view of the window handle is null."); - } - let ns_window: *mut Object = msg_send![ns_view, window]; - if ns_window.is_null() { - bail!("Ns window of the ns view is null."); - } - let _: () = msg_send![ns_window, setOpaque: false]; - let _: () = msg_send![ns_window, setLevel: MAXIMUM_WINDOW_LEVEL]; - // NSWindowCollectionBehaviorCanJoinAllSpaces | NSWindowCollectionBehaviorIgnoresCycle - let _: () = msg_send![ns_window, setCollectionBehavior: 5]; - let current_style_mask: u64 = msg_send![ns_window, styleMask]; - // NSWindowStyleMaskNonactivatingPanel - let new_style_mask = current_style_mask | (1 << 7); - let _: () = msg_send![ns_window, setStyleMask: new_style_mask]; - let _: () = msg_send![ns_window, setIgnoresMouseEvents: true]; - } - } - Ok(()) -} - -fn create_windows(event_loop: &EventLoop<(String, CustomEvent)>) -> ResultType> { - let mut windows = Vec::new(); - let map_display_origins: HashMap<_, _> = crate::server::display_service::try_get_displays()? - .into_iter() - .map(|display| (display.name(), display.origin())) - .collect(); - // We can't use `crate::server::display_service::try_get_displays()` here. - // Because the `display` returned by `crate::server::display_service::try_get_displays()`: - // 1. `display.origin()` is the logic position. - // 2. `display.width()` and `display.height()` are the physical size. - for monitor in event_loop.available_monitors() { - let Some(origin) = map_display_origins.get(&monitor.native_id().to_string()) else { - // unreachable! - bail!( - "Failed to find display origin for monitor: {}", - monitor.native_id() - ); - }; - - let window_builder = WindowBuilder::new() - .with_title("RustDesk whiteboard") - .with_transparent(true) - .with_decorations(false) - .with_position(monitor.position()) - .with_inner_size(monitor.size()); - - let window = Arc::new(window_builder.build::<(String, CustomEvent)>(event_loop)?); - set_window_properties(&window)?; - - let mut scale_factor = window.scale_factor(); - if scale_factor == 0.0 { - scale_factor = 1.0; - } - let physical_size = window.inner_size(); - let logical_size = physical_size.to_logical::(scale_factor); - let inner_position = window.inner_position()?; - let outer_position = inner_position; - windows.push(WindowState { - window, - logical_size, - outer_position, - display_origin: (origin.0 as f64, origin.1 as f64), - }); - } - Ok(windows) -} - -fn draw_cursors( - windows: &Vec, - window_id: WindowId, - window_ripples: &mut HashMap>, - last_cursors: &HashMap, - map_cursor_text: &mut HashMap<(String, u32), CoreGraphicsTextLayout>, -) { - for window in windows.iter() { - if window.window.id() != window_id { - continue; - } - - if let Ok(handle) = window.window.window_handle() { - if let RawWindowHandle::AppKit(appkit_handle) = handle.as_raw() { - unsafe { - let ns_view = appkit_handle.ns_view.as_ptr() as *mut Object; - let current_context: *mut Object = - msg_send![class!(NSGraphicsContext), currentContext]; - if !current_context.is_null() { - let cg_context_ptr: *mut std::ffi::c_void = - msg_send![current_context, CGContext]; - if !cg_context_ptr.is_null() { - let cg_context_ref = - CGContextRef::from_ptr_mut(cg_context_ptr as *mut _); - let mut context = CoreGraphicsContext::new_y_up( - cg_context_ref, - window.logical_size.height, - None, - ); - context.clear(None, piet::Color::TRANSPARENT); - - if let Some(ripples) = window_ripples.get_mut(&window_id) { - Ripple::retain_active(ripples); - for ripple in ripples.iter() { - let (radius, alpha) = ripple.get_radius_alpha(); - let color = piet::Color::rgba(1.0, 0.25, 0.25, alpha * 0.5); - let circle = - piet::kurbo::Circle::new((ripple.x, ripple.y), radius); - context.stroke(circle, &color, 2.0); - } - } - - for info in last_cursors.values() { - if info.window_id != window.window.id() { - continue; - } - let cursor = &info.cursor; - - let (x, y) = (cursor.x as f64, cursor.y as f64); - let size = 1.0; - - let mut pb = BezPath::new(); - pb.move_to((x, y)); - pb.line_to((x, y + 16.0 * size)); - pb.line_to((x + 4.0 * size, y + 13.0 * size)); - pb.line_to((x + 7.0 * size, y + 20.0 * size)); - pb.line_to((x + 9.0 * size, y + 19.0 * size)); - pb.line_to((x + 6.0 * size, y + 12.0 * size)); - pb.line_to((x + 11.0 * size, y + 12.0 * size)); - - let rgba = super::argb_to_rgba(cursor.argb); - let color = piet::Color::rgba8(rgba.0, rgba.1, rgba.2, rgba.3); - context.fill(pb, &color); - - let pos = - (x + CURSOR_TEXT_OFFSET * size, y + CURSOR_TEXT_OFFSET * size); - let get_rounded_rect = |layout: &CoreGraphicsTextLayout| { - let text_pos = Point::new(pos.0, pos.1); - let padded_bounds = (layout.image_bounds() - + text_pos.to_vec2()) - .inflate(3.0, 3.0); - padded_bounds.to_rounded_rect(5.0) - }; - - if let Some(layout) = map_cursor_text.get(&info.text_key) { - context.fill(get_rounded_rect(layout), &piet::Color::WHITE); - context.draw_text(layout, pos); - } else { - let text = context.text(); - let color = piet::Color::rgba8(0, 0, 0, 255); - if let Ok(layout) = text - .new_text_layout(cursor.text.clone()) - .font(FontFamily::SYSTEM_UI, CURSOR_TEXT_FONT_SIZE) - .text_color(color) - .build() - { - context - .fill(get_rounded_rect(&layout), &piet::Color::WHITE); - context.draw_text(&layout, pos); - map_cursor_text.insert(info.text_key.clone(), layout); - } - } - } - if let Err(e) = context.finish() { - log::error!("Failed to draw cursor: {}", e); - } - } else { - log::warn!("CGContext is null"); - } - } - let _: () = msg_send![ns_view, setNeedsDisplay:true]; - } - } - } - } -} - -pub(super) fn create_event_loop() -> ResultType<()> { - crate::platform::hide_dock(); - let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build(); - - let windows = create_windows(&event_loop)?; - - let proxy = event_loop.create_proxy(); - EVENT_PROXY.write().unwrap().replace(proxy); - let _call_on_ret = crate::common::SimpleCallOnReturn { - b: true, - f: Box::new(move || { - let _ = EVENT_PROXY.write().unwrap().take(); - }), - }; - - let mut window_ripples: HashMap> = HashMap::new(); - let mut last_cursors: HashMap = HashMap::new(); - let mut map_cursor_text: HashMap<(String, u32), CoreGraphicsTextLayout> = HashMap::new(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Poll; - - match event { - Event::NewEvents(StartCause::Init) => { - for window in windows.iter() { - window.window.set_outer_position(window.outer_position); - window.window.request_redraw(); - } - crate::platform::hide_dock(); - } - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - Event::RedrawRequested(window_id) => { - draw_cursors( - &windows, - window_id, - &mut window_ripples, - &last_cursors, - &mut map_cursor_text, - ); - } - Event::MainEventsCleared => { - for window in windows.iter() { - window.window.request_redraw(); - } - } - Event::UserEvent((k, evt)) => match evt { - CustomEvent::Cursor(cursor) => { - for window in windows.iter() { - let (l, t, r, b) = ( - window.display_origin.0, - window.display_origin.1, - window.display_origin.0 + window.logical_size.width, - window.display_origin.1 + window.logical_size.height, - ); - if (cursor.x as f64) < l - || (cursor.x as f64) > r - || (cursor.y as f64) < t - || (cursor.y as f64) > b - { - continue; - } - - if cursor.btns != 0 { - let window_id = window.window.id(); - let ripple = Ripple { - x: (cursor.x as f64 - window.display_origin.0), - y: (cursor.y as f64 - window.display_origin.1), - start_time: Instant::now(), - }; - if let Some(ripples) = window_ripples.get_mut(&window_id) { - ripples.push(ripple); - } else { - window_ripples.insert(window_id, vec![ripple]); - } - } - last_cursors.insert( - k, - CursorInfo { - window_id: window.window.id(), - text_key: (cursor.text.clone(), cursor.argb), - cursor: Cursor { - x: (cursor.x - window.display_origin.0 as f32), - y: (cursor.y - window.display_origin.1 as f32), - ..cursor - }, - }, - ); - window.window.request_redraw(); - break; - } - } - CustomEvent::Exit => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - _ => (), - } - }); -} diff --git a/src/whiteboard/mod.rs b/src/whiteboard/mod.rs deleted file mode 100644 index 42befe84f..000000000 --- a/src/whiteboard/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use serde_derive::{Deserialize, Serialize}; - -mod client; -mod server; - -#[cfg(target_os = "windows")] -mod windows; -#[cfg(target_os = "linux")] -mod linux; -#[cfg(target_os = "macos")] -mod macos; -#[cfg(any(target_os = "windows", target_os = "linux"))] -mod win_linux; - -#[cfg(target_os = "windows")] -use windows::create_event_loop; -#[cfg(target_os = "macos")] -use macos::create_event_loop; -#[cfg(target_os = "linux")] -pub use linux::is_supported; - -pub use client::*; -pub use server::*; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "t", content = "c")] -pub enum CustomEvent { - Cursor(Cursor), - Clear, - Exit, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "t")] -pub struct Cursor { - pub x: f32, - pub y: f32, - pub argb: u32, - pub btns: i32, - pub text: String, -} diff --git a/src/whiteboard/server.rs b/src/whiteboard/server.rs deleted file mode 100644 index 040110598..000000000 --- a/src/whiteboard/server.rs +++ /dev/null @@ -1,171 +0,0 @@ -use super::CustomEvent; -use crate::ipc::{new_listener, Connection, Data}; -#[cfg(any(target_os = "windows", target_os = "macos"))] -use hbb_common::tokio::sync::mpsc::unbounded_channel; -#[cfg(any(target_os = "windows", target_os = "linux"))] -use hbb_common::ResultType; -use hbb_common::{ - allow_err, log, - tokio::{self, sync::mpsc::UnboundedReceiver}, -}; -use lazy_static::lazy_static; -use std::sync::RwLock; -use std::time::{Duration, Instant}; - -#[cfg(any(target_os = "windows", target_os = "macos"))] -use tao::event_loop::EventLoopProxy; -#[cfg(target_os = "linux")] -use winit::event_loop::EventLoopProxy; - -lazy_static! { - pub(super) static ref EVENT_PROXY: RwLock>> = - RwLock::new(None); -} - -const RIPPLE_DURATION: Duration = Duration::from_millis(500); -#[cfg(target_os = "macos")] -type RippleFloat = f64; -#[cfg(any(target_os = "windows", target_os = "linux"))] -type RippleFloat = f32; - -#[cfg(target_os = "linux")] -pub use super::linux::run; - -#[cfg(any(target_os = "windows", target_os = "macos"))] -pub fn run() { - let (tx_exit, rx_exit) = unbounded_channel(); - std::thread::spawn(move || { - start_ipc(rx_exit); - }); - if let Err(e) = super::create_event_loop() { - log::error!("Failed to create event loop: {}", e); - tx_exit.send(()).ok(); - return; - } -} - -#[tokio::main(flavor = "current_thread")] -pub(super) async fn start_ipc(mut rx_exit: UnboundedReceiver<()>) { - match new_listener("_whiteboard").await { - Ok(mut incoming) => loop { - tokio::select! { - _ = rx_exit.recv() => { - log::info!("Exiting IPC"); - break; - } - res = incoming.next() => match res { - Some(result) => match result { - Ok(stream) => { - log::debug!("Got new connection"); - tokio::spawn(handle_new_stream(Connection::new(stream))); - } - Err(err) => { - log::error!("Couldn't get whiteboard client: {:?}", err); - } - }, - None => { - log::error!("Failed to get whiteboard client"); - } - } - } - }, - Err(err) => { - log::error!("Failed to start whiteboard ipc server: {}", err); - } - } -} - -async fn handle_new_stream(mut conn: Connection) { - loop { - tokio::select! { - res = conn.next() => { - match res { - Err(err) => { - log::info!("whiteboard ipc connection closed: {}", err); - break; - } - Ok(Some(data)) => { - match data { - Data::Whiteboard((k, evt)) => { - if matches!(evt, CustomEvent::Exit) { - log::info!("whiteboard ipc connection closed"); - break; - } else { - EVENT_PROXY.read().unwrap().as_ref().map(|ep| { - allow_err!(ep.send_event((k, evt))); - }); - } - } - _ => {} - } - } - Ok(None) => { - log::info!("whiteboard ipc connection closed"); - break; - } - } - } - } - } - EVENT_PROXY.read().unwrap().as_ref().map(|ep| { - allow_err!(ep.send_event(("".to_string(), CustomEvent::Exit))); - }); -} - -#[cfg(any(target_os = "windows", target_os = "linux"))] -pub(super) fn get_displays_rect() -> ResultType<(i32, i32, u32, u32)> { - let displays = crate::server::display_service::try_get_displays()?; - let mut min_x = i32::MAX; - let mut min_y = i32::MAX; - let mut max_x = i32::MIN; - let mut max_y = i32::MIN; - - for display in displays { - let (x, y) = (display.origin().0 as i32, display.origin().1 as i32); - let (w, h) = (display.width() as i32, display.height() as i32); - min_x = min_x.min(x); - min_y = min_y.min(y); - max_x = max_x.max(x + w); - max_y = max_y.max(y + h); - } - let (x, y) = (min_x, min_y); - let (w, h) = ((max_x - min_x) as u32, (max_y - min_y) as u32); - Ok((x, y, w, h)) -} - -#[inline] -pub(super) fn argb_to_rgba(argb: u32) -> (u8, u8, u8, u8) { - ( - (argb >> 16 & 0xFF) as u8, - (argb >> 8 & 0xFF) as u8, - (argb & 0xFF) as u8, - (argb >> 24 & 0xFF) as u8, - ) -} - -pub(super) struct Ripple { - pub x: RippleFloat, - pub y: RippleFloat, - pub start_time: Instant, -} - -impl Ripple { - #[inline] - pub fn retain_active(ripples: &mut Vec) { - ripples.retain(|r| r.start_time.elapsed() < RIPPLE_DURATION); - } - - pub fn get_radius_alpha(&self) -> (RippleFloat, RippleFloat) { - let elapsed = self.start_time.elapsed(); - #[cfg(target_os = "macos")] - let progress = (elapsed.as_secs_f64() / RIPPLE_DURATION.as_secs_f64()).min(1.0); - #[cfg(any(target_os = "windows", target_os = "linux"))] - let progress = (elapsed.as_secs_f32() / RIPPLE_DURATION.as_secs_f32()).min(1.0); - #[cfg(target_os = "macos")] - let radius = 25.0 * progress; - #[cfg(any(target_os = "windows", target_os = "linux"))] - let radius = 45.0 * progress; - let alpha = 1.0 - progress; - (radius, alpha) - } -} diff --git a/src/whiteboard/win_linux.rs b/src/whiteboard/win_linux.rs deleted file mode 100644 index 9e4722fff..000000000 --- a/src/whiteboard/win_linux.rs +++ /dev/null @@ -1,180 +0,0 @@ -use hbb_common::{bail, ResultType}; -use tiny_skia::{FillRule, Paint, PathBuilder, PixmapMut, Point, Rect, Transform}; -use ttf_parser::Face; -// A helper struct to bridge `ttf-parser` and `tiny-skia`. -struct PathBuilderWrapper<'a> { - path_builder: &'a mut PathBuilder, - transform: Transform, -} - -impl ttf_parser::OutlineBuilder for PathBuilderWrapper<'_> { - fn move_to(&mut self, x: f32, y: f32) { - let mut pt = Point::from_xy(x, y); - self.transform.map_point(&mut pt); - self.path_builder.move_to(pt.x, pt.y); - } - - fn line_to(&mut self, x: f32, y: f32) { - let mut pt = Point::from_xy(x, y); - self.transform.map_point(&mut pt); - self.path_builder.line_to(pt.x, pt.y); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - let mut pt1 = Point::from_xy(x1, y1); - self.transform.map_point(&mut pt1); - let mut pt = Point::from_xy(x, y); - self.transform.map_point(&mut pt); - self.path_builder.quad_to(pt1.x, pt1.y, pt.x, pt.y); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - let mut pt1 = Point::from_xy(x1, y1); - self.transform.map_point(&mut pt1); - let mut pt2 = Point::from_xy(x2, y2); - self.transform.map_point(&mut pt2); - let mut pt = Point::from_xy(x, y); - self.transform.map_point(&mut pt); - self.path_builder - .cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt.x, pt.y); - } - - fn close(&mut self) { - self.path_builder.close(); - } -} - -// Draws a string of text with the white background rectangle onto the pixmap. -pub(super) fn draw_text( - pixmap: &mut PixmapMut, - face: &Face, - text: &str, - x: f32, - y: f32, - paint: &Paint, - font_size: f32, -) { - let units_per_em = face.units_per_em() as f32; - let scale = font_size / units_per_em; - - // --- 1. Calculate text dimensions for the background --- - let mut total_width = 0.0; - for ch in text.chars() { - let glyph_id = face.glyph_index(ch).unwrap_or_default(); - if let Some(h_advance) = face.glyph_hor_advance(glyph_id) { - total_width += h_advance as f32 * scale; - } - } - - // Use font metrics for a consistent background height. - let font_height = (face.ascender() - face.descender()) as f32 * scale; - let ascent = face.ascender() as f32 * scale; - // Add some padding around the text - let padding = 3.0; - - let mut bg_filled = false; - // --- 2. Draw the white background rectangle --- - if let Some(bg_rect) = Rect::from_xywh( - x - padding, - y - ascent - padding, - total_width + 2.0 * padding, - font_height + 2.0 * padding, - ) { - // Corner radius - let radius = 5.0; - let path = { - let mut pb = PathBuilder::new(); - let r_x = bg_rect.x(); - let r_y = bg_rect.y(); - let r_w = bg_rect.width(); - let r_h = bg_rect.height(); - pb.move_to(r_x + radius, r_y); - pb.line_to(r_x + r_w - radius, r_y); - pb.quad_to(r_x + r_w, r_y, r_x + r_w, r_y + radius); - pb.line_to(r_x + r_w, r_y + r_h - radius); - pb.quad_to(r_x + r_w, r_y + r_h, r_x + r_w - radius, r_y + r_h); - pb.line_to(r_x + radius, r_y + r_h); - pb.quad_to(r_x, r_y + r_h, r_x, r_y + r_h - radius); - pb.line_to(r_x, r_y + radius); - pb.quad_to(r_x, r_y, r_x + radius, r_y); - pb.close(); - pb.finish() - }; - - if let Some(path) = path { - let mut bg_paint = Paint::default(); - bg_paint.set_color_rgba8(255, 255, 255, 255); - bg_paint.anti_alias = true; - pixmap.fill_path( - &path, - &bg_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - bg_filled = true; - } - } - - // --- 3. Draw the text --- - let transform = Transform::from_translate(x, y).pre_scale(scale, -scale); - let mut path_builder = PathBuilder::new(); - let mut current_x = 0.0; - - for ch in text.chars() { - let glyph_id = face.glyph_index(ch).unwrap_or_default(); - - let mut builder = PathBuilderWrapper { - path_builder: &mut path_builder, - transform: transform.post_translate(current_x, 0.0), - }; - - face.outline_glyph(glyph_id, &mut builder); - - if let Some(h_advance) = face.glyph_hor_advance(glyph_id) { - current_x += h_advance as f32 * scale; - } - } - - if let Some(path) = path_builder.finish() { - if bg_filled { - let mut text_paint = Paint::default(); - text_paint.set_color_rgba8(0, 0, 0, 255); - text_paint.anti_alias = true; - pixmap.fill_path( - &path, - &text_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - } else { - pixmap.fill_path(&path, paint, FillRule::Winding, Transform::identity(), None); - } - } -} - -pub(super) fn create_font_face() -> ResultType> { - let mut font_db = fontdb::Database::new(); - font_db.load_system_fonts(); - let query = fontdb::Query { - families: &[fontdb::Family::Monospace, fontdb::Family::SansSerif], - ..fontdb::Query::default() - }; - let Some(font_id) = font_db.query(&query) else { - bail!("No monospace or sans-serif font found!"); - }; - let Some((font_source, face_index)) = font_db.face_source(font_id) else { - bail!("No face found for font!"); - }; - // Load the font data into a static slice to satisfy `ttf-parser`'s lifetime requirements. - // We use `Box::leak` to leak the memory, which is acceptable here since the font data - // is needed for the entire lifetime of the application. - let font_data: &'static [u8] = Box::leak(match font_source { - fontdb::Source::File(path) => std::fs::read(path)?.into_boxed_slice(), - fontdb::Source::Binary(data) => data.as_ref().as_ref().to_vec().into_boxed_slice(), - fontdb::Source::SharedFile(path, _) => std::fs::read(path)?.into_boxed_slice(), - }); - let face = Face::parse(font_data, face_index)?; - Ok(face) -} diff --git a/src/whiteboard/windows.rs b/src/whiteboard/windows.rs deleted file mode 100644 index dc6a8c30e..000000000 --- a/src/whiteboard/windows.rs +++ /dev/null @@ -1,230 +0,0 @@ -use super::{ - server::{Ripple, EVENT_PROXY}, - win_linux::{create_font_face, draw_text}, - Cursor, CustomEvent, -}; -use hbb_common::{anyhow::anyhow, log, ResultType}; -use softbuffer::{Context, Surface}; -use std::{collections::HashMap, num::NonZeroU32, sync::Arc, time::Instant}; -use tao::{ - dpi::{PhysicalPosition, PhysicalSize}, - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoopBuilder}, - platform::windows::WindowBuilderExtWindows, - window::WindowBuilder, -}; -use tiny_skia::{Color, FillRule, Paint, PathBuilder, PixmapMut, Stroke, Transform}; - -pub(super) fn create_event_loop() -> ResultType<()> { - let face = match create_font_face() { - Ok(face) => Some(face), - Err(err) => { - log::error!("Failed to create font face: {}", err); - None - } - }; - - let event_loop = EventLoopBuilder::<(String, CustomEvent)>::with_user_event().build(); - let mut window_builder = WindowBuilder::new() - .with_title("RustDesk whiteboard") - .with_transparent(true) - .with_always_on_top(true) - .with_skip_taskbar(true) - .with_decorations(false); - - let mut final_size = None; - if let Ok((x, y, w, h)) = super::server::get_displays_rect() { - if w > 0 && h > 0 { - final_size = Some(PhysicalSize::new(w, h)); - window_builder = window_builder - .with_position(PhysicalPosition::new(x, y)) - .with_inner_size(PhysicalSize::new(1, 1)); - } else { - window_builder = - window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None))); - } - } else { - window_builder = - window_builder.with_fullscreen(Some(tao::window::Fullscreen::Borderless(None))); - } - - let window = Arc::new(window_builder.build::<(String, CustomEvent)>(&event_loop)?); - window.set_ignore_cursor_events(true)?; - - let context = Context::new(window.clone()).map_err(|e| { - log::error!("Failed to create context: {}", e); - anyhow!(e.to_string()) - })?; - let mut surface = Surface::new(&context, window.clone()).map_err(|e| { - log::error!("Failed to create surface: {}", e); - anyhow!(e.to_string()) - })?; - - let proxy = event_loop.create_proxy(); - EVENT_PROXY.write().unwrap().replace(proxy); - let _call_on_ret = crate::common::SimpleCallOnReturn { - b: true, - f: Box::new(move || { - let _ = EVENT_PROXY.write().unwrap().take(); - }), - }; - - let mut ripples: Vec = Vec::new(); - let mut last_cursors: HashMap = HashMap::new(); - let mut resized = final_size.is_none(); - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Poll; - - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - Event::RedrawRequested(_) => { - if !resized { - if let Some(size) = final_size.take() { - window.set_inner_size(size); - } - resized = true; - return; - } - - let (width, height) = { - let size = window.inner_size(); - (size.width, size.height) - }; - - let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height)) - else { - return; - }; - if let Err(e) = surface.resize(width, height) { - log::error!("Failed to resize surface: {}", e); - return; - } - - let mut buffer = match surface.buffer_mut() { - Ok(buf) => buf, - Err(e) => { - log::error!("Failed to get buffer: {}", e); - return; - } - }; - let Some(mut pixmap) = PixmapMut::from_bytes( - bytemuck::cast_slice_mut(&mut buffer), - width.get(), - height.get(), - ) else { - log::error!("Failed to create pixmap from buffer"); - return; - }; - pixmap.fill(Color::TRANSPARENT); - - Ripple::retain_active(&mut ripples); - for ripple in &ripples { - let (radius, alpha) = ripple.get_radius_alpha(); - - let mut ripple_paint = Paint::default(); - // Note: The real color is bgra here. - ripple_paint.set_color_rgba8(64, 64, 255, (alpha * 128.0) as u8); - ripple_paint.anti_alias = true; - - let mut ripple_pb = PathBuilder::new(); - ripple_pb.push_circle(ripple.x, ripple.y, radius); - if let Some(path) = ripple_pb.finish() { - pixmap.fill_path( - &path, - &ripple_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - } - } - - for cursor in last_cursors.values() { - let (x, y) = (cursor.x, cursor.y); - let size = 1.5f32; - - let mut pb = PathBuilder::new(); - pb.move_to(x, y); - pb.line_to(x, y + 16.0 * size); - pb.line_to(x + 4.0 * size, y + 13.0 * size); - pb.line_to(x + 7.0 * size, y + 20.0 * size); - pb.line_to(x + 9.0 * size, y + 19.0 * size); - pb.line_to(x + 6.0 * size, y + 12.0 * size); - pb.line_to(x + 11.0 * size, y + 12.0 * size); - pb.close(); - - if let Some(path) = pb.finish() { - let rgba = super::argb_to_rgba(cursor.argb); - let mut arrow_paint = Paint::default(); - // Note: The real color is bgra here. - arrow_paint.set_color_rgba8(rgba.2, rgba.1, rgba.0, rgba.3); - arrow_paint.anti_alias = true; - pixmap.fill_path( - &path, - &arrow_paint, - FillRule::Winding, - Transform::identity(), - None, - ); - - let mut black_paint = Paint::default(); - black_paint.set_color_rgba8(0, 0, 0, 255); - black_paint.anti_alias = true; - let mut stroke = Stroke::default(); - stroke.width = 1.0f32; - pixmap.stroke_path( - &path, - &black_paint, - &stroke, - Transform::identity(), - None, - ); - - face.as_ref().map(|face| { - draw_text( - &mut pixmap, - face, - &cursor.text, - x + 24.0 * size, - y + 24.0 * size, - &arrow_paint, - 14.0f32, - ); - }); - } - } - - if let Err(e) = buffer.present() { - log::error!("Failed to present surface: {}", e); - return; - } - } - Event::MainEventsCleared => { - window.request_redraw(); - } - Event::UserEvent((k, evt)) => match evt { - CustomEvent::Cursor(cursor) => { - if cursor.btns != 0 { - ripples.push(Ripple { - x: cursor.x, - y: cursor.y, - start_time: Instant::now(), - }); - } - last_cursors.insert(k, cursor); - } - CustomEvent::Exit => { - *control_flow = ControlFlow::Exit; - } - _ => {} - }, - _ => (), - } - }); -} diff --git a/src/windows.cc b/src/windows.cc new file mode 100644 index 000000000..162c8a0a7 --- /dev/null +++ b/src/windows.cc @@ -0,0 +1,392 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT(build/include_order) +#include + +void flog(char const *fmt, ...) +{ + FILE *h = fopen("C:\\Windows\\temp\\test_rustdesk.log", "at"); + if (!h) + return; + va_list arg; + va_start(arg, fmt); + vfprintf(h, fmt, arg); + va_end(arg); + fclose(h); +} + +// ultravnc has rdp support +// https://github.com/veyon/ultravnc/blob/master/winvnc/winvnc/service.cpp +// https://github.com/TigerVNC/tigervnc/blob/master/win/winvnc/VNCServerService.cxx +// https://blog.csdn.net/MA540213/article/details/84638264 + +DWORD GetLogonPid(DWORD dwSessionId, BOOL as_user) +{ + DWORD dwLogonPid = 0; + HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnap != INVALID_HANDLE_VALUE) + { + PROCESSENTRY32W procEntry; + procEntry.dwSize = sizeof procEntry; + + if (Process32FirstW(hSnap, &procEntry)) + do + { + DWORD dwLogonSessionId = 0; + if (_wcsicmp(procEntry.szExeFile, as_user ? L"explorer.exe" : L"winlogon.exe") == 0 && + ProcessIdToSessionId(procEntry.th32ProcessID, &dwLogonSessionId) && + dwLogonSessionId == dwSessionId) + { + dwLogonPid = procEntry.th32ProcessID; + break; + } + } while (Process32NextW(hSnap, &procEntry)); + CloseHandle(hSnap); + } + return dwLogonPid; +} + +// if should try WTSQueryUserToken? +// https://stackoverflow.com/questions/7285666/example-code-a-service-calls-createprocessasuser-i-want-the-process-to-run-in +BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken, DWORD dwSessionId, BOOL as_user) +{ + BOOL bResult = FALSE; + DWORD Id = GetLogonPid(dwSessionId, as_user); + if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id)) + { + bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken); + CloseHandle(hProcess); + } + return bResult; +} + +// START the app as system +extern "C" +{ + HANDLE LaunchProcessWin(LPCWSTR cmd, DWORD dwSessionId, BOOL as_user) + { + HANDLE hProcess = NULL; + HANDLE hToken = NULL; + if (GetSessionUserTokenWin(&hToken, dwSessionId, as_user)) + { + STARTUPINFOW si; + ZeroMemory(&si, sizeof si); + si.cb = sizeof si; + si.dwFlags = STARTF_USESHOWWINDOW; + wchar_t buf[MAX_PATH]; + wcscpy_s(buf, sizeof(buf), cmd); + PROCESS_INFORMATION pi; + LPVOID lpEnvironment = NULL; + DWORD dwCreationFlags = DETACHED_PROCESS; + if (as_user) + { + + CreateEnvironmentBlock(&lpEnvironment, // Environment block + hToken, // New token + TRUE); // Inheritance + } + if (lpEnvironment) + { + dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; + } + if (CreateProcessAsUserW(hToken, NULL, buf, NULL, NULL, FALSE, dwCreationFlags, lpEnvironment, NULL, &si, &pi)) + { + CloseHandle(pi.hThread); + hProcess = pi.hProcess; + } + CloseHandle(hToken); + if (lpEnvironment) + DestroyEnvironmentBlock(lpEnvironment); + } + return hProcess; + } + + // Switch the current thread to the specified desktop + static bool + switchToDesktop(HDESK desktop) + { + HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId()); + if (!SetThreadDesktop(desktop)) + { + return false; + } + if (!CloseDesktop(old_desktop)) + { + // + } + return true; + } + + // https://github.com/TigerVNC/tigervnc/blob/8c6c584377feba0e3b99eecb3ef33b28cee318cb/win/rfb_win32/Service.cxx + + // Determine whether the thread's current desktop is the input one + BOOL + inputDesktopSelected() + { + HDESK current = GetThreadDesktop(GetCurrentThreadId()); + HDESK input = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!input) + { + return FALSE; + } + + DWORD size; + char currentname[256]; + char inputname[256]; + + if (!GetUserObjectInformation(current, UOI_NAME, currentname, sizeof(currentname), &size)) + { + CloseDesktop(input); + return FALSE; + } + if (!GetUserObjectInformation(input, UOI_NAME, inputname, sizeof(inputname), &size)) + { + CloseDesktop(input); + return FALSE; + } + CloseDesktop(input); + // flog("%s %s\n", currentname, inputname); + return strcmp(currentname, inputname) == 0 ? TRUE : FALSE; + } + + // Switch the current thread into the input desktop + bool + selectInputDesktop() + { + // - Open the input desktop + HDESK desktop = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!desktop) + { + return false; + } + + // - Switch into it + if (!switchToDesktop(desktop)) + { + CloseDesktop(desktop); + return false; + } + + // *** + DWORD size = 256; + char currentname[256]; + if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) + { + // + } + + return true; + } + + int handleMask(uint8_t *rwbuffer, const uint8_t *mask, int width, int height, int bmWidthBytes, int bmHeight) + { + auto andMask = mask; + auto andMaskSize = bmWidthBytes * bmHeight; + auto offset = height * bmWidthBytes; + auto xorMask = mask + offset; + auto xorMaskSize = andMaskSize - offset; + int doOutline = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int byte = y * bmWidthBytes + x / 8; + int bit = 7 - x % 8; + + if (byte < andMaskSize && !(andMask[byte] & (1 << bit))) + { + // Valid pixel, so make it opaque + rwbuffer[3] = 0xff; + + // Black or white? + if (xorMask[byte] & (1 << bit)) + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0xff; + else + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0; + } + else if (byte < xorMaskSize && xorMask[byte] & (1 << bit)) + { + // Replace any XORed pixels with black, because RFB doesn't support + // XORing of cursors. XORing is used for the I-beam cursor, which is most + // often used over a white background, but also sometimes over a black + // background. We set the XOR'd pixels to black, then draw a white outline + // around the whole cursor. + + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = 0; + rwbuffer[3] = 0xff; + + doOutline = 1; + } + else + { + // Transparent pixel + rwbuffer[0] = rwbuffer[1] = rwbuffer[2] = rwbuffer[3] = 0; + } + + rwbuffer += 4; + } + } + return doOutline; + } + + void drawOutline(uint8_t *out0, const uint8_t *in0, int width, int height, int out0_size) + { + auto in = in0; + auto out0_end = out0 + out0_size; + auto offset = width * 4 + 4; + auto out = out0 + offset; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + // Visible pixel? + if (in[3] > 0) + { + auto n = 4 * 3; + auto p = out - (width + 2) * 4 - 4; + // Outline above... + if (p >= out0 && p + n <= out0_end) memset(p, 0xff, n); + // ...besides... + p = out - 4; + if (p + n <= out0_end) memset(p, 0xff, n); + // ...and above + p = out + (width + 2) * 4 - 4; + if (p + n <= out0_end) memset(p, 0xff, n); + } + in += 4; + out += 4; + } + // outline is slightly larger + out += 2 * 4; + } + + // Pass 2, overwrite with actual cursor + in = in0; + out = out0 + offset; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + if (in[3] > 0 && out + 4 <= out0_end) + memcpy(out, in, 4); + in += 4; + out += 4; + } + out += 2 * 4; + } + } + + int ffi(unsigned v) + { + static const int MultiplyDeBruijnBitPosition[32] = + { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + return MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; + } + + int get_di_bits(uint8_t *out, HDC dc, HBITMAP hbmColor, int width, int height) + { + BITMAPV5HEADER bi; + memset(&bi, 0, sizeof(BITMAPV5HEADER)); + + bi.bV5Size = sizeof(BITMAPV5HEADER); + bi.bV5Width = width; + bi.bV5Height = -height; // Negative for top-down + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x000000FF; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x00FF0000; + bi.bV5AlphaMask = 0xFF000000; + + if (!GetDIBits(dc, hbmColor, 0, height, + out, (LPBITMAPINFO)&bi, DIB_RGB_COLORS)) + return 1; + + // We may not get the RGBA order we want, so shuffle things around + int ridx, gidx, bidx, aidx; + + ridx = ffi(bi.bV5RedMask) / 8; + gidx = ffi(bi.bV5GreenMask) / 8; + bidx = ffi(bi.bV5BlueMask) / 8; + // Usually not set properly + aidx = 6 - ridx - gidx - bidx; + + if ((bi.bV5RedMask != ((unsigned)0xff << ridx * 8)) || + (bi.bV5GreenMask != ((unsigned)0xff << gidx * 8)) || + (bi.bV5BlueMask != ((unsigned)0xff << bidx * 8))) + return 1; + + auto rwbuffer = out; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + uint8_t r, g, b, a; + + r = rwbuffer[ridx]; + g = rwbuffer[gidx]; + b = rwbuffer[bidx]; + a = rwbuffer[aidx]; + + rwbuffer[0] = r; + rwbuffer[1] = g; + rwbuffer[2] = b; + rwbuffer[3] = a; + + rwbuffer += 4; + } + } + return 0; + } + + void blank_screen(BOOL set) + { + if (set) + { + SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)2); + } + else + { + SendMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)-1); + } + } + + void AddRecentDocument(PCWSTR path) + { + SHAddToRecentDocs(SHARD_PATHW, path); + } + + uint32_t get_active_user(PWSTR bufin, uint32_t nin) + { + uint32_t nout = 0; + auto id = WTSGetActiveConsoleSessionId(); + PWSTR buf = NULL; + DWORD n = 0; + if (WTSQuerySessionInformationW(NULL, id, WTSUserName, &buf, &n)) + { + if (buf) { + nout = min(nin, n); + memcpy(bufin, buf, nout); + WTSFreeMemory(buf); + } + } + return nout; + } +} // end of extern "C" diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index d41b91c22..000000000 --- a/vcpkg.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "dependencies": [ - { - "name": "aom", - "host": true - }, - { - "name": "aom", - "host": false - }, - { - "name": "cpu-features", - "platform": "android" - }, - { - "name": "libjpeg-turbo", - "host": true - }, - { - "name": "libjpeg-turbo", - "host": false - }, - { - "name": "oboe", - "platform": "android" - }, - { - "name": "opus", - "host": true - }, - { - "name": "opus", - "host": false - }, - { - "name": "libvpx", - "host": true - }, - { - "name": "libvpx", - "host": false - }, - { - "name": "libyuv", - "host": true - }, - { - "name": "libyuv", - "host": false - }, - { - "name": "mfx-dispatch", - "host": true, - "platform": "((x86 | x64) & (android | linux)) | (windows & !uwp)" - }, - { - "name": "mfx-dispatch", - "host": false, - "platform": "((x86 | x64) & (android | linux)) | (windows & !uwp)" - }, - { - "name": "ffmpeg", - "host": true, - "features": [ - { - "name": "amf", - "platform": "((windows | linux) & static)" - }, - { - "name": "nvcodec", - "platform": "((windows | linux) & static)" - }, - { - "name": "qsv", - "platform": "(windows & static)" - } - ], - "platform": "((windows | (linux & !arm32) | osx) & static)" - }, - { - "name": "ffmpeg", - "host": false, - "platform": "((android | ios | (linux & arm32)) & static)" - } - ], - "vcpkg-configuration": { - "default-registry": { - "kind": "builtin", - "baseline": "120deac3062162151622ca4860575a33844ba10b" - }, - "overlay-ports": [ - "./res/vcpkg" - ] - }, - "overrides": [ - { - "name": "ffnvcodec", - "version": "12.1.14.0" - }, - { - "name": "amd-amf", - "version": "1.4.35" - } - ] -}