mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-21 01:53:20 +03:00
Merge branch 'master' into master
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/java:1": {},
|
"ghcr.io/devcontainers/features/java:1": {},
|
||||||
"ghcr.io/akhildevelops/devcontainer-features/android-cli:latest": {
|
"ghcr.io/akhildevelops/devcontainer-features/android-cli:latest": {
|
||||||
"PACKAGES": "platform-tools,ndk;22.1.7171670"
|
"PACKAGES": "platform-tools,ndk;23.2.8568313"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|||||||
75
.github/workflows/bridge.yml
vendored
Normal file
75
.github/workflows/bridge.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# This yaml shares the build bridge steps with ci and nightly.
|
||||||
|
name: Build flutter-rust-bridge
|
||||||
|
# 2023-04-19 15:48:00+00:00
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate_bridge:
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- {
|
||||||
|
target: x86_64-unknown-linux-gnu,
|
||||||
|
os: ubuntu-20.04,
|
||||||
|
extra-build-args: "",
|
||||||
|
}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install prerequisites
|
||||||
|
run: |
|
||||||
|
sudo apt install ca-certificates -y
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang cmake libclang-dev ninja-build llvm-dev libclang-10-dev llvm-10-dev pkg-config
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: ${{ matrix.job.target }}
|
||||||
|
override: true
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: bridge-${{ matrix.job.os }}
|
||||||
|
workspace: "/tmp/flutter_rust_bridge/frb_codegen"
|
||||||
|
|
||||||
|
- 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 flutter_rust_bridge_codegen
|
||||||
|
pushd flutter && 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
|
||||||
|
|
||||||
|
- 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
|
||||||
1709
.github/workflows/flutter-build.yml
vendored
Normal file
1709
.github/workflows/flutter-build.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
956
.github/workflows/flutter-ci.yml
vendored
956
.github/workflows/flutter-ci.yml
vendored
@@ -16,959 +16,9 @@ on:
|
|||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "README.md"
|
- "README.md"
|
||||||
|
|
||||||
env:
|
|
||||||
LLVM_VERSION: "15.0.6"
|
|
||||||
FLUTTER_VERSION: "3.7.5"
|
|
||||||
# vcpkg version: 2022.05.10
|
|
||||||
# for multiarch gcc compatibility
|
|
||||||
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
|
|
||||||
VERSION: "1.2.0"
|
|
||||||
NDK_VERSION: "r23"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-for-windows:
|
run-ci:
|
||||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
uses: ./.github/workflows/flutter-build.yml
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
|
||||||
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
|
||||||
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install LLVM and Clang
|
|
||||||
uses: KyleMayes/install-llvm-action@v1
|
|
||||||
with:
|
with:
|
||||||
version: ${{ env.LLVM_VERSION }}
|
upload-artifact: false
|
||||||
|
|
||||||
- name: Install flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: "stable"
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Replace engine with rustdesk custom flutter engine
|
|
||||||
run: |
|
|
||||||
flutter doctor -v
|
|
||||||
flutter precache --windows
|
|
||||||
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.7.0-rustdesk/windows-x64-release-flutter.zip -OutFile windows-x64-flutter-release.zip
|
|
||||||
Expand-Archive windows-x64-flutter-release.zip -DestinationPath engine
|
|
||||||
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
override: true
|
|
||||||
components: rustfmt
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: ${{ matrix.job.os }}
|
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
|
||||||
run: |
|
|
||||||
cargo install flutter_rust_bridge_codegen
|
|
||||||
Push-Location flutter ; flutter pub get ; Pop-Location
|
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
|
||||||
|
|
||||||
- name: Restore from cache and install vcpkg
|
|
||||||
uses: lukka/run-vcpkg@v7
|
|
||||||
with:
|
|
||||||
setupOnly: true
|
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
|
||||||
|
|
||||||
- name: Install vcpkg dependencies
|
|
||||||
run: |
|
|
||||||
$VCPKG_ROOT/vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build rustdesk
|
|
||||||
run: python3 .\build.py --portable --hwcodec --flutter
|
|
||||||
|
|
||||||
build-for-macOS:
|
|
||||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
target: x86_64-apple-darwin,
|
|
||||||
os: macos-latest,
|
|
||||||
extra-build-args: "",
|
|
||||||
}
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install build runtime
|
|
||||||
run: |
|
|
||||||
brew install llvm create-dmg nasm yasm cmake gcc wget ninja pkg-config
|
|
||||||
|
|
||||||
- name: Install flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: "stable"
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: ${{ matrix.job.os }}
|
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cargo install flutter_rust_bridge_codegen
|
|
||||||
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
|
|
||||||
|
|
||||||
- name: Restore from cache and install vcpkg
|
|
||||||
uses: lukka/run-vcpkg@v7
|
|
||||||
with:
|
|
||||||
setupOnly: true
|
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
|
||||||
|
|
||||||
- name: Install vcpkg dependencies
|
|
||||||
run: |
|
|
||||||
$VCPKG_ROOT/vcpkg install libvpx libyuv opus
|
|
||||||
|
|
||||||
- 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: |
|
|
||||||
# --hwcodec not supported on macos yet
|
|
||||||
./build.py --flutter ${{ matrix.job.extra-build-args }}
|
|
||||||
|
|
||||||
build-vcpkg-deps-linux:
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
# - { arch: armv7, os: ubuntu-20.04 }
|
|
||||||
- { arch: x86_64, os: ubuntu-20.04 }
|
|
||||||
- { arch: aarch64, os: ubuntu-20.04 }
|
|
||||||
steps:
|
|
||||||
- name: Create vcpkg artifacts folder
|
|
||||||
run: mkdir -p /opt/artifacts
|
|
||||||
|
|
||||||
- name: Cache Vcpkg
|
|
||||||
id: cache-vcpkg
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: /opt/artifacts
|
|
||||||
key: vcpkg-${{ matrix.job.arch }}
|
|
||||||
|
|
||||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
|
||||||
name: Run vcpkg install on ${{ matrix.job.arch }}
|
|
||||||
id: vcpkg
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.job.arch }}
|
|
||||||
distro: ubuntu18.04
|
|
||||||
githubToken: ${{ github.token }}
|
|
||||||
setup: |
|
|
||||||
ls -l "/opt/artifacts"
|
|
||||||
dockerRunArgs: |
|
|
||||||
--volume "/opt/artifacts:/artifacts"
|
|
||||||
shell: /bin/bash
|
|
||||||
install: |
|
|
||||||
apt update -y
|
|
||||||
case "${{ matrix.job.arch }}" in
|
|
||||||
x86_64)
|
|
||||||
# CMake 3.15+
|
|
||||||
apt install -y gpg wget ca-certificates
|
|
||||||
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
|
|
||||||
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
|
||||||
apt update -y
|
|
||||||
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev
|
|
||||||
;;
|
|
||||||
aarch64|armv7)
|
|
||||||
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev automake libtool
|
|
||||||
esac
|
|
||||||
cmake --version
|
|
||||||
gcc -v
|
|
||||||
run: |
|
|
||||||
# disable git safe.directory
|
|
||||||
git config --global --add safe.directory "*"
|
|
||||||
case "${{ matrix.job.arch }}" in
|
|
||||||
x86_64)
|
|
||||||
export VCPKG_FORCE_SYSTEM_BINARIES=1
|
|
||||||
pushd /artifacts
|
|
||||||
git clone https://github.com/microsoft/vcpkg.git || true
|
|
||||||
pushd vcpkg
|
|
||||||
git reset --hard ${{ env.VCPKG_COMMIT_ID }}
|
|
||||||
./bootstrap-vcpkg.sh
|
|
||||||
./vcpkg install libvpx libyuv opus
|
|
||||||
;;
|
|
||||||
aarch64|armv7)
|
|
||||||
pushd /artifacts
|
|
||||||
# libyuv
|
|
||||||
git clone https://chromium.googlesource.com/libyuv/libyuv || true
|
|
||||||
pushd libyuv
|
|
||||||
git pull
|
|
||||||
mkdir -p build
|
|
||||||
pushd build
|
|
||||||
mkdir -p /artifacts/vcpkg/installed
|
|
||||||
cmake .. -DCMAKE_INSTALL_PREFIX=/artifacts/vcpkg/installed
|
|
||||||
make -j4 && make install
|
|
||||||
popd
|
|
||||||
popd
|
|
||||||
# libopus, ubuntu 18.04 prebuilt is not be compiled with -fPIC
|
|
||||||
wget -O opus.tar.gz http://archive.ubuntu.com/ubuntu/pool/main/o/opus/opus_1.1.2.orig.tar.gz
|
|
||||||
tar -zxvf opus.tar.gz; ls -l
|
|
||||||
pushd opus-1.1.2
|
|
||||||
./autogen.sh; ./configure --prefix=/artifacts/vcpkg/installed
|
|
||||||
make -j4; make install
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@master
|
|
||||||
with:
|
|
||||||
name: vcpkg-artifact-${{ matrix.job.arch }}
|
|
||||||
path: |
|
|
||||||
/opt/artifacts/vcpkg/installed
|
|
||||||
|
|
||||||
generate-bridge-linux:
|
|
||||||
name: generate bridge
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-args: "",
|
|
||||||
}
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
run: |
|
|
||||||
sudo apt update -y
|
|
||||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang cmake libclang-dev ninja-build llvm-dev libclang-10-dev llvm-10-dev pkg-config
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: bridge-${{ matrix.job.os }}
|
|
||||||
workspace: "/tmp/flutter_rust_bridge/frb_codegen"
|
|
||||||
|
|
||||||
- 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 flutter_rust_bridge_codegen
|
|
||||||
pushd flutter && 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
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
build-rustdesk-android:
|
|
||||||
needs: [generate-bridge-linux]
|
|
||||||
name: build rustdesk android apk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: aarch64-linux-android,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "",
|
|
||||||
openssl-arch: android-arm64
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: armv7-linux-androideabi,
|
|
||||||
os: ubuntu-18.04,
|
|
||||||
extra-build-features: "",
|
|
||||||
openssl-arch: android-arm
|
|
||||||
}
|
|
||||||
steps:
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt-get -qq install -y 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 libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ libc6-dev gcc-multilib g++-multilib openjdk-11-jdk-headless
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: "stable"
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
- uses: nttld/setup-ndk@v1
|
|
||||||
id: setup-ndk
|
|
||||||
with:
|
|
||||||
ndk-version: ${{ env.NDK_VERSION }}
|
|
||||||
add-to-path: true
|
|
||||||
|
|
||||||
- name: Clone deps
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pushd /opt
|
|
||||||
git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
|
|
||||||
|
|
||||||
- name: Restore bridge files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: bridge-artifact
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: rustdesk-lib-cache
|
|
||||||
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
|
|
||||||
|
|
||||||
- name: Disable rust bridge build
|
|
||||||
run: |
|
|
||||||
sed -i "s/gen_flutter_rust_bridge();/\/\//g" build.rs
|
|
||||||
|
|
||||||
- name: Build rustdesk lib
|
|
||||||
env:
|
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
|
||||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
|
||||||
VCPKG_ROOT: /opt/rustdesk_thirdparty_lib/vcpkg
|
|
||||||
run: |
|
|
||||||
rustup target add ${{ matrix.job.target }}
|
|
||||||
cargo install cargo-ndk
|
|
||||||
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 /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/arm64-v8a/*.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/
|
|
||||||
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
|
|
||||||
# build flutter
|
|
||||||
pushd flutter
|
|
||||||
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.target }}-release.apk
|
|
||||||
;;
|
|
||||||
armv7-linux-androideabi)
|
|
||||||
mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a
|
|
||||||
cp /opt/rustdesk_thirdparty_lib/android/app/src/main/jniLibs/armeabi-v7a/*.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/
|
|
||||||
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so
|
|
||||||
# build flutter
|
|
||||||
pushd flutter
|
|
||||||
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.target }}-release.apk
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
popd
|
|
||||||
mkdir -p signed-apk; pushd signed-apk
|
|
||||||
mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-release.apk .
|
|
||||||
|
|
||||||
build-rustdesk-lib-linux-amd64:
|
|
||||||
needs: [generate-bridge-linux, build-vcpkg-deps-linux]
|
|
||||||
name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
# use a high level qemu-user-static
|
|
||||||
job:
|
|
||||||
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
|
||||||
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "flatpak",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "appimage",
|
|
||||||
}
|
|
||||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
|
||||||
steps:
|
|
||||||
- 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 update -y
|
|
||||||
sudo apt install qemu-user-static
|
|
||||||
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set Swap Space
|
|
||||||
uses: pierotofy/set-swap-space@master
|
|
||||||
with:
|
|
||||||
swap-size-gb: 12
|
|
||||||
|
|
||||||
- name: Free Space
|
|
||||||
run: |
|
|
||||||
df
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: rustdesk-lib-cache
|
|
||||||
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
|
|
||||||
cache-directories: "/opt/rust-registry"
|
|
||||||
|
|
||||||
- name: Install local registry
|
|
||||||
run: |
|
|
||||||
mkdir -p /opt/rust-registry
|
|
||||||
cargo install cargo-local-registry
|
|
||||||
|
|
||||||
- name: Build local registry
|
|
||||||
uses: nick-fields/retry@v2
|
|
||||||
id: build-local-registry
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
max_attempts: 3
|
|
||||||
timeout_minutes: 15
|
|
||||||
retry_on: error
|
|
||||||
command: cargo local-registry --sync ./Cargo.lock /opt/rust-registry
|
|
||||||
|
|
||||||
- name: Disable rust bridge build
|
|
||||||
run: |
|
|
||||||
sed -i "s/gen_flutter_rust_bridge();/\/\//g" build.rs
|
|
||||||
# only build cdylib
|
|
||||||
sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml
|
|
||||||
|
|
||||||
- name: Restore bridge files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: bridge-artifact
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Restore vcpkg files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: vcpkg-artifact-${{ matrix.job.arch }}
|
|
||||||
path: /opt/artifacts/vcpkg/installed
|
|
||||||
|
|
||||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
|
||||||
name: Build rustdesk library for ${{ matrix.job.arch }}
|
|
||||||
id: vcpkg
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.job.arch }}
|
|
||||||
distro: ubuntu18.04
|
|
||||||
# not ready yet
|
|
||||||
# distro: ubuntu18.04-rustdesk
|
|
||||||
githubToken: ${{ github.token }}
|
|
||||||
setup: |
|
|
||||||
ls -l "${PWD}"
|
|
||||||
ls -l /opt/artifacts/vcpkg/installed
|
|
||||||
dockerRunArgs: |
|
|
||||||
--volume "${PWD}:/workspace"
|
|
||||||
--volume "/opt/artifacts:/opt/artifacts"
|
|
||||||
--volume "/opt/rust-registry:/opt/rust-registry"
|
|
||||||
shell: /bin/bash
|
|
||||||
install: |
|
|
||||||
apt update -y
|
|
||||||
echo -e "installing deps"
|
|
||||||
apt-get -qq install -y 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 libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
|
||||||
# we have libopus compiled by us.
|
|
||||||
apt 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
|
|
||||||
wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-1.64.0-${{ matrix.job.target }}.tar.gz
|
|
||||||
tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz
|
|
||||||
cd rust-1.64.0-${{ matrix.job.target }} && ./install.sh
|
|
||||||
rm -rf rust-1.64.0-${{ matrix.job.target }}
|
|
||||||
# edit config
|
|
||||||
mkdir -p ~/.cargo/
|
|
||||||
echo """
|
|
||||||
[source.crates-io]
|
|
||||||
registry = 'https://github.com/rust-lang/crates.io-index'
|
|
||||||
replace-with = 'local-registry'
|
|
||||||
|
|
||||||
[source.local-registry]
|
|
||||||
local-registry = '/opt/rust-registry/'
|
|
||||||
""" > ~/.cargo/config
|
|
||||||
cat ~/.cargo/config
|
|
||||||
# start build
|
|
||||||
pushd /workspace
|
|
||||||
# mock
|
|
||||||
case "${{ matrix.job.arch }}" in
|
|
||||||
x86_64)
|
|
||||||
# no need mock on x86_64
|
|
||||||
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
|
||||||
cargo build --lib --features hwcodec,flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@master
|
|
||||||
with:
|
|
||||||
name: librustdesk-${{ matrix.job.arch }}-${{ matrix.job.extra-build-features }}.so
|
|
||||||
path: target/release/liblibrustdesk.so
|
|
||||||
|
|
||||||
build-rustdesk-lib-linux-arm:
|
|
||||||
needs: [generate-bridge-linux, build-vcpkg-deps-linux]
|
|
||||||
name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
# use a high level qemu-user-static
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
arch: aarch64,
|
|
||||||
target: aarch64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
use-cross: true,
|
|
||||||
extra-build-features: "",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: aarch64,
|
|
||||||
target: aarch64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-18.04, # just for naming package, not running host
|
|
||||||
use-cross: true,
|
|
||||||
extra-build-features: "appimage",
|
|
||||||
}
|
|
||||||
# - { arch: aarch64, target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true, extra-build-features: "flatpak" }
|
|
||||||
# - {
|
|
||||||
# arch: armv7,
|
|
||||||
# target: arm-unknown-linux-gnueabihf,
|
|
||||||
# os: ubuntu-20.04,
|
|
||||||
# use-cross: true,
|
|
||||||
# extra-build-features: "",
|
|
||||||
# }
|
|
||||||
# - { arch: armv7, target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true, extra-build-features: "flatpak" }
|
|
||||||
# - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
|
||||||
steps:
|
|
||||||
- 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 update -y
|
|
||||||
sudo apt install qemu-user-static
|
|
||||||
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set Swap Space
|
|
||||||
uses: pierotofy/set-swap-space@master
|
|
||||||
with:
|
|
||||||
swap-size-gb: 12
|
|
||||||
|
|
||||||
- name: Free Space
|
|
||||||
run: |
|
|
||||||
df
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal # minimal component installation (ie, no documentation)
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: rustdesk-lib-cache
|
|
||||||
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
|
|
||||||
cache-directories: "/opt/rust-registry"
|
|
||||||
|
|
||||||
- name: Install local registry
|
|
||||||
run: |
|
|
||||||
mkdir -p /opt/rust-registry
|
|
||||||
cargo install cargo-local-registry
|
|
||||||
|
|
||||||
- name: Build local registry
|
|
||||||
uses: nick-fields/retry@v2
|
|
||||||
id: build-local-registry
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
max_attempts: 3
|
|
||||||
timeout_minutes: 15
|
|
||||||
retry_on: error
|
|
||||||
command: cargo local-registry --sync ./Cargo.lock /opt/rust-registry
|
|
||||||
|
|
||||||
- name: Disable rust bridge build
|
|
||||||
run: |
|
|
||||||
sed -i "s/gen_flutter_rust_bridge();/\/\//g" build.rs
|
|
||||||
# only build cdylib
|
|
||||||
sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml
|
|
||||||
|
|
||||||
- name: Restore bridge files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: bridge-artifact
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Restore vcpkg files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: vcpkg-artifact-${{ matrix.job.arch }}
|
|
||||||
path: /opt/artifacts/vcpkg/installed
|
|
||||||
|
|
||||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
|
||||||
name: Build rustdesk library for ${{ matrix.job.arch }}
|
|
||||||
id: vcpkg
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.job.arch }}
|
|
||||||
distro: ubuntu18.04-rustdesk
|
|
||||||
githubToken: ${{ github.token }}
|
|
||||||
setup: |
|
|
||||||
ls -l "${PWD}"
|
|
||||||
ls -l /opt/artifacts/vcpkg/installed
|
|
||||||
dockerRunArgs: |
|
|
||||||
--volume "${PWD}:/workspace"
|
|
||||||
--volume "/opt/artifacts:/opt/artifacts"
|
|
||||||
--volume "/opt/rust-registry:/opt/rust-registry"
|
|
||||||
shell: /bin/bash
|
|
||||||
install: |
|
|
||||||
apt update -y
|
|
||||||
echo -e "installing deps"
|
|
||||||
apt-get -qq install -y 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 libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev pkg-config tree g++ gcc libvpx-dev tree > /dev/null
|
|
||||||
# we have libopus compiled by us.
|
|
||||||
apt 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
|
|
||||||
wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-1.64.0-${{ matrix.job.target }}.tar.gz
|
|
||||||
tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz
|
|
||||||
cd rust-1.64.0-${{ matrix.job.target }} && ./install.sh
|
|
||||||
rm -rf rust-1.64.0-${{ matrix.job.target }}
|
|
||||||
# edit config
|
|
||||||
mkdir -p ~/.cargo/
|
|
||||||
echo """
|
|
||||||
[source.crates-io]
|
|
||||||
registry = 'https://github.com/rust-lang/crates.io-index'
|
|
||||||
replace-with = 'local-registry'
|
|
||||||
|
|
||||||
[source.local-registry]
|
|
||||||
local-registry = '/opt/rust-registry/'
|
|
||||||
""" > ~/.cargo/config
|
|
||||||
cat ~/.cargo/config
|
|
||||||
# start build
|
|
||||||
pushd /workspace
|
|
||||||
# mock
|
|
||||||
case "${{ matrix.job.arch }}" in
|
|
||||||
aarch64)
|
|
||||||
cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/aarch64-linux-gnu/
|
|
||||||
cp -r /opt/artifacts/vcpkg/installed/include/* /usr/include/
|
|
||||||
ls -l /opt/artifacts/vcpkg/installed/lib/
|
|
||||||
mkdir -p /vcpkg/installed/arm64-linux
|
|
||||||
ln -s /usr/lib/aarch64-linux-gnu /vcpkg/installed/arm64-linux/lib
|
|
||||||
ln -s /usr/include /vcpkg/installed/arm64-linux/include
|
|
||||||
export VCPKG_ROOT=/vcpkg
|
|
||||||
# disable hwcodec for compilation
|
|
||||||
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
|
||||||
;;
|
|
||||||
armv7)
|
|
||||||
cp -r /opt/artifacts/vcpkg/installed/lib/* /usr/lib/arm-linux-gnueabihf/
|
|
||||||
cp -r /opt/artifacts/vcpkg/installed/include/* /usr/include/
|
|
||||||
mkdir -p /vcpkg/installed/arm-linux
|
|
||||||
ln -s /usr/lib/arm-linux-gnueabihf /vcpkg/installed/arm-linux/lib
|
|
||||||
ln -s /usr/include /vcpkg/installed/arm-linux/include
|
|
||||||
export VCPKG_ROOT=/vcpkg
|
|
||||||
# disable hwcodec for compilation
|
|
||||||
cargo build --lib --features flutter,flutter_texture_render,${{ matrix.job.extra-build-features }} --release
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Upload Artifacts
|
|
||||||
uses: actions/upload-artifact@master
|
|
||||||
with:
|
|
||||||
name: librustdesk-${{ matrix.job.arch }}-${{ matrix.job.extra-build-features }}.so
|
|
||||||
path: target/release/liblibrustdesk.so
|
|
||||||
|
|
||||||
build-rustdesk-linux-arm:
|
|
||||||
needs: [build-rustdesk-lib-linux-arm]
|
|
||||||
name: build-rustdesk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ubuntu-20.04 # 20.04 has more performance on arm build
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
arch: aarch64,
|
|
||||||
target: aarch64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-18.04, # just for naming package, not running host
|
|
||||||
use-cross: true,
|
|
||||||
extra-build-features: "",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: aarch64,
|
|
||||||
target: aarch64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-18.04, # just for naming package, not running host
|
|
||||||
use-cross: true,
|
|
||||||
extra-build-features: "appimage",
|
|
||||||
}
|
|
||||||
# - {
|
|
||||||
# arch: aarch64,
|
|
||||||
# target: aarch64-unknown-linux-gnu,
|
|
||||||
# os: ubuntu-18.04, # just for naming package, not running host
|
|
||||||
# use-cross: true,
|
|
||||||
# extra-build-features: "flatpak",
|
|
||||||
# }
|
|
||||||
# - { arch: armv7, target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true, extra-build-features: "" }
|
|
||||||
# - { arch: armv7, target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true, extra-build-features: "flatpak" }
|
|
||||||
# - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Restore bridge files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: bridge-artifact
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Prepare env
|
|
||||||
run: |
|
|
||||||
sudo apt update -y
|
|
||||||
sudo apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev libarchive-tools
|
|
||||||
mkdir -p ./target/release/
|
|
||||||
|
|
||||||
- name: Restore the rustdesk lib file
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: librustdesk-${{ matrix.job.arch }}-${{ matrix.job.extra-build-features }}.so
|
|
||||||
path: ./target/release/
|
|
||||||
|
|
||||||
- name: Download Flutter
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# disable git safe.directory
|
|
||||||
git config --global --add safe.directory "*"
|
|
||||||
pushd /opt
|
|
||||||
# clone repo and reset to flutter 3.7.0
|
|
||||||
git clone https://github.com/sony/flutter-elinux.git || true
|
|
||||||
pushd flutter-elinux
|
|
||||||
# reset to flutter 3.7.0
|
|
||||||
git fetch
|
|
||||||
git reset --hard 51a1d685901f79fbac51665a967c3a1a789ecee5
|
|
||||||
popd
|
|
||||||
|
|
||||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
|
||||||
name: Build rustdesk binary for ${{ matrix.job.arch }}
|
|
||||||
id: vcpkg
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.job.arch }}
|
|
||||||
distro: ubuntu18.04-rustdesk
|
|
||||||
githubToken: ${{ github.token }}
|
|
||||||
setup: |
|
|
||||||
ls -l "${PWD}"
|
|
||||||
dockerRunArgs: |
|
|
||||||
--volume "${PWD}:/workspace"
|
|
||||||
--volume "/opt/artifacts:/opt/artifacts"
|
|
||||||
--volume "/opt/flutter-elinux:/opt/flutter-elinux"
|
|
||||||
shell: /bin/bash
|
|
||||||
install: |
|
|
||||||
apt update -y
|
|
||||||
apt-get -qq install -y git cmake g++ gcc build-essential nasm yasm curl unzip xz-utils python3 wget pkg-config ninja-build pkg-config libgtk-3-dev liblzma-dev clang libappindicator3-dev rpm
|
|
||||||
run: |
|
|
||||||
# disable git safe.directory
|
|
||||||
git config --global --add safe.directory "*"
|
|
||||||
pushd /workspace
|
|
||||||
# we use flutter-elinux to build our rustdesk
|
|
||||||
export PATH=/opt/flutter-elinux/bin:$PATH
|
|
||||||
sed -i "s/flutter build linux --release/flutter-elinux build linux/g" ./build.py
|
|
||||||
# Setup flutter-elinux. Run doctor to check if issues here.
|
|
||||||
flutter-elinux doctor -v
|
|
||||||
# Patch arm64 engine for flutter 3.6.0+
|
|
||||||
flutter-elinux precache --linux
|
|
||||||
pushd /tmp
|
|
||||||
curl -O https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.7.0-stable.tar.xz
|
|
||||||
tar -xvf flutter_linux_3.7.0-stable.tar.xz flutter/bin/cache/artifacts/engine/linux-x64/shader_lib
|
|
||||||
cp -R flutter/bin/cache/artifacts/engine/linux-x64/shader_lib /opt/flutter-elinux/flutter/bin/cache/artifacts/engine/linux-arm64
|
|
||||||
popd
|
|
||||||
case ${{ matrix.job.arch }} in
|
|
||||||
aarch64)
|
|
||||||
sed -i "s/Architecture: amd64/Architecture: arm64/g" ./build.py
|
|
||||||
sed -i "s/x64\/release/arm64\/release/g" ./build.py
|
|
||||||
;;
|
|
||||||
armv7)
|
|
||||||
sed -i "s/Architecture: amd64/Architecture: arm/g" ./build.py
|
|
||||||
sed -i "s/x64\/release/arm\/release/g" ./build.py
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
python3 ./build.py --flutter --hwcodec --skip-cargo
|
|
||||||
|
|
||||||
build-rustdesk-linux-amd64:
|
|
||||||
needs: [build-rustdesk-lib-linux-amd64]
|
|
||||||
name: build-rustdesk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
|
||||||
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "flatpak",
|
|
||||||
}
|
|
||||||
- {
|
|
||||||
arch: x86_64,
|
|
||||||
target: x86_64-unknown-linux-gnu,
|
|
||||||
os: ubuntu-20.04,
|
|
||||||
extra-build-features: "appimage",
|
|
||||||
}
|
|
||||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Restore bridge files
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: bridge-artifact
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Prepare env
|
|
||||||
run: |
|
|
||||||
sudo apt update -y
|
|
||||||
sudo apt-get -qq install -y git curl wget nasm yasm libgtk-3-dev libarchive-tools
|
|
||||||
mkdir -p ./target/release/
|
|
||||||
|
|
||||||
- name: Restore the rustdesk lib file
|
|
||||||
uses: actions/download-artifact@master
|
|
||||||
with:
|
|
||||||
name: librustdesk-${{ matrix.job.arch }}-${{ matrix.job.extra-build-features }}.so
|
|
||||||
path: ./target/release/
|
|
||||||
|
|
||||||
- uses: Kingtous/run-on-arch-action@amd64-support
|
|
||||||
name: Build rustdesk binary for ${{ matrix.job.arch }}
|
|
||||||
id: vcpkg
|
|
||||||
with:
|
|
||||||
arch: ${{ matrix.job.arch }}
|
|
||||||
distro: ubuntu18.04
|
|
||||||
githubToken: ${{ github.token }}
|
|
||||||
setup: |
|
|
||||||
ls -l "${PWD}"
|
|
||||||
dockerRunArgs: |
|
|
||||||
--volume "${PWD}:/workspace"
|
|
||||||
--volume "/opt/artifacts:/opt/artifacts"
|
|
||||||
shell: /bin/bash
|
|
||||||
install: |
|
|
||||||
apt update -y
|
|
||||||
apt-get -qq install -y git cmake g++ gcc build-essential nasm yasm curl unzip xz-utils python3 wget pkg-config ninja-build pkg-config libgtk-3-dev liblzma-dev clang libappindicator3-dev rpm
|
|
||||||
run: |
|
|
||||||
# disable git safe.directory
|
|
||||||
git config --global --add safe.directory "*"
|
|
||||||
# Setup Flutter
|
|
||||||
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
|
|
||||||
ls -l .
|
|
||||||
export PATH=/opt/flutter/bin:$PATH
|
|
||||||
flutter doctor -v
|
|
||||||
pushd /workspace
|
|
||||||
python3 ./build.py --flutter --hwcodec --skip-cargo
|
|
||||||
|
|||||||
1515
.github/workflows/flutter-nightly.yml
vendored
1515
.github/workflows/flutter-nightly.yml
vendored
File diff suppressed because it is too large
Load Diff
370
.github/workflows/history.yml
vendored
Normal file
370
.github/workflows/history.yml
vendored
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
name: Flutter Windows History Build
|
||||||
|
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
env:
|
||||||
|
LLVM_VERSION: "10.0"
|
||||||
|
# Note: currently 3.0.5 does not support arm64 officially, we use latest stable version first.
|
||||||
|
FLUTTER_VERSION: "3.0.5"
|
||||||
|
TAG_NAME: "tmp"
|
||||||
|
# vcpkg version: 2022.05.10
|
||||||
|
# for multiarch gcc compatibility
|
||||||
|
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
|
||||||
|
VERSION: "1.2.0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-for-windows-2022-12-05:
|
||||||
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||||
|
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||||
|
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: '8d1254cf14b69f545c9cefa026c5eeb0e7dd3e7c'
|
||||||
|
|
||||||
|
- name: Install LLVM and Clang
|
||||||
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
|
with:
|
||||||
|
version: ${{ env.LLVM_VERSION }}
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Replace engine with rustdesk custom flutter engine
|
||||||
|
run: |
|
||||||
|
flutter doctor -v
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.0.5-rustdesk.2/windows-x64-flutter-release.zip -OutFile windows-x64-flutter-release.zip
|
||||||
|
Expand-Archive windows-x64-flutter-release.zip -DestinationPath engine
|
||||||
|
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-3.0.5-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: ${{ matrix.job.target }}
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
run: |
|
||||||
|
dart pub global activate ffigen --version 5.0.1
|
||||||
|
$exists = Test-Path ~/.cargo/bin/flutter_rust_bridge_codegen.exe
|
||||||
|
Push-Location ..
|
||||||
|
git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1
|
||||||
|
Push-Location flutter_rust_bridge/frb_codegen ; cargo install --path . ; Pop-Location
|
||||||
|
Pop-Location
|
||||||
|
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||||
|
- name: Restore from cache and install vcpkg
|
||||||
|
uses: lukka/run-vcpkg@v7
|
||||||
|
with:
|
||||||
|
setupOnly: true
|
||||||
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
|
||||||
|
- name: Install vcpkg dependencies
|
||||||
|
run: |
|
||||||
|
$VCPKG_ROOT/vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
run: python3 .\build.py --portable --hwcodec --flutter
|
||||||
|
|
||||||
|
- name: Build self-extracted executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd ./libs/portable
|
||||||
|
python3 ./generate.py -f ../../flutter/build/windows/runner/Release/ -o . -e ../../flutter/build/windows/runner/Release/rustdesk.exe
|
||||||
|
popd
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-2022-12-05-${{ matrix.job.target }}.exe
|
||||||
|
|
||||||
|
- name: Publish Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
|
files: |
|
||||||
|
./SignOutput/rustdesk-*.exe
|
||||||
|
|
||||||
|
build-for-windows-2022-12-12:
|
||||||
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||||
|
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||||
|
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: '3dd43b79ec0409fc38103bed0c7eb0bc3cd993d5'
|
||||||
|
|
||||||
|
- name: Install LLVM and Clang
|
||||||
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
|
with:
|
||||||
|
version: ${{ env.LLVM_VERSION }}
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Replace engine with rustdesk custom flutter engine
|
||||||
|
run: |
|
||||||
|
flutter doctor -v
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.0.5-rustdesk.2/windows-x64-flutter-release.zip -OutFile windows-x64-flutter-release.zip
|
||||||
|
Expand-Archive windows-x64-flutter-release.zip -DestinationPath engine
|
||||||
|
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-3.0.5-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: ${{ matrix.job.target }}
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
run: |
|
||||||
|
dart pub global activate ffigen --version 5.0.1
|
||||||
|
$exists = Test-Path ~/.cargo/bin/flutter_rust_bridge_codegen.exe
|
||||||
|
Push-Location ..
|
||||||
|
git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1
|
||||||
|
Push-Location flutter_rust_bridge/frb_codegen ; cargo install --path . ; Pop-Location
|
||||||
|
Pop-Location
|
||||||
|
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||||
|
- name: Restore from cache and install vcpkg
|
||||||
|
uses: lukka/run-vcpkg@v7
|
||||||
|
with:
|
||||||
|
setupOnly: true
|
||||||
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
|
||||||
|
- name: Install vcpkg dependencies
|
||||||
|
run: |
|
||||||
|
$VCPKG_ROOT/vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
run: python3 .\build.py --portable --hwcodec --flutter
|
||||||
|
|
||||||
|
- name: Build self-extracted executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd ./libs/portable
|
||||||
|
python3 ./generate.py -f ../../flutter/build/windows/runner/Release/ -o . -e ../../flutter/build/windows/runner/Release/rustdesk.exe
|
||||||
|
popd
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-2022-12-12-${{ matrix.job.target }}.exe
|
||||||
|
|
||||||
|
- name: Publish Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
|
files: |
|
||||||
|
./SignOutput/rustdesk-*.exe
|
||||||
|
|
||||||
|
build-for-windows-2022-12-19:
|
||||||
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||||
|
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||||
|
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: '1054715891c4e73ad9b164acec6dadecfc599a65'
|
||||||
|
|
||||||
|
- name: Install LLVM and Clang
|
||||||
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
|
with:
|
||||||
|
version: ${{ env.LLVM_VERSION }}
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Replace engine with rustdesk custom flutter engine
|
||||||
|
run: |
|
||||||
|
flutter doctor -v
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.0.5-rustdesk.2/windows-x64-flutter-release.zip -OutFile windows-x64-flutter-release.zip
|
||||||
|
Expand-Archive windows-x64-flutter-release.zip -DestinationPath engine
|
||||||
|
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-3.0.5-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: ${{ matrix.job.target }}
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
run: |
|
||||||
|
dart pub global activate ffigen --version 5.0.1
|
||||||
|
$exists = Test-Path ~/.cargo/bin/flutter_rust_bridge_codegen.exe
|
||||||
|
Push-Location ..
|
||||||
|
git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1
|
||||||
|
Push-Location flutter_rust_bridge/frb_codegen ; cargo install --path . ; Pop-Location
|
||||||
|
Pop-Location
|
||||||
|
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||||
|
- name: Restore from cache and install vcpkg
|
||||||
|
uses: lukka/run-vcpkg@v7
|
||||||
|
with:
|
||||||
|
setupOnly: true
|
||||||
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
|
||||||
|
- name: Install vcpkg dependencies
|
||||||
|
run: |
|
||||||
|
$VCPKG_ROOT/vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
run: python3 .\build.py --portable --hwcodec --flutter
|
||||||
|
|
||||||
|
- name: Build self-extracted executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd ./libs/portable
|
||||||
|
python3 ./generate.py -f ../../flutter/build/windows/runner/Release/ -o . -e ../../flutter/build/windows/runner/Release/rustdesk.exe
|
||||||
|
popd
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-2022-12-19-${{ matrix.job.target }}.exe
|
||||||
|
|
||||||
|
- name: Publish Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
|
files: |
|
||||||
|
./SignOutput/rustdesk-*.exe
|
||||||
|
|
||||||
|
build-for-windows-2022-12-26:
|
||||||
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
# - { target: i686-pc-windows-msvc , os: windows-2019 }
|
||||||
|
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||||
|
- { target: x86_64-pc-windows-msvc, os: windows-2019 }
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: 'b241925fe093dc4da804a5aac419375f4ca7653f'
|
||||||
|
|
||||||
|
- name: Install LLVM and Clang
|
||||||
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
|
with:
|
||||||
|
version: ${{ env.LLVM_VERSION }}
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Replace engine with rustdesk custom flutter engine
|
||||||
|
run: |
|
||||||
|
flutter doctor -v
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/Kingtous/engine/releases/download/v3.0.5-rustdesk.2/windows-x64-flutter-release.zip -OutFile windows-x64-flutter-release.zip
|
||||||
|
Expand-Archive windows-x64-flutter-release.zip -DestinationPath engine
|
||||||
|
mv -Force engine/* C:/hostedtoolcache/windows/flutter/stable-3.0.5-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: "1.62"
|
||||||
|
target: ${{ matrix.job.target }}
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
run: |
|
||||||
|
dart pub global activate ffigen --version 5.0.1
|
||||||
|
$exists = Test-Path ~/.cargo/bin/flutter_rust_bridge_codegen.exe
|
||||||
|
Push-Location ..
|
||||||
|
git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1
|
||||||
|
Push-Location flutter_rust_bridge/frb_codegen ; cargo install --path . ; Pop-Location
|
||||||
|
Pop-Location
|
||||||
|
Push-Location flutter ; flutter pub get ; Pop-Location
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
||||||
|
- name: Restore from cache and install vcpkg
|
||||||
|
uses: lukka/run-vcpkg@v7
|
||||||
|
with:
|
||||||
|
setupOnly: true
|
||||||
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
|
||||||
|
- name: Install vcpkg dependencies
|
||||||
|
run: |
|
||||||
|
$VCPKG_ROOT/vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
run: python3 .\build.py --portable --hwcodec --flutter
|
||||||
|
|
||||||
|
- name: Build self-extracted executable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
pushd ./libs/portable
|
||||||
|
python3 ./generate.py -f ../../flutter/build/windows/runner/Release/ -o . -e ../../flutter/build/windows/runner/Release/rustdesk.exe
|
||||||
|
popd
|
||||||
|
mkdir -p ./SignOutput
|
||||||
|
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-2022-12-26-${{ matrix.job.target }}.exe
|
||||||
|
|
||||||
|
- name: Publish Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
prerelease: true
|
||||||
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
|
files: |
|
||||||
|
./SignOutput/rustdesk-*.exe
|
||||||
88
.github/workflows/vcpkg-deps-linux.yml
vendored
Normal file
88
.github/workflows/vcpkg-deps-linux.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
name: Build vcpkg dependencies for linux clients
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-vcpkg-deps-linux:
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- { arch: armv7, os: ubuntu-20.04 }
|
||||||
|
- { arch: x86_64, os: ubuntu-20.04 }
|
||||||
|
- { arch: aarch64, os: ubuntu-20.04 }
|
||||||
|
steps:
|
||||||
|
- name: Create vcpkg artifacts folder
|
||||||
|
run: mkdir -p /opt/artifacts
|
||||||
|
|
||||||
|
- name: Cache Vcpkg
|
||||||
|
id: cache-vcpkg
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /opt/artifacts
|
||||||
|
key: vcpkg-${{ matrix.job.arch }}
|
||||||
|
|
||||||
|
- uses: Kingtous/run-on-arch-action@amd64-support
|
||||||
|
name: Run vcpkg install on ${{ matrix.job.arch }}
|
||||||
|
id: vcpkg
|
||||||
|
with:
|
||||||
|
arch: ${{ matrix.job.arch }}
|
||||||
|
distro: ubuntu18.04
|
||||||
|
githubToken: ${{ github.token }}
|
||||||
|
setup: |
|
||||||
|
ls -l "/opt/artifacts"
|
||||||
|
dockerRunArgs: |
|
||||||
|
--volume "/opt/artifacts:/artifacts"
|
||||||
|
shell: /bin/bash
|
||||||
|
install: |
|
||||||
|
apt update -y
|
||||||
|
case "${{ matrix.job.arch }}" in
|
||||||
|
x86_64)
|
||||||
|
# CMake 3.15+
|
||||||
|
apt install -y gpg wget ca-certificates
|
||||||
|
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
|
||||||
|
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
|
||||||
|
apt update -y
|
||||||
|
apt install -y curl zip unzip tar git cmake g++ gcc build-essential pkg-config wget nasm yasm ninja-build libjpeg8-dev
|
||||||
|
cmake --version
|
||||||
|
gcc -v
|
||||||
|
;;
|
||||||
|
aarch64|armv7)
|
||||||
|
apt install -y curl zip unzip git
|
||||||
|
esac
|
||||||
|
run: |
|
||||||
|
# disable git safe.directory
|
||||||
|
git config --global --add safe.directory "*"
|
||||||
|
case "${{ matrix.job.arch }}" in
|
||||||
|
x86_64)
|
||||||
|
export VCPKG_FORCE_SYSTEM_BINARIES=1
|
||||||
|
pushd /artifacts
|
||||||
|
git clone https://github.com/microsoft/vcpkg.git || true
|
||||||
|
pushd vcpkg
|
||||||
|
git reset --hard ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
./bootstrap-vcpkg.sh
|
||||||
|
./vcpkg install libvpx libyuv opus
|
||||||
|
;;
|
||||||
|
aarch64)
|
||||||
|
pushd /artifacts
|
||||||
|
rm -rf rustdesk_thirdparty_lib
|
||||||
|
git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
|
||||||
|
mkdir -p /artifacts/vcpkg/installed
|
||||||
|
mv ./rustdesk_thirdparty_lib/vcpkg/installed/arm64-linux /artifacts/vcpkg/installed/arm64-linux
|
||||||
|
;;
|
||||||
|
armv7)
|
||||||
|
pushd /artifacts
|
||||||
|
rm -rf rustdesk_thirdparty_lib
|
||||||
|
git clone https://github.com/Kingtous/rustdesk_thirdparty_lib.git --depth=1
|
||||||
|
mkdir -p /artifacts/vcpkg/installed
|
||||||
|
mv ./rustdesk_thirdparty_lib/vcpkg/installed/arm-linux /artifacts/vcpkg/installed/arm-linux
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@master
|
||||||
|
with:
|
||||||
|
name: vcpkg-artifact-${{ matrix.job.arch }}
|
||||||
|
path: |
|
||||||
|
/opt/artifacts/vcpkg/installed
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -27,6 +27,7 @@ rustdesk
|
|||||||
appimage/AppDir
|
appimage/AppDir
|
||||||
appimage/*.AppImage
|
appimage/*.AppImage
|
||||||
appimage/appimage-build
|
appimage/appimage-build
|
||||||
|
appimage/*.xz
|
||||||
# flutter
|
# flutter
|
||||||
flutter/linux/build/**
|
flutter/linux/build/**
|
||||||
flutter/linux/cmake-build-debug/**
|
flutter/linux/cmake-build-debug/**
|
||||||
@@ -38,6 +39,8 @@ flatpak/.flatpak-builder/shared-modules/**
|
|||||||
flatpak/.flatpak-builder/shared-modules/*.tar.xz
|
flatpak/.flatpak-builder/shared-modules/*.tar.xz
|
||||||
flatpak/.flatpak-builder/debian-binary
|
flatpak/.flatpak-builder/debian-binary
|
||||||
flatpak/build/**
|
flatpak/build/**
|
||||||
|
flatpak/repo/**
|
||||||
|
flatpak/*.flatpak
|
||||||
# bridge file
|
# bridge file
|
||||||
lib/generated_bridge.dart
|
lib/generated_bridge.dart
|
||||||
# vscode devcontainer
|
# vscode devcontainer
|
||||||
@@ -45,3 +48,5 @@ lib/generated_bridge.dart
|
|||||||
.vscode-server/
|
.vscode-server/
|
||||||
.ssh
|
.ssh
|
||||||
.devcontainer/.*
|
.devcontainer/.*
|
||||||
|
# build cache in examples
|
||||||
|
examples/**/target/
|
||||||
1414
Cargo.lock
generated
1414
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
40
Cargo.toml
40
Cargo.toml
@@ -19,7 +19,6 @@ path = "src/naming.rs"
|
|||||||
inline = []
|
inline = []
|
||||||
hbbs = []
|
hbbs = []
|
||||||
cli = []
|
cli = []
|
||||||
with_rc = ["simple_rc"]
|
|
||||||
flutter_texture_render = []
|
flutter_texture_render = []
|
||||||
appimage = []
|
appimage = []
|
||||||
flatpak = []
|
flatpak = []
|
||||||
@@ -30,6 +29,9 @@ flutter = ["flutter_rust_bridge"]
|
|||||||
default = ["use_dasp"]
|
default = ["use_dasp"]
|
||||||
hwcodec = ["scrap/hwcodec"]
|
hwcodec = ["scrap/hwcodec"]
|
||||||
mediacodec = ["scrap/mediacodec"]
|
mediacodec = ["scrap/mediacodec"]
|
||||||
|
linux_headless = ["pam", "users"]
|
||||||
|
virtual_display_driver = ["virtual_display"]
|
||||||
|
plugin_framework = []
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -57,21 +59,22 @@ rpassword = "7.0"
|
|||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
bytes = { version = "1.2", features = ["serde"] }
|
bytes = { version = "1.2", features = ["serde"] }
|
||||||
default-net = "0.12.0"
|
default-net = "0.14"
|
||||||
wol-rs = "1.0"
|
wol-rs = "1.0"
|
||||||
flutter_rust_bridge = { version = "1.61.1", optional = true }
|
flutter_rust_bridge = { version = "1.61.1", optional = true }
|
||||||
errno = "0.3"
|
errno = "0.3"
|
||||||
rdev = { git = "https://github.com/fufesou/rdev" }
|
rdev = { git = "https://github.com/fufesou/rdev" }
|
||||||
url = { version = "2.1", features = ["serde"] }
|
url = { version = "2.1", features = ["serde"] }
|
||||||
dlopen = "0.1"
|
crossbeam-queue = "0.3"
|
||||||
hex = "0.4.3"
|
hex = "0.4"
|
||||||
|
|
||||||
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
|
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
|
||||||
chrono = "0.4.23"
|
chrono = "0.4"
|
||||||
cidr-utils = "0.5.9"
|
cidr-utils = "0.5"
|
||||||
|
libloading = "0.7"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
||||||
cpal = "0.14"
|
cpal = "0.15"
|
||||||
|
ringbuf = "0.3"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
machine-uid = "0.2"
|
machine-uid = "0.2"
|
||||||
@@ -91,10 +94,10 @@ winit = "0.26"
|
|||||||
winapi = { version = "0.3", features = ["winuser", "wincrypt"] }
|
winapi = { version = "0.3", features = ["winuser", "wincrypt"] }
|
||||||
winreg = "0.10"
|
winreg = "0.10"
|
||||||
windows-service = "0.4"
|
windows-service = "0.4"
|
||||||
virtual_display = { path = "libs/virtual_display" }
|
virtual_display = { path = "libs/virtual_display", optional = true }
|
||||||
impersonate_system = { git = "https://github.com/21pages/impersonate-system" }
|
impersonate_system = { git = "https://github.com/21pages/impersonate-system" }
|
||||||
shared_memory = "0.12.4"
|
shared_memory = "0.12"
|
||||||
shutdown_hooks = "0.1.0"
|
shutdown_hooks = "0.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
objc = "0.2"
|
objc = "0.2"
|
||||||
@@ -102,10 +105,10 @@ cocoa = "0.24"
|
|||||||
dispatch = "0.2"
|
dispatch = "0.2"
|
||||||
core-foundation = "0.9"
|
core-foundation = "0.9"
|
||||||
core-graphics = "0.22"
|
core-graphics = "0.22"
|
||||||
include_dir = "0.7.2"
|
include_dir = "0.7"
|
||||||
dark-light = "1.0"
|
dark-light = "1.0"
|
||||||
fruitbasket = "0.10.0"
|
fruitbasket = "0.10"
|
||||||
objc_id = "0.1.1"
|
objc_id = "0.1"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
|
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
|
||||||
tray-icon = "0.4"
|
tray-icon = "0.4"
|
||||||
@@ -121,7 +124,8 @@ mouce = { git="https://github.com/fufesou/mouce.git" }
|
|||||||
evdev = { git="https://github.com/fufesou/evdev" }
|
evdev = { git="https://github.com/fufesou/evdev" }
|
||||||
dbus = "0.9"
|
dbus = "0.9"
|
||||||
dbus-crossroads = "0.5"
|
dbus-crossroads = "0.5"
|
||||||
xrandr-parser = "0.3.0"
|
pam = { git="https://github.com/fufesou/pam", optional = true }
|
||||||
|
users = { version = "0.11.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = "0.11"
|
android_logger = "0.11"
|
||||||
@@ -131,8 +135,8 @@ jni = "0.19"
|
|||||||
flutter_rust_bridge = "1.61.1"
|
flutter_rust_bridge = "1.61.1"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/simple_rc", "libs/portable"]
|
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable"]
|
||||||
exclude = ["vdi/host"]
|
exclude = ["vdi/host", "examples/custom_plugin"]
|
||||||
|
|
||||||
[package.metadata.winres]
|
[package.metadata.winres]
|
||||||
LegalCopyright = "Copyright © 2022 Purslane, Inc."
|
LegalCopyright = "Copyright © 2022 Purslane, Inc."
|
||||||
@@ -146,7 +150,6 @@ winapi = { version = "0.3", features = [ "winnt" ] }
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
hbb_common = { path = "libs/hbb_common" }
|
hbb_common = { path = "libs/hbb_common" }
|
||||||
simple_rc = { path = "libs/simple_rc", optional = true }
|
|
||||||
flutter_rust_bridge_codegen = "1.61.1"
|
flutter_rust_bridge_codegen = "1.61.1"
|
||||||
os-version = "0.2"
|
os-version = "0.2"
|
||||||
|
|
||||||
@@ -157,7 +160,6 @@ hound = "3.5"
|
|||||||
name = "RustDesk"
|
name = "RustDesk"
|
||||||
identifier = "com.carriez.rustdesk"
|
identifier = "com.carriez.rustdesk"
|
||||||
icon = ["res/32x32.png", "res/128x128.png", "res/128x128@2x.png"]
|
icon = ["res/32x32.png", "res/128x128.png", "res/128x128@2x.png"]
|
||||||
deb_depends = ["libgtk-3-0", "libxcb-randr0", "libxdo3", "libxfixes3", "libxcb-shape0", "libxcb-xfixes0", "libasound2", "libsystemd0", "curl", "libvdpau1", "libva2"]
|
|
||||||
osx_minimum_system_version = "10.14"
|
osx_minimum_system_version = "10.14"
|
||||||
|
|
||||||
#https://github.com/johnthagen/min-sized-rust
|
#https://github.com/johnthagen/min-sized-rust
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ AppDir:
|
|||||||
- sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted
|
- sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted
|
||||||
universe multiverse
|
universe multiverse
|
||||||
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
|
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
|
||||||
|
- sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted
|
||||||
|
universe multiverse
|
||||||
|
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
|
||||||
include:
|
include:
|
||||||
- libc6
|
- libc6
|
||||||
- libgtk-3-0
|
- libgtk-3-0
|
||||||
@@ -47,6 +50,9 @@ AppDir:
|
|||||||
- libva-x11-2
|
- libva-x11-2
|
||||||
- libvdpau1
|
- libvdpau1
|
||||||
- libgstreamer-plugins-base1.0-0
|
- libgstreamer-plugins-base1.0-0
|
||||||
|
- libwayland-cursor0
|
||||||
|
- libwayland-egl1
|
||||||
|
- libpulse0
|
||||||
exclude:
|
exclude:
|
||||||
- humanity-icon-theme
|
- humanity-icon-theme
|
||||||
- hicolor-icon-theme
|
- hicolor-icon-theme
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ AppDir:
|
|||||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse
|
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse
|
||||||
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted
|
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted
|
||||||
universe multiverse
|
universe multiverse
|
||||||
|
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted
|
||||||
|
universe multiverse
|
||||||
- sourceline: deb http://ppa.launchpad.net/pipewire-debian/pipewire-upstream/ubuntu
|
- sourceline: deb http://ppa.launchpad.net/pipewire-debian/pipewire-upstream/ubuntu
|
||||||
bionic main
|
bionic main
|
||||||
include:
|
include:
|
||||||
@@ -50,6 +52,9 @@ AppDir:
|
|||||||
- libva-x11-2
|
- libva-x11-2
|
||||||
- libvdpau1
|
- libvdpau1
|
||||||
- libgstreamer-plugins-base1.0-0
|
- libgstreamer-plugins-base1.0-0
|
||||||
|
- libwayland-cursor0
|
||||||
|
- libwayland-egl1
|
||||||
|
- libpulse0
|
||||||
exclude:
|
exclude:
|
||||||
- humanity-icon-theme
|
- humanity-icon-theme
|
||||||
- hicolor-icon-theme
|
- hicolor-icon-theme
|
||||||
|
|||||||
146
build.py
146
build.py
@@ -15,9 +15,21 @@ osx = platform.platform().startswith(
|
|||||||
'Darwin') or platform.platform().startswith("macOS")
|
'Darwin') or platform.platform().startswith("macOS")
|
||||||
hbb_name = 'rustdesk' + ('.exe' if windows else '')
|
hbb_name = 'rustdesk' + ('.exe' if windows else '')
|
||||||
exe_path = 'target/release/' + hbb_name
|
exe_path = 'target/release/' + hbb_name
|
||||||
flutter_win_target_dir = 'flutter/build/windows/runner/Release/'
|
if windows:
|
||||||
|
flutter_build_dir = 'build/windows/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
|
skip_cargo = False
|
||||||
|
|
||||||
|
def get_arch() -> str:
|
||||||
|
custom_arch = os.environ.get("ARCH")
|
||||||
|
if custom_arch is None:
|
||||||
|
return "amd64"
|
||||||
|
return custom_arch
|
||||||
|
|
||||||
def system2(cmd):
|
def system2(cmd):
|
||||||
err = os.system(cmd)
|
err = os.system(cmd)
|
||||||
if err != 0:
|
if err != 0:
|
||||||
@@ -35,11 +47,13 @@ def get_version():
|
|||||||
def parse_rc_features(feature):
|
def parse_rc_features(feature):
|
||||||
available_features = {
|
available_features = {
|
||||||
'IddDriver': {
|
'IddDriver': {
|
||||||
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip',
|
'platform': ['windows'],
|
||||||
'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/checksum_md5',
|
'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.3/RustDeskIddDriver_x64.zip',
|
||||||
|
'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.3/checksum_md5',
|
||||||
'exclude': ['README.md', 'certmgr.exe', 'install_cert_runas_admin.bat'],
|
'exclude': ['README.md', 'certmgr.exe', 'install_cert_runas_admin.bat'],
|
||||||
},
|
},
|
||||||
'PrivacyMode': {
|
'PrivacyMode': {
|
||||||
|
'platform': ['windows'],
|
||||||
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
|
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1'
|
||||||
'/TempTopMostWindow_x64_pic_en.zip',
|
'/TempTopMostWindow_x64_pic_en.zip',
|
||||||
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1/checksum_md5',
|
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1/checksum_md5',
|
||||||
@@ -49,15 +63,33 @@ def parse_rc_features(feature):
|
|||||||
apply_features = {}
|
apply_features = {}
|
||||||
if not feature:
|
if not feature:
|
||||||
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':
|
if isinstance(feature, str) and feature.upper() == 'ALL':
|
||||||
return available_features
|
return get_all_features()
|
||||||
elif isinstance(feature, list):
|
elif isinstance(feature, list):
|
||||||
|
if windows:
|
||||||
# force add PrivacyMode
|
# force add PrivacyMode
|
||||||
feature.append('PrivacyMode')
|
feature.append('PrivacyMode')
|
||||||
for feat in feature:
|
for feat in feature:
|
||||||
if isinstance(feat, str) and feat.upper() == 'ALL':
|
if isinstance(feat, str) and feat.upper() == 'ALL':
|
||||||
return available_features
|
return get_all_features()
|
||||||
if feat in available_features:
|
if feat in available_features:
|
||||||
|
if platform_check(available_features[feat]['platform']):
|
||||||
apply_features[feat] = available_features[feat]
|
apply_features[feat] = available_features[feat]
|
||||||
else:
|
else:
|
||||||
print(f'Unrecognized feature {feat}')
|
print(f'Unrecognized feature {feat}')
|
||||||
@@ -106,6 +138,10 @@ def make_parser():
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Skip cargo build process, only flutter version + Linux supported currently'
|
help='Skip cargo build process, only flutter version + Linux supported currently'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--package",
|
||||||
|
type=str
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -201,14 +237,12 @@ def download_extract_features(features, res_dir):
|
|||||||
print(f'{feat} extract end')
|
print(f'{feat} extract end')
|
||||||
|
|
||||||
|
|
||||||
def get_rc_features(args):
|
def external_resources(flutter, args, res_dir):
|
||||||
flutter = args.flutter
|
|
||||||
features = parse_rc_features(args.feature)
|
features = parse_rc_features(args.feature)
|
||||||
if not features:
|
if not features:
|
||||||
return []
|
return
|
||||||
|
|
||||||
print(f'Build with features {list(features.keys())}')
|
print(f'Build with features {list(features.keys())}')
|
||||||
res_dir = 'resources'
|
|
||||||
if os.path.isdir(res_dir) and not os.path.islink(res_dir):
|
if os.path.isdir(res_dir) and not os.path.islink(res_dir):
|
||||||
shutil.rmtree(res_dir)
|
shutil.rmtree(res_dir)
|
||||||
elif os.path.exists(res_dir):
|
elif os.path.exists(res_dir):
|
||||||
@@ -216,22 +250,19 @@ def get_rc_features(args):
|
|||||||
os.makedirs(res_dir, exist_ok=True)
|
os.makedirs(res_dir, exist_ok=True)
|
||||||
download_extract_features(features, res_dir)
|
download_extract_features(features, res_dir)
|
||||||
if flutter:
|
if flutter:
|
||||||
os.makedirs(flutter_win_target_dir, exist_ok=True)
|
os.makedirs(flutter_build_dir_2, exist_ok=True)
|
||||||
for f in pathlib.Path(res_dir).iterdir():
|
for f in pathlib.Path(res_dir).iterdir():
|
||||||
print(f'{f}')
|
print(f'{f}')
|
||||||
if f.is_file():
|
if f.is_file():
|
||||||
shutil.copy2(f, flutter_win_target_dir)
|
shutil.copy2(f, flutter_build_dir_2)
|
||||||
else:
|
else:
|
||||||
shutil.copytree(f, f'{flutter_win_target_dir}{f.stem}')
|
shutil.copytree(f, f'{flutter_build_dir_2}{f.stem}')
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return ['with_rc']
|
|
||||||
|
|
||||||
|
|
||||||
def get_features(args):
|
def get_features(args):
|
||||||
features = ['inline'] if not args.flutter else []
|
features = ['inline'] if not args.flutter else []
|
||||||
if windows:
|
if windows:
|
||||||
features.extend(get_rc_features(args))
|
features.append('virtual_display_driver')
|
||||||
if args.hwcodec:
|
if args.hwcodec:
|
||||||
features.append('hwcodec')
|
features.append('hwcodec')
|
||||||
if args.flutter:
|
if args.flutter:
|
||||||
@@ -251,13 +282,13 @@ def generate_control_file(version):
|
|||||||
|
|
||||||
content = """Package: rustdesk
|
content = """Package: rustdesk
|
||||||
Version: %s
|
Version: %s
|
||||||
Architecture: amd64
|
Architecture: %s
|
||||||
Maintainer: open-trade <info@rustdesk.com>
|
Maintainer: rustdesk <info@rustdesk.com>
|
||||||
Homepage: https://rustdesk.com
|
Homepage: https://rustdesk.com
|
||||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0
|
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g
|
||||||
Description: A remote control software.
|
Description: A remote control software.
|
||||||
|
|
||||||
""" % version
|
""" % (version, get_arch())
|
||||||
file = open(control_file_path, "w")
|
file = open(control_file_path, "w")
|
||||||
file.write(content)
|
file.write(content)
|
||||||
file.close()
|
file.close()
|
||||||
@@ -277,12 +308,54 @@ def build_flutter_deb(version, features):
|
|||||||
system2('flutter build linux --release')
|
system2('flutter build linux --release')
|
||||||
system2('mkdir -p tmpdeb/usr/bin/')
|
system2('mkdir -p tmpdeb/usr/bin/')
|
||||||
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
||||||
|
system2('mkdir -p tmpdeb/etc/rustdesk/')
|
||||||
|
system2('mkdir -p tmpdeb/etc/pam.d/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/applications/')
|
system2('mkdir -p tmpdeb/usr/share/applications/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
||||||
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
||||||
system2(
|
system2(
|
||||||
'cp -r build/linux/x64/release/bundle/* tmpdeb/usr/lib/rustdesk/')
|
f'cp -r {flutter_build_dir}/* tmpdeb/usr/lib/rustdesk/')
|
||||||
|
system2(
|
||||||
|
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
|
system2(
|
||||||
|
'cp ../res/128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
|
||||||
|
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(
|
||||||
|
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
|
||||||
|
system2(
|
||||||
|
'cp ../res/startwm.sh tmpdeb/etc/rustdesk/')
|
||||||
|
system2(
|
||||||
|
'cp ../res/xorg.conf tmpdeb/etc/rustdesk/')
|
||||||
|
system2(
|
||||||
|
'cp ../res/pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
|
||||||
|
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('usr/share/rustdesk/files/systemd/rustdesk.service')
|
||||||
|
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/lib/rustdesk')
|
||||||
|
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
|
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/lib/rustdesk/')
|
||||||
system2(
|
system2(
|
||||||
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
system2(
|
system2(
|
||||||
@@ -307,7 +380,6 @@ def build_flutter_deb(version, features):
|
|||||||
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
|
||||||
os.chdir("..")
|
os.chdir("..")
|
||||||
|
|
||||||
|
|
||||||
def build_flutter_dmg(version, features):
|
def build_flutter_dmg(version, features):
|
||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
||||||
@@ -329,7 +401,7 @@ def build_flutter_arch_manjaro(version, features):
|
|||||||
ffi_bindgen_function_refactor()
|
ffi_bindgen_function_refactor()
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
system2('flutter build linux --release')
|
system2('flutter build linux --release')
|
||||||
system2('strip build/linux/x64/release/bundle/lib/librustdesk.so')
|
system2(f'strip {flutter_build_dir}/lib/librustdesk.so')
|
||||||
os.chdir('../res')
|
os.chdir('../res')
|
||||||
system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
|
system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
|
||||||
|
|
||||||
@@ -344,11 +416,11 @@ def build_flutter_windows(version, features):
|
|||||||
system2('flutter build windows --release')
|
system2('flutter build windows --release')
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
|
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
|
||||||
flutter_win_target_dir)
|
flutter_build_dir_2)
|
||||||
os.chdir('libs/portable')
|
os.chdir('libs/portable')
|
||||||
system2('pip3 install -r requirements.txt')
|
system2('pip3 install -r requirements.txt')
|
||||||
system2(
|
system2(
|
||||||
f'python3 ./generate.py -f ../../{flutter_win_target_dir} -o . -e ../../{flutter_win_target_dir}/rustdesk.exe')
|
f'python3 ./generate.py -f ../../{flutter_build_dir_2} -o . -e ../../{flutter_build_dir_2}/rustdesk.exe')
|
||||||
os.chdir('../..')
|
os.chdir('../..')
|
||||||
if os.path.exists('./rustdesk_portable.exe'):
|
if os.path.exists('./rustdesk_portable.exe'):
|
||||||
os.replace('./target/release/rustdesk-portable-packer.exe',
|
os.replace('./target/release/rustdesk-portable-packer.exe',
|
||||||
@@ -381,6 +453,12 @@ def main():
|
|||||||
if args.skip_cargo:
|
if args.skip_cargo:
|
||||||
skip_cargo = True
|
skip_cargo = True
|
||||||
portable = args.portable
|
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:
|
if windows:
|
||||||
# build virtual display dynamic library
|
# build virtual display dynamic library
|
||||||
os.chdir('libs/virtual_display/dylib')
|
os.chdir('libs/virtual_display/dylib')
|
||||||
@@ -401,7 +479,12 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print('Not signed')
|
print('Not signed')
|
||||||
system2(
|
system2(
|
||||||
f'cp -rf target/release/RustDesk.exe rustdesk-{version}-win7-install.exe')
|
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('mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..')
|
||||||
elif os.path.isfile('/usr/bin/pacman'):
|
elif os.path.isfile('/usr/bin/pacman'):
|
||||||
# pacman -S -needed base-devel
|
# pacman -S -needed base-devel
|
||||||
system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)
|
system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)
|
||||||
@@ -506,12 +589,21 @@ def main():
|
|||||||
'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
|
'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
|
||||||
system2(
|
system2(
|
||||||
'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
|
'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
|
||||||
system2('cp -a res/DEBIAN/* tmpdeb/DEBIAN/')
|
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('strip tmpdeb/usr/bin/rustdesk')
|
||||||
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
||||||
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
|
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
|
||||||
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
|
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
|
||||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
||||||
|
md5_file('etc/rustdesk/startwm.sh')
|
||||||
|
md5_file('etc/X11/rustdesk/xorg.conf')
|
||||||
|
md5_file('etc/pam.d/rustdesk')
|
||||||
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
|
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
|
||||||
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
||||||
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
|
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
|
||||||
|
|||||||
20
build.rs
20
build.rs
@@ -41,20 +41,6 @@ fn build_manifest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "with_rc"))]
|
|
||||||
fn build_rc_source() {
|
|
||||||
use simple_rc::{generate_with_conf, Config, ConfigItem};
|
|
||||||
generate_with_conf(&Config {
|
|
||||||
outfile: "src/rc.rs".to_owned(),
|
|
||||||
confs: vec![ConfigItem {
|
|
||||||
inc: "resources".to_owned(),
|
|
||||||
exc: vec![],
|
|
||||||
suppressed_front: "resources".to_owned(),
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_oboe() {
|
fn install_oboe() {
|
||||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||||
if target_os != "android" {
|
if target_os != "android" {
|
||||||
@@ -133,15 +119,15 @@ fn main() {
|
|||||||
gen_flutter_rust_bridge();
|
gen_flutter_rust_bridge();
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
#[cfg(all(windows, feature = "with_rc"))]
|
|
||||||
build_rc_source();
|
|
||||||
#[cfg(all(windows, feature = "inline"))]
|
#[cfg(all(windows, feature = "inline"))]
|
||||||
build_manifest();
|
build_manifest();
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
build_windows();
|
build_windows();
|
||||||
|
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||||
|
if target_os == "macos" {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
build_mac();
|
build_mac();
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
println!("cargo:rustc-link-lib=framework=ApplicationServices");
|
println!("cargo:rustc-link-lib=framework=ApplicationServices");
|
||||||
|
}
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
}
|
}
|
||||||
|
|||||||
133
docs/CODE_OF_CONDUCT-PL.md
Normal file
133
docs/CODE_OF_CONDUCT-PL.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
45
docs/CONTRIBUTING-PL.md
Normal file
45
docs/CONTRIBUTING-PL.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 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).
|
||||||
14
docs/DEVCONTAINER-PL.md
Normal file
14
docs/DEVCONTAINER-PL.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
Po uruchomieniu devcontainer w kontenerze docker, tworzony jest plik binarny linux w trybue debugowania.
|
||||||
|
|
||||||
|
Obecnie devcontainer oferuje kompilowanie wersji dla linux i android w obu trybach - debugowania i wersji finalnej.
|
||||||
|
|
||||||
|
Poniżej tabela poleceń do uruchomienia z głównego folderu do tworzenia wybranych kompilacji.
|
||||||
|
|
||||||
|
Polecenie|Typ kompilacji|Tryb
|
||||||
|
-|-|-|
|
||||||
|
`.devcontainer/build.sh --debug linux`|Linux|debug
|
||||||
|
`.devcontainer/build.sh --release linux`|Linux|release
|
||||||
|
`.devcontainer/build.sh --debug android`|android-arm64|debug
|
||||||
|
`.devcontainer/build.sh --release android`|android-arm64|debug
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
<img src="../res/logo-header.svg" alt="RustDesk - Twój zdalny pulpit"><br>
|
||||||
<a href="#darmowe-serwery-publiczne">Serwery</a> •
|
<a href="#darmowe-serwery-publiczne">Serwery</a> •
|
||||||
<a href="#podstawowe-kroki-do-kompilacji">Kompilacja</a> •
|
<a href="#podstawowe-kroki-do-kompilacji">Kompilacja</a> •
|
||||||
<a href="#jak-kompilować-za-pomocą-dockera">Docker</a> •
|
<a href="#jak-kompilować-za-pomocą-dockera">Docker</a> •
|
||||||
@@ -13,27 +13,45 @@ Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](http
|
|||||||
|
|
||||||
[](https://ko-fi.com/I2I04VU09)
|
[](https://ko-fi.com/I2I04VU09)
|
||||||
|
|
||||||
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/server), lub [napisać własny serwer rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
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/server), lub [napisać własny serwer](https://github.com/rustdesk/rustdesk-server-demo).
|
||||||
|
|
||||||
RustDesk zaprasza do współpracy każdego. Zobacz [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) pomoc w uruchomieniu programu.
|

|
||||||
|
|
||||||
[**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.
|
||||||
|
|
||||||
|
[**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)
|
||||||
|
|
||||||
|
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
||||||
|
alt="Get it on F-Droid"
|
||||||
|
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||||
|
|
||||||
## Darmowe Serwery Publiczne
|
## Darmowe Serwery Publiczne
|
||||||
|
|
||||||
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.
|
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 |
|
| Lokalizacja | Dostawca | Specyfikacja |
|
||||||
| --------- | ------------- | ------------------ |
|
| --------- | ------------- | ------------------ |
|
||||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
| Korea Płd. (Seul) | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
| Niemcy | Hetzner | 2 vCPU / 4GB RAM |
|
||||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
| Niemcy | Codext | 4 vCPU / 8GB RAM |
|
||||||
| Finland (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
|
| Finlandia (Helsinki) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
|
||||||
| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
|
| USA (Ashburn) | [Netlock](https://netlockendpoint.com) | 4 vCPU / 8GB RAM |
|
||||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
| Ukraina (Kijów) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||||
|
|
||||||
|
## Konterner Programisty (Dev Container)
|
||||||
|
|
||||||
|
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/rustdesk/rustdesk)
|
||||||
|
|
||||||
|
Jeżeli masz zainstalowany VS Code i Docker, możesz kliknąć w powyższy link, aby rozpocząć. Kliknięcie spowoduje automatyczną instalację rozszrzenia Kontenera Programisty w VS Code (jeżeli wymagany), sklonuje kod źródłowy do kontenera, i przygotuje kontener do użycia.
|
||||||
|
|
||||||
|
Więcej informacji w pliku [DEVCONTAINER-PL.md](docs/DEVCONTAINER-PL.md) for more info.
|
||||||
|
|
||||||
## Zależności
|
## Zależności
|
||||||
|
|
||||||
Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobrać bibliotekę dynamiczną sciter samodzielnie.
|
Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobrać samodzielnie bibliotekę sciter.
|
||||||
|
|
||||||
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
[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) |
|
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||||
@@ -43,7 +61,7 @@ Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobra
|
|||||||
|
|
||||||
- Przygotuj środowisko programistyczne Rust i środowisko programowania C++
|
- Przygotuj środowisko programistyczne Rust i środowisko programowania C++
|
||||||
|
|
||||||
- Zainstaluj [vcpkg](https://github.com/microsoft/vcpkg), i ustaw `VCPKG_ROOT` env zmienną prawidłowo
|
- Zainstaluj [vcpkg](https://github.com/microsoft/vcpkg), i ustaw prawidłowo zmienną `VCPKG_ROOT`
|
||||||
|
|
||||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static
|
||||||
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
- Linux/MacOS: vcpkg install libvpx libyuv opus
|
||||||
@@ -58,6 +76,12 @@ Wersje desktopowe używają [sciter](https://sciter.com/) dla GUI, proszę pobra
|
|||||||
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
|
sudo apt install -y 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)
|
### Fedora 28 (CentOS 8)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -82,7 +106,7 @@ export VCPKG_ROOT=$HOME/vcpkg
|
|||||||
vcpkg/vcpkg install libvpx libyuv opus
|
vcpkg/vcpkg install libvpx libyuv opus
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fix libvpx (For Fedora)
|
### Popraw libvpx (Dla Fedora)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd vcpkg/buildtrees/libvpx/src
|
cd vcpkg/buildtrees/libvpx/src
|
||||||
@@ -110,7 +134,31 @@ cargo run
|
|||||||
|
|
||||||
### Zmień Wayland na X11 (Xorg)
|
### 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.
|
RustDesk nie obsługuje Waylanda. Sprawdź [tutaj](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/), jak skonfigurować Xorg jako domyślną sesję GNOME.
|
||||||
|
|
||||||
|
## Wspracie Wayland
|
||||||
|
|
||||||
|
Wygląda na to, że Wayland nie wspiera żadnego API do wysyłania naciśnięć klawiszy do innych okien. Dlatego rustdesk używa API z niższego poziomu, urządzenia o nazwie `/dev/uinput` (poziom jądra Linux).
|
||||||
|
|
||||||
|
Gdy po stronie kontrolowanej pracuje Wayland, musisz uruchomić program w następujący sposób:
|
||||||
|
```bash
|
||||||
|
# Start uinput service
|
||||||
|
$ sudo rustdesk --service
|
||||||
|
$ rustdesk
|
||||||
|
```
|
||||||
|
**Uwaga**: Nagrywanie ekranu Wayland wykorzystuje różne interfejsy. RustDesk obecnie obsługuje tylko org.freedesktop.portal.ScreenCast.
|
||||||
|
```bash
|
||||||
|
$ dbus-send --session --print-reply \
|
||||||
|
--dest=org.freedesktop.portal.Desktop \
|
||||||
|
/org/freedesktop/portal/desktop \
|
||||||
|
org.freedesktop.DBus.Properties.Get \
|
||||||
|
string:org.freedesktop.portal.ScreenCast string:version
|
||||||
|
# Not support
|
||||||
|
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
|
||||||
|
# Support
|
||||||
|
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
|
||||||
|
variant uint32 4
|
||||||
|
```
|
||||||
|
|
||||||
## Jak kompilować za pomocą Dockera
|
## Jak kompilować za pomocą Dockera
|
||||||
|
|
||||||
@@ -134,7 +182,7 @@ Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zost
|
|||||||
target/debug/rustdesk
|
target/debug/rustdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
Lub, jeśli uruchamiasz plik wykonywalny wersji:
|
Lub jeśli uruchamiasz plik wykonywalny wersji:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
target/release/rustdesk
|
target/release/rustdesk
|
||||||
@@ -144,16 +192,18 @@ Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium Ru
|
|||||||
|
|
||||||
## Struktura plików
|
## Struktura plików
|
||||||
|
|
||||||
- **[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/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/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: przechwytywanie ekranu
|
- **[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ą
|
- **[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/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/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 peer
|
- **[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), wait for remote direct (TCP hole punching) or relayed connection
|
- **[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)**: specyficzny dla danej platformy kod
|
- **[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
|
||||||
|
|
||||||
## Migawki(Snapshoty)
|
## Zrzuty ekranu
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
9
docs/SECURITY-PL.md
Normal file
9
docs/SECURITY-PL.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# 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.
|
||||||
28
examples/custom_plugin/Cargo.toml
Normal file
28
examples/custom_plugin/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "custom_plugin"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "custom_plugin"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["flutter"]
|
||||||
|
flutter = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
rustdesk = { path = "../../", version = "1.2.0", features = ["flutter"]}
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = 'abort'
|
||||||
|
strip = true
|
||||||
|
#opt-level = 'z' # only have smaller size after strip
|
||||||
|
rpath = true
|
||||||
30
examples/custom_plugin/src/lib.rs
Normal file
30
examples/custom_plugin/src/lib.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use librustdesk::api::RustDeskApiTable;
|
||||||
|
/// This file demonstrates how to write a custom plugin for RustDesk.
|
||||||
|
use std::ffi::{c_char, c_int, CString};
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref PLUGIN_NAME: CString = CString::new("A Template Rust Plugin").unwrap();
|
||||||
|
pub static ref PLUGIN_ID: CString = CString::new("TemplatePlugin").unwrap();
|
||||||
|
// Do your own logic based on the API provided by RustDesk.
|
||||||
|
pub static ref API: RustDeskApiTable = RustDeskApiTable::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn plugin_name() -> *const c_char {
|
||||||
|
return PLUGIN_NAME.as_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn plugin_id() -> *const c_char {
|
||||||
|
return PLUGIN_ID.as_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn plugin_init() -> c_int {
|
||||||
|
return 0 as _;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn plugin_dispose() -> c_int {
|
||||||
|
return 0 as _;
|
||||||
|
}
|
||||||
@@ -58,13 +58,12 @@ function build {
|
|||||||
fi
|
fi
|
||||||
make clean
|
make clean
|
||||||
./configure --target=$LIBVPX_TARGET \
|
./configure --target=$LIBVPX_TARGET \
|
||||||
--enable-pic --disable-vp8 \
|
--enable-pic
|
||||||
--disable-webm-io \
|
--disable-webm-io \
|
||||||
--disable-unit-tests \
|
--disable-unit-tests \
|
||||||
--disable-examples \
|
--disable-examples \
|
||||||
--disable-libyuv \
|
--disable-libyuv \
|
||||||
--disable-postproc \
|
--disable-postproc \
|
||||||
--disable-vp8 \
|
|
||||||
--disable-tools \
|
--disable-tools \
|
||||||
--disable-docs \
|
--disable-docs \
|
||||||
--prefix=$PREFIX
|
--prefix=$PREFIX
|
||||||
|
|||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>9.0</string>
|
<string>11.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
# platform :ios, '9.0'
|
# platform :ios, '11.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@@ -1,194 +1,146 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- device_info (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/Analytics (8.15.0):
|
- DKImagePickerController/Core (4.3.4):
|
||||||
- Firebase/Core
|
- DKImagePickerController/ImageDataManager
|
||||||
- Firebase/Core (8.15.0):
|
- DKImagePickerController/Resource
|
||||||
- Firebase/CoreOnly
|
- DKImagePickerController/ImageDataManager (4.3.4)
|
||||||
- FirebaseAnalytics (~> 8.15.0)
|
- DKImagePickerController/PhotoGallery (4.3.4):
|
||||||
- Firebase/CoreOnly (8.15.0):
|
- DKImagePickerController/Core
|
||||||
- FirebaseCore (= 8.15.0)
|
- DKPhotoGallery
|
||||||
- firebase_analytics (9.1.8):
|
- DKImagePickerController/Resource (4.3.4)
|
||||||
- Firebase/Analytics (= 8.15.0)
|
- DKPhotoGallery (0.0.17):
|
||||||
- firebase_core
|
- 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
|
||||||
- firebase_core (1.17.0):
|
|
||||||
- Firebase/CoreOnly (= 8.15.0)
|
|
||||||
- Flutter
|
|
||||||
- FirebaseAnalytics (8.15.0):
|
|
||||||
- FirebaseAnalytics/AdIdSupport (= 8.15.0)
|
|
||||||
- FirebaseCore (~> 8.0)
|
|
||||||
- FirebaseInstallations (~> 8.0)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/MethodSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/Network (~> 7.7)
|
|
||||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- FirebaseAnalytics/AdIdSupport (8.15.0):
|
|
||||||
- FirebaseCore (~> 8.0)
|
|
||||||
- FirebaseInstallations (~> 8.0)
|
|
||||||
- GoogleAppMeasurement (= 8.15.0)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/MethodSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/Network (~> 7.7)
|
|
||||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- FirebaseCore (8.15.0):
|
|
||||||
- FirebaseCoreDiagnostics (~> 8.0)
|
|
||||||
- GoogleUtilities/Environment (~> 7.7)
|
|
||||||
- GoogleUtilities/Logger (~> 7.7)
|
|
||||||
- FirebaseCoreDiagnostics (8.15.0):
|
|
||||||
- GoogleDataTransport (~> 9.1)
|
|
||||||
- GoogleUtilities/Environment (~> 7.7)
|
|
||||||
- GoogleUtilities/Logger (~> 7.7)
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- FirebaseInstallations (8.15.0):
|
|
||||||
- FirebaseCore (~> 8.0)
|
|
||||||
- GoogleUtilities/Environment (~> 7.7)
|
|
||||||
- GoogleUtilities/UserDefaults (~> 7.7)
|
|
||||||
- PromisesObjC (< 3.0, >= 1.2)
|
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- GoogleAppMeasurement (8.15.0):
|
- flutter_keyboard_visibility (0.0.1):
|
||||||
- GoogleAppMeasurement/AdIdSupport (= 8.15.0)
|
- Flutter
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
|
- FMDB (2.7.5):
|
||||||
- GoogleUtilities/MethodSwizzler (~> 7.7)
|
- FMDB/standard (= 2.7.5)
|
||||||
- GoogleUtilities/Network (~> 7.7)
|
- FMDB/standard (2.7.5)
|
||||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- GoogleAppMeasurement/AdIdSupport (8.15.0):
|
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (= 8.15.0)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/MethodSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/Network (~> 7.7)
|
|
||||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- GoogleAppMeasurement/WithoutAdIdSupport (8.15.0):
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/MethodSwizzler (~> 7.7)
|
|
||||||
- GoogleUtilities/Network (~> 7.7)
|
|
||||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
|
||||||
- nanopb (~> 2.30908.0)
|
|
||||||
- GoogleDataTransport (9.1.4):
|
|
||||||
- GoogleUtilities/Environment (~> 7.7)
|
|
||||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
|
||||||
- PromisesObjC (< 3.0, >= 1.2)
|
|
||||||
- GoogleUtilities/AppDelegateSwizzler (7.7.0):
|
|
||||||
- GoogleUtilities/Environment
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- GoogleUtilities/Network
|
|
||||||
- GoogleUtilities/Environment (7.7.0):
|
|
||||||
- PromisesObjC (< 3.0, >= 1.2)
|
|
||||||
- GoogleUtilities/Logger (7.7.0):
|
|
||||||
- GoogleUtilities/Environment
|
|
||||||
- GoogleUtilities/MethodSwizzler (7.7.0):
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- GoogleUtilities/Network (7.7.0):
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- "GoogleUtilities/NSData+zlib"
|
|
||||||
- GoogleUtilities/Reachability
|
|
||||||
- "GoogleUtilities/NSData+zlib (7.7.0)"
|
|
||||||
- GoogleUtilities/Reachability (7.7.0):
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- GoogleUtilities/UserDefaults (7.7.0):
|
|
||||||
- GoogleUtilities/Logger
|
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner (5.0.11)
|
- MTBBarcodeScanner (5.0.11)
|
||||||
- nanopb (2.30908.0):
|
- package_info_plus (0.4.5):
|
||||||
- nanopb/decode (= 2.30908.0)
|
|
||||||
- nanopb/encode (= 2.30908.0)
|
|
||||||
- nanopb/decode (2.30908.0)
|
|
||||||
- nanopb/encode (2.30908.0)
|
|
||||||
- package_info (0.0.1):
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- PromisesObjC (2.1.0)
|
- FlutterMacOS
|
||||||
- qr_code_scanner (0.2.0):
|
- qr_code_scanner (0.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- shared_preferences_ios (0.0.1):
|
- SDWebImage (5.15.5):
|
||||||
|
- SDWebImage/Core (= 5.15.5)
|
||||||
|
- SDWebImage/Core (5.15.5)
|
||||||
|
- sqflite (0.0.2):
|
||||||
|
- Flutter
|
||||||
|
- FMDB (>= 2.7.5)
|
||||||
|
- SwiftyGif (5.4.4)
|
||||||
|
- uni_links (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- video_player_avfoundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
- wakelock (0.0.1):
|
- wakelock (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- device_info (from `.symlinks/plugins/device_info/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||||
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
|
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
|
||||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
|
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- Firebase
|
- DKImagePickerController
|
||||||
- FirebaseAnalytics
|
- DKPhotoGallery
|
||||||
- FirebaseCore
|
- FMDB
|
||||||
- FirebaseCoreDiagnostics
|
|
||||||
- FirebaseInstallations
|
|
||||||
- GoogleAppMeasurement
|
|
||||||
- GoogleDataTransport
|
|
||||||
- GoogleUtilities
|
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- nanopb
|
- SDWebImage
|
||||||
- PromisesObjC
|
- SwiftyGif
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
device_info:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
firebase_analytics:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/firebase_analytics/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
firebase_core:
|
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_keyboard_visibility:
|
||||||
|
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
package_info:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_ios:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||||
qr_code_scanner:
|
qr_code_scanner:
|
||||||
:path: ".symlinks/plugins/qr_code_scanner/ios"
|
:path: ".symlinks/plugins/qr_code_scanner/ios"
|
||||||
shared_preferences_ios:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
|
uni_links:
|
||||||
|
:path: ".symlinks/plugins/uni_links/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
video_player_avfoundation:
|
||||||
|
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||||
wakelock:
|
wakelock:
|
||||||
:path: ".symlinks/plugins/wakelock/ios"
|
:path: ".symlinks/plugins/wakelock/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
|
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||||
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
|
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||||
firebase_analytics: 92d27947c7516981cabdc0acbb33cd0687bcda44
|
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||||
firebase_core: aa1b92020533f5c23955e388c347c58fd64f8627
|
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||||
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
FirebaseCoreDiagnostics: 92e07a649aeb66352b319d43bdd2ee3942af84cb
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd
|
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
|
||||||
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
|
|
||||||
GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b
|
|
||||||
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
|
|
||||||
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
|
||||||
PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72
|
|
||||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||||
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe
|
||||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
|
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||||
|
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||||
|
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||||
|
|
||||||
PODFILE CHECKSUM: a00077baecbb97321490c14848fceed3893ca92a
|
PODFILE CHECKSUM: c649b4e69a3086d323110011d04604e416ad0dcd
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.12.0
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -228,6 +228,7 @@
|
|||||||
};
|
};
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@@ -264,6 +265,7 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@@ -338,6 +340,7 @@
|
|||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGNING_ALLOWED = NO;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
@@ -351,7 +354,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -414,6 +417,7 @@
|
|||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGNING_ALLOWED = NO;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
@@ -433,7 +437,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -469,6 +473,7 @@
|
|||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGNING_ALLOWED = NO;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
@@ -482,7 +487,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import Flutter
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func dummyMethodToEnforceBundling() {
|
public func dummyMethodToEnforceBundling() {
|
||||||
get_rgba();
|
// get_rgba();
|
||||||
free_rgba(nil);
|
// free_rgba(nil);
|
||||||
get_by_name("", "");
|
// get_by_name("", "");
|
||||||
set_by_name("", "");
|
// set_by_name("", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,5 +49,9 @@
|
|||||||
<string>This app needs camera access to scan QR codes</string>
|
<string>This app needs camera access to scan QR codes</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>This app needs photo library access to get QR codes from image</string>
|
<string>This app needs photo library access to get QR codes from image</string>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
void* get_rgba();
|
//void* get_rgba();
|
||||||
void free_rgba(void*);
|
//void free_rgba(void*);
|
||||||
void set_by_name(const char*, const char*);
|
//void set_by_name(const char*, const char*);
|
||||||
const char* get_by_name(const char*, const char*);
|
//const char* get_by_name(const char*, const char*);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ final isIOS = Platform.isIOS;
|
|||||||
final isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
final isDesktop = Platform.isWindows || Platform.isMacOS || Platform.isLinux;
|
||||||
var isWeb = false;
|
var isWeb = false;
|
||||||
var isWebDesktop = false;
|
var isWebDesktop = false;
|
||||||
|
var isMobile = isAndroid || isIOS;
|
||||||
var version = "";
|
var version = "";
|
||||||
int androidVersion = 0;
|
int androidVersion = 0;
|
||||||
|
|
||||||
@@ -186,6 +187,71 @@ class MyTheme {
|
|||||||
static const Color button = Color(0xFF2C8CFF);
|
static const Color button = Color(0xFF2C8CFF);
|
||||||
static const Color hoverBorder = Color(0xFF999999);
|
static const Color hoverBorder = Color(0xFF999999);
|
||||||
|
|
||||||
|
// ListTile
|
||||||
|
static const ListTileThemeData listTileTheme = ListTileThemeData(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
? 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
|
||||||
|
? EdgeInsets.fromLTRB(p, 0, p, (p - 4))
|
||||||
|
: EdgeInsets.fromLTRB(p, 0, (p - mobileTextButtonPaddingLR), (p / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static EdgeInsets dialogButtonPadding = isDesktop
|
||||||
|
? EdgeInsets.only(left: dialogPadding)
|
||||||
|
: EdgeInsets.only(left: dialogPadding / 3);
|
||||||
|
|
||||||
static ThemeData lightTheme = ThemeData(
|
static ThemeData lightTheme = ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
hoverColor: Color.fromARGB(255, 224, 224, 224),
|
hoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||||
@@ -236,7 +302,7 @@ class MyTheme {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: mobileTextButtonTheme,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: MyTheme.accent,
|
backgroundColor: MyTheme.accent,
|
||||||
@@ -254,21 +320,8 @@ class MyTheme {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkboxTheme: const CheckboxThemeData(
|
checkboxTheme: checkboxTheme,
|
||||||
splashRadius: 0,
|
listTileTheme: listTileTheme,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
listTileTheme: ListTileThemeData(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
menuBarTheme: MenuBarThemeData(
|
menuBarTheme: MenuBarThemeData(
|
||||||
style:
|
style:
|
||||||
MenuStyle(backgroundColor: MaterialStatePropertyAll(Colors.white))),
|
MenuStyle(backgroundColor: MaterialStatePropertyAll(Colors.white))),
|
||||||
@@ -334,7 +387,7 @@ class MyTheme {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: mobileTextButtonTheme,
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: MyTheme.accent,
|
backgroundColor: MyTheme.accent,
|
||||||
@@ -357,21 +410,8 @@ class MyTheme {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
checkboxTheme: const CheckboxThemeData(
|
checkboxTheme: checkboxTheme,
|
||||||
splashRadius: 0,
|
listTileTheme: listTileTheme,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
listTileTheme: ListTileThemeData(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
menuBarTheme: MenuBarThemeData(
|
menuBarTheme: MenuBarThemeData(
|
||||||
style: MenuStyle(
|
style: MenuStyle(
|
||||||
backgroundColor: MaterialStatePropertyAll(Color(0xFF121212)))),
|
backgroundColor: MaterialStatePropertyAll(Color(0xFF121212)))),
|
||||||
@@ -757,6 +797,7 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
fontWeight: FontWeight.w300,
|
fontWeight: FontWeight.w300,
|
||||||
@@ -771,6 +812,10 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
class CustomAlertDialog extends StatelessWidget {
|
||||||
const CustomAlertDialog(
|
const CustomAlertDialog(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
@@ -798,8 +843,8 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
if (!scopeNode.hasFocus) scopeNode.requestFocus();
|
if (!scopeNode.hasFocus) scopeNode.requestFocus();
|
||||||
});
|
});
|
||||||
const double padding = 30;
|
|
||||||
bool tabTapped = false;
|
bool tabTapped = false;
|
||||||
|
|
||||||
return FocusScope(
|
return FocusScope(
|
||||||
node: scopeNode,
|
node: scopeNode,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@@ -826,20 +871,16 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: title,
|
title: title,
|
||||||
titlePadding: EdgeInsets.fromLTRB(padding, 24, padding, 0),
|
|
||||||
contentPadding: EdgeInsets.fromLTRB(
|
|
||||||
contentPadding ?? padding,
|
|
||||||
25,
|
|
||||||
contentPadding ?? padding,
|
|
||||||
actions is List ? 10 : padding,
|
|
||||||
),
|
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: contentBoxConstraints,
|
constraints: contentBoxConstraints,
|
||||||
child: content,
|
child: content,
|
||||||
),
|
),
|
||||||
actions: actions,
|
actions: actions,
|
||||||
actionsPadding: EdgeInsets.fromLTRB(padding, 0, padding, padding),
|
titlePadding: MyTheme.dialogTitlePadding(content: content != null),
|
||||||
),
|
contentPadding:
|
||||||
|
MyTheme.dialogContentPadding(actions: actions is List),
|
||||||
|
actionsPadding: MyTheme.dialogActionsPadding(),
|
||||||
|
buttonPadding: MyTheme.dialogButtonPadding),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1115,38 +1156,23 @@ class AndroidPermissionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO move this to mobile/widgets.
|
||||||
|
// Used only for mobile, pages remote, settings, dialog
|
||||||
|
// TODO remove argument contentPadding, it’s not used, getToggle() has not
|
||||||
RadioListTile<T> getRadio<T>(
|
RadioListTile<T> getRadio<T>(
|
||||||
String name, T toValue, T curValue, void Function(T?) onChange,
|
Widget title, T toValue, T curValue, ValueChanged<T?>? onChange,
|
||||||
{EdgeInsetsGeometry? contentPadding}) {
|
{EdgeInsetsGeometry? contentPadding}) {
|
||||||
return RadioListTile<T>(
|
return RadioListTile<T>(
|
||||||
contentPadding: contentPadding,
|
contentPadding: contentPadding ?? EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
controlAffinity: ListTileControlAffinity.trailing,
|
controlAffinity: ListTileControlAffinity.trailing,
|
||||||
title: Text(translate(name)),
|
title: title,
|
||||||
value: toValue,
|
value: toValue,
|
||||||
groupValue: curValue,
|
groupValue: curValue,
|
||||||
onChanged: onChange,
|
onChanged: onChange,
|
||||||
dense: true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckboxListTile getToggle(
|
|
||||||
String id, void Function(void Function()) setState, option, name,
|
|
||||||
{FFI? ffi}) {
|
|
||||||
final opt = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
|
||||||
return CheckboxListTile(
|
|
||||||
value: opt,
|
|
||||||
onChanged: (v) {
|
|
||||||
setState(() {
|
|
||||||
bind.sessionToggleOption(id: id, value: option);
|
|
||||||
});
|
|
||||||
if (option == "show-quality-monitor") {
|
|
||||||
(ffi ?? gFFI).qualityMonitorModel.checkShowQualityMonitor(id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dense: true,
|
|
||||||
title: Text(translate(name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// find ffi, tag is Remote ID
|
/// find ffi, tag is Remote ID
|
||||||
/// for session specific usage
|
/// for session specific usage
|
||||||
FFI ffi(String? tag) {
|
FFI ffi(String? tag) {
|
||||||
@@ -1522,14 +1548,14 @@ bool checkArguments() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String? id =
|
String? id =
|
||||||
kBootArgs.length < connectIndex + 1 ? null : kBootArgs[connectIndex + 1];
|
kBootArgs.length <= connectIndex + 1 ? null : kBootArgs[connectIndex + 1];
|
||||||
String? password =
|
String? password =
|
||||||
kBootArgs.length < connectIndex + 2 ? null : kBootArgs[connectIndex + 2];
|
kBootArgs.length <= connectIndex + 2 ? null : kBootArgs[connectIndex + 2];
|
||||||
if (password != null && password.startsWith("--")) {
|
if (password != null && password.startsWith("--")) {
|
||||||
password = null;
|
password = null;
|
||||||
}
|
}
|
||||||
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid");
|
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid");
|
||||||
String? switchUuid = kBootArgs.length < switchUuidIndex + 1
|
String? switchUuid = kBootArgs.length <= switchUuidIndex + 1
|
||||||
? null
|
? null
|
||||||
: kBootArgs[switchUuidIndex + 1];
|
: kBootArgs[switchUuidIndex + 1];
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
@@ -1574,8 +1600,10 @@ bool callUniLinksUriHandler(Uri uri) {
|
|||||||
final peerId = uri.path.substring("/new/".length);
|
final peerId = uri.path.substring("/new/".length);
|
||||||
var param = uri.queryParameters;
|
var param = uri.queryParameters;
|
||||||
String? switch_uuid = param["switch_uuid"];
|
String? switch_uuid = param["switch_uuid"];
|
||||||
|
String? password = param["password"];
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
rustDeskWinManager.newRemoteDesktop(peerId, switch_uuid: switch_uuid);
|
rustDeskWinManager.newRemoteDesktop(peerId,
|
||||||
|
password: password, switch_uuid: switch_uuid);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2007,3 +2035,12 @@ Widget futureBuilder(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onCopyFingerprint(String value) {
|
||||||
|
if (value.isNotEmpty) {
|
||||||
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
|
showToast('$value\n${translate("Copied")}');
|
||||||
|
} else {
|
||||||
|
showToast(translate("no fingerprints"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,6 +121,29 @@ class ConnectionTypeState {
|
|||||||
Get.find<ConnectionType>(tag: tag(id));
|
Get.find<ConnectionType>(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<RxString>(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<RxString>(tag: tag(id));
|
||||||
|
}
|
||||||
|
|
||||||
class ShowRemoteCursorState {
|
class ShowRemoteCursorState {
|
||||||
static String tag(String id) => 'show_remote_cursor_$id';
|
static String tag(String id) => 'show_remote_cursor_$id';
|
||||||
|
|
||||||
@@ -261,3 +284,25 @@ class PeerStringOption {
|
|||||||
static RxString find(String id, String opt) =>
|
static RxString find(String id, String opt) =>
|
||||||
Get.find<RxString>(tag: tag(id, opt));
|
Get.find<RxString>(tag: tag(id, opt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initSharedStates(String id) {
|
||||||
|
PrivacyModeState.init(id);
|
||||||
|
BlockInputState.init(id);
|
||||||
|
CurrentDisplayState.init(id);
|
||||||
|
KeyboardEnabledState.init(id);
|
||||||
|
ShowRemoteCursorState.init(id);
|
||||||
|
RemoteCursorMovedState.init(id);
|
||||||
|
FingerprintState.init(id);
|
||||||
|
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSharedStates(String id) {
|
||||||
|
PrivacyModeState.delete(id);
|
||||||
|
BlockInputState.delete(id);
|
||||||
|
CurrentDisplayState.delete(id);
|
||||||
|
ShowRemoteCursorState.delete(id);
|
||||||
|
KeyboardEnabledState.delete(id);
|
||||||
|
RemoteCursorMovedState.delete(id);
|
||||||
|
FingerprintState.delete(id);
|
||||||
|
PeerBoolOption.delete(id, 'zoom-cursor');
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
|
void clientClose(String id, OverlayDialogManager dialogManager) {
|
||||||
|
msgBox(id, 'info', 'Close', 'Are you sure to close the connection?', '',
|
||||||
|
dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
abstract class ValidationRule {
|
abstract class ValidationRule {
|
||||||
String get name;
|
String get name;
|
||||||
bool validate(String value);
|
bool validate(String value);
|
||||||
@@ -290,3 +300,972 @@ Future<String> changeDirectAccessPort(
|
|||||||
});
|
});
|
||||||
return controller.text;
|
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;
|
||||||
|
|
||||||
|
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,
|
||||||
|
required this.title,
|
||||||
|
required this.controller})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: title,
|
||||||
|
hintText: hintText,
|
||||||
|
prefixIcon: prefixIcon,
|
||||||
|
suffixIcon: suffixIcon,
|
||||||
|
helperText: helperText,
|
||||||
|
helperMaxLines: 8,
|
||||||
|
errorText: errorText,
|
||||||
|
),
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
autofocus: true,
|
||||||
|
obscureText: obscureText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).paddingSymmetric(vertical: 4.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordWidget extends StatefulWidget {
|
||||||
|
PasswordWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.controller,
|
||||||
|
this.autoFocus = true,
|
||||||
|
this.hintText,
|
||||||
|
this.errorText,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final bool autoFocus;
|
||||||
|
final String? hintText;
|
||||||
|
final String? errorText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PasswordWidget> createState() => _PasswordWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PasswordWidgetState extends State<PasswordWidget> {
|
||||||
|
bool _passwordVisible = false;
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.autoFocus) {
|
||||||
|
_timer =
|
||||||
|
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_focusNode.unfocus();
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DialogTextField(
|
||||||
|
title: translate(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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wrongPasswordDialog(
|
||||||
|
String id, OverlayDialogManager dialogManager, type, title, text) {
|
||||||
|
dialogManager.dismissAll();
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
cancel() {
|
||||||
|
close();
|
||||||
|
closeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
enterPasswordDialog(id, 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(String id, OverlayDialogManager dialogManager) async {
|
||||||
|
await _connectDialog(
|
||||||
|
id,
|
||||||
|
dialogManager,
|
||||||
|
passwordController: TextEditingController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enterUserLoginDialog(String id, OverlayDialogManager dialogManager) async {
|
||||||
|
await _connectDialog(
|
||||||
|
id,
|
||||||
|
dialogManager,
|
||||||
|
osUsernameController: TextEditingController(),
|
||||||
|
osPasswordController: TextEditingController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enterUserLoginAndPasswordDialog(
|
||||||
|
String id, OverlayDialogManager dialogManager) async {
|
||||||
|
await _connectDialog(
|
||||||
|
id,
|
||||||
|
dialogManager,
|
||||||
|
osUsernameController: TextEditingController(),
|
||||||
|
osPasswordController: TextEditingController(),
|
||||||
|
passwordController: TextEditingController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectDialog(
|
||||||
|
String id,
|
||||||
|
OverlayDialogManager dialogManager, {
|
||||||
|
TextEditingController? osUsernameController,
|
||||||
|
TextEditingController? osPasswordController,
|
||||||
|
TextEditingController? passwordController,
|
||||||
|
}) async {
|
||||||
|
var rememberPassword = false;
|
||||||
|
if (passwordController != null) {
|
||||||
|
rememberPassword = await bind.sessionGetRemember(id: id) ?? false;
|
||||||
|
}
|
||||||
|
var rememberAccount = false;
|
||||||
|
if (osUsernameController != null) {
|
||||||
|
rememberAccount = await bind.sessionGetRemember(id: id) ?? false;
|
||||||
|
}
|
||||||
|
dialogManager.dismissAll();
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
cancel() {
|
||||||
|
close();
|
||||||
|
closeConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
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(id: id, name: 'os-username', value: osUsername);
|
||||||
|
bind.sessionPeerOption(id: id, name: 'os-password', value: osPassword);
|
||||||
|
}
|
||||||
|
gFFI.login(
|
||||||
|
osUsername,
|
||||||
|
osPassword,
|
||||||
|
id,
|
||||||
|
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<bool?>? 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: [
|
||||||
|
descWidget(translate('login_linux_tip')),
|
||||||
|
DialogTextField(
|
||||||
|
title: translate(DialogTextField.kUsernameTitle),
|
||||||
|
controller: osUsernameController,
|
||||||
|
prefixIcon: DialogTextField.kUsernameIcon,
|
||||||
|
errorText: null,
|
||||||
|
),
|
||||||
|
PasswordWidget(
|
||||||
|
controller: osPasswordController,
|
||||||
|
autoFocus: false,
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
String id, OverlayDialogManager dialogManager, String type) {
|
||||||
|
dialogManager.dismissAll();
|
||||||
|
dialogManager.show(
|
||||||
|
tag: '$id-wait-uac',
|
||||||
|
(setState, close) => CustomAlertDialog(
|
||||||
|
title: null,
|
||||||
|
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another username && password dialog?
|
||||||
|
void showRequestElevationDialog(String id, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minTextStyle = TextStyle(fontSize: 14);
|
||||||
|
|
||||||
|
var content = Obx(() => Column(children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Radio(
|
||||||
|
value: '',
|
||||||
|
groupValue: groupValue.value,
|
||||||
|
onChanged: onRadioChanged),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
Text(translate('Ask the remote user for authentication'))),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
translate(
|
||||||
|
'Choose this if the remote account is administrator'),
|
||||||
|
style: TextStyle(fontSize: 13))
|
||||||
|
.marginOnly(left: 40),
|
||||||
|
).marginOnly(bottom: 15),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Radio(
|
||||||
|
value: 'logon',
|
||||||
|
groupValue: groupValue.value,
|
||||||
|
onChanged: onRadioChanged),
|
||||||
|
Expanded(
|
||||||
|
child: Text(translate(
|
||||||
|
'Transmit the username and password of administrator')),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
'${translate('Username')}:',
|
||||||
|
style: minTextStyle,
|
||||||
|
).marginOnly(right: 10)),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: TextField(
|
||||||
|
controller: userController,
|
||||||
|
style: minTextStyle,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 15),
|
||||||
|
hintText: translate('eg: admin'),
|
||||||
|
errorText: errUser.isEmpty ? null : errUser.value),
|
||||||
|
onChanged: (s) {
|
||||||
|
if (s.isNotEmpty) {
|
||||||
|
errUser.value = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).marginOnly(left: 40),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
'${translate('Password')}:',
|
||||||
|
style: minTextStyle,
|
||||||
|
).marginOnly(right: 10)),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: TextField(
|
||||||
|
controller: pwdController,
|
||||||
|
obscureText: true,
|
||||||
|
style: minTextStyle,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 15),
|
||||||
|
errorText: errPwd.isEmpty ? null : errPwd.value),
|
||||||
|
onChanged: (s) {
|
||||||
|
if (s.isNotEmpty) {
|
||||||
|
errPwd.value = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).marginOnly(left: 40),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(translate('still_click_uac_tip'),
|
||||||
|
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold))
|
||||||
|
.marginOnly(top: 20)),
|
||||||
|
]));
|
||||||
|
|
||||||
|
dialogManager.dismissAll();
|
||||||
|
dialogManager.show(tag: '$id-request-elevation', (setState, close) {
|
||||||
|
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(
|
||||||
|
id: id,
|
||||||
|
username: userController.text,
|
||||||
|
password: pwdController.text);
|
||||||
|
} else {
|
||||||
|
bind.sessionElevateDirect(id: id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate('Request Elevation')),
|
||||||
|
content: content,
|
||||||
|
actions: [
|
||||||
|
dialogButton('Cancel', onPressed: close, isOutline: true),
|
||||||
|
dialogButton('OK', onPressed: submit),
|
||||||
|
],
|
||||||
|
onSubmit: submit,
|
||||||
|
onCancel: close,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void showOnBlockDialog(
|
||||||
|
String id,
|
||||||
|
String type,
|
||||||
|
String title,
|
||||||
|
String text,
|
||||||
|
OverlayDialogManager dialogManager,
|
||||||
|
) {
|
||||||
|
if (dialogManager.existing('$id-wait-uac') ||
|
||||||
|
dialogManager.existing('$id-request-elevation')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||||
|
void submit() {
|
||||||
|
close();
|
||||||
|
showRequestElevationDialog(id, 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(String id, String type, String title, String text,
|
||||||
|
OverlayDialogManager dialogManager) {
|
||||||
|
dialogManager.show(tag: '$id-$type', (setState, close) {
|
||||||
|
void submit() {
|
||||||
|
close();
|
||||||
|
showRequestElevationDialog(id, dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: null,
|
||||||
|
content: msgboxContent(type, title, text),
|
||||||
|
actions: [
|
||||||
|
dialogButton('Cancel', onPressed: () {
|
||||||
|
close();
|
||||||
|
}, isOutline: true),
|
||||||
|
dialogButton('Retry', onPressed: submit),
|
||||||
|
],
|
||||||
|
onSubmit: submit,
|
||||||
|
onCancel: close,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void showWaitAcceptDialog(String id, String type, String title, String text,
|
||||||
|
OverlayDialogManager dialogManager) {
|
||||||
|
dialogManager.dismissAll();
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
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, OverlayDialogManager dialogManager) async {
|
||||||
|
final res =
|
||||||
|
await dialogManager.show<bool>((setState, close) => 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(id: id);
|
||||||
|
}
|
||||||
|
|
||||||
|
showSetOSPassword(
|
||||||
|
String id,
|
||||||
|
bool login,
|
||||||
|
OverlayDialogManager dialogManager,
|
||||||
|
) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
|
||||||
|
var autoLogin = await bind.sessionGetOption(id: id, arg: 'auto-login') != '';
|
||||||
|
controller.text = password;
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
submit() {
|
||||||
|
var text = controller.text.trim();
|
||||||
|
bind.sessionPeerOption(id: id, name: 'os-password', value: text);
|
||||||
|
bind.sessionPeerOption(
|
||||||
|
id: id, name: 'auto-login', value: autoLogin ? 'Y' : '');
|
||||||
|
if (text != '' && login) {
|
||||||
|
bind.sessionInputOsPassword(id: id, value: text);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
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: close,
|
||||||
|
isOutline: true,
|
||||||
|
),
|
||||||
|
dialogButton(
|
||||||
|
"OK",
|
||||||
|
icon: Icon(Icons.done_rounded),
|
||||||
|
onPressed: submit,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSubmit: submit,
|
||||||
|
onCancel: close,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showSetOSAccount(
|
||||||
|
String id,
|
||||||
|
OverlayDialogManager dialogManager,
|
||||||
|
) async {
|
||||||
|
final usernameController = TextEditingController();
|
||||||
|
final passwdController = TextEditingController();
|
||||||
|
var username = await bind.sessionGetOption(id: id, arg: 'os-username') ?? '';
|
||||||
|
var password = await bind.sessionGetOption(id: id, arg: 'os-password') ?? '';
|
||||||
|
usernameController.text = username;
|
||||||
|
passwdController.text = password;
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
submit() {
|
||||||
|
final username = usernameController.text.trim();
|
||||||
|
final password = usernameController.text.trim();
|
||||||
|
bind.sessionPeerOption(id: id, name: 'os-username', value: username);
|
||||||
|
bind.sessionPeerOption(id: id, 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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showAuditDialog(String id, dialogManager) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
submit() {
|
||||||
|
var text = controller.text.trim();
|
||||||
|
if (text != '') {
|
||||||
|
bind.sessionSendNote(id: id, note: text);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
late 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) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate('Note')),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 250,
|
||||||
|
height: 120,
|
||||||
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
textInputAction: TextInputAction.newline,
|
||||||
|
decoration: const InputDecoration.collapsed(
|
||||||
|
hintText: 'input note here',
|
||||||
|
),
|
||||||
|
maxLines: null,
|
||||||
|
maxLength: 256,
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
)),
|
||||||
|
actions: [
|
||||||
|
dialogButton('Cancel', onPressed: close, isOutline: true),
|
||||||
|
dialogButton('OK', onPressed: submit)
|
||||||
|
],
|
||||||
|
onSubmit: submit,
|
||||||
|
onCancel: close,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void showConfirmSwitchSidesDialog(
|
||||||
|
String id, OverlayDialogManager dialogManager) async {
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
submit() async {
|
||||||
|
await bind.sessionSwitchSides(id: id);
|
||||||
|
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(String id, FFI ffi) async {
|
||||||
|
double qualityInitValue = 50;
|
||||||
|
double fpsInitValue = 30;
|
||||||
|
bool qualitySet = false;
|
||||||
|
bool fpsSet = false;
|
||||||
|
setCustomValues({double? quality, double? fps}) async {
|
||||||
|
if (quality != null) {
|
||||||
|
qualitySet = true;
|
||||||
|
await bind.sessionSetCustomImageQuality(id: id, value: quality.toInt());
|
||||||
|
}
|
||||||
|
if (fps != null) {
|
||||||
|
fpsSet = true;
|
||||||
|
await bind.sessionSetCustomFps(id: id, fps: fps.toInt());
|
||||||
|
}
|
||||||
|
if (!qualitySet) {
|
||||||
|
qualitySet = true;
|
||||||
|
await bind.sessionSetCustomImageQuality(
|
||||||
|
id: id, value: qualityInitValue.toInt());
|
||||||
|
}
|
||||||
|
if (!fpsSet) {
|
||||||
|
fpsSet = true;
|
||||||
|
await bind.sessionSetCustomFps(id: id, fps: fpsInitValue.toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final btnClose = dialogButton('Close', onPressed: () async {
|
||||||
|
await setCustomValues();
|
||||||
|
ffi.dialogManager.dismissAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
// quality
|
||||||
|
final quality = await bind.sessionGetCustomImageQuality(id: id);
|
||||||
|
qualityInitValue =
|
||||||
|
quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0;
|
||||||
|
const qualityMinValue = 10.0;
|
||||||
|
const qualityMaxValue = 100.0;
|
||||||
|
if (qualityInitValue < qualityMinValue) {
|
||||||
|
qualityInitValue = qualityMinValue;
|
||||||
|
}
|
||||||
|
if (qualityInitValue > qualityMaxValue) {
|
||||||
|
qualityInitValue = qualityMaxValue;
|
||||||
|
}
|
||||||
|
final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
|
||||||
|
final debouncerQuality = Debouncer<double>(
|
||||||
|
Duration(milliseconds: 1000),
|
||||||
|
onChanged: (double v) {
|
||||||
|
setCustomValues(quality: v);
|
||||||
|
},
|
||||||
|
initialValue: qualityInitValue,
|
||||||
|
);
|
||||||
|
final qualitySlider = Obx(() => Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Slider(
|
||||||
|
value: qualitySliderValue.value,
|
||||||
|
min: qualityMinValue,
|
||||||
|
max: qualityMaxValue,
|
||||||
|
divisions: 18,
|
||||||
|
onChanged: (double value) {
|
||||||
|
qualitySliderValue.value = value;
|
||||||
|
debouncerQuality.value = value;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
'${qualitySliderValue.value.round()}%',
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
translate('Bitrate'),
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
// fps
|
||||||
|
final fpsOption = await bind.sessionGetOption(id: id, arg: 'custom-fps');
|
||||||
|
fpsInitValue = fpsOption == null ? 30 : double.tryParse(fpsOption) ?? 30;
|
||||||
|
if (fpsInitValue < 5 || fpsInitValue > 120) {
|
||||||
|
fpsInitValue = 30;
|
||||||
|
}
|
||||||
|
final RxDouble fpsSliderValue = RxDouble(fpsInitValue);
|
||||||
|
final debouncerFps = Debouncer<double>(
|
||||||
|
Duration(milliseconds: 1000),
|
||||||
|
onChanged: (double v) {
|
||||||
|
setCustomValues(fps: v);
|
||||||
|
},
|
||||||
|
initialValue: qualityInitValue,
|
||||||
|
);
|
||||||
|
bool? direct;
|
||||||
|
try {
|
||||||
|
direct =
|
||||||
|
ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect;
|
||||||
|
} catch (_) {}
|
||||||
|
final fpsSlider = Offstage(
|
||||||
|
offstage: (await bind.mainIsUsingPublicServer() && direct != true) ||
|
||||||
|
version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Obx((() => Slider(
|
||||||
|
value: fpsSliderValue.value,
|
||||||
|
min: 5,
|
||||||
|
max: 120,
|
||||||
|
divisions: 23,
|
||||||
|
onChanged: (double value) {
|
||||||
|
fpsSliderValue.value = value;
|
||||||
|
debouncerFps.value = value;
|
||||||
|
},
|
||||||
|
)))),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Obx(() => Text(
|
||||||
|
'${fpsSliderValue.value.round()}',
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
))),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
translate('FPS'),
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final content = Column(
|
||||||
|
children: [qualitySlider, fpsSlider],
|
||||||
|
);
|
||||||
|
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
|
import './dialog.dart';
|
||||||
|
|
||||||
class _IconOP extends StatelessWidget {
|
class _IconOP extends StatelessWidget {
|
||||||
final String icon;
|
final String icon;
|
||||||
@@ -324,17 +325,16 @@ class LoginWidgetUserPass extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
DialogTextField(
|
DialogTextField(
|
||||||
title: translate("Username"),
|
title: translate(DialogTextField.kUsernameTitle),
|
||||||
controller: username,
|
controller: username,
|
||||||
focusNode: userFocusNode,
|
focusNode: userFocusNode,
|
||||||
prefixIcon: Icon(Icons.account_circle_outlined),
|
prefixIcon: DialogTextField.kUsernameIcon,
|
||||||
errorText: usernameMsg),
|
errorText: usernameMsg),
|
||||||
DialogTextField(
|
PasswordWidget(
|
||||||
title: translate("Password"),
|
|
||||||
obscureText: true,
|
|
||||||
controller: pass,
|
controller: pass,
|
||||||
prefixIcon: Icon(Icons.lock_outline),
|
autoFocus: false,
|
||||||
errorText: passMsg),
|
errorText: passMsg,
|
||||||
|
),
|
||||||
Obx(() => CheckboxListTile(
|
Obx(() => CheckboxListTile(
|
||||||
contentPadding: const EdgeInsets.all(0),
|
contentPadding: const EdgeInsets.all(0),
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -377,49 +377,6 @@ class LoginWidgetUserPass extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DialogTextField extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final bool obscureText;
|
|
||||||
final String? errorText;
|
|
||||||
final String? helperText;
|
|
||||||
final Widget? prefixIcon;
|
|
||||||
final TextEditingController controller;
|
|
||||||
final FocusNode? focusNode;
|
|
||||||
|
|
||||||
DialogTextField(
|
|
||||||
{Key? key,
|
|
||||||
this.focusNode,
|
|
||||||
this.obscureText = false,
|
|
||||||
this.errorText,
|
|
||||||
this.helperText,
|
|
||||||
this.prefixIcon,
|
|
||||||
required this.title,
|
|
||||||
required this.controller})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: title,
|
|
||||||
prefixIcon: prefixIcon,
|
|
||||||
helperText: helperText,
|
|
||||||
helperMaxLines: 8,
|
|
||||||
errorText: errorText),
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
autofocus: true,
|
|
||||||
obscureText: obscureText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddingSymmetric(vertical: 4.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// common login dialog for desktop
|
/// common login dialog for desktop
|
||||||
/// call this directly
|
/// call this directly
|
||||||
Future<bool?> loginDialog() async {
|
Future<bool?> loginDialog() async {
|
||||||
|
|||||||
@@ -388,6 +388,15 @@ class BlockableOverlayState extends OverlayKeyState {
|
|||||||
_middleBlocked.value = blocked;
|
_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 {
|
class BlockableOverlay extends StatelessWidget {
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
},
|
},
|
||||||
child: Icon(
|
child: Icon(
|
||||||
peerCardUiType.value == PeerUiType.grid
|
peerCardUiType.value == PeerUiType.grid
|
||||||
? Icons.list_rounded
|
? Icons.list
|
||||||
: Icons.grid_view_rounded,
|
: Icons.grid_view_rounded,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
@@ -455,12 +455,12 @@ class _PeerSortDropdownState extends State<PeerSortDropdown> {
|
|||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
);
|
);
|
||||||
|
|
||||||
final translated_text =
|
final translated_text = {
|
||||||
PeerSortType.values.map((e) => translate(e)).toList();
|
for (var e in PeerSortType.values) e: translate(e)
|
||||||
|
};
|
||||||
|
|
||||||
final double max_width =
|
final double max_width =
|
||||||
50 + translated_text.map((e) => e.length).reduce(max) * 10;
|
50 + translated_text.values.map((e) => e.length).reduce(max) * 10;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(4.0),
|
padding: EdgeInsets.all(4.0),
|
||||||
decoration: deco,
|
decoration: deco,
|
||||||
@@ -496,20 +496,20 @@ class _PeerSortDropdownState extends State<PeerSortDropdown> {
|
|||||||
),
|
),
|
||||||
enabled: false,
|
enabled: false,
|
||||||
),
|
),
|
||||||
...translated_text
|
...translated_text.entries
|
||||||
.map<DropdownMenuItem<String>>(
|
.map<DropdownMenuItem<String>>(
|
||||||
(String value) => DropdownMenuItem<String>(
|
(MapEntry entry) => DropdownMenuItem<String>(
|
||||||
value: value,
|
value: entry.key,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
value == peerSort.value
|
entry.key == peerSort.value
|
||||||
? Icons.radio_button_checked_rounded
|
? Icons.radio_button_checked_rounded
|
||||||
: Icons.radio_button_off_rounded,
|
: Icons.radio_button_off_rounded,
|
||||||
size: 18,
|
size: 18,
|
||||||
).paddingOnly(right: 12),
|
).paddingOnly(right: 12),
|
||||||
Text(
|
Text(
|
||||||
value,
|
entry.value,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
456
flutter/lib/common/widgets/toolbar.dart
Normal file
456
flutter/lib/common/widgets/toolbar.dart
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
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/consts.dart';
|
||||||
|
import 'package:flutter_hbb/models/model.dart';
|
||||||
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class TTextMenu {
|
||||||
|
final Widget child;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
Widget? trailingIcon;
|
||||||
|
bool divider;
|
||||||
|
TTextMenu(
|
||||||
|
{required this.child,
|
||||||
|
required this.onPressed,
|
||||||
|
this.trailingIcon,
|
||||||
|
this.divider = false});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TRadioMenu<T> {
|
||||||
|
final Widget child;
|
||||||
|
final T value;
|
||||||
|
final T groupValue;
|
||||||
|
final ValueChanged<T?>? onChanged;
|
||||||
|
|
||||||
|
TRadioMenu(
|
||||||
|
{required this.child,
|
||||||
|
required this.value,
|
||||||
|
required this.groupValue,
|
||||||
|
required this.onChanged});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TToggleMenu {
|
||||||
|
final Widget child;
|
||||||
|
final bool value;
|
||||||
|
final ValueChanged<bool?>? onChanged;
|
||||||
|
TToggleMenu(
|
||||||
|
{required this.child, required this.value, required this.onChanged});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||||
|
final ffiModel = ffi.ffiModel;
|
||||||
|
final pi = ffiModel.pi;
|
||||||
|
final perms = ffiModel.permissions;
|
||||||
|
|
||||||
|
List<TTextMenu> v = [];
|
||||||
|
// elevation
|
||||||
|
if (ffi.elevationModel.showRequestMenu) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text(translate('Request Elevation')),
|
||||||
|
onPressed: () => showRequestElevationDialog(id, ffi.dialogManager)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// osAccount / osPassword
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Row(children: [
|
||||||
|
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
||||||
|
Offstage(
|
||||||
|
offstage: isDesktop,
|
||||||
|
child:
|
||||||
|
Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12))
|
||||||
|
]),
|
||||||
|
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
|
||||||
|
onPressed: () => pi.is_headless
|
||||||
|
? showSetOSAccount(id, ffi.dialogManager)
|
||||||
|
: showSetOSPassword(id, false, ffi.dialogManager)),
|
||||||
|
);
|
||||||
|
// paste
|
||||||
|
if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) {
|
||||||
|
v.add(TTextMenu(
|
||||||
|
child: Text(translate('Paste')),
|
||||||
|
onPressed: () async {
|
||||||
|
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
if (data != null && data.text != null) {
|
||||||
|
bind.sessionInputString(id: id, value: data.text ?? "");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// reset canvas
|
||||||
|
if (isMobile) {
|
||||||
|
v.add(TTextMenu(
|
||||||
|
child: Text(translate('Reset canvas')),
|
||||||
|
onPressed: () => ffi.cursorModel.reset()));
|
||||||
|
}
|
||||||
|
// transferFile
|
||||||
|
if (isDesktop) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text(translate('Transfer File')),
|
||||||
|
onPressed: () => connect(context, id, isFileTransfer: true)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// tcpTunneling
|
||||||
|
if (isDesktop) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text(translate('TCP Tunneling')),
|
||||||
|
onPressed: () => connect(context, id, isTcpTunneling: true)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// note
|
||||||
|
if (bind.sessionGetAuditServerSync(id: id, typ: "conn").isNotEmpty) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text(translate('Note')),
|
||||||
|
onPressed: () => showAuditDialog(id, ffi.dialogManager)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// divider
|
||||||
|
if (isDesktop) {
|
||||||
|
v.add(TTextMenu(child: Offstage(), onPressed: () {}, divider: true));
|
||||||
|
}
|
||||||
|
// ctrlAltDel
|
||||||
|
if (!ffiModel.viewOnly &&
|
||||||
|
ffiModel.keyboard &&
|
||||||
|
(pi.platform == kPeerPlatformLinux || pi.sasEnabled)) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text('${translate("Insert")} Ctrl + Alt + Del'),
|
||||||
|
onPressed: () => bind.sessionCtrlAltDel(id: id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// restart
|
||||||
|
if (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, ffi.dialogManager)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// insertLock
|
||||||
|
if (!ffiModel.viewOnly && ffi.ffiModel.keyboard) {
|
||||||
|
v.add(
|
||||||
|
TTextMenu(
|
||||||
|
child: Text(translate('Insert Lock')),
|
||||||
|
onPressed: () => bind.sessionLockScreen(id: id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// blockUserInput
|
||||||
|
if (ffi.ffiModel.keyboard &&
|
||||||
|
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(
|
||||||
|
id: id, value: '${blockInput.value ? 'un' : ''}block-input');
|
||||||
|
blockInput.value = !blockInput.value;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// switchSides
|
||||||
|
if (isDesktop &&
|
||||||
|
ffiModel.keyboard &&
|
||||||
|
pi.platform != kPeerPlatformAndroid &&
|
||||||
|
pi.platform != kPeerPlatformMacOS &&
|
||||||
|
version_cmp(pi.version, '1.2.0') >= 0) {
|
||||||
|
v.add(TTextMenu(
|
||||||
|
child: Text(translate('Switch Sides')),
|
||||||
|
onPressed: () => showConfirmSwitchSidesDialog(id, ffi.dialogManager)));
|
||||||
|
}
|
||||||
|
// refresh
|
||||||
|
if (pi.version.isNotEmpty) {
|
||||||
|
v.add(TTextMenu(
|
||||||
|
child: Text(translate('Refresh')),
|
||||||
|
onPressed: () => bind.sessionRefresh(id: id)));
|
||||||
|
}
|
||||||
|
// record
|
||||||
|
var codecFormat = ffi.qualityMonitorModel.data.codecFormat;
|
||||||
|
if (!isDesktop &&
|
||||||
|
(ffi.recordingModel.start ||
|
||||||
|
(perms["recording"] != false &&
|
||||||
|
(codecFormat == "VP8" || codecFormat == "VP9")))) {
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
// fingerprint
|
||||||
|
if (!isDesktop) {
|
||||||
|
v.add(TTextMenu(
|
||||||
|
child: Text(translate('Copy Fingerprint')),
|
||||||
|
onPressed: () => onCopyFingerprint(FingerprintState.find(id).value),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TRadioMenu<String>>> toolbarViewStyle(
|
||||||
|
BuildContext context, String id, FFI ffi) async {
|
||||||
|
final groupValue = await bind.sessionGetViewStyle(id: id) ?? '';
|
||||||
|
void onChanged(String? value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
bind
|
||||||
|
.sessionSetViewStyle(id: id, value: value)
|
||||||
|
.then((_) => ffi.canvasModel.updateViewStyle());
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Scale original')),
|
||||||
|
value: kRemoteViewStyleOriginal,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged),
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Scale adaptive')),
|
||||||
|
value: kRemoteViewStyleAdaptive,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TRadioMenu<String>>> toolbarImageQuality(
|
||||||
|
BuildContext context, String id, FFI ffi) async {
|
||||||
|
final groupValue = await bind.sessionGetImageQuality(id: id) ?? '';
|
||||||
|
onChanged(String? value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionSetImageQuality(id: id, value: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Good image quality')),
|
||||||
|
value: kRemoteImageQualityBest,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged),
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Balanced')),
|
||||||
|
value: kRemoteImageQualityBalanced,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged),
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Optimize reaction time')),
|
||||||
|
value: kRemoteImageQualityLow,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: onChanged),
|
||||||
|
TRadioMenu<String>(
|
||||||
|
child: Text(translate('Custom')),
|
||||||
|
value: kRemoteImageQualityCustom,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: (value) {
|
||||||
|
onChanged(value);
|
||||||
|
customImageQualityDialog(id, ffi);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TRadioMenu<String>>> toolbarCodec(
|
||||||
|
BuildContext context, String id, FFI ffi) async {
|
||||||
|
final alternativeCodecs = await bind.sessionAlternativeCodecs(id: id);
|
||||||
|
final groupValue =
|
||||||
|
await bind.sessionGetOption(id: id, arg: 'codec-preference') ?? '';
|
||||||
|
final List<bool> codecs = [];
|
||||||
|
try {
|
||||||
|
final Map codecsJson = jsonDecode(alternativeCodecs);
|
||||||
|
final vp8 = codecsJson['vp8'] ?? false;
|
||||||
|
final h264 = codecsJson['h264'] ?? false;
|
||||||
|
final h265 = codecsJson['h265'] ?? false;
|
||||||
|
codecs.add(vp8);
|
||||||
|
codecs.add(h264);
|
||||||
|
codecs.add(h265);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Show Codec Preference err=$e");
|
||||||
|
}
|
||||||
|
final visible = codecs.length == 3 && (codecs[0] || codecs[1] || codecs[2]);
|
||||||
|
if (!visible) return [];
|
||||||
|
onChanged(String? value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionPeerOption(
|
||||||
|
id: id, name: 'codec-preference', value: value);
|
||||||
|
bind.sessionChangePreferCodec(id: id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TRadioMenu<String> radio(String label, String value, bool enabled) {
|
||||||
|
return TRadioMenu<String>(
|
||||||
|
child: Text(translate(label)),
|
||||||
|
value: value,
|
||||||
|
groupValue: groupValue,
|
||||||
|
onChanged: enabled ? onChanged : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
radio('Auto', 'auto', true),
|
||||||
|
if (isDesktop || codecs[0]) radio('VP8', 'vp8', codecs[0]),
|
||||||
|
radio('VP9', 'vp9', true),
|
||||||
|
if (isDesktop || codecs[1]) radio('H264', 'h264', codecs[1]),
|
||||||
|
if (isDesktop || codecs[2]) radio('H265', 'h265', codecs[2]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||||
|
BuildContext context, String id, FFI ffi) async {
|
||||||
|
List<TToggleMenu> v = [];
|
||||||
|
final ffiModel = ffi.ffiModel;
|
||||||
|
final pi = ffiModel.pi;
|
||||||
|
final perms = ffiModel.permissions;
|
||||||
|
|
||||||
|
// show remote cursor
|
||||||
|
if (pi.platform != kPeerPlatformAndroid &&
|
||||||
|
!ffi.canvasModel.cursorEmbedded &&
|
||||||
|
!pi.is_wayland) {
|
||||||
|
final state = ShowRemoteCursorState.find(id);
|
||||||
|
final enabled = !ffiModel.viewOnly;
|
||||||
|
final option = 'show-remote-cursor';
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
child: Text(translate('Show remote cursor')),
|
||||||
|
value: state.value,
|
||||||
|
onChanged: enabled
|
||||||
|
? (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionToggleOption(id: id, value: option);
|
||||||
|
state.value =
|
||||||
|
bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
}
|
||||||
|
: null));
|
||||||
|
}
|
||||||
|
// zoom cursor
|
||||||
|
final viewStyle = await bind.sessionGetViewStyle(id: id) ?? '';
|
||||||
|
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(id: id, value: option);
|
||||||
|
peerState.value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// show quality monitor
|
||||||
|
final option = 'show-quality-monitor';
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: bind.sessionGetToggleOptionSync(id: id, arg: option),
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionToggleOption(id: id, value: option);
|
||||||
|
ffi.qualityMonitorModel.checkShowQualityMonitor(id);
|
||||||
|
},
|
||||||
|
child: Text(translate('Show quality monitor'))));
|
||||||
|
// mute
|
||||||
|
if (perms['audio'] != false) {
|
||||||
|
final option = 'disable-audio';
|
||||||
|
final value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Mute'))));
|
||||||
|
}
|
||||||
|
// file copy and paste
|
||||||
|
if (Platform.isWindows &&
|
||||||
|
pi.platform == kPeerPlatformWindows &&
|
||||||
|
perms['file'] != false) {
|
||||||
|
final option = 'enable-file-transfer';
|
||||||
|
final value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Allow file copy and paste'))));
|
||||||
|
}
|
||||||
|
// disable clipboard
|
||||||
|
if (ffiModel.keyboard && perms['clipboard'] != false) {
|
||||||
|
final enabled = !ffiModel.viewOnly;
|
||||||
|
final option = 'disable-clipboard';
|
||||||
|
var value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
if (ffiModel.viewOnly) value = true;
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: enabled
|
||||||
|
? (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Text(translate('Disable clipboard'))));
|
||||||
|
}
|
||||||
|
// lock after session end
|
||||||
|
if (ffiModel.keyboard) {
|
||||||
|
final option = 'lock-after-session-end';
|
||||||
|
final value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Lock after session end'))));
|
||||||
|
}
|
||||||
|
// privacy mode
|
||||||
|
if (ffiModel.keyboard && pi.features.privacyMode) {
|
||||||
|
final option = 'privacy-mode';
|
||||||
|
final rxValue = PrivacyModeState.find(id);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: rxValue.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
if (ffiModel.pi.currentDisplay != 0) {
|
||||||
|
msgBox(id, 'custom-nook-nocancel-hasclose', 'info',
|
||||||
|
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Privacy mode'))));
|
||||||
|
}
|
||||||
|
// swap key
|
||||||
|
if (ffiModel.keyboard &&
|
||||||
|
((Platform.isMacOS && pi.platform != kPeerPlatformMacOS) ||
|
||||||
|
(!Platform.isMacOS && pi.platform == kPeerPlatformMacOS))) {
|
||||||
|
final option = 'allow_swap_key';
|
||||||
|
final value = bind.sessionGetToggleOptionSync(id: id, arg: option);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
bind.sessionToggleOption(id: id, value: option);
|
||||||
|
},
|
||||||
|
child: Text(translate('Swap control-command key'))));
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ import '../../common/widgets/peer_tab_page.dart';
|
|||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../widgets/button.dart';
|
import '../widgets/button.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_hbb/common/widgets/dialog.dart';
|
||||||
|
|
||||||
/// Connection page for connecting to a remote peer.
|
/// Connection page for connecting to a remote peer.
|
||||||
class ConnectionPage extends StatefulWidget {
|
class ConnectionPage extends StatefulWidget {
|
||||||
const ConnectionPage({Key? key}) : super(key: key);
|
const ConnectionPage({Key? key}) : super(key: key);
|
||||||
@@ -223,9 +225,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
children: [
|
children: [
|
||||||
Button(
|
Button(
|
||||||
isOutline: true,
|
isOutline: true,
|
||||||
onTap: () {
|
onTap: () => onConnect(isFileTransfer: true),
|
||||||
onConnect(isFileTransfer: true);
|
|
||||||
},
|
|
||||||
text: "Transfer File",
|
text: "Transfer File",
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ class _GeneralState extends State<_General> {
|
|||||||
|
|
||||||
Widget audio(BuildContext context) {
|
Widget audio(BuildContext context) {
|
||||||
String getDefault() {
|
String getDefault() {
|
||||||
if (Platform.isWindows) return 'System Sound';
|
if (Platform.isWindows) return translate('System Sound');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ class _GeneralState extends State<_General> {
|
|||||||
return futureBuilder(future: () async {
|
return futureBuilder(future: () async {
|
||||||
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
devices.insert(0, 'System Sound');
|
devices.insert(0, translate('System Sound'));
|
||||||
}
|
}
|
||||||
String current = await getValue();
|
String current = await getValue();
|
||||||
return {'devices': devices, 'current': current};
|
return {'devices': devices, 'current': current};
|
||||||
@@ -415,7 +415,7 @@ class _GeneralState extends State<_General> {
|
|||||||
List<String> keys = langsMap.keys.toList();
|
List<String> keys = langsMap.keys.toList();
|
||||||
List<String> values = langsMap.values.toList();
|
List<String> values = langsMap.values.toList();
|
||||||
keys.insert(0, '');
|
keys.insert(0, '');
|
||||||
values.insert(0, 'Default');
|
values.insert(0, translate('Default'));
|
||||||
String currentKey = data['lang']!;
|
String currentKey = data['lang']!;
|
||||||
if (!keys.contains(currentKey)) {
|
if (!keys.contains(currentKey)) {
|
||||||
currentKey = '';
|
currentKey = '';
|
||||||
@@ -1228,9 +1228,9 @@ class _DisplayState extends State<_Display> {
|
|||||||
children: [
|
children: [
|
||||||
Slider(
|
Slider(
|
||||||
value: fpsValue.value,
|
value: fpsValue.value,
|
||||||
min: 10.0,
|
min: 5.0,
|
||||||
max: 120.0,
|
max: 120.0,
|
||||||
divisions: 22,
|
divisions: 23,
|
||||||
onChanged: (double value) async {
|
onChanged: (double value) async {
|
||||||
fpsValue.value = value;
|
fpsValue.value = value;
|
||||||
await bind.mainSetUserDefaultOption(
|
await bind.mainSetUserDefaultOption(
|
||||||
@@ -1258,9 +1258,6 @@ class _DisplayState extends State<_Display> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget codec(BuildContext context) {
|
Widget codec(BuildContext context) {
|
||||||
if (!bind.mainHasHwcodec()) {
|
|
||||||
return Offstage();
|
|
||||||
}
|
|
||||||
final key = 'codec-preference';
|
final key = 'codec-preference';
|
||||||
onChanged(String value) async {
|
onChanged(String value) async {
|
||||||
await bind.mainSetUserDefaultOption(key: key, value: value);
|
await bind.mainSetUserDefaultOption(key: key, value: value);
|
||||||
@@ -1268,28 +1265,45 @@ class _DisplayState extends State<_Display> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
final groupValue = bind.mainGetUserDefaultOption(key: key);
|
||||||
|
var hwRadios = [];
|
||||||
|
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: onChanged));
|
||||||
|
}
|
||||||
|
if (h265) {
|
||||||
|
hwRadios.add(_Radio(context,
|
||||||
|
value: 'h265',
|
||||||
|
groupValue: groupValue,
|
||||||
|
label: 'H265',
|
||||||
|
onChanged: onChanged));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("failed to parse supported hwdecodings, err=$e");
|
||||||
|
}
|
||||||
return _Card(title: 'Default Codec', children: [
|
return _Card(title: 'Default Codec', children: [
|
||||||
_Radio(context,
|
_Radio(context,
|
||||||
value: 'auto',
|
value: 'auto',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
label: 'Auto',
|
label: 'Auto',
|
||||||
onChanged: onChanged),
|
onChanged: onChanged),
|
||||||
|
_Radio(context,
|
||||||
|
value: 'vp8',
|
||||||
|
groupValue: groupValue,
|
||||||
|
label: 'VP8',
|
||||||
|
onChanged: onChanged),
|
||||||
_Radio(context,
|
_Radio(context,
|
||||||
value: 'vp9',
|
value: 'vp9',
|
||||||
groupValue: groupValue,
|
groupValue: groupValue,
|
||||||
label: 'VP9',
|
label: 'VP9',
|
||||||
onChanged: onChanged),
|
onChanged: onChanged),
|
||||||
_Radio(context,
|
...hwRadios,
|
||||||
value: 'h264',
|
|
||||||
groupValue: groupValue,
|
|
||||||
label: 'H264',
|
|
||||||
onChanged: onChanged),
|
|
||||||
_Radio(context,
|
|
||||||
value: 'h265',
|
|
||||||
groupValue: groupValue,
|
|
||||||
label: 'H265',
|
|
||||||
onChanged: onChanged),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,11 +1390,18 @@ class _AboutState extends State<_About> {
|
|||||||
final license = await bind.mainGetLicense();
|
final license = await bind.mainGetLicense();
|
||||||
final version = await bind.mainGetVersion();
|
final version = await bind.mainGetVersion();
|
||||||
final buildDate = await bind.mainGetBuildDate();
|
final buildDate = await bind.mainGetBuildDate();
|
||||||
return {'license': license, 'version': version, 'buildDate': buildDate};
|
final fingerprint = await bind.mainGetFingerprint();
|
||||||
|
return {
|
||||||
|
'license': license,
|
||||||
|
'version': version,
|
||||||
|
'buildDate': buildDate,
|
||||||
|
'fingerprint': fingerprint
|
||||||
|
};
|
||||||
}(), hasData: (data) {
|
}(), hasData: (data) {
|
||||||
final license = data['license'].toString();
|
final license = data['license'].toString();
|
||||||
final version = data['version'].toString();
|
final version = data['version'].toString();
|
||||||
final buildDate = data['buildDate'].toString();
|
final buildDate = data['buildDate'].toString();
|
||||||
|
final fingerprint = data['fingerprint'].toString();
|
||||||
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return DesktopScrollWrapper(
|
||||||
@@ -1401,6 +1422,9 @@ class _AboutState extends State<_About> {
|
|||||||
SelectionArea(
|
SelectionArea(
|
||||||
child: Text('${translate('Build Date')}: $buildDate')
|
child: Text('${translate('Build Date')}: $buildDate')
|
||||||
.marginSymmetric(vertical: 4.0)),
|
.marginSymmetric(vertical: 4.0)),
|
||||||
|
SelectionArea(
|
||||||
|
child: Text('${translate('Fingerprint')}: $fingerprint')
|
||||||
|
.marginSymmetric(vertical: 4.0)),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchUrlString('https://rustdesk.com/privacy');
|
launchUrlString('https://rustdesk.com/privacy');
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class DesktopTabPage extends StatefulWidget {
|
|||||||
DesktopTabController tabController = Get.find();
|
DesktopTabController tabController = Get.find();
|
||||||
tabController.add(TabInfo(
|
tabController.add(TabInfo(
|
||||||
key: kTabLabelSettingPage,
|
key: kTabLabelSettingPage,
|
||||||
label: translate(kTabLabelSettingPage),
|
label: kTabLabelSettingPage,
|
||||||
selectedIcon: Icons.build_sharp,
|
selectedIcon: Icons.build_sharp,
|
||||||
unselectedIcon: Icons.build_outlined,
|
unselectedIcon: Icons.build_outlined,
|
||||||
page: DesktopSettingPage(
|
page: DesktopSettingPage(
|
||||||
@@ -46,7 +46,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
RemoteCountState.init();
|
RemoteCountState.init();
|
||||||
tabController.add(TabInfo(
|
tabController.add(TabInfo(
|
||||||
key: kTabLabelHomePage,
|
key: kTabLabelHomePage,
|
||||||
label: translate(kTabLabelHomePage),
|
label: kTabLabelHomePage,
|
||||||
selectedIcon: Icons.home_sharp,
|
selectedIcon: Icons.home_sharp,
|
||||||
unselectedIcon: Icons.home_outlined,
|
unselectedIcon: Icons.home_outlined,
|
||||||
closable: false,
|
closable: false,
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
switchType: SwitchType.scheckbox,
|
switchType: SwitchType.scheckbox,
|
||||||
text: translate("Show Hidden Files"),
|
text: translate("Show Hidden Files"),
|
||||||
getter: () async {
|
getter: () async {
|
||||||
return controller.options.value.isWindows;
|
return controller.options.value.showHidden;
|
||||||
},
|
},
|
||||||
setter: (bool v) async {
|
setter: (bool v) async {
|
||||||
controller.toggleShowHidden();
|
controller.toggleShowHidden();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import '../../consts.dart';
|
|||||||
import '../../common/widgets/overlay.dart';
|
import '../../common/widgets/overlay.dart';
|
||||||
import '../../common/widgets/remote_input.dart';
|
import '../../common/widgets/remote_input.dart';
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../mobile/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
@@ -77,15 +77,8 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
late FFI _ffi;
|
late FFI _ffi;
|
||||||
|
|
||||||
void _initStates(String id) {
|
void _initStates(String id) {
|
||||||
PrivacyModeState.init(id);
|
initSharedStates(id);
|
||||||
BlockInputState.init(id);
|
_zoomCursor = PeerBoolOption.find(id, 'zoom-cursor');
|
||||||
CurrentDisplayState.init(id);
|
|
||||||
KeyboardEnabledState.init(id);
|
|
||||||
ShowRemoteCursorState.init(id);
|
|
||||||
RemoteCursorMovedState.init(id);
|
|
||||||
final optZoomCursor = 'zoom-cursor';
|
|
||||||
PeerBoolOption.init(id, optZoomCursor, () => false);
|
|
||||||
_zoomCursor = PeerBoolOption.find(id, optZoomCursor);
|
|
||||||
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
||||||
_keyboardEnabled = KeyboardEnabledState.find(id);
|
_keyboardEnabled = KeyboardEnabledState.find(id);
|
||||||
_remoteCursorMoved = RemoteCursorMovedState.find(id);
|
_remoteCursorMoved = RemoteCursorMovedState.find(id);
|
||||||
@@ -93,15 +86,6 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
_textureId = RxInt(-1);
|
_textureId = RxInt(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeStates(String id) {
|
|
||||||
PrivacyModeState.delete(id);
|
|
||||||
BlockInputState.delete(id);
|
|
||||||
CurrentDisplayState.delete(id);
|
|
||||||
ShowRemoteCursorState.delete(id);
|
|
||||||
KeyboardEnabledState.delete(id);
|
|
||||||
RemoteCursorMovedState.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -158,12 +142,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
// _isCustomCursorInited = true;
|
// _isCustomCursorInited = true;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
_ffi.dialogManager.setOverlayState(_blockableOverlayState);
|
_blockableOverlayState.applyFfi(_ffi);
|
||||||
_ffi.chatModel.setOverlayState(_blockableOverlayState);
|
|
||||||
// make remote page penetrable automatically, effective for chat over remote
|
|
||||||
_blockableOverlayState.onMiddleBlockedClick = () {
|
|
||||||
_blockableOverlayState.setMiddleBlocked(false);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -222,7 +201,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
Get.delete<FFI>(tag: widget.id);
|
Get.delete<FFI>(tag: widget.id);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_removeStates(widget.id);
|
removeSharedStates(widget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(BuildContext context) {
|
Widget buildBody(BuildContext context) {
|
||||||
@@ -310,7 +289,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void leaveView(PointerExitEvent evt) {
|
void leaveView(PointerExitEvent evt) {
|
||||||
if (_ffi.ffiModel.keyboard()) {
|
if (_ffi.ffiModel.keyboard) {
|
||||||
_ffi.inputModel.tryMoveEdgeOnExit(evt.position);
|
_ffi.inputModel.tryMoveEdgeOnExit(evt.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:ui' as ui;
|
|||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/common/shared_state.dart';
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
@@ -158,20 +159,36 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final msgDirect = translate(
|
bool secure =
|
||||||
connectionType.direct.value == ConnectionType.strDirect
|
connectionType.secure.value == ConnectionType.strSecure;
|
||||||
? 'Direct Connection'
|
bool direct =
|
||||||
: 'Relay Connection');
|
connectionType.direct.value == ConnectionType.strDirect;
|
||||||
final msgSecure = translate(
|
var msgConn;
|
||||||
connectionType.secure.value == ConnectionType.strSecure
|
if (secure && direct) {
|
||||||
? 'Secure Connection'
|
msgConn = translate("Direct and encrypted connection");
|
||||||
: 'Insecure Connection');
|
} else if (secure && !direct) {
|
||||||
|
msgConn = translate("Relayed and encrypted connection");
|
||||||
|
} else if (!secure && direct) {
|
||||||
|
msgConn = translate("Direct and unencrypted connection");
|
||||||
|
} else {
|
||||||
|
msgConn = translate("Relayed and unencrypted connection");
|
||||||
|
}
|
||||||
|
var msgFingerprint = '${translate('Fingerprint')}:\n';
|
||||||
|
var fingerprint = FingerprintState.find(key).value;
|
||||||
|
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(
|
final tab = Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: '$msgDirect\n$msgSecure',
|
message: '$msgConn\n$msgFingerprint',
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
'assets/${connectionType.secure.value}${connectionType.direct.value}.svg',
|
'assets/${connectionType.secure.value}${connectionType.direct.value}.svg',
|
||||||
width: themeConf.iconSize,
|
width: themeConf.iconSize,
|
||||||
@@ -259,7 +276,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!ffi.canvasModel.cursorEmbedded && !ffi.ffiModel.viewOnly) {
|
if (!ffi.canvasModel.cursorEmbedded &&
|
||||||
|
!ffi.ffiModel.viewOnly &&
|
||||||
|
!pi.is_wayland) {
|
||||||
menu.add(MenuEntryDivider<String>());
|
menu.add(MenuEntryDivider<String>());
|
||||||
menu.add(RemoteMenuEntry.showRemoteCursor(
|
menu.add(RemoteMenuEntry.showRemoteCursor(
|
||||||
key,
|
key,
|
||||||
@@ -283,6 +302,17 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Copy Fingerprint'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () => onCopyFingerprint(FingerprintState.find(key).value),
|
||||||
|
padding: padding,
|
||||||
|
dismissOnClicked: true,
|
||||||
|
dismissCallback: cancelFunc,
|
||||||
|
));
|
||||||
|
|
||||||
return mod_menu.PopupMenu<String>(
|
return mod_menu.PopupMenu<String>(
|
||||||
items: menu
|
items: menu
|
||||||
.map((entry) => entry.build(
|
.map((entry) => entry.build(
|
||||||
|
|||||||
49
flutter/lib/desktop/plugin/common.dart
Normal file
49
flutter/lib/desktop/plugin/common.dart
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
typedef PluginId = String;
|
||||||
|
|
||||||
|
// ui location
|
||||||
|
const String kLocationHostMainDisplayOthers =
|
||||||
|
'host|main|settings|display|others';
|
||||||
|
const String kLocationClientRemoteToolbarDisplay =
|
||||||
|
'client|remote|toolbar|display';
|
||||||
|
|
||||||
|
class MsgFromUi {
|
||||||
|
String remotePeerId;
|
||||||
|
String localPeerId;
|
||||||
|
String id;
|
||||||
|
String name;
|
||||||
|
String location;
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
String action;
|
||||||
|
|
||||||
|
MsgFromUi({
|
||||||
|
required this.remotePeerId,
|
||||||
|
required this.localPeerId,
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.location,
|
||||||
|
required this.key,
|
||||||
|
required this.value,
|
||||||
|
required this.action,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'remote_peer_id': remotePeerId,
|
||||||
|
'local_peer_id': localPeerId,
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'location': location,
|
||||||
|
'key': key,
|
||||||
|
'value': value,
|
||||||
|
'action': action,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode(toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
166
flutter/lib/desktop/plugin/desc.dart
Normal file
166
flutter/lib/desktop/plugin/desc.dart
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
const String kValueTrue = '1';
|
||||||
|
const String kValueFalse = '0';
|
||||||
|
|
||||||
|
class UiType {
|
||||||
|
String key;
|
||||||
|
String text;
|
||||||
|
String tooltip;
|
||||||
|
String action;
|
||||||
|
|
||||||
|
UiType(this.key, this.text, this.tooltip, this.action);
|
||||||
|
|
||||||
|
UiType.fromJson(Map<String, dynamic> json)
|
||||||
|
: key = json['key'] ?? '',
|
||||||
|
text = json['text'] ?? '',
|
||||||
|
tooltip = json['tooltip'] ?? '',
|
||||||
|
action = json['action'] ?? '';
|
||||||
|
|
||||||
|
static UiType? create(Map<String, dynamic> json) {
|
||||||
|
if (json['t'] == 'Button') {
|
||||||
|
return UiButton.fromJson(json['c']);
|
||||||
|
} else if (json['t'] == 'Checkbox') {
|
||||||
|
return UiCheckbox.fromJson(json['c']);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UiButton extends UiType {
|
||||||
|
String icon;
|
||||||
|
|
||||||
|
UiButton(
|
||||||
|
{required String key,
|
||||||
|
required String text,
|
||||||
|
required this.icon,
|
||||||
|
required String tooltip,
|
||||||
|
required String action})
|
||||||
|
: super(key, text, tooltip, action);
|
||||||
|
|
||||||
|
UiButton.fromJson(Map<String, dynamic> json)
|
||||||
|
: icon = json['icon'] ?? '',
|
||||||
|
super.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UiCheckbox extends UiType {
|
||||||
|
UiCheckbox(
|
||||||
|
{required String key,
|
||||||
|
required String text,
|
||||||
|
required String tooltip,
|
||||||
|
required String action})
|
||||||
|
: super(key, text, tooltip, action);
|
||||||
|
|
||||||
|
UiCheckbox.fromJson(Map<String, dynamic> json) : super.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Location {
|
||||||
|
// location key:
|
||||||
|
// host|main|settings|display|others
|
||||||
|
// client|remote|toolbar|display
|
||||||
|
HashMap<String, UiType> ui;
|
||||||
|
|
||||||
|
Location(this.ui);
|
||||||
|
Location.fromJson(Map<String, dynamic> json) : ui = HashMap() {
|
||||||
|
json.forEach((key, value) {
|
||||||
|
var ui = UiType.create(value);
|
||||||
|
if (ui != null) {
|
||||||
|
this.ui[ui.key] = ui;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigItem {
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
String description;
|
||||||
|
String defaultValue;
|
||||||
|
|
||||||
|
ConfigItem(this.key, this.value, this.defaultValue, this.description);
|
||||||
|
ConfigItem.fromJson(Map<String, dynamic> json)
|
||||||
|
: key = json['key'] ?? '',
|
||||||
|
value = json['value'] ?? '',
|
||||||
|
description = json['description'] ?? '',
|
||||||
|
defaultValue = json['default'] ?? '';
|
||||||
|
|
||||||
|
static String get trueValue => kValueTrue;
|
||||||
|
static String get falseValue => kValueFalse;
|
||||||
|
static bool isTrue(String value) => value == kValueTrue;
|
||||||
|
static bool isFalse(String value) => value == kValueFalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
List<ConfigItem> local;
|
||||||
|
List<ConfigItem> peer;
|
||||||
|
|
||||||
|
Config(this.local, this.peer);
|
||||||
|
Config.fromJson(Map<String, dynamic> json)
|
||||||
|
: local = (json['local'] as List<dynamic>)
|
||||||
|
.map((e) => ConfigItem.fromJson(e))
|
||||||
|
.toList(),
|
||||||
|
peer = (json['peer'] as List<dynamic>)
|
||||||
|
.map((e) => ConfigItem.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Desc {
|
||||||
|
String id;
|
||||||
|
String name;
|
||||||
|
String version;
|
||||||
|
String description;
|
||||||
|
String author;
|
||||||
|
String home;
|
||||||
|
String license;
|
||||||
|
String published;
|
||||||
|
String released;
|
||||||
|
String github;
|
||||||
|
Location location;
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
Desc(
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.version,
|
||||||
|
this.description,
|
||||||
|
this.author,
|
||||||
|
this.home,
|
||||||
|
this.license,
|
||||||
|
this.published,
|
||||||
|
this.released,
|
||||||
|
this.github,
|
||||||
|
this.location,
|
||||||
|
this.config);
|
||||||
|
|
||||||
|
Desc.fromJson(Map<String, dynamic> json)
|
||||||
|
: id = json['id'] ?? '',
|
||||||
|
name = json['name'] ?? '',
|
||||||
|
version = json['version'] ?? '',
|
||||||
|
description = json['description'] ?? '',
|
||||||
|
author = json['author'] ?? '',
|
||||||
|
home = json['home'] ?? '',
|
||||||
|
license = json['license'] ?? '',
|
||||||
|
published = json['published'] ?? '',
|
||||||
|
released = json['released'] ?? '',
|
||||||
|
github = json['github'] ?? '',
|
||||||
|
location = Location(HashMap<String, UiType>.from(json['location'])),
|
||||||
|
config = Config(
|
||||||
|
(json['config'] as List<dynamic>)
|
||||||
|
.map((e) => ConfigItem.fromJson(e))
|
||||||
|
.toList(),
|
||||||
|
(json['config'] as List<dynamic>)
|
||||||
|
.map((e) => ConfigItem.fromJson(e))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
final mapPluginDesc = <String, Desc>{};
|
||||||
|
|
||||||
|
void updateDesc(Map<String, dynamic> desc) {
|
||||||
|
Desc d = Desc.fromJson(desc);
|
||||||
|
mapPluginDesc[d.id] = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
Desc? getDesc(String id) {
|
||||||
|
return mapPluginDesc[id];
|
||||||
|
}
|
||||||
60
flutter/lib/desktop/plugin/event.dart
Normal file
60
flutter/lib/desktop/plugin/event.dart
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
void handlePluginEvent(
|
||||||
|
Map<String, dynamic> evt,
|
||||||
|
String peer,
|
||||||
|
Function(Map<String, dynamic> e) handleMsgBox,
|
||||||
|
) {
|
||||||
|
// content
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "t": "Option",
|
||||||
|
// "c": {
|
||||||
|
// "id": "id from RustDesk platform",
|
||||||
|
// "name": "Privacy Mode",
|
||||||
|
// "version": "v0.1.0",
|
||||||
|
// "location": "client|remote|toolbar|display",
|
||||||
|
// "key": "privacy-mode",
|
||||||
|
// "value": "1"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "t": "MsgBox",
|
||||||
|
// "c": {
|
||||||
|
// "type": "custom-nocancel",
|
||||||
|
// "title": "Privacy Mode",
|
||||||
|
// "text": "Failed unknown",
|
||||||
|
// "link": ""
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
if (evt['content']?['c'] == null) return;
|
||||||
|
final t = evt['content']?['t'];
|
||||||
|
if (t == 'Option') {
|
||||||
|
handleOptionEvent(evt['content']?['c'], peer);
|
||||||
|
} else if (t == 'MsgBox') {
|
||||||
|
handleMsgBox(evt['content']?['c']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleOptionEvent(Map<String, dynamic> evt, String peer) {
|
||||||
|
// content
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// "id": "id from RustDesk platform",
|
||||||
|
// "name": "Privacy Mode",
|
||||||
|
// "version": "v0.1.0",
|
||||||
|
// "location": "client|remote|toolbar|display",
|
||||||
|
// "key": "privacy-mode",
|
||||||
|
// "value": "1"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
final key = evt['key'];
|
||||||
|
final value = evt['value'];
|
||||||
|
if (key == 'privacy-mode') {
|
||||||
|
if (value == '1') {
|
||||||
|
// enable privacy mode
|
||||||
|
} else {
|
||||||
|
// disable privacy mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
flutter/lib/desktop/plugin/model.dart
Normal file
44
flutter/lib/desktop/plugin/model.dart
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import './common.dart';
|
||||||
|
import './desc.dart';
|
||||||
|
|
||||||
|
final Map<String, LocationModel> locationModels = {};
|
||||||
|
|
||||||
|
class PluginModel with ChangeNotifier {
|
||||||
|
final List<UiType> uiList = [];
|
||||||
|
|
||||||
|
void add(UiType ui) {
|
||||||
|
uiList.add(ui);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEmpty => uiList.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocationModel with ChangeNotifier {
|
||||||
|
final Map<PluginId, PluginModel> pluginModels = {};
|
||||||
|
|
||||||
|
void add(PluginId id, UiType ui) {
|
||||||
|
if (pluginModels[id] != null) {
|
||||||
|
pluginModels[id]!.add(ui);
|
||||||
|
} else {
|
||||||
|
var model = PluginModel();
|
||||||
|
model.add(ui);
|
||||||
|
pluginModels[id] = model;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEmpty => pluginModels.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLocationUi(String location, PluginId id, UiType ui) {
|
||||||
|
locationModels[location]?.add(id, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationModel addLocation(String location) {
|
||||||
|
if (locationModels[location] == null) {
|
||||||
|
locationModels[location] = LocationModel();
|
||||||
|
}
|
||||||
|
return locationModels[location]!;
|
||||||
|
}
|
||||||
175
flutter/lib/desktop/plugin/widget.dart
Normal file
175
flutter/lib/desktop/plugin/widget.dart
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hbb/models/model.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
||||||
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
|
||||||
|
import './desc.dart';
|
||||||
|
import './model.dart';
|
||||||
|
import './common.dart';
|
||||||
|
|
||||||
|
class LocationItem extends StatelessWidget {
|
||||||
|
final String peerId;
|
||||||
|
final FFI ffi;
|
||||||
|
final String location;
|
||||||
|
final LocationModel locationModel;
|
||||||
|
|
||||||
|
LocationItem({
|
||||||
|
Key? key,
|
||||||
|
required this.peerId,
|
||||||
|
required this.ffi,
|
||||||
|
required this.location,
|
||||||
|
required this.locationModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool get isEmpty => locationModel.isEmpty;
|
||||||
|
|
||||||
|
static LocationItem createLocationItem(
|
||||||
|
String peerId, FFI ffi, String location) {
|
||||||
|
final model = addLocation(location);
|
||||||
|
return LocationItem(
|
||||||
|
peerId: peerId,
|
||||||
|
ffi: ffi,
|
||||||
|
location: location,
|
||||||
|
locationModel: model,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: locationModel,
|
||||||
|
child: Consumer<LocationModel>(builder: (context, model, child) {
|
||||||
|
return Column(
|
||||||
|
children: model.pluginModels.entries
|
||||||
|
.map((entry) => _buildPluginItem(entry.key, entry.value))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPluginItem(PluginId id, PluginModel model) => PluginItem(
|
||||||
|
pluginId: id,
|
||||||
|
peerId: peerId,
|
||||||
|
ffi: ffi,
|
||||||
|
location: location,
|
||||||
|
pluginModel: model,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginItem extends StatelessWidget {
|
||||||
|
final PluginId pluginId;
|
||||||
|
final String peerId;
|
||||||
|
final FFI ffi;
|
||||||
|
final String location;
|
||||||
|
final PluginModel pluginModel;
|
||||||
|
|
||||||
|
PluginItem({
|
||||||
|
Key? key,
|
||||||
|
required this.pluginId,
|
||||||
|
required this.peerId,
|
||||||
|
required this.ffi,
|
||||||
|
required this.location,
|
||||||
|
required this.pluginModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool get isEmpty => pluginModel.isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: pluginModel,
|
||||||
|
child: Consumer<PluginModel>(builder: (context, model, child) {
|
||||||
|
return Column(
|
||||||
|
children: model.uiList.map((ui) => _buildItem(ui)).toList(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// to-do: add plugin icon and tooltip
|
||||||
|
Widget _buildItem(UiType ui) {
|
||||||
|
switch (ui.runtimeType) {
|
||||||
|
case UiButton:
|
||||||
|
return _buildMenuButton(ui as UiButton);
|
||||||
|
case UiCheckbox:
|
||||||
|
return _buildCheckboxMenuButton(ui as UiCheckbox);
|
||||||
|
default:
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List _makeEvent(
|
||||||
|
String localPeerId,
|
||||||
|
String key, {
|
||||||
|
bool? v,
|
||||||
|
}) {
|
||||||
|
final event = MsgFromUi(
|
||||||
|
remotePeerId: peerId,
|
||||||
|
localPeerId: localPeerId,
|
||||||
|
id: pluginId,
|
||||||
|
name: getDesc(pluginId)?.name ?? '',
|
||||||
|
location: location,
|
||||||
|
key: key,
|
||||||
|
value:
|
||||||
|
v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
|
||||||
|
action: '',
|
||||||
|
);
|
||||||
|
return Uint8List.fromList(event.toString().codeUnits);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenuButton(UiButton ui) {
|
||||||
|
return MenuButton(
|
||||||
|
onPressed: () {
|
||||||
|
() async {
|
||||||
|
final localPeerId = await bind.mainGetMyId();
|
||||||
|
bind.pluginEvent(
|
||||||
|
id: pluginId,
|
||||||
|
event: _makeEvent(localPeerId, ui.key),
|
||||||
|
);
|
||||||
|
}();
|
||||||
|
},
|
||||||
|
trailingIcon: Icon(
|
||||||
|
IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
|
||||||
|
// to-do: RustDesk translate or plugin translate ?
|
||||||
|
child: Text(ui.text),
|
||||||
|
ffi: ffi,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckboxMenuButton(UiCheckbox ui) {
|
||||||
|
final v =
|
||||||
|
bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: ui.key);
|
||||||
|
if (v == null) {
|
||||||
|
// session or plugin not found
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
return CkbMenuButton(
|
||||||
|
value: ConfigItem.isTrue(v),
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v != null) {
|
||||||
|
() async {
|
||||||
|
final localPeerId = await bind.mainGetMyId();
|
||||||
|
bind.pluginEvent(
|
||||||
|
id: pluginId,
|
||||||
|
event: _makeEvent(localPeerId, ui.key, v: v),
|
||||||
|
);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// to-do: rustdesk translate or plugin translate ?
|
||||||
|
child: Text(ui.text),
|
||||||
|
ffi: ffi,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleReloading(Map<String, dynamic> evt, String peer) {
|
||||||
|
if (evt['id'] == null || evt['location'] == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ui = UiType.fromJson(evt);
|
||||||
|
addLocationUi(evt['location']!, evt['id']!, ui);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -869,7 +869,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
|||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.label.value,
|
translate(widget.label.value),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
|
|||||||
@@ -134,10 +134,8 @@ void runMainApp(bool startService) async {
|
|||||||
await restoreWindowPosition(WindowType.Main);
|
await restoreWindowPosition(WindowType.Main);
|
||||||
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
|
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
|
||||||
final handledByUniLinks = await initUniLinks();
|
final handledByUniLinks = await initUniLinks();
|
||||||
final handledByCli = checkArguments();
|
debugPrint("handled by uni links: $handledByUniLinks");
|
||||||
debugPrint(
|
if (handledByUniLinks || checkArguments()) {
|
||||||
"handled by uni links: $handledByUniLinks, handled by cli: $handledByCli");
|
|
||||||
if (handledByUniLinks || handledByCli) {
|
|
||||||
windowManager.hide();
|
windowManager.hide();
|
||||||
} else {
|
} else {
|
||||||
windowManager.show();
|
windowManager.show();
|
||||||
@@ -250,6 +248,7 @@ void hideCmWindow() {
|
|||||||
windowManager.setOpacity(0);
|
windowManager.setOpacity(0);
|
||||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
bind.mainHideDocker();
|
bind.mainHideDocker();
|
||||||
|
await windowManager.minimize();
|
||||||
await windowManager.hide();
|
await windowManager.hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import 'package:toggle_switch/toggle_switch.dart';
|
|||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
|
|
||||||
class FileManagerPage extends StatefulWidget {
|
class FileManagerPage extends StatefulWidget {
|
||||||
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
||||||
|
|||||||
@@ -4,22 +4,25 @@ import 'dart:ui' as ui;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
|
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
|
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
|
||||||
import 'package:flutter_hbb/models/chat_model.dart';
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/overlay.dart';
|
import '../../common/widgets/overlay.dart';
|
||||||
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/remote_input.dart';
|
import '../../common/widgets/remote_input.dart';
|
||||||
import '../../models/input_model.dart';
|
import '../../models/input_model.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../utils/image.dart';
|
import '../../utils/image.dart';
|
||||||
import '../widgets/dialog.dart';
|
|
||||||
import '../widgets/gestures.dart';
|
import '../widgets/gestures.dart';
|
||||||
|
|
||||||
final initText = '\1' * 1024;
|
final initText = '\1' * 1024;
|
||||||
@@ -42,6 +45,8 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
||||||
Orientation? _currentOrientation;
|
Orientation? _currentOrientation;
|
||||||
|
|
||||||
|
final _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
final keyboardVisibilityController = KeyboardVisibilityController();
|
final keyboardVisibilityController = KeyboardVisibilityController();
|
||||||
late final StreamSubscription<bool> keyboardSubscription;
|
late final StreamSubscription<bool> keyboardSubscription;
|
||||||
final FocusNode _mobileFocusNode = FocusNode();
|
final FocusNode _mobileFocusNode = FocusNode();
|
||||||
@@ -66,6 +71,8 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
gFFI.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
||||||
keyboardSubscription =
|
keyboardSubscription =
|
||||||
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
|
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
|
||||||
|
_blockableOverlayState.applyFfi(gFFI);
|
||||||
|
initSharedStates(widget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -82,6 +89,7 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
overlays: SystemUiOverlay.values);
|
overlays: SystemUiOverlay.values);
|
||||||
Wakelock.disable();
|
Wakelock.disable();
|
||||||
keyboardSubscription.cancel();
|
keyboardSubscription.cancel();
|
||||||
|
removeSharedStates(widget.id);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,128 +548,21 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
final x = 120.0;
|
final x = 120.0;
|
||||||
final y = size.height;
|
final y = size.height;
|
||||||
final more = <PopupMenuItem<String>>[];
|
final menus = toolbarControls(context, id, gFFI);
|
||||||
final pi = gFFI.ffiModel.pi;
|
final more = menus
|
||||||
final perms = gFFI.ffiModel.permissions;
|
.asMap()
|
||||||
if (pi.version.isNotEmpty) {
|
.entries
|
||||||
more.add(PopupMenuItem<String>(
|
.map((e) => PopupMenuItem<int>(child: e.value.child, value: e.key))
|
||||||
child: Text(translate('Refresh')), value: 'refresh'));
|
.toList();
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Row(
|
|
||||||
children: ([
|
|
||||||
Text(translate('OS Password')),
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () {
|
|
||||||
showSetOSPassword(id, false, gFFI.dialogManager);
|
|
||||||
},
|
|
||||||
child: Icon(Icons.edit, color: MyTheme.accent),
|
|
||||||
)
|
|
||||||
])),
|
|
||||||
value: 'enter_os_password'));
|
|
||||||
if (!isWebDesktop) {
|
|
||||||
if (perms['keyboard'] != false && perms['clipboard'] != false) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Paste')), value: 'paste'));
|
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Reset canvas')), value: 'reset_canvas'));
|
|
||||||
}
|
|
||||||
if (perms['keyboard'] != false) {
|
|
||||||
// * Currently mobile does not enable map mode
|
|
||||||
// more.add(PopupMenuItem<String>(
|
|
||||||
// child: Text(translate('Physical Keyboard Input Mode')),
|
|
||||||
// value: 'input-mode'));
|
|
||||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text('${translate('Insert')} Ctrl + Alt + Del'),
|
|
||||||
value: 'cad'));
|
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Insert Lock')), value: 'lock'));
|
|
||||||
if (pi.platform == kPeerPlatformWindows &&
|
|
||||||
await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') !=
|
|
||||||
true) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate(
|
|
||||||
'${gFFI.ffiModel.inputBlocked ? 'Unb' : 'B'}lock user input')),
|
|
||||||
value: 'block-input'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (perms["restart"] != false &&
|
|
||||||
(pi.platform == kPeerPlatformLinux ||
|
|
||||||
pi.platform == kPeerPlatformWindows ||
|
|
||||||
pi.platform == kPeerPlatformMacOS)) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Restart Remote Device')), value: 'restart'));
|
|
||||||
}
|
|
||||||
// Currently only support VP9
|
|
||||||
if (gFFI.recordingModel.start ||
|
|
||||||
(perms["recording"] != false &&
|
|
||||||
gFFI.qualityMonitorModel.data.codecFormat == "VP9")) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(translate(gFFI.recordingModel.start
|
|
||||||
? 'Stop session recording'
|
|
||||||
: 'Start session recording')),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(left: 12),
|
|
||||||
child: Icon(
|
|
||||||
gFFI.recordingModel.start
|
|
||||||
? Icons.pause_circle_filled
|
|
||||||
: Icons.videocam_outlined,
|
|
||||||
color: MyTheme.accent),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
value: 'record'));
|
|
||||||
}
|
|
||||||
() async {
|
() async {
|
||||||
var value = await showMenu(
|
var index = await showMenu(
|
||||||
context: context,
|
context: context,
|
||||||
position: RelativeRect.fromLTRB(x, y, x, y),
|
position: RelativeRect.fromLTRB(x, y, x, y),
|
||||||
items: more,
|
items: more,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
);
|
);
|
||||||
if (value == 'cad') {
|
if (index != null && index < menus.length) {
|
||||||
bind.sessionCtrlAltDel(id: widget.id);
|
menus[index].onPressed.call();
|
||||||
// * Currently mobile does not enable map mode
|
|
||||||
// } else if (value == 'input-mode') {
|
|
||||||
// changePhysicalKeyboardInputMode();
|
|
||||||
} else if (value == 'lock') {
|
|
||||||
bind.sessionLockScreen(id: widget.id);
|
|
||||||
} else if (value == 'block-input') {
|
|
||||||
bind.sessionToggleOption(
|
|
||||||
id: widget.id,
|
|
||||||
value: '${gFFI.ffiModel.inputBlocked ? 'un' : ''}block-input');
|
|
||||||
gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked;
|
|
||||||
} else if (value == 'refresh') {
|
|
||||||
bind.sessionRefresh(id: widget.id);
|
|
||||||
} else if (value == 'paste') {
|
|
||||||
() async {
|
|
||||||
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
|
|
||||||
if (data != null && data.text != null) {
|
|
||||||
bind.sessionInputString(id: widget.id, value: data.text ?? "");
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
} else if (value == 'enter_os_password') {
|
|
||||||
// FIXME:
|
|
||||||
// null means no session of id
|
|
||||||
// empty string means no password
|
|
||||||
var password = await bind.sessionGetOption(id: id, arg: 'os-password');
|
|
||||||
if (password != null) {
|
|
||||||
bind.sessionInputOsPassword(id: widget.id, value: password);
|
|
||||||
} else {
|
|
||||||
showSetOSPassword(id, true, gFFI.dialogManager);
|
|
||||||
}
|
|
||||||
} else if (value == 'reset_canvas') {
|
|
||||||
gFFI.cursorModel.reset();
|
|
||||||
} else if (value == 'restart') {
|
|
||||||
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
|
||||||
} else if (value == 'record') {
|
|
||||||
gFFI.recordingModel.toggle();
|
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
@@ -695,10 +596,8 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
// return CustomAlertDialog(
|
// return CustomAlertDialog(
|
||||||
// title: Text(translate('Physical Keyboard Input Mode')),
|
// title: Text(translate('Physical Keyboard Input Mode')),
|
||||||
// content: Column(mainAxisSize: MainAxisSize.min, children: [
|
// content: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
// getRadio('Legacy mode', 'legacy', current, setMode,
|
// getRadio('Legacy mode', 'legacy', current, setMode),
|
||||||
// contentPadding: EdgeInsets.zero),
|
// getRadio('Map mode', 'map', current, setMode),
|
||||||
// getRadio('Map mode', 'map', current, setMode,
|
|
||||||
// contentPadding: EdgeInsets.zero),
|
|
||||||
// ]));
|
// ]));
|
||||||
// }, clickMaskDismiss: true);
|
// }, clickMaskDismiss: true);
|
||||||
// }
|
// }
|
||||||
@@ -918,14 +817,6 @@ class CursorPaint extends StatelessWidget {
|
|||||||
|
|
||||||
void showOptions(
|
void showOptions(
|
||||||
BuildContext context, String id, OverlayDialogManager dialogManager) async {
|
BuildContext context, String id, OverlayDialogManager dialogManager) async {
|
||||||
String quality =
|
|
||||||
await bind.sessionGetImageQuality(id: id) ?? kRemoteImageQualityBalanced;
|
|
||||||
if (quality == '') quality = kRemoteImageQualityBalanced;
|
|
||||||
String codec =
|
|
||||||
await bind.sessionGetOption(id: id, arg: 'codec-preference') ?? 'auto';
|
|
||||||
if (codec == '') codec = 'auto';
|
|
||||||
String viewStyle = await bind.sessionGetViewStyle(id: id) ?? '';
|
|
||||||
|
|
||||||
var displays = <Widget>[];
|
var displays = <Widget>[];
|
||||||
final pi = gFFI.ffiModel.pi;
|
final pi = gFFI.ffiModel.pi;
|
||||||
final image = gFFI.ffiModel.getConnectionImage();
|
final image = gFFI.ffiModel.getConnectionImage();
|
||||||
@@ -968,155 +859,65 @@ void showOptions(
|
|||||||
if (displays.isNotEmpty) {
|
if (displays.isNotEmpty) {
|
||||||
displays.add(const Divider(color: MyTheme.border));
|
displays.add(const Divider(color: MyTheme.border));
|
||||||
}
|
}
|
||||||
final perms = gFFI.ffiModel.permissions;
|
|
||||||
final hasHwcodec = bind.mainHasHwcodec();
|
List<TRadioMenu<String>> viewStyleRadios =
|
||||||
final List<bool> codecs = [];
|
await toolbarViewStyle(context, id, gFFI);
|
||||||
if (hasHwcodec) {
|
List<TRadioMenu<String>> imageQualityRadios =
|
||||||
try {
|
await toolbarImageQuality(context, id, gFFI);
|
||||||
final Map codecsJson =
|
List<TRadioMenu<String>> codecRadios = await toolbarCodec(context, id, gFFI);
|
||||||
jsonDecode(await bind.sessionSupportedHwcodec(id: id));
|
List<TToggleMenu> displayToggles =
|
||||||
final h264 = codecsJson['h264'] ?? false;
|
await toolbarDisplayToggle(context, id, gFFI);
|
||||||
final h265 = codecsJson['h265'] ?? false;
|
|
||||||
codecs.add(h264);
|
|
||||||
codecs.add(h265);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Show Codec Preference err=$e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogManager.show((setState, close) {
|
dialogManager.show((setState, close) {
|
||||||
final more = <Widget>[];
|
var viewStyle =
|
||||||
if (perms['audio'] != false) {
|
(viewStyleRadios.isNotEmpty ? viewStyleRadios[0].groupValue : '').obs;
|
||||||
more.add(getToggle(id, setState, 'disable-audio', 'Mute'));
|
var imageQuality =
|
||||||
}
|
(imageQualityRadios.isNotEmpty ? imageQualityRadios[0].groupValue : '')
|
||||||
if (perms['keyboard'] != false) {
|
.obs;
|
||||||
if (perms['clipboard'] != false) {
|
var codec = (codecRadios.isNotEmpty ? codecRadios[0].groupValue : '').obs;
|
||||||
more.add(
|
|
||||||
getToggle(id, setState, 'disable-clipboard', 'Disable clipboard'));
|
|
||||||
}
|
|
||||||
more.add(getToggle(
|
|
||||||
id, setState, 'lock-after-session-end', 'Lock after session end'));
|
|
||||||
if (pi.platform == kPeerPlatformWindows) {
|
|
||||||
more.add(getToggle(id, setState, 'privacy-mode', 'Privacy mode'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setQuality(String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
quality = value;
|
|
||||||
bind.sessionSetImageQuality(id: id, value: value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setViewStyle(String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
viewStyle = value;
|
|
||||||
bind
|
|
||||||
.sessionSetViewStyle(id: id, value: value)
|
|
||||||
.then((_) => gFFI.canvasModel.updateViewStyle());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setCodec(String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
codec = value;
|
|
||||||
bind
|
|
||||||
.sessionPeerOption(id: id, name: "codec-preference", value: value)
|
|
||||||
.then((_) => bind.sessionChangePreferCodec(id: id));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final radios = [
|
final radios = [
|
||||||
getRadio(
|
for (var e in viewStyleRadios)
|
||||||
'Scale original', kRemoteViewStyleOriginal, viewStyle, setViewStyle),
|
Obx(() => getRadio<String>(e.child, e.value, viewStyle.value, (v) {
|
||||||
getRadio(
|
e.onChanged?.call(v);
|
||||||
'Scale adaptive', kRemoteViewStyleAdaptive, viewStyle, setViewStyle),
|
if (v != null) viewStyle.value = v;
|
||||||
|
})),
|
||||||
const Divider(color: MyTheme.border),
|
const Divider(color: MyTheme.border),
|
||||||
getRadio(
|
for (var e in imageQualityRadios)
|
||||||
'Good image quality', kRemoteImageQualityBest, quality, setQuality),
|
Obx(() => getRadio<String>(e.child, e.value, imageQuality.value, (v) {
|
||||||
getRadio('Balanced', kRemoteImageQualityBalanced, quality, setQuality),
|
e.onChanged?.call(v);
|
||||||
getRadio('Optimize reaction time', kRemoteImageQualityLow, quality,
|
if (v != null) imageQuality.value = v;
|
||||||
setQuality),
|
})),
|
||||||
const Divider(color: MyTheme.border)
|
const Divider(color: MyTheme.border),
|
||||||
|
for (var e in codecRadios)
|
||||||
|
Obx(() => getRadio<String>(e.child, e.value, codec.value, (v) {
|
||||||
|
e.onChanged?.call(v);
|
||||||
|
if (v != null) codec.value = v;
|
||||||
|
})),
|
||||||
|
if (codecRadios.isNotEmpty) const Divider(color: MyTheme.border),
|
||||||
];
|
];
|
||||||
|
final rxToggleValues = displayToggles.map((e) => e.value.obs).toList();
|
||||||
if (hasHwcodec && codecs.length == 2 && (codecs[0] || codecs[1])) {
|
final toggles = displayToggles
|
||||||
radios.addAll([
|
.asMap()
|
||||||
getRadio(translate('Auto'), 'auto', codec, setCodec),
|
.entries
|
||||||
getRadio('VP9', 'vp9', codec, setCodec),
|
.map((e) => Obx(() => CheckboxListTile(
|
||||||
]);
|
contentPadding: EdgeInsets.zero,
|
||||||
if (codecs[0]) {
|
visualDensity: VisualDensity.compact,
|
||||||
radios.add(getRadio('H264', 'h264', codec, setCodec));
|
value: rxToggleValues[e.key].value,
|
||||||
}
|
onChanged: (v) {
|
||||||
if (codecs[1]) {
|
e.value.onChanged?.call(v);
|
||||||
radios.add(getRadio('H265', 'h265', codec, setCodec));
|
if (v != null) rxToggleValues[e.key].value = v;
|
||||||
}
|
},
|
||||||
radios.add(const Divider(color: MyTheme.border));
|
title: e.value.child)))
|
||||||
}
|
.toList();
|
||||||
|
|
||||||
final toggles = [
|
|
||||||
getToggle(id, setState, 'show-quality-monitor', 'Show quality monitor'),
|
|
||||||
];
|
|
||||||
if (!gFFI.canvasModel.cursorEmbedded) {
|
|
||||||
toggles.insert(0,
|
|
||||||
getToggle(id, setState, 'show-remote-cursor', 'Show remote cursor'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: displays + radios + toggles + more),
|
children: displays + radios + toggles),
|
||||||
contentPadding: 0,
|
|
||||||
);
|
);
|
||||||
}, clickMaskDismiss: true, backDismiss: true);
|
}, clickMaskDismiss: true, backDismiss: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showSetOSPassword(
|
|
||||||
String id, bool login, OverlayDialogManager dialogManager) async {
|
|
||||||
final controller = TextEditingController();
|
|
||||||
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
|
|
||||||
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
|
|
||||||
controller.text = password;
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: Text(translate('OS Password')),
|
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
PasswordWidget(controller: controller),
|
|
||||||
CheckboxListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
title: Text(
|
|
||||||
translate('Auto Login'),
|
|
||||||
),
|
|
||||||
value: autoLogin,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == null) return;
|
|
||||||
setState(() => autoLogin = v);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
actions: [
|
|
||||||
dialogButton('Cancel', onPressed: close, isOutline: true),
|
|
||||||
dialogButton(
|
|
||||||
'OK',
|
|
||||||
onPressed: () {
|
|
||||||
var text = controller.text.trim();
|
|
||||||
bind.sessionPeerOption(id: id, name: "os-password", value: text);
|
|
||||||
bind.sessionPeerOption(
|
|
||||||
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
|
|
||||||
if (text != "" && login) {
|
|
||||||
bind.sessionInputOsPassword(id: id, value: text);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendPrompt(bool isMac, String key) {
|
void sendPrompt(bool isMac, String key) {
|
||||||
final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl;
|
final old = isMac ? gFFI.inputModel.command : gFFI.inputModel.ctrl;
|
||||||
if (isMac) {
|
if (isMac) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
@@ -45,6 +46,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
var _autoRecordIncomingSession = false;
|
var _autoRecordIncomingSession = false;
|
||||||
var _localIP = "";
|
var _localIP = "";
|
||||||
var _directAccessPort = "";
|
var _directAccessPort = "";
|
||||||
|
var _fingerprint = "";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -135,6 +137,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
_directAccessPort = directAccessPort;
|
_directAccessPort = directAccessPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final fingerprint = await bind.mainGetFingerprint();
|
||||||
|
if (_fingerprint != fingerprint) {
|
||||||
|
update = true;
|
||||||
|
_fingerprint = fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@@ -462,6 +470,14 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
leading: Icon(Icons.info)),
|
leading: Icon(Icons.info)),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
onPressed: (context) => onCopyFingerprint(_fingerprint),
|
||||||
|
title: Text(translate("Fingerprint")),
|
||||||
|
value: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text(_fingerprint),
|
||||||
|
),
|
||||||
|
leading: Icon(Icons.fingerprint)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -502,19 +518,18 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: SizedBox.shrink(),
|
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
getRadio('Default', '', lang, setLang),
|
getRadio(Text(translate('Default')), '', lang, setLang),
|
||||||
Divider(color: MyTheme.border),
|
Divider(color: MyTheme.border),
|
||||||
] +
|
] +
|
||||||
langs.map((e) {
|
langs.map((e) {
|
||||||
final key = e[0] as String;
|
final key = e[0] as String;
|
||||||
final name = e[1] as String;
|
final name = e[1] as String;
|
||||||
return getRadio(name, key, lang, setLang);
|
return getRadio(Text(translate(name)), key, lang, setLang);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
actions: []);
|
);
|
||||||
}, backDismiss: true, clickMaskDismiss: true);
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
@@ -536,14 +551,14 @@ void showThemeSettings(OverlayDialogManager dialogManager) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: SizedBox.shrink(),
|
|
||||||
contentPadding: 10,
|
|
||||||
content: Column(children: [
|
content: Column(children: [
|
||||||
getRadio('Light', ThemeMode.light, themeMode, setTheme),
|
getRadio(
|
||||||
getRadio('Dark', ThemeMode.dark, themeMode, setTheme),
|
Text(translate('Light')), ThemeMode.light, themeMode, setTheme),
|
||||||
getRadio('Follow System', ThemeMode.system, themeMode, setTheme)
|
getRadio(Text(translate('Dark')), ThemeMode.dark, themeMode, setTheme),
|
||||||
|
getRadio(Text(translate('Follow System')), ThemeMode.system, themeMode,
|
||||||
|
setTheme)
|
||||||
]),
|
]),
|
||||||
actions: []);
|
);
|
||||||
}, backDismiss: true, clickMaskDismiss: true);
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,51 +4,16 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/model.dart';
|
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
void clientClose(String id, OverlayDialogManager dialogManager) {
|
void _showSuccess() {
|
||||||
msgBox(id, 'info', 'Close', 'Are you sure to close the connection?', '',
|
|
||||||
dialogManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showSuccess() {
|
|
||||||
showToast(translate("Successful"));
|
showToast(translate("Successful"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void showError() {
|
void _showError() {
|
||||||
showToast(translate("Error"));
|
showToast(translate("Error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void showRestartRemoteDevice(
|
|
||||||
PeerInfo pi, String id, OverlayDialogManager dialogManager) async {
|
|
||||||
final res =
|
|
||||||
await dialogManager.show<bool>((setState, close) => CustomAlertDialog(
|
|
||||||
title: Row(children: [
|
|
||||||
Icon(Icons.warning_rounded, color: Colors.redAccent, size: 28),
|
|
||||||
Text(translate("Restart Remote Device")).paddingOnly(left: 10),
|
|
||||||
]),
|
|
||||||
content: Text(
|
|
||||||
"${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
|
|
||||||
actions: [
|
|
||||||
dialogButton(
|
|
||||||
"Cancel",
|
|
||||||
icon: Icon(Icons.close_rounded),
|
|
||||||
onPressed: close,
|
|
||||||
isOutline: true,
|
|
||||||
),
|
|
||||||
dialogButton(
|
|
||||||
"OK",
|
|
||||||
icon: Icon(Icons.done_rounded),
|
|
||||||
onPressed: () => close(true),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onCancel: close,
|
|
||||||
onSubmit: () => close(true),
|
|
||||||
));
|
|
||||||
if (res == true) bind.sessionRestartRemoteDevice(id: id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
||||||
final pw = await bind.mainGetPermanentPassword();
|
final pw = await bind.mainGetPermanentPassword();
|
||||||
final p0 = TextEditingController(text: pw);
|
final p0 = TextEditingController(text: pw);
|
||||||
@@ -61,10 +26,10 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
|||||||
dialogManager.showLoading(translate("Waiting"));
|
dialogManager.showLoading(translate("Waiting"));
|
||||||
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
|
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
|
||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
showSuccess();
|
_showSuccess();
|
||||||
} else {
|
} else {
|
||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
showError();
|
_showError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,117 +122,29 @@ void setTemporaryPasswordLengthDialog(
|
|||||||
bind.mainUpdateTemporaryPassword();
|
bind.mainUpdateTemporaryPassword();
|
||||||
Future.delayed(Duration(milliseconds: 200), () {
|
Future.delayed(Duration(milliseconds: 200), () {
|
||||||
close();
|
close();
|
||||||
showSuccess();
|
_showSuccess();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Text(translate("Set one-time password length")),
|
title: Text(translate("Set one-time password length")),
|
||||||
content: Column(
|
content: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children:
|
children: lengths
|
||||||
lengths.map((e) => getRadio(e, e, length, setLength)).toList()),
|
.map(
|
||||||
actions: [],
|
(value) => Row(
|
||||||
contentPadding: 14,
|
children: [
|
||||||
|
Text(value),
|
||||||
|
Radio(
|
||||||
|
value: value, groupValue: length, onChanged: setLength),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList()),
|
||||||
);
|
);
|
||||||
}, backDismiss: true, clickMaskDismiss: true);
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async {
|
|
||||||
final controller = TextEditingController();
|
|
||||||
var remember = await bind.sessionGetRemember(id: id) ?? false;
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
cancel() {
|
|
||||||
close();
|
|
||||||
closeConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
var text = controller.text.trim();
|
|
||||||
if (text == '') return;
|
|
||||||
gFFI.login(id, text, remember);
|
|
||||||
close();
|
|
||||||
dialogManager.showLoading(translate('Logging in...'),
|
|
||||||
onCancel: closeConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
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: [
|
|
||||||
PasswordWidget(controller: controller),
|
|
||||||
CheckboxListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
title: Text(
|
|
||||||
translate('Remember password'),
|
|
||||||
),
|
|
||||||
value: remember,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v != null) {
|
|
||||||
setState(() => remember = v);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
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 wrongPasswordDialog(
|
|
||||||
String id, OverlayDialogManager dialogManager, type, title, text) {
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
cancel() {
|
|
||||||
close();
|
|
||||||
closeConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
enterPasswordDialog(id, 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 showServerSettingsWithValue(
|
void showServerSettingsWithValue(
|
||||||
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
||||||
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
|
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
|
||||||
@@ -393,232 +270,6 @@ void showServerSettingsWithValue(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void showWaitUacDialog(
|
|
||||||
String id, OverlayDialogManager dialogManager, String type) {
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
dialogManager.show(
|
|
||||||
tag: '$id-wait-uac',
|
|
||||||
(setState, close) => CustomAlertDialog(
|
|
||||||
title: null,
|
|
||||||
content: msgboxContent(type, 'Wait', 'wait_accept_uac_tip'),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void showRequestElevationDialog(String id, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const minTextStyle = TextStyle(fontSize: 14);
|
|
||||||
|
|
||||||
var content = Obx(() => Column(children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Radio(
|
|
||||||
value: '',
|
|
||||||
groupValue: groupValue.value,
|
|
||||||
onChanged: onRadioChanged),
|
|
||||||
Expanded(
|
|
||||||
child:
|
|
||||||
Text(translate('Ask the remote user for authentication'))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
translate(
|
|
||||||
'Choose this if the remote account is administrator'),
|
|
||||||
style: TextStyle(fontSize: 13))
|
|
||||||
.marginOnly(left: 40),
|
|
||||||
).marginOnly(bottom: 15),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Radio(
|
|
||||||
value: 'logon',
|
|
||||||
groupValue: groupValue.value,
|
|
||||||
onChanged: onRadioChanged),
|
|
||||||
Expanded(
|
|
||||||
child: Text(translate(
|
|
||||||
'Transmit the username and password of administrator')),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Text(
|
|
||||||
'${translate('Username')}:',
|
|
||||||
style: minTextStyle,
|
|
||||||
).marginOnly(right: 10)),
|
|
||||||
Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: TextField(
|
|
||||||
controller: userController,
|
|
||||||
style: minTextStyle,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 15),
|
|
||||||
hintText: translate('eg: admin'),
|
|
||||||
errorText: errUser.isEmpty ? null : errUser.value),
|
|
||||||
onChanged: (s) {
|
|
||||||
if (s.isNotEmpty) {
|
|
||||||
errUser.value = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
).marginOnly(left: 40),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Text(
|
|
||||||
'${translate('Password')}:',
|
|
||||||
style: minTextStyle,
|
|
||||||
).marginOnly(right: 10)),
|
|
||||||
Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: TextField(
|
|
||||||
controller: pwdController,
|
|
||||||
obscureText: true,
|
|
||||||
style: minTextStyle,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
isDense: true,
|
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 15),
|
|
||||||
errorText: errPwd.isEmpty ? null : errPwd.value),
|
|
||||||
onChanged: (s) {
|
|
||||||
if (s.isNotEmpty) {
|
|
||||||
errPwd.value = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).marginOnly(left: 40),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(translate('still_click_uac_tip'),
|
|
||||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold))
|
|
||||||
.marginOnly(top: 20)),
|
|
||||||
]));
|
|
||||||
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
dialogManager.show(tag: '$id-request-elevation', (setState, close) {
|
|
||||||
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(
|
|
||||||
id: id,
|
|
||||||
username: userController.text,
|
|
||||||
password: pwdController.text);
|
|
||||||
} else {
|
|
||||||
bind.sessionElevateDirect(id: id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: Text(translate('Request Elevation')),
|
|
||||||
content: content,
|
|
||||||
actions: [
|
|
||||||
dialogButton('Cancel', onPressed: close, isOutline: true),
|
|
||||||
dialogButton('OK', onPressed: submit),
|
|
||||||
],
|
|
||||||
onSubmit: submit,
|
|
||||||
onCancel: close,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void showOnBlockDialog(
|
|
||||||
String id,
|
|
||||||
String type,
|
|
||||||
String title,
|
|
||||||
String text,
|
|
||||||
OverlayDialogManager dialogManager,
|
|
||||||
) {
|
|
||||||
if (dialogManager.existing('$id-wait-uac') ||
|
|
||||||
dialogManager.existing('$id-request-elevation')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
|
||||||
void submit() {
|
|
||||||
close();
|
|
||||||
showRequestElevationDialog(id, 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(String id, String type, String title, String text,
|
|
||||||
OverlayDialogManager dialogManager) {
|
|
||||||
dialogManager.show(tag: '$id-$type', (setState, close) {
|
|
||||||
void submit() {
|
|
||||||
close();
|
|
||||||
showRequestElevationDialog(id, dialogManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: null,
|
|
||||||
content: msgboxContent(type, title, text),
|
|
||||||
actions: [
|
|
||||||
dialogButton('Cancel', onPressed: () {
|
|
||||||
close();
|
|
||||||
}, isOutline: true),
|
|
||||||
dialogButton('Retry', onPressed: submit),
|
|
||||||
],
|
|
||||||
onSubmit: submit,
|
|
||||||
onCancel: close,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void showWaitAcceptDialog(String id, String type, String title, String text,
|
|
||||||
OverlayDialogManager dialogManager) {
|
|
||||||
dialogManager.dismissAll();
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
onCancel() {
|
|
||||||
closeConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: null,
|
|
||||||
content: msgboxContent(type, title, text),
|
|
||||||
actions: [
|
|
||||||
dialogButton('Cancel', onPressed: onCancel, isOutline: true),
|
|
||||||
],
|
|
||||||
onCancel: onCancel,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> validateAsync(String value) async {
|
Future<String?> validateAsync(String value) async {
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
@@ -627,62 +278,3 @@ Future<String?> validateAsync(String value) async {
|
|||||||
final res = await bind.mainTestIfValidServer(server: value);
|
final res = await bind.mainTestIfValidServer(server: value);
|
||||||
return res.isEmpty ? null : res;
|
return res.isEmpty ? null : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordWidget extends StatefulWidget {
|
|
||||||
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
final TextEditingController controller;
|
|
||||||
final bool autoFocus;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<PasswordWidget> createState() => _PasswordWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PasswordWidgetState extends State<PasswordWidget> {
|
|
||||||
bool _passwordVisible = false;
|
|
||||||
final _focusNode = FocusNode();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (widget.autoFocus) {
|
|
||||||
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_focusNode.unfocus();
|
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return TextField(
|
|
||||||
focusNode: _focusNode,
|
|
||||||
controller: widget.controller,
|
|
||||||
obscureText: !_passwordVisible,
|
|
||||||
//This will obscure text dynamically
|
|
||||||
keyboardType: TextInputType.visiblePassword,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('Password'),
|
|
||||||
hintText: translate('Enter your password'),
|
|
||||||
// Here is key idea
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ class AbModel {
|
|||||||
abError.value = "";
|
abError.value = "";
|
||||||
final api = "${await bind.mainGetApiServer()}/api/ab/get";
|
final api = "${await bind.mainGetApiServer()}/api/ab/get";
|
||||||
try {
|
try {
|
||||||
final resp = await http.post(Uri.parse(api), headers: getHttpHeaders());
|
var authHeaders = getHttpHeaders();
|
||||||
|
authHeaders['Content-Type'] = "application/json";
|
||||||
|
final resp = await http.post(Uri.parse(api), headers: authHeaders);
|
||||||
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
|
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
|
||||||
Map<String, dynamic> json = jsonDecode(resp.body);
|
Map<String, dynamic> json = jsonDecode(resp.body);
|
||||||
if (json.containsKey('error')) {
|
if (json.containsKey('error')) {
|
||||||
|
|||||||
@@ -45,8 +45,12 @@ class InputModel {
|
|||||||
var command = false;
|
var command = false;
|
||||||
|
|
||||||
// trackpad
|
// trackpad
|
||||||
var trackpadScrollDistance = Offset.zero;
|
final _trackpadSpeed = 0.02;
|
||||||
|
var _trackpadLastDelta = Offset.zero;
|
||||||
|
var _trackpadScrollUnsent = Offset.zero;
|
||||||
|
var _stopFling = true;
|
||||||
Timer? _flingTimer;
|
Timer? _flingTimer;
|
||||||
|
final _flingBaseDelay = 10;
|
||||||
|
|
||||||
// mouse
|
// mouse
|
||||||
final isPhysicalMouse = false.obs;
|
final isPhysicalMouse = false.obs;
|
||||||
@@ -55,10 +59,12 @@ class InputModel {
|
|||||||
|
|
||||||
get id => parent.target?.id ?? "";
|
get id => parent.target?.id ?? "";
|
||||||
|
|
||||||
|
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
||||||
|
|
||||||
InputModel(this.parent);
|
InputModel(this.parent);
|
||||||
|
|
||||||
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
|
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
|
||||||
if (!stateGlobal.grabKeyboard) {
|
if (isDesktop && !stateGlobal.grabKeyboard) {
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,44 +117,41 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mapKeyboardMode(RawKeyEvent e) {
|
void mapKeyboardMode(RawKeyEvent e) {
|
||||||
int scanCode;
|
int positionCode = -1;
|
||||||
int keyCode;
|
int platformCode = -1;
|
||||||
bool down;
|
bool down;
|
||||||
|
|
||||||
if (e.data is RawKeyEventDataMacOs) {
|
if (e.data is RawKeyEventDataMacOs) {
|
||||||
RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs;
|
RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs;
|
||||||
scanCode = newData.keyCode;
|
positionCode = newData.keyCode;
|
||||||
keyCode = newData.keyCode;
|
platformCode = newData.keyCode;
|
||||||
} else if (e.data is RawKeyEventDataWindows) {
|
} else if (e.data is RawKeyEventDataWindows) {
|
||||||
RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows;
|
RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows;
|
||||||
scanCode = newData.scanCode;
|
positionCode = newData.scanCode;
|
||||||
keyCode = newData.keyCode;
|
platformCode = newData.keyCode;
|
||||||
} else if (e.data is RawKeyEventDataLinux) {
|
} else if (e.data is RawKeyEventDataLinux) {
|
||||||
RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux;
|
RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux;
|
||||||
// scanCode and keyCode of RawKeyEventDataLinux are incorrect.
|
// scanCode and keyCode of RawKeyEventDataLinux are incorrect.
|
||||||
// 1. scanCode means keycode
|
// 1. scanCode means keycode
|
||||||
// 2. keyCode means keysym
|
// 2. keyCode means keysym
|
||||||
scanCode = 0;
|
positionCode = newData.scanCode;
|
||||||
keyCode = newData.scanCode;
|
platformCode = newData.keyCode;
|
||||||
} else if (e.data is RawKeyEventDataAndroid) {
|
} else if (e.data is RawKeyEventDataAndroid) {
|
||||||
RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid;
|
RawKeyEventDataAndroid newData = e.data as RawKeyEventDataAndroid;
|
||||||
scanCode = newData.scanCode + 8;
|
positionCode = newData.scanCode + 8;
|
||||||
keyCode = newData.keyCode;
|
platformCode = newData.keyCode;
|
||||||
} else {
|
} else {}
|
||||||
scanCode = -1;
|
|
||||||
keyCode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e is RawKeyDownEvent) {
|
if (e is RawKeyDownEvent) {
|
||||||
down = true;
|
down = true;
|
||||||
} else {
|
} else {
|
||||||
down = false;
|
down = false;
|
||||||
}
|
}
|
||||||
inputRawKey(e.character ?? '', keyCode, scanCode, down);
|
inputRawKey(e.character ?? '', platformCode, positionCode, down);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send raw Key Event
|
/// Send raw Key Event
|
||||||
void inputRawKey(String name, int keyCode, int scanCode, bool down) {
|
void inputRawKey(String name, int platformCode, int positionCode, bool down) {
|
||||||
const capslock = 1;
|
const capslock = 1;
|
||||||
const numlock = 2;
|
const numlock = 2;
|
||||||
const scrolllock = 3;
|
const scrolllock = 3;
|
||||||
@@ -168,8 +171,8 @@ class InputModel {
|
|||||||
bind.sessionHandleFlutterKeyEvent(
|
bind.sessionHandleFlutterKeyEvent(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
keycode: keyCode,
|
platformCode: platformCode,
|
||||||
scancode: scanCode,
|
positionCode: positionCode,
|
||||||
lockModes: lockModes,
|
lockModes: lockModes,
|
||||||
downOrUp: down);
|
downOrUp: down);
|
||||||
}
|
}
|
||||||
@@ -199,7 +202,7 @@ class InputModel {
|
|||||||
/// [down] indicates the key's state(down or up).
|
/// [down] indicates the key's state(down or up).
|
||||||
/// [press] indicates a click event(down and up).
|
/// [press] indicates a click event(down and up).
|
||||||
void inputKey(String name, {bool? down, bool? press}) {
|
void inputKey(String name, {bool? down, bool? press}) {
|
||||||
if (!parent.target!.ffiModel.keyboard()) return;
|
if (!keyboardPerm) return;
|
||||||
bind.sessionInputKey(
|
bind.sessionInputKey(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
@@ -282,7 +285,7 @@ class InputModel {
|
|||||||
|
|
||||||
/// Send mouse press event.
|
/// Send mouse press event.
|
||||||
void sendMouse(String type, MouseButtons button) {
|
void sendMouse(String type, MouseButtons button) {
|
||||||
if (!parent.target!.ffiModel.keyboard()) return;
|
if (!keyboardPerm) return;
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
id: id,
|
id: id,
|
||||||
msg: json.encode(modify({'type': type, 'buttons': button.value})));
|
msg: json.encode(modify({'type': type, 'buttons': button.value})));
|
||||||
@@ -299,7 +302,7 @@ class InputModel {
|
|||||||
|
|
||||||
/// Send mouse movement event with distance in [x] and [y].
|
/// Send mouse movement event with distance in [x] and [y].
|
||||||
void moveMouse(double x, double y) {
|
void moveMouse(double x, double y) {
|
||||||
if (!parent.target!.ffiModel.keyboard()) return;
|
if (!keyboardPerm) return;
|
||||||
var x2 = x.toInt();
|
var x2 = x.toInt();
|
||||||
var y2 = y.toInt();
|
var y2 = y.toInt();
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
@@ -307,6 +310,7 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onPointHoverImage(PointerHoverEvent e) {
|
void onPointHoverImage(PointerHoverEvent e) {
|
||||||
|
_stopFling = true;
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (!isPhysicalMouse.value) {
|
if (!isPhysicalMouse.value) {
|
||||||
isPhysicalMouse.value = true;
|
isPhysicalMouse.value = true;
|
||||||
@@ -324,17 +328,33 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {}
|
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
|
||||||
|
_stopFling = true;
|
||||||
|
}
|
||||||
|
|
||||||
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
||||||
// TODO(support zoom in/out)
|
// TODO(support zoom in/out)
|
||||||
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
||||||
var delta = e.panDelta;
|
var delta = e.panDelta;
|
||||||
trackpadScrollDistance += delta;
|
_trackpadLastDelta = delta;
|
||||||
|
_trackpadScrollUnsent += (delta * _trackpadSpeed);
|
||||||
|
var x = _trackpadScrollUnsent.dx.truncate();
|
||||||
|
var y = _trackpadScrollUnsent.dy.truncate();
|
||||||
|
_trackpadScrollUnsent -= Offset(_trackpadScrollUnsent.dx - x.toDouble(),
|
||||||
|
_trackpadScrollUnsent.dy - y.toDouble());
|
||||||
|
|
||||||
|
if (x == 0 && y == 0) {
|
||||||
|
x = delta.dx > 1 ? 1 : (delta.dx < -1 ? -1 : 0);
|
||||||
|
y = delta.dy > 1 ? 1 : (delta.dy < -1 ? -1 : 0);
|
||||||
|
if (x.abs() > y.abs()) {
|
||||||
|
y = 0;
|
||||||
|
} else {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
id: id,
|
id: id, msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
|
||||||
msg:
|
|
||||||
'{"type": "trackpad", "x": "${delta.dx.toInt()}", "y": "${delta.dy.toInt()}"}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple simulation for fling.
|
// Simple simulation for fling.
|
||||||
@@ -357,18 +377,68 @@ class InputModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
void _scheduleFling2(double x, double y, int delay) {
|
||||||
var x = _signOrZero(trackpadScrollDistance.dx);
|
if ((x == 0 && y == 0) || _stopFling) {
|
||||||
var y = _signOrZero(trackpadScrollDistance.dy);
|
return;
|
||||||
var dx = trackpadScrollDistance.dx.abs() ~/ 40;
|
}
|
||||||
var dy = trackpadScrollDistance.dy.abs() ~/ 40;
|
|
||||||
_scheduleFling(x, y, dx, dy);
|
|
||||||
|
|
||||||
trackpadScrollDistance = Offset.zero;
|
_flingTimer = Timer(Duration(milliseconds: delay), () {
|
||||||
|
if (_stopFling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final d = 0.95;
|
||||||
|
x *= d;
|
||||||
|
y *= d;
|
||||||
|
final dx0 = x * _trackpadSpeed * 2;
|
||||||
|
final dy0 = y * _trackpadSpeed * 2;
|
||||||
|
|
||||||
|
// Try set delta (x,y) and delay.
|
||||||
|
var dx = dx0.toInt();
|
||||||
|
var dy = dy0.toInt();
|
||||||
|
var delay = _flingBaseDelay;
|
||||||
|
|
||||||
|
// Try set min delta (x,y), and increase delay.
|
||||||
|
if (dx == 0 && dy == 0) {
|
||||||
|
final thr = 25;
|
||||||
|
var vx = thr;
|
||||||
|
var vy = thr;
|
||||||
|
if (dx0 != 0) {
|
||||||
|
vx = 1.0 ~/ dx0.abs();
|
||||||
|
}
|
||||||
|
if (dy0 != 0) {
|
||||||
|
vy = 1.0 ~/ dy0.abs();
|
||||||
|
}
|
||||||
|
if (vx < vy && vx < thr) {
|
||||||
|
delay *= vx;
|
||||||
|
dx = dx0 > 0 ? 1 : (dx0 < 0 ? -1 : 0);
|
||||||
|
} else if (vy < thr) {
|
||||||
|
delay *= vy;
|
||||||
|
dy = dy0 > 0 ? 1 : (dy0 < 0 ? -1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dx == 0 && dy == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.sessionSendMouse(
|
||||||
|
id: id, msg: '{"type": "trackpad", "x": "$dx", "y": "$dy"}');
|
||||||
|
_scheduleFling2(x, y, delay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
||||||
|
_stopFling = false;
|
||||||
|
_trackpadScrollUnsent = Offset.zero;
|
||||||
|
_scheduleFling2(
|
||||||
|
_trackpadLastDelta.dx, _trackpadLastDelta.dy, _flingBaseDelay);
|
||||||
|
_trackpadLastDelta = Offset.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointDownImage(PointerDownEvent e) {
|
void onPointDownImage(PointerDownEvent e) {
|
||||||
debugPrint("onPointDownImage");
|
debugPrint("onPointDownImage");
|
||||||
|
_stopFling = true;
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
isPhysicalMouse.value = false;
|
isPhysicalMouse.value = false;
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import 'package:flutter_hbb/models/peer_tab_model.dart';
|
|||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
import 'package:flutter_hbb/models/user_model.dart';
|
import 'package:flutter_hbb/models/user_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/plugin/event.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/plugin/desc.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/plugin/widget.dart';
|
||||||
import 'package:flutter_hbb/common/shared_state.dart';
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:image/image.dart' as img2;
|
import 'package:image/image.dart' as img2;
|
||||||
@@ -25,7 +28,7 @@ import 'package:get/get.dart';
|
|||||||
|
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
import '../utils/image.dart' as img;
|
import '../utils/image.dart' as img;
|
||||||
import '../mobile/widgets/dialog.dart';
|
import '../common/widgets/dialog.dart';
|
||||||
import 'input_model.dart';
|
import 'input_model.dart';
|
||||||
import 'platform_model.dart';
|
import 'platform_model.dart';
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keyboard() => _permissions['keyboard'] != false;
|
bool get keyboard => _permissions['keyboard'] != false;
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
_pi = PeerInfo();
|
_pi = PeerInfo();
|
||||||
@@ -224,6 +227,15 @@ class FfiModel with ChangeNotifier {
|
|||||||
parent.target?.chatModel.onVoiceCallIncoming();
|
parent.target?.chatModel.onVoiceCallIncoming();
|
||||||
} else if (name == "update_voice_call_state") {
|
} else if (name == "update_voice_call_state") {
|
||||||
parent.target?.serverModel.updateVoiceCallState(evt);
|
parent.target?.serverModel.updateVoiceCallState(evt);
|
||||||
|
} else if (name == "fingerprint") {
|
||||||
|
FingerprintState.find(peerId).value = evt['fingerprint'] ?? '';
|
||||||
|
} else if (name == "plugin_desc") {
|
||||||
|
updateDesc(evt);
|
||||||
|
} else if (name == "plugin_event") {
|
||||||
|
handlePluginEvent(
|
||||||
|
evt, peerId, (Map<String, dynamic> e) => handleMsgBox(e, peerId));
|
||||||
|
} else if (name == "plugin_reload") {
|
||||||
|
handleReloading(evt, peerId);
|
||||||
} else {
|
} else {
|
||||||
debugPrint("Unknown event name: $name");
|
debugPrint("Unknown event name: $name");
|
||||||
}
|
}
|
||||||
@@ -293,6 +305,11 @@ class FfiModel with ChangeNotifier {
|
|||||||
wrongPasswordDialog(id, dialogManager, type, title, text);
|
wrongPasswordDialog(id, dialogManager, type, title, text);
|
||||||
} else if (type == 'input-password') {
|
} else if (type == 'input-password') {
|
||||||
enterPasswordDialog(id, dialogManager);
|
enterPasswordDialog(id, dialogManager);
|
||||||
|
} else if (type == 'session-login' || type == 'session-re-login') {
|
||||||
|
enterUserLoginDialog(id, dialogManager);
|
||||||
|
} else if (type == 'session-login-password' ||
|
||||||
|
type == 'session-login-password') {
|
||||||
|
enterUserLoginAndPasswordDialog(id, dialogManager);
|
||||||
} else if (type == 'restarting') {
|
} else if (type == 'restarting') {
|
||||||
showMsgBox(id, type, title, text, link, false, dialogManager,
|
showMsgBox(id, type, title, text, link, false, dialogManager,
|
||||||
hasCancel: false);
|
hasCancel: false);
|
||||||
@@ -452,6 +469,16 @@ class FfiModel with ChangeNotifier {
|
|||||||
setViewOnly(peerId,
|
setViewOnly(peerId,
|
||||||
bind.sessionGetToggleOptionSync(id: peerId, arg: 'view-only'));
|
bind.sessionGetToggleOptionSync(id: peerId, arg: 'view-only'));
|
||||||
}
|
}
|
||||||
|
if (connType == ConnType.defaultConn) {
|
||||||
|
final platform_additions = evt['platform_additions'];
|
||||||
|
if (platform_additions != null && platform_additions != '') {
|
||||||
|
try {
|
||||||
|
_pi.platform_additions = json.decode(platform_additions);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to decode platform_additions $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,12 +552,19 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
void setViewOnly(String id, bool value) {
|
void setViewOnly(String id, bool value) {
|
||||||
if (version_cmp(_pi.version, '1.2.0') < 0) return;
|
if (version_cmp(_pi.version, '1.2.0') < 0) return;
|
||||||
|
// tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389
|
||||||
|
// because below rx not used in mobile version, so not initialized, below code will cause crash
|
||||||
|
// current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!!
|
||||||
|
try {
|
||||||
if (value) {
|
if (value) {
|
||||||
ShowRemoteCursorState.find(id).value = value;
|
ShowRemoteCursorState.find(id).value = value;
|
||||||
} else {
|
} else {
|
||||||
ShowRemoteCursorState.find(id).value =
|
ShowRemoteCursorState.find(id).value =
|
||||||
bind.sessionGetToggleOptionSync(id: id, arg: 'show-remote-cursor');
|
bind.sessionGetToggleOptionSync(id: id, arg: 'show-remote-cursor');
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
if (_viewOnly != value) {
|
if (_viewOnly != value) {
|
||||||
_viewOnly = value;
|
_viewOnly = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -554,7 +588,13 @@ class ImageModel with ChangeNotifier {
|
|||||||
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
|
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
|
||||||
|
|
||||||
onRgba(Uint8List rgba) {
|
onRgba(Uint8List rgba) {
|
||||||
if (_waitForImage[id]!) {
|
final waitforImage = _waitForImage[id];
|
||||||
|
if (waitforImage == null) {
|
||||||
|
debugPrint('Exception, peer $id not found for waiting image');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waitforImage == true) {
|
||||||
_waitForImage[id] = false;
|
_waitForImage[id] = false;
|
||||||
parent.target?.dialogManager.dismissAll();
|
parent.target?.dialogManager.dismissAll();
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
@@ -878,7 +918,7 @@ class CanvasModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If keyboard is not permitted, do not move cursor when mouse is moving.
|
// If keyboard is not permitted, do not move cursor when mouse is moving.
|
||||||
if (parent.target != null && parent.target!.ffiModel.keyboard()) {
|
if (parent.target != null && parent.target!.ffiModel.keyboard) {
|
||||||
// Draw cursor if is not desktop.
|
// Draw cursor if is not desktop.
|
||||||
if (!isDesktop) {
|
if (!isDesktop) {
|
||||||
parent.target!.cursorModel.moveLocal(x, y);
|
parent.target!.cursorModel.moveLocal(x, y);
|
||||||
@@ -1542,6 +1582,7 @@ class FFI {
|
|||||||
id = 'pf_$id';
|
id = 'pf_$id';
|
||||||
} else {
|
} else {
|
||||||
chatModel.resetClientMode();
|
chatModel.resetClientMode();
|
||||||
|
connType = ConnType.defaultConn;
|
||||||
canvasModel.id = id;
|
canvasModel.id = id;
|
||||||
imageModel.id = id;
|
imageModel.id = id;
|
||||||
cursorModel.id = id;
|
cursorModel.id = id;
|
||||||
@@ -1602,8 +1643,14 @@ class FFI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Login with [password], choose if the client should [remember] it.
|
/// Login with [password], choose if the client should [remember] it.
|
||||||
void login(String id, String password, bool remember) {
|
void login(String osUsername, String osPassword, String id, String password,
|
||||||
bind.sessionLogin(id: id, password: password, remember: remember);
|
bool remember) {
|
||||||
|
bind.sessionLogin(
|
||||||
|
id: id,
|
||||||
|
osUsername: osUsername,
|
||||||
|
osPassword: osPassword,
|
||||||
|
password: password,
|
||||||
|
remember: remember);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the remote session.
|
/// Close the remote session.
|
||||||
@@ -1687,6 +1734,10 @@ class PeerInfo {
|
|||||||
List<Display> displays = [];
|
List<Display> displays = [];
|
||||||
Features features = Features();
|
Features features = Features();
|
||||||
List<Resolution> resolutions = [];
|
List<Resolution> resolutions = [];
|
||||||
|
Map<String, dynamic> platform_additions = {};
|
||||||
|
|
||||||
|
bool get is_wayland => platform_additions['is_wayland'] == true;
|
||||||
|
bool get is_headless => platform_additions['headless'] == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasKey = 'canvas';
|
const canvasKey = 'canvas';
|
||||||
|
|||||||
@@ -1236,10 +1236,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: texture_rgba_renderer
|
name: texture_rgba_renderer
|
||||||
sha256: "52bc9f217b7b07a760ee837d5a17329ad1f78ae8ed1e3fa612c6f1bed3c77f79"
|
sha256: cb048abdd800468ca40749ca10d1db9d1e6a055d1cde6234c05191293f0c7d61
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.13"
|
version: "0.0.16"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1571,5 +1571,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.0 <4.0.0"
|
dart: ">=2.18.0 <3.0.0"
|
||||||
flutter: ">=3.3.0"
|
flutter: ">=3.3.0"
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ dependencies:
|
|||||||
password_strength: ^0.2.0
|
password_strength: ^0.2.0
|
||||||
flutter_launcher_icons: ^0.11.0
|
flutter_launcher_icons: ^0.11.0
|
||||||
flutter_keyboard_visibility: ^5.4.0
|
flutter_keyboard_visibility: ^5.4.0
|
||||||
texture_rgba_renderer: ^0.0.13
|
texture_rgba_renderer: ^0.0.16
|
||||||
percent_indicator: ^4.2.2
|
percent_indicator: ^4.2.2
|
||||||
dropdown_button2: ^2.0.0
|
dropdown_button2: ^2.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def main():
|
|||||||
b = toks[1].replace('ControlKey(ControlKey::', '').replace("Chr('", '').replace("' as _)),", '').replace(')),', '')
|
b = toks[1].replace('ControlKey(ControlKey::', '').replace("Chr('", '').replace("' as _)),", '').replace(')),', '')
|
||||||
KEY_MAP[0] += ' "%s": "%s",\n'%(a, b)
|
KEY_MAP[0] += ' "%s": "%s",\n'%(a, b)
|
||||||
print()
|
print()
|
||||||
print('export function checkIfRetry(msgtype: string, title: string, text: string) {')
|
print('export function checkIfRetry(msgtype: string, title: string, text: string, retry_for_relay: boolean) {')
|
||||||
print(' return %s'%check_if_retry[0].replace('to_lowercase', 'toLowerCase').replace('contains', 'indexOf').replace('!', '').replace('")', '") < 0'))
|
print(' return %s'%check_if_retry[0].replace('to_lowercase', 'toLowerCase').replace('contains', 'indexOf').replace('!', '').replace('")', '") < 0'))
|
||||||
print(';}')
|
print(';}')
|
||||||
print()
|
print()
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ impl Enigo {
|
|||||||
|
|
||||||
impl Default for Enigo {
|
impl Default for Enigo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let is_x11 = "x11" == hbb_common::platform::linux::get_display_server();
|
let is_x11 = hbb_common::platform::linux::is_x11_or_headless();
|
||||||
Self {
|
Self {
|
||||||
is_x11,
|
is_x11,
|
||||||
tfc: if is_x11 {
|
tfc: if is_x11 {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731
|
||||||
//
|
//
|
||||||
// JP/KR mapping https://github.com/TigerVNC/tigervnc/blob/1a008c1380305648ab50f1d99e73439747e9d61d/vncviewer/win32.c#L267
|
// JP/KR mapping https://github.com/TigerVNC/tigervnc/blob/1a008c1380305648ab50f1d99e73439747e9d61d/vncviewer/win32.c#L267
|
||||||
|
|||||||
@@ -247,6 +247,40 @@ impl KeyboardControllable for Enigo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
fn key_down(&mut self, key: Key) -> crate::ResultType {
|
||||||
|
match &key {
|
||||||
|
Key::Layout(c) => {
|
||||||
|
// to-do: dup code
|
||||||
|
// https://github.com/rustdesk/rustdesk/blob/1bc0dd791ed8344997024dc46626bd2ca7df73d2/src/server/input_service.rs#L1348
|
||||||
|
let code = self.get_layoutdependent_keycode(*c);
|
||||||
|
if code as u16 != 0xFFFF {
|
||||||
|
let vk = code & 0x00FF;
|
||||||
|
let flag = code >> 8;
|
||||||
|
let modifiers = [Key::Shift, Key::Control, Key::Alt];
|
||||||
|
let mod_len = modifiers.len();
|
||||||
|
for pos in 0..mod_len {
|
||||||
|
if flag & (0x0001 << pos) != 0 {
|
||||||
|
self.key_down(modifiers[pos])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = keybd_event(0, vk, 0);
|
||||||
|
let err = if res == 0 { get_error() } else { "".to_owned() };
|
||||||
|
|
||||||
|
for pos in 0..mod_len {
|
||||||
|
let rpos = mod_len - 1 - pos;
|
||||||
|
if flag & (0x0001 << rpos) != 0 {
|
||||||
|
self.key_up(modifiers[pos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !err.is_empty() {
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!("Failed to get keycode of {}", c).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
let code = self.key_to_keycode(key);
|
let code = self.key_to_keycode(key);
|
||||||
if code == 0 || code == 65535 {
|
if code == 0 || code == 65535 {
|
||||||
return Err("".into());
|
return Err("".into());
|
||||||
@@ -258,6 +292,8 @@ impl KeyboardControllable for Enigo {
|
|||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,9 +359,6 @@ impl Enigo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn key_to_keycode(&self, key: Key) -> u16 {
|
fn key_to_keycode(&self, key: Key) -> u16 {
|
||||||
unsafe {
|
|
||||||
LAYOUT = std::ptr::null_mut();
|
|
||||||
}
|
|
||||||
// do not use the codes from crate winapi they're
|
// do not use the codes from crate winapi they're
|
||||||
// wrongly typed with i32 instead of i16 use the
|
// wrongly typed with i32 instead of i16 use the
|
||||||
// ones provided by win/keycodes.rs that are prefixed
|
// ones provided by win/keycodes.rs that are prefixed
|
||||||
@@ -411,30 +444,24 @@ impl Enigo {
|
|||||||
Key::RightAlt => EVK_RMENU,
|
Key::RightAlt => EVK_RMENU,
|
||||||
|
|
||||||
Key::Raw(raw_keycode) => raw_keycode,
|
Key::Raw(raw_keycode) => raw_keycode,
|
||||||
Key::Layout(c) => self.get_layoutdependent_keycode(c.to_string()),
|
|
||||||
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
|
Key::Super | Key::Command | Key::Windows | Key::Meta => EVK_LWIN,
|
||||||
|
Key::Layout(..) => {
|
||||||
|
// unreachable
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_layoutdependent_keycode(&self, string: String) -> u16 {
|
fn get_layoutdependent_keycode(&self, chr: char) -> u16 {
|
||||||
// get the first char from the string ignore the rest
|
unsafe {
|
||||||
// ensure its not a multybyte char
|
LAYOUT = std::ptr::null_mut();
|
||||||
if let Some(chr) = string.chars().nth(0) {
|
}
|
||||||
// NOTE VkKeyScanW uses the current keyboard LAYOUT
|
// NOTE VkKeyScanW uses the current keyboard LAYOUT
|
||||||
// to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout
|
// to specify a LAYOUT use VkKeyScanExW and GetKeyboardLayout
|
||||||
// or load one with LoadKeyboardLayoutW
|
// or load one with LoadKeyboardLayoutW
|
||||||
let current_window_thread_id =
|
let current_window_thread_id =
|
||||||
unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) };
|
unsafe { GetWindowThreadProcessId(GetForegroundWindow(), std::ptr::null_mut()) };
|
||||||
unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) };
|
unsafe { LAYOUT = GetKeyboardLayout(current_window_thread_id) };
|
||||||
let keycode_and_shiftstate = unsafe { VkKeyScanExW(chr as _, LAYOUT) };
|
unsafe { VkKeyScanExW(chr as _, LAYOUT) as _ }
|
||||||
if keycode_and_shiftstate == (EVK_DECIMAL as i16) && chr == '.' {
|
|
||||||
// a workaround of italian keyboard shift + '.' issue
|
|
||||||
EVK_PERIOD as _
|
|
||||||
} else {
|
|
||||||
keycode_and_shiftstate as _
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ tokio-socks = { git = "https://github.com/open-trade/tokio-socks" }
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
dlopen = "0.1"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ message VideoFrame {
|
|||||||
YUV yuv = 8;
|
YUV yuv = 8;
|
||||||
EncodedVideoFrames h264s = 10;
|
EncodedVideoFrames h264s = 10;
|
||||||
EncodedVideoFrames h265s = 11;
|
EncodedVideoFrames h265s = 11;
|
||||||
|
EncodedVideoFrames vp8s = 12;
|
||||||
}
|
}
|
||||||
int64 timestamp = 9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message IdPk {
|
message IdPk {
|
||||||
@@ -53,6 +53,11 @@ message FileTransfer {
|
|||||||
bool show_hidden = 2;
|
bool show_hidden = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message OSLogin {
|
||||||
|
string username = 1;
|
||||||
|
string password = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message LoginRequest {
|
message LoginRequest {
|
||||||
string username = 1;
|
string username = 1;
|
||||||
bytes password = 2;
|
bytes password = 2;
|
||||||
@@ -66,6 +71,7 @@ message LoginRequest {
|
|||||||
bool video_ack_required = 9;
|
bool video_ack_required = 9;
|
||||||
uint64 session_id = 10;
|
uint64 session_id = 10;
|
||||||
string version = 11;
|
string version = 11;
|
||||||
|
OSLogin os_login = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ChatMessage { string text = 1; }
|
message ChatMessage { string text = 1; }
|
||||||
@@ -77,6 +83,7 @@ message Features {
|
|||||||
message SupportedEncoding {
|
message SupportedEncoding {
|
||||||
bool h264 = 1;
|
bool h264 = 1;
|
||||||
bool h265 = 2;
|
bool h265 = 2;
|
||||||
|
bool vp8 = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PeerInfo {
|
message PeerInfo {
|
||||||
@@ -91,6 +98,8 @@ message PeerInfo {
|
|||||||
Features features = 9;
|
Features features = 9;
|
||||||
SupportedEncoding encoding = 10;
|
SupportedEncoding encoding = 10;
|
||||||
SupportedResolutions resolutions = 11;
|
SupportedResolutions resolutions = 11;
|
||||||
|
// Use JSON's key-value format which is friendly for peer to handle.
|
||||||
|
string platform_additions = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
@@ -202,11 +211,13 @@ message KeyEvent {
|
|||||||
bool press = 2;
|
bool press = 2;
|
||||||
oneof union {
|
oneof union {
|
||||||
ControlKey control_key = 3;
|
ControlKey control_key = 3;
|
||||||
// high word, sym key code. win: virtual-key code, linux: keysym ?, macos:
|
// position key code. win: scancode, linux: key code, macos: key code
|
||||||
// low word, position key code. win: scancode, linux: key code, macos: key code
|
|
||||||
uint32 chr = 4;
|
uint32 chr = 4;
|
||||||
uint32 unicode = 5;
|
uint32 unicode = 5;
|
||||||
string seq = 6;
|
string seq = 6;
|
||||||
|
// high word. virtual keycode
|
||||||
|
// low word. unicode
|
||||||
|
uint32 win2win_hotkey = 7;
|
||||||
}
|
}
|
||||||
repeated ControlKey modifiers = 8;
|
repeated ControlKey modifiers = 8;
|
||||||
KeyboardMode mode = 9;
|
KeyboardMode mode = 9;
|
||||||
@@ -456,18 +467,20 @@ enum ImageQuality {
|
|||||||
Best = 4;
|
Best = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message VideoCodecState {
|
message SupportedDecoding {
|
||||||
enum PreferCodec {
|
enum PreferCodec {
|
||||||
Auto = 0;
|
Auto = 0;
|
||||||
VPX = 1;
|
VP9 = 1;
|
||||||
H264 = 2;
|
H264 = 2;
|
||||||
H265 = 3;
|
H265 = 3;
|
||||||
|
VP8 = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 score_vpx = 1;
|
int32 ability_vp9 = 1;
|
||||||
int32 score_h264 = 2;
|
int32 ability_h264 = 2;
|
||||||
int32 score_h265 = 3;
|
int32 ability_h265 = 3;
|
||||||
PreferCodec prefer = 4;
|
PreferCodec prefer = 4;
|
||||||
|
int32 ability_vp8 = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OptionMessage {
|
message OptionMessage {
|
||||||
@@ -485,7 +498,7 @@ message OptionMessage {
|
|||||||
BoolOption disable_audio = 7;
|
BoolOption disable_audio = 7;
|
||||||
BoolOption disable_clipboard = 8;
|
BoolOption disable_clipboard = 8;
|
||||||
BoolOption enable_file_transfer = 9;
|
BoolOption enable_file_transfer = 9;
|
||||||
VideoCodecState video_codec_state = 10;
|
SupportedDecoding supported_decoding = 10;
|
||||||
int32 custom_fps = 11;
|
int32 custom_fps = 11;
|
||||||
BoolOption disable_keyboard = 12;
|
BoolOption disable_keyboard = 12;
|
||||||
}
|
}
|
||||||
@@ -511,7 +524,6 @@ message AudioFormat {
|
|||||||
|
|
||||||
message AudioFrame {
|
message AudioFrame {
|
||||||
bytes data = 1;
|
bytes data = 1;
|
||||||
int64 timestamp = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify peer to show message box.
|
// Notify peer to show message box.
|
||||||
@@ -588,6 +600,17 @@ message SwitchSidesResponse {
|
|||||||
|
|
||||||
message SwitchBack {}
|
message SwitchBack {}
|
||||||
|
|
||||||
|
message PluginRequest {
|
||||||
|
string id = 1;
|
||||||
|
bytes content = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PluginResponse {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string msg = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message Misc {
|
message Misc {
|
||||||
oneof union {
|
oneof union {
|
||||||
ChatMessage chat_message = 4;
|
ChatMessage chat_message = 4;
|
||||||
@@ -609,6 +632,8 @@ message Misc {
|
|||||||
SwitchSidesRequest switch_sides_request = 21;
|
SwitchSidesRequest switch_sides_request = 21;
|
||||||
SwitchBack switch_back = 22;
|
SwitchBack switch_back = 22;
|
||||||
Resolution change_resolution = 24;
|
Resolution change_resolution = 24;
|
||||||
|
PluginRequest plugin_request = 25;
|
||||||
|
PluginResponse plugin_response = 26;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,15 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default();
|
pub static ref APP_HOME_DIR: Arc<RwLock<String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(any(target_os = "android", target_os = "ios"))]
|
pub const LINK_DOCS_HOME: &str = "https://rustdesk.com/docs/en/";
|
||||||
|
pub const LINK_DOCS_X11_REQUIRED: &str = "https://rustdesk.com/docs/en/manual/linux/#x11-required";
|
||||||
|
pub const LINK_HEADLESS_LINUX_SUPPORT: &str =
|
||||||
|
"https://github.com/rustdesk/rustdesk/wiki/Headless-Linux-Support";
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref HELPER_URL: HashMap<&'static str, &'static str> = HashMap::from([
|
pub static ref HELPER_URL: HashMap<&'static str, &'static str> = HashMap::from([
|
||||||
("rustdesk docs home", "https://rustdesk.com/docs/en/"),
|
("rustdesk docs home", LINK_DOCS_HOME),
|
||||||
("rustdesk docs x11-required", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
|
("rustdesk docs x11-required", LINK_DOCS_X11_REQUIRED),
|
||||||
|
("rustdesk x11 headless", LINK_HEADLESS_LINUX_SUPPORT),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +301,7 @@ pub struct TransferSerde {
|
|||||||
pub read_jobs: Vec<String>,
|
pub read_jobs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
fn patch(path: PathBuf) -> PathBuf {
|
fn patch(path: PathBuf) -> PathBuf {
|
||||||
if let Some(_tmp) = path.to_str() {
|
if let Some(_tmp) = path.to_str() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -914,15 +919,13 @@ impl PeerConfig {
|
|||||||
decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
decrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||||
config.password = password;
|
config.password = password;
|
||||||
store = store || store2;
|
store = store || store2;
|
||||||
if let Some(v) = config.options.get_mut("rdp_password") {
|
for opt in ["rdp_password", "os-username", "os-password"] {
|
||||||
let (password, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
if let Some(v) = config.options.get_mut(opt) {
|
||||||
*v = password;
|
let (encrypted, _, store2) =
|
||||||
|
decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
||||||
|
*v = encrypted;
|
||||||
store = store || store2;
|
store = store || store2;
|
||||||
}
|
}
|
||||||
if let Some(v) = config.options.get_mut("os-password") {
|
|
||||||
let (password, _, store2) = decrypt_str_or_original(v, PASSWORD_ENC_VERSION);
|
|
||||||
*v = password;
|
|
||||||
store = store || store2;
|
|
||||||
}
|
}
|
||||||
if store {
|
if store {
|
||||||
config.store(id);
|
config.store(id);
|
||||||
@@ -940,12 +943,11 @@ impl PeerConfig {
|
|||||||
let _lock = CONFIG.read().unwrap();
|
let _lock = CONFIG.read().unwrap();
|
||||||
let mut config = self.clone();
|
let mut config = self.clone();
|
||||||
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
config.password = encrypt_vec_or_original(&config.password, PASSWORD_ENC_VERSION);
|
||||||
if let Some(v) = config.options.get_mut("rdp_password") {
|
for opt in ["rdp_password", "os-username", "os-password"] {
|
||||||
|
if let Some(v) = config.options.get_mut(opt) {
|
||||||
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION)
|
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION)
|
||||||
}
|
}
|
||||||
if let Some(v) = config.options.get_mut("os-password") {
|
}
|
||||||
*v = encrypt_str_or_original(v, PASSWORD_ENC_VERSION)
|
|
||||||
};
|
|
||||||
if let Err(err) = store_path(Self::path(id), config) {
|
if let Err(err) = store_path(Self::path(id), config) {
|
||||||
log::error!("Failed to store config: {}", err);
|
log::error!("Failed to store config: {}", err);
|
||||||
}
|
}
|
||||||
@@ -1359,9 +1361,9 @@ impl UserDefaultConfig {
|
|||||||
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
"view_style" => self.get_string(key, "original", vec!["adaptive"]),
|
||||||
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
"scroll_style" => self.get_string(key, "scrollauto", vec!["scrollbar"]),
|
||||||
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
"image_quality" => self.get_string(key, "balanced", vec!["best", "low", "custom"]),
|
||||||
"codec-preference" => self.get_string(key, "auto", vec!["vp9", "h264", "h265"]),
|
"codec-preference" => self.get_string(key, "auto", vec!["vp8", "vp9", "h264", "h265"]),
|
||||||
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
"custom_image_quality" => self.get_double_string(key, 50.0, 10.0, 100.0),
|
||||||
"custom-fps" => self.get_double_string(key, 30.0, 10.0, 120.0),
|
"custom-fps" => self.get_double_string(key, 30.0, 5.0, 120.0),
|
||||||
_ => self
|
_ => self
|
||||||
.options
|
.options
|
||||||
.get(key)
|
.get(key)
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ pub use libc;
|
|||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub use sysinfo;
|
pub use sysinfo;
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
pub use dlopen;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
pub type Stream = quic::Connection;
|
pub type Stream = quic::Connection;
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref DISTRO: Distro = Distro::new();
|
pub static ref DISTRO: Distro = Distro::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DISPLAY_SERVER_WAYLAND: &str = "wayland";
|
||||||
|
pub const DISPLAY_SERVER_X11: &str = "x11";
|
||||||
|
|
||||||
pub struct Distro {
|
pub struct Distro {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version_id: String,
|
pub version_id: String,
|
||||||
@@ -12,13 +15,12 @@ pub struct Distro {
|
|||||||
|
|
||||||
impl Distro {
|
impl Distro {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release".to_owned())
|
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.trim()
|
.trim()
|
||||||
.trim_matches('"')
|
.trim_matches('"')
|
||||||
.to_string();
|
.to_string();
|
||||||
let version_id =
|
let version_id = run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release")
|
||||||
run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release".to_owned())
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.trim()
|
.trim()
|
||||||
.trim_matches('"')
|
.trim_matches('"')
|
||||||
@@ -27,8 +29,27 @@ impl Distro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_gdm_user(username: &str) -> bool {
|
||||||
|
username == "gdm"
|
||||||
|
// || username == "lightgdm"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_desktop_wayland() -> bool {
|
||||||
|
get_display_server() == DISPLAY_SERVER_WAYLAND
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_x11_or_headless() -> bool {
|
||||||
|
!is_desktop_wayland()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -1
|
||||||
|
const INVALID_SESSION: &str = "4294967295";
|
||||||
|
|
||||||
pub fn get_display_server() -> String {
|
pub fn get_display_server() -> String {
|
||||||
let mut session = get_values_of_seat0([0].to_vec())[0].clone();
|
let mut session = get_values_of_seat0(&[0])[0].clone();
|
||||||
if session.is_empty() {
|
if session.is_empty() {
|
||||||
// loginctl has not given the expected output. try something else.
|
// loginctl has not given the expected output. try something else.
|
||||||
if let Ok(sid) = std::env::var("XDG_SESSION_ID") {
|
if let Ok(sid) = std::env::var("XDG_SESSION_ID") {
|
||||||
@@ -36,14 +57,20 @@ pub fn get_display_server() -> String {
|
|||||||
session = sid;
|
session = sid;
|
||||||
}
|
}
|
||||||
if session.is_empty() {
|
if session.is_empty() {
|
||||||
session = run_cmds("cat /proc/self/sessionid".to_owned()).unwrap_or_default();
|
session = run_cmds("cat /proc/self/sessionid").unwrap_or_default();
|
||||||
|
if session == INVALID_SESSION {
|
||||||
|
session = "".to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if session.is_empty() {
|
||||||
|
"".to_owned()
|
||||||
|
} else {
|
||||||
get_display_server_of_session(&session)
|
get_display_server_of_session(&session)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_display_server_of_session(session: &str) -> String {
|
pub fn get_display_server_of_session(session: &str) -> String {
|
||||||
let mut display_server = if let Ok(output) =
|
let mut display_server = if let Ok(output) =
|
||||||
run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
|
run_loginctl(Some(vec!["show-session", "-p", "Type", session]))
|
||||||
// Check session type of the session
|
// Check session type of the session
|
||||||
@@ -61,7 +88,7 @@ fn get_display_server_of_session(session: &str) -> String {
|
|||||||
.replace("TTY=", "")
|
.replace("TTY=", "")
|
||||||
.trim_end()
|
.trim_end()
|
||||||
.into();
|
.into();
|
||||||
if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{tty}.\\\\+Xorg\""))
|
if let Ok(xorg_results) = run_cmds(&format!("ps -e | grep \"{tty}.\\\\+Xorg\""))
|
||||||
// And check if Xorg is running on that tty
|
// And check if Xorg is running on that tty
|
||||||
{
|
{
|
||||||
if xorg_results.trim_end() != "" {
|
if xorg_results.trim_end() != "" {
|
||||||
@@ -87,44 +114,68 @@ fn get_display_server_of_session(session: &str) -> String {
|
|||||||
display_server.to_lowercase()
|
display_server.to_lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_values_of_seat0(indices: Vec<usize>) -> Vec<String> {
|
#[inline]
|
||||||
|
fn line_values(indices: &[usize], line: &str) -> Vec<String> {
|
||||||
|
indices
|
||||||
|
.into_iter()
|
||||||
|
.map(|idx| line.split_whitespace().nth(*idx).unwrap_or("").to_owned())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_values_of_seat0(indices: &[usize]) -> Vec<String> {
|
||||||
|
_get_values_of_seat0(indices, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_values_of_seat0_with_gdm_wayland(indices: &[usize]) -> Vec<String> {
|
||||||
|
_get_values_of_seat0(indices, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _get_values_of_seat0(indices: &[usize], ignore_gdm_wayland: bool) -> Vec<String> {
|
||||||
if let Ok(output) = run_loginctl(None) {
|
if let Ok(output) = run_loginctl(None) {
|
||||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||||
if line.contains("seat0") {
|
if line.contains("seat0") {
|
||||||
if let Some(sid) = line.split_whitespace().next() {
|
if let Some(sid) = line.split_whitespace().next() {
|
||||||
if is_active(sid) {
|
if is_active(sid) {
|
||||||
return indices
|
if ignore_gdm_wayland {
|
||||||
.into_iter()
|
if is_gdm_user(line.split_whitespace().nth(2).unwrap_or(""))
|
||||||
.map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
|
&& get_display_server_of_session(sid) == DISPLAY_SERVER_WAYLAND
|
||||||
.collect::<Vec<String>>();
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return line_values(indices, line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
|
// some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73
|
||||||
if let Ok(output) = run_loginctl(None) {
|
|
||||||
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
for line in String::from_utf8_lossy(&output.stdout).lines() {
|
||||||
if let Some(sid) = line.split_whitespace().next() {
|
if let Some(sid) = line.split_whitespace().next() {
|
||||||
|
if is_active(sid) {
|
||||||
let d = get_display_server_of_session(sid);
|
let d = get_display_server_of_session(sid);
|
||||||
if is_active(sid) && d != "tty" {
|
if ignore_gdm_wayland {
|
||||||
return indices
|
if is_gdm_user(line.split_whitespace().nth(2).unwrap_or(""))
|
||||||
.into_iter()
|
&& d == DISPLAY_SERVER_WAYLAND
|
||||||
.map(|idx| line.split_whitespace().nth(idx).unwrap_or("").to_owned())
|
{
|
||||||
.collect::<Vec<String>>();
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d == "tty" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return line_values(indices, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return indices
|
line_values(indices, "")
|
||||||
.iter()
|
|
||||||
.map(|_x| "".to_owned())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_active(sid: &str) -> bool {
|
pub fn is_active(sid: &str) -> bool {
|
||||||
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) {
|
if let Ok(output) = run_loginctl(Some(vec!["show-session", "-p", "State", sid])) {
|
||||||
String::from_utf8_lossy(&output.stdout).contains("active")
|
String::from_utf8_lossy(&output.stdout).contains("active")
|
||||||
} else {
|
} else {
|
||||||
@@ -132,9 +183,9 @@ fn is_active(sid: &str) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_cmds(cmds: String) -> ResultType<String> {
|
pub fn run_cmds(cmds: &str) -> ResultType<String> {
|
||||||
let output = std::process::Command::new("sh")
|
let output = std::process::Command::new("sh")
|
||||||
.args(vec!["-c", &cmds])
|
.args(vec!["-c", cmds])
|
||||||
.output()?;
|
.output()?;
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ pub mod linux;
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub mod macos;
|
pub mod macos;
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
use crate::{config::Config, log};
|
use crate::{config::Config, log};
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
static mut GLOBAL_CALLBACK: Option<Box<dyn Fn()>> = None;
|
static mut GLOBAL_CALLBACK: Option<Box<dyn Fn()>> = None;
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
extern "C" fn breakdown_signal_handler(sig: i32) {
|
extern "C" fn breakdown_signal_handler(sig: i32) {
|
||||||
let mut stack = vec![];
|
let mut stack = vec![];
|
||||||
backtrace::trace(|frame| {
|
backtrace::trace(|frame| {
|
||||||
@@ -29,6 +33,16 @@ extern "C" fn breakdown_signal_handler(sig: i32) {
|
|||||||
info = "Always use software rendering will be set.".to_string();
|
info = "Always use software rendering will be set.".to_string();
|
||||||
log::info!("{}", info);
|
log::info!("{}", info);
|
||||||
}
|
}
|
||||||
|
if stack.iter().any(|s| {
|
||||||
|
s.to_lowercase().contains("nvidia")
|
||||||
|
|| s.to_lowercase().contains("amf")
|
||||||
|
|| s.to_lowercase().contains("mfx")
|
||||||
|
|| s.contains("cuProfilerStop")
|
||||||
|
}) {
|
||||||
|
Config::set_option("enable-hwcodec".to_string(), "N".to_string());
|
||||||
|
info = "Perhaps hwcodec causing the crash, disable it first".to_string();
|
||||||
|
log::info!("{}", info);
|
||||||
|
}
|
||||||
log::error!(
|
log::error!(
|
||||||
"Got signal {} and exit. stack:\n{}",
|
"Got signal {} and exit. stack:\n{}",
|
||||||
sig,
|
sig,
|
||||||
@@ -51,6 +65,7 @@ extern "C" fn breakdown_signal_handler(sig: i32) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
pub fn register_breakdown_handler<T>(callback: T)
|
pub fn register_breakdown_handler<T>(callback: T)
|
||||||
where
|
where
|
||||||
T: Fn() + 'static,
|
T: Fn() + 'static,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ fn new_socket(addr: SocketAddr, reuse: bool, buf_size: usize) -> Result<Socket,
|
|||||||
if buf_size > 0 {
|
if buf_size > 0 {
|
||||||
socket.set_recv_buffer_size(buf_size).ok();
|
socket.set_recv_buffer_size(buf_size).ok();
|
||||||
}
|
}
|
||||||
log::info!(
|
log::debug!(
|
||||||
"Receive buf size of udp {}: {:?}",
|
"Receive buf size of udp {}: {:?}",
|
||||||
addr,
|
addr,
|
||||||
socket.recv_buffer_size()
|
socket.recv_buffer_size()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
|
|||||||
let mut target = if target_os == "macos" {
|
let mut target = if target_os == "macos" {
|
||||||
if target_arch == "x64" {
|
if target_arch == "x64" {
|
||||||
"x64-osx".to_owned()
|
"x64-osx".to_owned()
|
||||||
} else if target_arch == "arm64"{
|
} else if target_arch == "arm64" {
|
||||||
"arm64-osx".to_owned()
|
"arm64-osx".to_owned()
|
||||||
} else {
|
} else {
|
||||||
format!("{}-{}", target_arch, target_os)
|
format!("{}-{}", target_arch, target_os)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use hbb_common::env_logger::{init_from_env, Env, DEFAULT_FILTER_ENV};
|
|||||||
use scrap::{
|
use scrap::{
|
||||||
codec::{EncoderApi, EncoderCfg},
|
codec::{EncoderApi, EncoderCfg},
|
||||||
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
Capturer, Display, TraitCapturer, VpxDecoder, VpxDecoderConfig, VpxEncoder, VpxEncoderConfig,
|
||||||
VpxVideoCodecId, STRIDE_ALIGN,
|
VpxVideoCodecId::{self, *},
|
||||||
|
STRIDE_ALIGN,
|
||||||
};
|
};
|
||||||
use std::{io::Write, time::Instant};
|
use std::{io::Write, time::Instant};
|
||||||
|
|
||||||
@@ -49,7 +50,7 @@ fn main() {
|
|||||||
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
"benchmark {}x{} bitrate:{}k hw_pixfmt:{:?}",
|
||||||
width, height, bitrate_k, args.flag_hw_pixfmt
|
width, height, bitrate_k, args.flag_hw_pixfmt
|
||||||
);
|
);
|
||||||
test_vp9(&yuvs, width, height, bitrate_k, yuv_count);
|
[VP8, VP9].map(|c| test_vpx(c, &yuvs, width, height, bitrate_k, yuv_count));
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
{
|
{
|
||||||
use hwcodec::AVPixelFormat;
|
use hwcodec::AVPixelFormat;
|
||||||
@@ -57,7 +58,7 @@ fn main() {
|
|||||||
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
Pixfmt::I420 => AVPixelFormat::AV_PIX_FMT_YUV420P,
|
||||||
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
Pixfmt::NV12 => AVPixelFormat::AV_PIX_FMT_NV12,
|
||||||
};
|
};
|
||||||
let yuvs = hw::vp9_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
let yuvs = hw::vpx_yuv_to_hw_yuv(yuvs, width, height, hw_pixfmt);
|
||||||
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
hw::test(&yuvs, width, height, bitrate_k, yuv_count, hw_pixfmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,13 +88,20 @@ fn capture_yuv(yuv_count: usize) -> (Vec<Vec<u8>>, usize, usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize, yuv_count: usize) {
|
fn test_vpx(
|
||||||
|
codec_id: VpxVideoCodecId,
|
||||||
|
yuvs: &Vec<Vec<u8>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
bitrate_k: usize,
|
||||||
|
yuv_count: usize,
|
||||||
|
) {
|
||||||
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
let config = EncoderCfg::VPX(VpxEncoderConfig {
|
||||||
width: width as _,
|
width: width as _,
|
||||||
height: height as _,
|
height: height as _,
|
||||||
timebase: [1, 1000],
|
timebase: [1, 1000],
|
||||||
bitrate: bitrate_k as _,
|
bitrate: bitrate_k as _,
|
||||||
codec: VpxVideoCodecId::VP9,
|
codec: codec_id,
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
});
|
});
|
||||||
let mut encoder = VpxEncoder::new(config).unwrap();
|
let mut encoder = VpxEncoder::new(config).unwrap();
|
||||||
@@ -104,35 +112,43 @@ fn test_vp9(yuvs: &Vec<Vec<u8>>, width: usize, height: usize, bitrate_k: usize,
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let _ = encoder.flush().unwrap();
|
let _ = encoder.flush().unwrap();
|
||||||
}
|
}
|
||||||
println!("vp9 encode: {:?}", start.elapsed() / yuv_count as _);
|
println!(
|
||||||
|
"{:?} encode: {:?}",
|
||||||
|
codec_id,
|
||||||
|
start.elapsed() / yuv_count as _
|
||||||
|
);
|
||||||
|
|
||||||
// prepare data separately
|
// prepare data separately
|
||||||
let mut vp9s = vec![];
|
let mut vpxs = vec![];
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for yuv in yuvs {
|
for yuv in yuvs {
|
||||||
for ref frame in encoder
|
for ref frame in encoder
|
||||||
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
|
.encode(start.elapsed().as_millis() as _, yuv, STRIDE_ALIGN)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
{
|
{
|
||||||
vp9s.push(frame.data.to_vec());
|
vpxs.push(frame.data.to_vec());
|
||||||
}
|
}
|
||||||
for ref frame in encoder.flush().unwrap() {
|
for ref frame in encoder.flush().unwrap() {
|
||||||
vp9s.push(frame.data.to_vec());
|
vpxs.push(frame.data.to_vec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(vp9s.len(), yuv_count);
|
assert_eq!(vpxs.len(), yuv_count);
|
||||||
|
|
||||||
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
let mut decoder = VpxDecoder::new(VpxDecoderConfig {
|
||||||
codec: VpxVideoCodecId::VP9,
|
codec: codec_id,
|
||||||
num_threads: (num_cpus::get() / 2) as _,
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
for vp9 in vp9s {
|
for vpx in vpxs {
|
||||||
let _ = decoder.decode(&vp9);
|
let _ = decoder.decode(&vpx);
|
||||||
let _ = decoder.flush();
|
let _ = decoder.flush();
|
||||||
}
|
}
|
||||||
println!("vp9 decode: {:?}", start.elapsed() / yuv_count as _);
|
println!(
|
||||||
|
"{:?} decode: {:?}",
|
||||||
|
codec_id,
|
||||||
|
start.elapsed() / yuv_count as _
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@@ -267,7 +283,7 @@ mod hw {
|
|||||||
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
|
Some(info.clone()) == best.h264 || Some(info.clone()) == best.h265
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vp9_yuv_to_hw_yuv(
|
pub fn vpx_yuv_to_hw_yuv(
|
||||||
yuvs: Vec<Vec<u8>>,
|
yuvs: Vec<Vec<u8>>,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ extern crate scrap;
|
|||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
|
use scrap::{i420_to_rgb, Display};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use scrap::{CapturerMag, TraitCapturer};
|
use scrap::{CapturerMag, TraitCapturer};
|
||||||
use scrap::{i420_to_rgb, Display};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let n = Display::all().unwrap().len();
|
let n = Display::all().unwrap().len();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use webm::mux;
|
|||||||
use webm::mux::Track;
|
use webm::mux::Track;
|
||||||
|
|
||||||
use scrap::vpxcodec as vpx_encode;
|
use scrap::vpxcodec as vpx_encode;
|
||||||
use scrap::{TraitCapturer, Capturer, Display, STRIDE_ALIGN};
|
use scrap::{Capturer, Display, TraitCapturer, STRIDE_ALIGN};
|
||||||
|
|
||||||
const USAGE: &'static str = "
|
const USAGE: &'static str = "
|
||||||
Simple WebM screen capture.
|
Simple WebM screen capture.
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ impl crate::TraitCapturer for Capturer {
|
|||||||
|
|
||||||
pub enum Frame<'a> {
|
pub enum Frame<'a> {
|
||||||
RAW(&'a [u8]),
|
RAW(&'a [u8]),
|
||||||
|
VP8(&'a [u8]),
|
||||||
VP9(&'a [u8]),
|
VP9(&'a [u8]),
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
#[cfg(feature = "hwcodec")]
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -11,30 +10,31 @@ use crate::hwcodec::*;
|
|||||||
use crate::mediacodec::{
|
use crate::mediacodec::{
|
||||||
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
MediaCodecDecoder, MediaCodecDecoders, H264_DECODER_SUPPORT, H265_DECODER_SUPPORT,
|
||||||
};
|
};
|
||||||
use crate::{vpxcodec::*, ImageFormat};
|
use crate::{vpxcodec::*, CodecName, ImageFormat};
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
use hbb_common::sysinfo::{System, SystemExt};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
anyhow::anyhow,
|
anyhow::anyhow,
|
||||||
|
config::PeerConfig,
|
||||||
log,
|
log,
|
||||||
message_proto::{video_frame, EncodedVideoFrames, Message, VideoCodecState},
|
message_proto::{
|
||||||
|
supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message,
|
||||||
|
SupportedDecoding, SupportedEncoding,
|
||||||
|
},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
||||||
use hbb_common::{
|
use hbb_common::{config::Config2, lazy_static};
|
||||||
config::{Config2, PeerConfig},
|
|
||||||
lazy_static,
|
|
||||||
message_proto::video_codec_state::PreferCodec,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "hwcodec")]
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref PEER_DECODER_STATES: Arc<Mutex<HashMap<i32, VideoCodecState>>> = Default::default();
|
static ref PEER_DECODINGS: Arc<Mutex<HashMap<i32, SupportedDecoding>>> = Default::default();
|
||||||
|
static ref CODEC_NAME: Arc<Mutex<CodecName>> = Arc::new(Mutex::new(CodecName::VP9));
|
||||||
}
|
}
|
||||||
const SCORE_VPX: i32 = 90;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HwEncoderConfig {
|
pub struct HwEncoderConfig {
|
||||||
pub codec_name: String,
|
pub name: String,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub bitrate: i32,
|
pub bitrate: i32,
|
||||||
@@ -58,10 +58,6 @@ pub trait EncoderApi {
|
|||||||
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
fn set_bitrate(&mut self, bitrate: u32) -> ResultType<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DecoderCfg {
|
|
||||||
pub vpx: VpxDecoderConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Encoder {
|
pub struct Encoder {
|
||||||
pub codec: Box<dyn EncoderApi>,
|
pub codec: Box<dyn EncoderApi>,
|
||||||
}
|
}
|
||||||
@@ -81,7 +77,8 @@ impl DerefMut for Encoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Decoder {
|
pub struct Decoder {
|
||||||
vpx: VpxDecoder,
|
vp8: VpxDecoder,
|
||||||
|
vp9: VpxDecoder,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
hw: HwDecoders,
|
hw: HwDecoders,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
@@ -91,10 +88,10 @@ pub struct Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum EncoderUpdate {
|
pub enum EncodingUpdate {
|
||||||
State(VideoCodecState),
|
New(SupportedDecoding),
|
||||||
Remove,
|
Remove,
|
||||||
DisableHwIfNotExist,
|
NewOnlyVP9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encoder {
|
impl Encoder {
|
||||||
@@ -111,7 +108,8 @@ impl Encoder {
|
|||||||
codec: Box::new(hw),
|
codec: Box::new(hw),
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
check_config_process(true);
|
check_config_process();
|
||||||
|
*CODEC_NAME.lock().unwrap() = CodecName::VP9;
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -120,42 +118,57 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
pub fn update(id: i32, update: EncodingUpdate) {
|
||||||
pub fn update_video_encoder(id: i32, update: EncoderUpdate) {
|
let mut decodings = PEER_DECODINGS.lock().unwrap();
|
||||||
|
match update {
|
||||||
|
EncodingUpdate::New(decoding) => {
|
||||||
|
decodings.insert(id, decoding);
|
||||||
|
}
|
||||||
|
EncodingUpdate::Remove => {
|
||||||
|
decodings.remove(&id);
|
||||||
|
}
|
||||||
|
EncodingUpdate::NewOnlyVP9 => {
|
||||||
|
decodings.insert(
|
||||||
|
id,
|
||||||
|
SupportedDecoding {
|
||||||
|
ability_vp9: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vp8_useable = decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_vp8 > 0);
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut h264_name = None;
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut h265_name = None;
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
{
|
{
|
||||||
let mut states = PEER_DECODER_STATES.lock().unwrap();
|
if enable_hwcodec_option() {
|
||||||
match update {
|
|
||||||
EncoderUpdate::State(state) => {
|
|
||||||
states.insert(id, state);
|
|
||||||
}
|
|
||||||
EncoderUpdate::Remove => {
|
|
||||||
states.remove(&id);
|
|
||||||
}
|
|
||||||
EncoderUpdate::DisableHwIfNotExist => {
|
|
||||||
if !states.contains_key(&id) {
|
|
||||||
states.insert(id, VideoCodecState::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let name = HwEncoder::current_name();
|
|
||||||
if states.len() > 0 {
|
|
||||||
let best = HwEncoder::best();
|
let best = HwEncoder::best();
|
||||||
let enabled_h264 = best.h264.is_some()
|
let h264_useable =
|
||||||
&& states.len() > 0
|
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h264 > 0);
|
||||||
&& states.iter().all(|(_, s)| s.score_h264 > 0);
|
let h265_useable =
|
||||||
let enabled_h265 = best.h265.is_some()
|
decodings.len() > 0 && decodings.iter().all(|(_, s)| s.ability_h265 > 0);
|
||||||
&& states.len() > 0
|
if h264_useable {
|
||||||
&& states.iter().all(|(_, s)| s.score_h265 > 0);
|
h264_name = best.h264.map_or(None, |c| Some(c.name));
|
||||||
|
}
|
||||||
|
if h265_useable {
|
||||||
|
h265_name = best.h265.map_or(None, |c| Some(c.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preference first
|
let mut name = CODEC_NAME.lock().unwrap();
|
||||||
let mut preference = PreferCodec::Auto;
|
let mut preference = PreferCodec::Auto;
|
||||||
let preferences: Vec<_> = states
|
let preferences: Vec<_> = decodings
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, s)| {
|
.filter(|(_, s)| {
|
||||||
s.prefer == PreferCodec::VPX.into()
|
s.prefer == PreferCodec::VP9.into()
|
||||||
|| s.prefer == PreferCodec::H264.into() && enabled_h264
|
|| s.prefer == PreferCodec::VP8.into() && vp8_useable
|
||||||
|| s.prefer == PreferCodec::H265.into() && enabled_h265
|
|| s.prefer == PreferCodec::H264.into() && h264_name.is_some()
|
||||||
|
|| s.prefer == PreferCodec::H265.into() && h265_name.is_some()
|
||||||
})
|
})
|
||||||
.map(|(_, s)| s.prefer)
|
.map(|(_, s)| s.prefer)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -163,129 +176,100 @@ impl Encoder {
|
|||||||
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut auto_codec = CodecName::VP9;
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
if vp8_useable && System::new_all().total_memory() <= 4 * 1024 * 1024 * 1024 {
|
||||||
|
// 4 Gb
|
||||||
|
auto_codec = CodecName::VP8
|
||||||
|
}
|
||||||
|
|
||||||
match preference {
|
match preference {
|
||||||
PreferCodec::VPX => *name.lock().unwrap() = None,
|
PreferCodec::VP8 => *name = CodecName::VP8,
|
||||||
PreferCodec::H264 => {
|
PreferCodec::VP9 => *name = CodecName::VP9,
|
||||||
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name))
|
PreferCodec::H264 => *name = h264_name.map_or(auto_codec, |c| CodecName::H264(c)),
|
||||||
}
|
PreferCodec::H265 => *name = h265_name.map_or(auto_codec, |c| CodecName::H265(c)),
|
||||||
PreferCodec::H265 => {
|
PreferCodec::Auto => *name = auto_codec,
|
||||||
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name))
|
|
||||||
}
|
|
||||||
PreferCodec::Auto => {
|
|
||||||
// score encoder
|
|
||||||
let mut score_vpx = SCORE_VPX;
|
|
||||||
let mut score_h264 = best.h264.as_ref().map_or(0, |c| c.score);
|
|
||||||
let mut score_h265 = best.h265.as_ref().map_or(0, |c| c.score);
|
|
||||||
|
|
||||||
// score decoder
|
|
||||||
score_vpx += states.iter().map(|s| s.1.score_vpx).sum::<i32>();
|
|
||||||
if enabled_h264 {
|
|
||||||
score_h264 += states.iter().map(|s| s.1.score_h264).sum::<i32>();
|
|
||||||
}
|
|
||||||
if enabled_h265 {
|
|
||||||
score_h265 += states.iter().map(|s| s.1.score_h265).sum::<i32>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if enabled_h265 && score_h265 >= score_vpx && score_h265 >= score_h264 {
|
|
||||||
*name.lock().unwrap() = best.h265.map_or(None, |c| Some(c.name));
|
|
||||||
} else if enabled_h264
|
|
||||||
&& score_h264 >= score_vpx
|
|
||||||
&& score_h264 >= score_h265
|
|
||||||
{
|
|
||||||
*name.lock().unwrap() = best.h264.map_or(None, |c| Some(c.name));
|
|
||||||
} else {
|
|
||||||
*name.lock().unwrap() = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"connection count:{}, used preference:{:?}, encoder:{:?}",
|
"connection count:{}, used preference:{:?}, encoder:{:?}",
|
||||||
states.len(),
|
decodings.len(),
|
||||||
preference,
|
preference,
|
||||||
name.lock().unwrap()
|
*name
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
*name.lock().unwrap() = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
|
||||||
{
|
|
||||||
let _ = id;
|
|
||||||
let _ = update;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn current_hw_encoder_name() -> Option<String> {
|
|
||||||
#[cfg(feature = "hwcodec")]
|
|
||||||
if enable_hwcodec_option() {
|
|
||||||
return HwEncoder::current_name().lock().unwrap().clone();
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_encoding() -> (bool, bool) {
|
#[inline]
|
||||||
|
pub fn negotiated_codec() -> CodecName {
|
||||||
|
CODEC_NAME.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supported_encoding() -> SupportedEncoding {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut encoding = SupportedEncoding {
|
||||||
|
vp8: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwEncoder::best();
|
let best = HwEncoder::best();
|
||||||
(
|
encoding.h264 = best.h264.is_some();
|
||||||
best.h264.as_ref().map_or(false, |c| c.score > 0),
|
encoding.h265 = best.h265.is_some();
|
||||||
best.h265.as_ref().map_or(false, |c| c.score > 0),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(false, false)
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
encoding
|
||||||
(false, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decoder {
|
impl Decoder {
|
||||||
pub fn video_codec_state(_id: &str) -> VideoCodecState {
|
pub fn supported_decodings(id_for_perfer: Option<&str>) -> SupportedDecoding {
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut decoding = SupportedDecoding {
|
||||||
|
ability_vp8: 1,
|
||||||
|
ability_vp9: 1,
|
||||||
|
prefer: id_for_perfer
|
||||||
|
.map_or(PreferCodec::Auto, |id| Self::codec_preference(id))
|
||||||
|
.into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let best = HwDecoder::best();
|
let best = HwDecoder::best();
|
||||||
return VideoCodecState {
|
decoding.ability_h264 = if best.h264.is_some() { 1 } else { 0 };
|
||||||
score_vpx: SCORE_VPX,
|
decoding.ability_h265 = if best.h265.is_some() { 1 } else { 0 };
|
||||||
score_h264: best.h264.map_or(0, |c| c.score),
|
|
||||||
score_h265: best.h265.map_or(0, |c| c.score),
|
|
||||||
prefer: Self::codec_preference(_id).into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "mediacodec")]
|
#[cfg(feature = "mediacodec")]
|
||||||
if enable_hwcodec_option() {
|
if enable_hwcodec_option() {
|
||||||
let score_h264 = if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
decoding.ability_h264 =
|
||||||
92
|
if H264_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let score_h265 = if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
decoding.ability_h265 =
|
||||||
94
|
if H265_DECODER_SUPPORT.load(std::sync::atomic::Ordering::SeqCst) {
|
||||||
|
1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
return VideoCodecState {
|
|
||||||
score_vpx: SCORE_VPX,
|
|
||||||
score_h264,
|
|
||||||
score_h265,
|
|
||||||
prefer: Self::codec_preference(_id).into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
VideoCodecState {
|
|
||||||
score_vpx: SCORE_VPX,
|
|
||||||
..Default::default()
|
|
||||||
}
|
}
|
||||||
|
decoding
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(config: DecoderCfg) -> Decoder {
|
pub fn new() -> Decoder {
|
||||||
let vpx = VpxDecoder::new(config.vpx).unwrap();
|
let vp8 = VpxDecoder::new(VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP8,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let vp9 = VpxDecoder::new(VpxDecoderConfig {
|
||||||
|
codec: VpxVideoCodecId::VP9,
|
||||||
|
num_threads: (num_cpus::get() / 2) as _,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
Decoder {
|
Decoder {
|
||||||
vpx,
|
vp8,
|
||||||
|
vp9,
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
hw: if enable_hwcodec_option() {
|
hw: if enable_hwcodec_option() {
|
||||||
HwDecoder::new_decoders()
|
HwDecoder::new_decoders()
|
||||||
@@ -310,8 +294,11 @@ impl Decoder {
|
|||||||
rgb: &mut Vec<u8>,
|
rgb: &mut Vec<u8>,
|
||||||
) -> ResultType<bool> {
|
) -> ResultType<bool> {
|
||||||
match frame {
|
match frame {
|
||||||
|
video_frame::Union::Vp8s(vp8s) => {
|
||||||
|
Decoder::handle_vpxs_video_frame(&mut self.vp8, vp8s, fmt, rgb)
|
||||||
|
}
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
Decoder::handle_vp9s_video_frame(&mut self.vpx, vp9s, fmt, rgb)
|
Decoder::handle_vpxs_video_frame(&mut self.vp9, vp9s, fmt, rgb)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
@@ -349,15 +336,15 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_vp9s_video_frame(
|
fn handle_vpxs_video_frame(
|
||||||
decoder: &mut VpxDecoder,
|
decoder: &mut VpxDecoder,
|
||||||
vp9s: &EncodedVideoFrames,
|
vpxs: &EncodedVideoFrames,
|
||||||
fmt: (ImageFormat, usize),
|
fmt: (ImageFormat, usize),
|
||||||
rgb: &mut Vec<u8>,
|
rgb: &mut Vec<u8>,
|
||||||
) -> ResultType<bool> {
|
) -> ResultType<bool> {
|
||||||
let mut last_frame = Image::new();
|
let mut last_frame = Image::new();
|
||||||
for vp9 in vp9s.frames.iter() {
|
for vpx in vpxs.frames.iter() {
|
||||||
for frame in decoder.decode(&vp9.data)? {
|
for frame in decoder.decode(&vpx.data)? {
|
||||||
drop(last_frame);
|
drop(last_frame);
|
||||||
last_frame = frame;
|
last_frame = frame;
|
||||||
}
|
}
|
||||||
@@ -408,14 +395,15 @@ impl Decoder {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "hwcodec", feature = "mediacodec"))]
|
|
||||||
fn codec_preference(id: &str) -> PreferCodec {
|
fn codec_preference(id: &str) -> PreferCodec {
|
||||||
let codec = PeerConfig::load(id)
|
let codec = PeerConfig::load(id)
|
||||||
.options
|
.options
|
||||||
.get("codec-preference")
|
.get("codec-preference")
|
||||||
.map_or("".to_owned(), |c| c.to_owned());
|
.map_or("".to_owned(), |c| c.to_owned());
|
||||||
if codec == "vp9" {
|
if codec == "vp8" {
|
||||||
PreferCodec::VPX
|
PreferCodec::VP8
|
||||||
|
} else if codec == "vp9" {
|
||||||
|
PreferCodec::VP9
|
||||||
} else if codec == "h264" {
|
} else if codec == "h264" {
|
||||||
PreferCodec::H264
|
PreferCodec::H264
|
||||||
} else if codec == "h265" {
|
} else if codec == "h265" {
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ use crate::{
|
|||||||
hw, ImageFormat, HW_STRIDE_ALIGN,
|
hw, ImageFormat, HW_STRIDE_ALIGN,
|
||||||
};
|
};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
|
allow_err,
|
||||||
anyhow::{anyhow, Context},
|
anyhow::{anyhow, Context},
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
config::HwCodecConfig,
|
config::HwCodecConfig,
|
||||||
get_time, lazy_static, log,
|
log,
|
||||||
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame},
|
||||||
ResultType,
|
ResultType,
|
||||||
};
|
};
|
||||||
@@ -18,18 +19,13 @@ use hwcodec::{
|
|||||||
Quality::{self, *},
|
Quality::{self, *},
|
||||||
RateControl::{self, *},
|
RateControl::{self, *},
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref HW_ENCODER_NAME: Arc<Mutex<Option<String>>> = Default::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
const CFG_KEY_ENCODER: &str = "bestHwEncoders";
|
||||||
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
const CFG_KEY_DECODER: &str = "bestHwDecoders";
|
||||||
|
|
||||||
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
const DEFAULT_PIXFMT: AVPixelFormat = AVPixelFormat::AV_PIX_FMT_YUV420P;
|
||||||
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
pub const DEFAULT_TIME_BASE: [i32; 2] = [1, 30];
|
||||||
const DEFAULT_GOP: i32 = 60;
|
const DEFAULT_GOP: i32 = i32::MAX;
|
||||||
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
const DEFAULT_HW_QUALITY: Quality = Quality_Default;
|
||||||
const DEFAULT_RC: RateControl = RC_DEFAULT;
|
const DEFAULT_RC: RateControl = RC_DEFAULT;
|
||||||
|
|
||||||
@@ -48,7 +44,7 @@ impl EncoderApi for HwEncoder {
|
|||||||
match cfg {
|
match cfg {
|
||||||
EncoderCfg::HW(config) => {
|
EncoderCfg::HW(config) => {
|
||||||
let ctx = EncodeContext {
|
let ctx = EncodeContext {
|
||||||
name: config.codec_name.clone(),
|
name: config.name.clone(),
|
||||||
width: config.width as _,
|
width: config.width as _,
|
||||||
height: config.height as _,
|
height: config.height as _,
|
||||||
pixfmt: DEFAULT_PIXFMT,
|
pixfmt: DEFAULT_PIXFMT,
|
||||||
@@ -59,12 +55,12 @@ impl EncoderApi for HwEncoder {
|
|||||||
quality: DEFAULT_HW_QUALITY,
|
quality: DEFAULT_HW_QUALITY,
|
||||||
rc: DEFAULT_RC,
|
rc: DEFAULT_RC,
|
||||||
};
|
};
|
||||||
let format = match Encoder::format_from_name(config.codec_name.clone()) {
|
let format = match Encoder::format_from_name(config.name.clone()) {
|
||||||
Ok(format) => format,
|
Ok(format) => format,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(anyhow!(format!(
|
return Err(anyhow!(format!(
|
||||||
"failed to get format from name:{}",
|
"failed to get format from name:{}",
|
||||||
config.codec_name
|
config.name
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -107,7 +103,6 @@ impl EncoderApi for HwEncoder {
|
|||||||
DataFormat::H264 => vf.set_h264s(frames),
|
DataFormat::H264 => vf.set_h264s(frames),
|
||||||
DataFormat::H265 => vf.set_h265s(frames),
|
DataFormat::H265 => vf.set_h265s(frames),
|
||||||
}
|
}
|
||||||
vf.timestamp = get_time();
|
|
||||||
msg_out.set_video_frame(vf);
|
msg_out.set_video_frame(vf);
|
||||||
Ok(msg_out)
|
Ok(msg_out)
|
||||||
} else {
|
} else {
|
||||||
@@ -133,10 +128,6 @@ impl HwEncoder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_name() -> Arc<Mutex<Option<String>>> {
|
|
||||||
HW_ENCODER_NAME.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
|
pub fn encode(&mut self, bgra: &[u8]) -> ResultType<Vec<EncodeFrame>> {
|
||||||
match self.pixfmt {
|
match self.pixfmt {
|
||||||
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
|
AVPixelFormat::AV_PIX_FMT_YUV420P => hw::hw_bgra_to_i420(
|
||||||
@@ -208,7 +199,7 @@ impl HwDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fail {
|
if fail {
|
||||||
check_config_process(true);
|
check_config_process();
|
||||||
}
|
}
|
||||||
HwDecoders { h264, h265 }
|
HwDecoders { h264, h265 }
|
||||||
}
|
}
|
||||||
@@ -332,13 +323,11 @@ pub fn check_config() {
|
|||||||
log::error!("Failed to serialize codec info");
|
log::error!("Failed to serialize codec info");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_config_process(force_reset: bool) {
|
pub fn check_config_process() {
|
||||||
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
|
use hbb_common::sysinfo::{ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if force_reset {
|
|
||||||
HwCodecConfig::remove();
|
HwCodecConfig::remove();
|
||||||
}
|
|
||||||
if let Ok(exe) = std::env::current_exe() {
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
if let Some(file_name) = exe.file_name().to_owned() {
|
if let Some(file_name) = exe.file_name().to_owned() {
|
||||||
let s = System::new_all();
|
let s = System::new_all();
|
||||||
@@ -353,7 +342,21 @@ pub fn check_config_process(force_reset: bool) {
|
|||||||
let second = 3;
|
let second = 3;
|
||||||
std::thread::sleep(std::time::Duration::from_secs(second));
|
std::thread::sleep(std::time::Duration::from_secs(second));
|
||||||
// kill: Different platforms have different results
|
// kill: Different platforms have different results
|
||||||
child.kill().ok();
|
allow_err!(child.kill());
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||||
|
match child.try_wait() {
|
||||||
|
Ok(Some(status)) => log::info!("Check hwcodec config, exit with: {status}"),
|
||||||
|
Ok(None) => {
|
||||||
|
log::info!(
|
||||||
|
"Check hwcodec config, status not ready yet, let's really wait"
|
||||||
|
);
|
||||||
|
let res = child.wait();
|
||||||
|
log::info!("Check hwcodec config, wait result: {res:?}");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Check hwcodec config, error attempting to wait: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
HwCodecConfig::refresh();
|
HwCodecConfig::refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub use self::vpxcodec::*;
|
pub use self::vpxcodec::*;
|
||||||
|
use hbb_common::message_proto::{video_frame, VideoFrame};
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(quartz)] {
|
if #[cfg(quartz)] {
|
||||||
@@ -63,6 +64,9 @@ pub fn would_block_if_equal(old: &mut Vec<u8>, b: &[u8]) -> std::io::Result<()>
|
|||||||
|
|
||||||
pub trait TraitCapturer {
|
pub trait TraitCapturer {
|
||||||
fn set_use_yuv(&mut self, use_yuv: bool);
|
fn set_use_yuv(&mut self, use_yuv: bool);
|
||||||
|
|
||||||
|
// We doesn't support
|
||||||
|
#[cfg(not(any(target_os = "ios")))]
|
||||||
fn frame<'a>(&'a mut self, timeout: std::time::Duration) -> std::io::Result<Frame<'a>>;
|
fn frame<'a>(&'a mut self, timeout: std::time::Duration) -> std::io::Result<Frame<'a>>;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -74,7 +78,7 @@ pub trait TraitCapturer {
|
|||||||
#[cfg(x11)]
|
#[cfg(x11)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_x11() -> bool {
|
pub fn is_x11() -> bool {
|
||||||
"x11" == hbb_common::platform::linux::get_display_server()
|
hbb_common::platform::linux::is_x11_or_headless()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(x11)]
|
#[cfg(x11)]
|
||||||
@@ -92,3 +96,55 @@ pub fn is_cursor_embedded() -> bool {
|
|||||||
pub fn is_cursor_embedded() -> bool {
|
pub fn is_cursor_embedded() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CodecName {
|
||||||
|
VP8,
|
||||||
|
VP9,
|
||||||
|
H264(String),
|
||||||
|
H265(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub enum CodecFormat {
|
||||||
|
VP8,
|
||||||
|
VP9,
|
||||||
|
H264,
|
||||||
|
H265,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&VideoFrame> for CodecFormat {
|
||||||
|
fn from(it: &VideoFrame) -> Self {
|
||||||
|
match it.union {
|
||||||
|
Some(video_frame::Union::Vp8s(_)) => CodecFormat::VP8,
|
||||||
|
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
||||||
|
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
||||||
|
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
||||||
|
_ => CodecFormat::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&CodecName> for CodecFormat {
|
||||||
|
fn from(value: &CodecName) -> Self {
|
||||||
|
match value {
|
||||||
|
CodecName::VP8 => Self::VP8,
|
||||||
|
CodecName::VP9 => Self::VP9,
|
||||||
|
CodecName::H264(_) => Self::H264,
|
||||||
|
CodecName::H265(_) => Self::H265,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for CodecFormat {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
CodecFormat::VP8 => "VP8".into(),
|
||||||
|
CodecFormat::VP9 => "VP9".into(),
|
||||||
|
CodecFormat::H264 => "H264".into(),
|
||||||
|
CodecFormat::H265 => "H265".into(),
|
||||||
|
CodecFormat::Unknown => "Unknow".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::CodecFormat;
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
use hbb_common::anyhow::anyhow;
|
use hbb_common::anyhow::anyhow;
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
@@ -21,13 +22,6 @@ use webm::mux::{self, Segment, Track, VideoTrack, Writer};
|
|||||||
|
|
||||||
const MIN_SECS: u64 = 1;
|
const MIN_SECS: u64 = 1;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum RecordCodecID {
|
|
||||||
VP9,
|
|
||||||
H264,
|
|
||||||
H265,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RecorderContext {
|
pub struct RecorderContext {
|
||||||
pub server: bool,
|
pub server: bool,
|
||||||
@@ -36,7 +30,7 @@ pub struct RecorderContext {
|
|||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub codec_id: RecordCodecID,
|
pub format: CodecFormat,
|
||||||
pub tx: Option<Sender<RecordState>>,
|
pub tx: Option<Sender<RecordState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +49,9 @@ impl RecorderContext {
|
|||||||
}
|
}
|
||||||
let file = if self.server { "s" } else { "c" }.to_string()
|
let file = if self.server { "s" } else { "c" }.to_string()
|
||||||
+ &self.id.clone()
|
+ &self.id.clone()
|
||||||
+ &chrono::Local::now().format("_%Y%m%d%H%M%S").to_string()
|
+ &chrono::Local::now().format("_%Y%m%d%H%M%S_").to_string()
|
||||||
+ if self.codec_id == RecordCodecID::VP9 {
|
+ &self.format.to_string()
|
||||||
|
+ if self.format == CodecFormat::VP9 || self.format == CodecFormat::VP8 {
|
||||||
".webm"
|
".webm"
|
||||||
} else {
|
} else {
|
||||||
".mp4"
|
".mp4"
|
||||||
@@ -107,8 +102,8 @@ impl DerefMut for Recorder {
|
|||||||
impl Recorder {
|
impl Recorder {
|
||||||
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
pub fn new(mut ctx: RecorderContext) -> ResultType<Self> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
let recorder = match ctx.codec_id {
|
let recorder = match ctx.format {
|
||||||
RecordCodecID::VP9 => Recorder {
|
CodecFormat::VP8 | CodecFormat::VP9 => Recorder {
|
||||||
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
inner: Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
ctx,
|
ctx,
|
||||||
},
|
},
|
||||||
@@ -126,8 +121,8 @@ impl Recorder {
|
|||||||
|
|
||||||
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
fn change(&mut self, mut ctx: RecorderContext) -> ResultType<()> {
|
||||||
ctx.set_filename()?;
|
ctx.set_filename()?;
|
||||||
self.inner = match ctx.codec_id {
|
self.inner = match ctx.format {
|
||||||
RecordCodecID::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
CodecFormat::VP8 | CodecFormat::VP9 => Box::new(WebmRecorder::new(ctx.clone())?),
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
_ => Box::new(HwRecorder::new(ctx.clone())?),
|
||||||
#[cfg(not(feature = "hwcodec"))]
|
#[cfg(not(feature = "hwcodec"))]
|
||||||
@@ -148,10 +143,19 @@ impl Recorder {
|
|||||||
|
|
||||||
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
pub fn write_frame(&mut self, frame: &video_frame::Union) -> ResultType<()> {
|
||||||
match frame {
|
match frame {
|
||||||
video_frame::Union::Vp9s(vp9s) => {
|
video_frame::Union::Vp8s(vp8s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::VP9 {
|
if self.ctx.format != CodecFormat::VP8 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::VP9,
|
format: CodecFormat::VP8,
|
||||||
|
..self.ctx.clone()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
vp8s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
|
}
|
||||||
|
video_frame::Union::Vp9s(vp9s) => {
|
||||||
|
if self.ctx.format != CodecFormat::VP9 {
|
||||||
|
self.change(RecorderContext {
|
||||||
|
format: CodecFormat::VP9,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -159,25 +163,25 @@ impl Recorder {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H264s(h264s) => {
|
video_frame::Union::H264s(h264s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::H264 {
|
if self.ctx.format != CodecFormat::H264 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::H264,
|
format: CodecFormat::H264,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.codec_id == RecordCodecID::H264 {
|
if self.ctx.format == CodecFormat::H264 {
|
||||||
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
h264s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(feature = "hwcodec")]
|
#[cfg(feature = "hwcodec")]
|
||||||
video_frame::Union::H265s(h265s) => {
|
video_frame::Union::H265s(h265s) => {
|
||||||
if self.ctx.codec_id != RecordCodecID::H265 {
|
if self.ctx.format != CodecFormat::H265 {
|
||||||
self.change(RecorderContext {
|
self.change(RecorderContext {
|
||||||
codec_id: RecordCodecID::H265,
|
format: CodecFormat::H265,
|
||||||
..self.ctx.clone()
|
..self.ctx.clone()
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
if self.ctx.codec_id == RecordCodecID::H265 {
|
if self.ctx.format == CodecFormat::H265 {
|
||||||
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
h265s.frames.iter().map(|f| self.write_video(f)).count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +225,11 @@ impl RecorderApi for WebmRecorder {
|
|||||||
ctx.width as _,
|
ctx.width as _,
|
||||||
ctx.height as _,
|
ctx.height as _,
|
||||||
None,
|
None,
|
||||||
mux::VideoCodecId::VP9,
|
if ctx.format == CodecFormat::VP9 {
|
||||||
|
mux::VideoCodecId::VP9
|
||||||
|
} else {
|
||||||
|
mux::VideoCodecId::VP8
|
||||||
|
},
|
||||||
);
|
);
|
||||||
Ok(WebmRecorder {
|
Ok(WebmRecorder {
|
||||||
vt,
|
vt,
|
||||||
@@ -279,7 +287,7 @@ impl RecorderApi for HwRecorder {
|
|||||||
filename: ctx.filename.clone(),
|
filename: ctx.filename.clone(),
|
||||||
width: ctx.width,
|
width: ctx.width,
|
||||||
height: ctx.height,
|
height: ctx.height,
|
||||||
is265: ctx.codec_id == RecordCodecID::H265,
|
is265: ctx.format == CodecFormat::H265,
|
||||||
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
framerate: crate::hwcodec::DEFAULT_TIME_BASE[1] as _,
|
||||||
})
|
})
|
||||||
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
.map_err(|_| anyhow!("Failed to create hardware muxer"))?;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use hbb_common::anyhow::{anyhow, Context};
|
use hbb_common::anyhow::{anyhow, Context};
|
||||||
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
|
use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame};
|
||||||
use hbb_common::{get_time, ResultType};
|
use hbb_common::ResultType;
|
||||||
|
|
||||||
use crate::STRIDE_ALIGN;
|
use crate::STRIDE_ALIGN;
|
||||||
use crate::{codec::EncoderApi, ImageFormat};
|
use crate::{codec::EncoderApi, ImageFormat};
|
||||||
@@ -30,6 +30,7 @@ pub struct VpxEncoder {
|
|||||||
ctx: vpx_codec_ctx_t,
|
ctx: vpx_codec_ctx_t,
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
id: VpxVideoCodecId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VpxDecoder {
|
pub struct VpxDecoder {
|
||||||
@@ -97,15 +98,10 @@ impl EncoderApi for VpxEncoder {
|
|||||||
{
|
{
|
||||||
match cfg {
|
match cfg {
|
||||||
crate::codec::EncoderCfg::VPX(config) => {
|
crate::codec::EncoderCfg::VPX(config) => {
|
||||||
let i;
|
let i = match config.codec {
|
||||||
if cfg!(feature = "VP8") {
|
|
||||||
i = match config.codec {
|
|
||||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_cx()),
|
||||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_cx()),
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_cx());
|
|
||||||
}
|
|
||||||
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
let mut c = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||||
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
call_vpx!(vpx_codec_enc_config_default(i, &mut c, 0));
|
||||||
|
|
||||||
@@ -187,12 +183,17 @@ impl EncoderApi for VpxEncoder {
|
|||||||
VP9E_SET_TILE_COLUMNS as _,
|
VP9E_SET_TILE_COLUMNS as _,
|
||||||
4 as c_int
|
4 as c_int
|
||||||
));
|
));
|
||||||
|
} else if config.codec == VpxVideoCodecId::VP8 {
|
||||||
|
// https://github.com/webmproject/libvpx/blob/972149cafeb71d6f08df89e91a0130d6a38c4b15/vpx/vp8cx.h#L172
|
||||||
|
// https://groups.google.com/a/webmproject.org/g/webm-discuss/c/DJhSrmfQ61M
|
||||||
|
call_vpx!(vpx_codec_control_(&mut ctx, VP8E_SET_CPUUSED as _, 12,));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ctx,
|
ctx,
|
||||||
width: config.width as _,
|
width: config.width as _,
|
||||||
height: config.height as _,
|
height: config.height as _,
|
||||||
|
id: config.codec,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("encoder type mismatch")),
|
_ => Err(anyhow!("encoder type mismatch")),
|
||||||
@@ -213,7 +214,7 @@ impl EncoderApi for VpxEncoder {
|
|||||||
|
|
||||||
// to-do: flush periodically, e.g. 1 second
|
// to-do: flush periodically, e.g. 1 second
|
||||||
if frames.len() > 0 {
|
if frames.len() > 0 {
|
||||||
Ok(VpxEncoder::create_msg(frames))
|
Ok(VpxEncoder::create_msg(self.id, frames))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("no valid frame"))
|
Err(anyhow!("no valid frame"))
|
||||||
}
|
}
|
||||||
@@ -280,14 +281,17 @@ impl VpxEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn create_msg(vp9s: Vec<EncodedVideoFrame>) -> Message {
|
pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec<EncodedVideoFrame>) -> Message {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let mut vf = VideoFrame::new();
|
let mut vf = VideoFrame::new();
|
||||||
vf.set_vp9s(EncodedVideoFrames {
|
let vpxs = EncodedVideoFrames {
|
||||||
frames: vp9s.into(),
|
frames: frames.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
};
|
||||||
vf.timestamp = get_time();
|
match codec_id {
|
||||||
|
VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs),
|
||||||
|
VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs),
|
||||||
|
}
|
||||||
msg_out.set_video_frame(vf);
|
msg_out.set_video_frame(vf);
|
||||||
msg_out
|
msg_out
|
||||||
}
|
}
|
||||||
@@ -383,15 +387,10 @@ impl VpxDecoder {
|
|||||||
pub fn new(config: VpxDecoderConfig) -> Result<Self> {
|
pub fn new(config: VpxDecoderConfig) -> Result<Self> {
|
||||||
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
// This is sound because `vpx_codec_ctx` is a repr(C) struct without any field that can
|
||||||
// cause UB if uninitialized.
|
// cause UB if uninitialized.
|
||||||
let i;
|
let i = match config.codec {
|
||||||
if cfg!(feature = "VP8") {
|
|
||||||
i = match config.codec {
|
|
||||||
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
VpxVideoCodecId::VP8 => call_vpx_ptr!(vpx_codec_vp8_dx()),
|
||||||
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
VpxVideoCodecId::VP9 => call_vpx_ptr!(vpx_codec_vp9_dx()),
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
i = call_vpx_ptr!(vpx_codec_vp9_dx());
|
|
||||||
}
|
|
||||||
let mut ctx = Default::default();
|
let mut ctx = Default::default();
|
||||||
let cfg = vpx_codec_dec_cfg_t {
|
let cfg = vpx_codec_dec_cfg_t {
|
||||||
threads: if config.num_threads == 0 {
|
threads: if config.num_threads == 0 {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
pub mod capturable;
|
||||||
pub mod pipewire;
|
pub mod pipewire;
|
||||||
mod pipewire_dbus;
|
mod pipewire_dbus;
|
||||||
pub mod capturable;
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "simple_rc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
walkdir = "2"
|
|
||||||
confy = { git = "https://github.com/open-trade/confy" }
|
|
||||||
hbb_common = { path = "../hbb_common" }
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
extern crate simple_rc;
|
|
||||||
|
|
||||||
use simple_rc::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
{
|
|
||||||
const CONF_FILE: &str = "simple_rc.toml";
|
|
||||||
generate(CONF_FILE).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
generate_with_conf(&Config {
|
|
||||||
outfile: "src/rc.rs".to_owned(),
|
|
||||||
confs: vec![ConfigItem {
|
|
||||||
inc: "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx".to_owned(),
|
|
||||||
// exc: vec!["*.dll".to_owned(), "*.exe".to_owned()],
|
|
||||||
exc: vec![],
|
|
||||||
suppressed_front: "D:/projects/windows".to_owned(),
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# The output source file
|
|
||||||
outfile = "src/rc.rs"
|
|
||||||
|
|
||||||
# The resource config list.
|
|
||||||
[[confs]]
|
|
||||||
# The file or director to integrate.
|
|
||||||
inc = "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx"
|
|
||||||
# The exclusions.
|
|
||||||
exc = ["*.dll", "*.exe"]
|
|
||||||
# The front path that will ignore for extracting.
|
|
||||||
# The following config will make base output path to be "RustDeskTempTopMostWindow/x64/Release/xxx".
|
|
||||||
suppressed_front = "D:/projects/windows"
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
use hbb_common::{bail, ResultType};
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path};
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
//mod rc;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct ConfigItem {
|
|
||||||
// include directory or file
|
|
||||||
pub inc: String,
|
|
||||||
// exclude files
|
|
||||||
pub exc: Vec<String>,
|
|
||||||
// out_path = origin_path - suppressed_front
|
|
||||||
pub suppressed_front: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
// output source file
|
|
||||||
pub outfile: String,
|
|
||||||
// config items
|
|
||||||
pub confs: Vec<ConfigItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_outin_files<'a>(item: &'a ConfigItem) -> ResultType<HashMap<String, String>> {
|
|
||||||
let mut outin_filemap = HashMap::new();
|
|
||||||
|
|
||||||
for entry in WalkDir::new(&item.inc).follow_links(true) {
|
|
||||||
let path = entry?.into_path();
|
|
||||||
if path.is_file() {
|
|
||||||
let mut exclude = false;
|
|
||||||
for excfile in item.exc.iter() {
|
|
||||||
if excfile.starts_with("*.") {
|
|
||||||
if let Some(ext) = path.extension().and_then(|x| x.to_str()) {
|
|
||||||
if excfile.ends_with(&format!(".{}", ext)) {
|
|
||||||
exclude = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if path.ends_with(Path::new(excfile)) {
|
|
||||||
exclude = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if exclude {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut suppressed_front = item.suppressed_front.clone();
|
|
||||||
if !suppressed_front.is_empty() && suppressed_front.ends_with('/') {
|
|
||||||
suppressed_front.push('/');
|
|
||||||
}
|
|
||||||
let outpath = path.strip_prefix(Path::new(&suppressed_front))?;
|
|
||||||
let outfile = if outpath.is_absolute() {
|
|
||||||
match outpath
|
|
||||||
.file_name()
|
|
||||||
.and_then(|f| f.to_str())
|
|
||||||
.map(|f| f.to_string())
|
|
||||||
{
|
|
||||||
None => {
|
|
||||||
bail!("Failed to get filename of {}", outpath.display());
|
|
||||||
}
|
|
||||||
Some(s) => s,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match outpath.to_str() {
|
|
||||||
None => {
|
|
||||||
bail!("Failed to convert {} to string", outpath.display());
|
|
||||||
}
|
|
||||||
// Simple replace \ to / here.
|
|
||||||
// A better way is to use lib [path-slash](https://github.com/rhysd/path-slash)
|
|
||||||
Some(s) => s.to_string().replace("\\", "/"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let infile = match path.canonicalize()?.to_str() {
|
|
||||||
None => {
|
|
||||||
bail!("Failed to get file path of {}", path.display());
|
|
||||||
}
|
|
||||||
Some(s) => s.to_string(),
|
|
||||||
};
|
|
||||||
if let Some(_) = outin_filemap.insert(outfile.clone(), infile) {
|
|
||||||
bail!("outfile {} is set before", outfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(outin_filemap)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate(conf_file: &str) -> ResultType<()> {
|
|
||||||
let conf = confy::load_path(conf_file)?;
|
|
||||||
generate_with_conf(&conf)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_with_conf<'a>(conf: &'a Config) -> ResultType<()> {
|
|
||||||
let mut outfile = File::create(&conf.outfile)?;
|
|
||||||
|
|
||||||
outfile.write(
|
|
||||||
br##"use hbb_common::{bail, ResultType};
|
|
||||||
use std::{
|
|
||||||
fs::{self, File},
|
|
||||||
io::prelude::*,
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
"##,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
outfile.write(b"#[allow(dead_code)]\n")?;
|
|
||||||
outfile.write(b"pub fn extract_resources(root_path: &str) -> ResultType<()> {\n")?;
|
|
||||||
outfile.write(b" let mut resources: Vec<(&str, &[u8])> = Vec::new();\n")?;
|
|
||||||
|
|
||||||
let mut outin_files = HashMap::new();
|
|
||||||
for item in conf.confs.iter() {
|
|
||||||
for (o, i) in get_outin_files(item)?.into_iter() {
|
|
||||||
if let Some(_) = outin_files.insert(o.clone(), i) {
|
|
||||||
bail!("outfile {} is set before", o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut count = 1;
|
|
||||||
for (o, i) in outin_files.iter() {
|
|
||||||
let mut infile = File::open(&i)?;
|
|
||||||
let mut buffer = Vec::<u8>::new();
|
|
||||||
infile.read_to_end(&mut buffer)?;
|
|
||||||
|
|
||||||
let var_outfile = format!("outfile_{}", count);
|
|
||||||
let var_outdata = format!("outdata_{}", count);
|
|
||||||
|
|
||||||
write!(outfile, " let {} = \"{}\";\n", var_outfile, o)?;
|
|
||||||
write!(outfile, " let {}: &[u8] = &[\n ", var_outdata)?;
|
|
||||||
|
|
||||||
let mut line_num = 20;
|
|
||||||
for v in buffer {
|
|
||||||
if line_num == 0 {
|
|
||||||
write!(outfile, "\n ")?;
|
|
||||||
line_num = 20;
|
|
||||||
}
|
|
||||||
write!(outfile, "{:#04x}, ", v)?;
|
|
||||||
line_num -= 1;
|
|
||||||
}
|
|
||||||
write!(outfile, "\n ];\n")?;
|
|
||||||
|
|
||||||
write!(
|
|
||||||
outfile,
|
|
||||||
" resources.push(({}, &{}));\n",
|
|
||||||
var_outfile, var_outdata
|
|
||||||
)?;
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
outfile.write(b" do_extract(root_path, resources)?;\n")?;
|
|
||||||
outfile.write(b" Ok(())\n")?;
|
|
||||||
outfile.write(b"}\n")?;
|
|
||||||
|
|
||||||
outfile.write(
|
|
||||||
br##"
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> {
|
|
||||||
let mut root_path = root_path.replace("\\", "/");
|
|
||||||
if !root_path.ends_with('/') {
|
|
||||||
root_path.push('/');
|
|
||||||
}
|
|
||||||
let root_path = Path::new(&root_path);
|
|
||||||
for (outfile, data) in resources {
|
|
||||||
let outfile_path = root_path.join(outfile);
|
|
||||||
match outfile_path.parent().and_then(|p| p.to_str()) {
|
|
||||||
None => {
|
|
||||||
bail!("Failed to get parent of {}", outfile_path.display());
|
|
||||||
}
|
|
||||||
Some(p) => {
|
|
||||||
fs::create_dir_all(p)?;
|
|
||||||
let mut of = File::create(outfile_path)?;
|
|
||||||
of.write_all(data)?;
|
|
||||||
of.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
"##,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
outfile.flush()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = 2 + 2;
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_extract() {
|
|
||||||
// use super::*;
|
|
||||||
// rc::extract_resources("D:").unwrap();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -7,5 +7,4 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
libloading = "0.7"
|
|
||||||
hbb_common = { path = "../hbb_common" }
|
hbb_common = { path = "../hbb_common" }
|
||||||
@@ -2,18 +2,21 @@
|
|||||||
pub mod win10;
|
pub mod win10;
|
||||||
|
|
||||||
use hbb_common::{bail, lazy_static, ResultType};
|
use hbb_common::{bail, lazy_static, ResultType};
|
||||||
use std::{path::Path, sync::Mutex};
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
// If device is uninstalled though "Device Manager" Window.
|
// If device is uninstalled though "Device Manager" Window.
|
||||||
// Rustdesk is unable to handle device any more...
|
// Rustdesk is unable to handle device any more...
|
||||||
static ref H_SW_DEVICE: Mutex<u64> = Mutex::new(0);
|
static ref H_SW_DEVICE: Mutex<u64> = Mutex::new(0);
|
||||||
static ref MONITOR_PLUGIN: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn get_dirver_install_path() -> &'static str {
|
pub fn get_driver_install_path() -> &'static str {
|
||||||
win10::DRIVER_INSTALL_PATH
|
win10::DRIVER_INSTALL_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,68 +140,48 @@ pub fn close_device() {
|
|||||||
unsafe {
|
unsafe {
|
||||||
win10::idd::DeviceClose(*H_SW_DEVICE.lock().unwrap() as win10::idd::HSWDEVICE);
|
win10::idd::DeviceClose(*H_SW_DEVICE.lock().unwrap() as win10::idd::HSWDEVICE);
|
||||||
*H_SW_DEVICE.lock().unwrap() = 0;
|
*H_SW_DEVICE.lock().unwrap() = 0;
|
||||||
MONITOR_PLUGIN.lock().unwrap().clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn plug_in_monitor() -> ResultType<()> {
|
pub fn plug_in_monitor(_monitor_index: u32, _edid: u32, _retries: u32) -> ResultType<()> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
unsafe {
|
unsafe {
|
||||||
let monitor_index = 0 as u32;
|
if win10::idd::MonitorPlugIn(_monitor_index as _, _edid as _, _retries as _)
|
||||||
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
|
== win10::idd::FALSE
|
||||||
for i in 0..plug_in_monitors.len() {
|
{
|
||||||
if let Some(d) = plug_in_monitors.get(i) {
|
bail!("{}", win10::get_last_msg()?);
|
||||||
if *d == monitor_index {
|
}
|
||||||
return Ok(());
|
}
|
||||||
}
|
Ok(())
|
||||||
};
|
}
|
||||||
}
|
|
||||||
if win10::idd::MonitorPlugIn(monitor_index, 0, 30) == win10::idd::FALSE {
|
#[no_mangle]
|
||||||
bail!("{}", win10::get_last_msg()?);
|
pub fn plug_out_monitor(_monitor_index: u32) -> ResultType<()> {
|
||||||
}
|
#[cfg(windows)]
|
||||||
(*plug_in_monitors).push(monitor_index);
|
unsafe {
|
||||||
}
|
if win10::idd::MonitorPlugOut(_monitor_index) == win10::idd::FALSE {
|
||||||
Ok(())
|
bail!("{}", win10::get_last_msg()?);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[no_mangle]
|
Ok(())
|
||||||
pub fn plug_out_monitor() -> ResultType<()> {
|
}
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe {
|
#[cfg(windows)]
|
||||||
let monitor_index = 0 as u32;
|
type PMonitorMode = win10::idd::PMonitorMode;
|
||||||
if win10::idd::MonitorPlugOut(monitor_index) == win10::idd::FALSE {
|
#[cfg(not(windows))]
|
||||||
bail!("{}", win10::get_last_msg()?);
|
type PMonitorMode = *mut std::ffi::c_void;
|
||||||
}
|
|
||||||
let mut plug_in_monitors = MONITOR_PLUGIN.lock().unwrap();
|
#[no_mangle]
|
||||||
for i in 0..plug_in_monitors.len() {
|
pub fn update_monitor_modes(
|
||||||
if let Some(d) = plug_in_monitors.get(i) {
|
_monitor_index: u32,
|
||||||
if *d == monitor_index {
|
_mode_count: u32,
|
||||||
plug_in_monitors.remove(i);
|
_modes: PMonitorMode,
|
||||||
break;
|
) -> ResultType<()> {
|
||||||
}
|
#[cfg(windows)]
|
||||||
};
|
unsafe {
|
||||||
}
|
if win10::idd::FALSE
|
||||||
}
|
== win10::idd::MonitorModesUpdate(_monitor_index as _, _mode_count as _, _modes)
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub fn update_monitor_modes() -> ResultType<()> {
|
|
||||||
#[cfg(windows)]
|
|
||||||
unsafe {
|
|
||||||
let monitor_index = 0 as u32;
|
|
||||||
let mut modes = vec![win10::idd::MonitorMode {
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
sync: 60,
|
|
||||||
}];
|
|
||||||
if win10::idd::FALSE
|
|
||||||
== win10::idd::MonitorModesUpdate(
|
|
||||||
monitor_index as win10::idd::UINT,
|
|
||||||
modes.len() as win10::idd::UINT,
|
|
||||||
modes.as_mut_ptr(),
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
bail!("{}", win10::get_last_msg()?);
|
bail!("{}", win10::get_last_msg()?);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ BOOL DeviceCreate(PHSWDEVICE hSwDevice)
|
|||||||
}
|
}
|
||||||
if (created == TRUE)
|
if (created == TRUE)
|
||||||
{
|
{
|
||||||
SetLastMsg("Device is created before, please uninstall it first\n");
|
SetLastMsg("Device is already created, please destroy it first\n");
|
||||||
if (g_printMsg)
|
if (g_printMsg)
|
||||||
{
|
{
|
||||||
printf(g_lastMsg);
|
printf(g_lastMsg);
|
||||||
@@ -288,7 +288,7 @@ BOOL MonitorPlugIn(UINT index, UINT edid, INT retries)
|
|||||||
|
|
||||||
if (retries < 0)
|
if (retries < 0)
|
||||||
{
|
{
|
||||||
SetLastMsg("invalid tries %d\n", retries);
|
SetLastMsg("Invalid tries %d\n", retries);
|
||||||
if (g_printMsg)
|
if (g_printMsg)
|
||||||
{
|
{
|
||||||
printf(g_lastMsg);
|
printf(g_lastMsg);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use virtual_display;
|
|||||||
|
|
||||||
fn prompt_input() -> u8 {
|
fn prompt_input() -> u8 {
|
||||||
println!("Press key execute:");
|
println!("Press key execute:");
|
||||||
println!(" 1. 'x' 1. exit");
|
println!(" 1. 'q' 1. quit");
|
||||||
println!(" 2. 'i' 2. install or update driver");
|
println!(" 2. 'i' 2. install or update driver");
|
||||||
println!(" 3. 'u' 3. uninstall driver");
|
println!(" 3. 'u' 3. uninstall driver");
|
||||||
println!(" 4. 'c' 4. create device");
|
println!(" 4. 'c' 4. create device");
|
||||||
@@ -18,18 +18,18 @@ fn prompt_input() -> u8 {
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plug_in() {
|
fn plug_in(monitor_index: u32) {
|
||||||
println!("Plug in monitor begin");
|
println!("Plug in monitor begin");
|
||||||
if let Err(e) = virtual_display::plug_in_monitor() {
|
if let Err(e) = virtual_display::plug_in_monitor(monitor_index as _) {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
} else {
|
} else {
|
||||||
println!("Plug in monitor done");
|
println!("Plug in monitor done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plug_out() {
|
fn plug_out(monitor_index: u32) {
|
||||||
println!("Plug out monitor begin");
|
println!("Plug out monitor begin");
|
||||||
if let Err(e) = virtual_display::plug_out_monitor() {
|
if let Err(e) = virtual_display::plug_out_monitor(monitor_index as _) {
|
||||||
println!("{}", e);
|
println!("{}", e);
|
||||||
} else {
|
} else {
|
||||||
println!("Plug out monitor done");
|
println!("Plug out monitor done");
|
||||||
@@ -38,8 +38,9 @@ fn plug_out() {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
loop {
|
loop {
|
||||||
match prompt_input() as char {
|
let chr = prompt_input();
|
||||||
'x' => break,
|
match chr as char {
|
||||||
|
'q' => break,
|
||||||
'i' => {
|
'i' => {
|
||||||
println!("Install or update driver begin");
|
println!("Install or update driver begin");
|
||||||
let mut reboot_required = false;
|
let mut reboot_required = false;
|
||||||
@@ -81,8 +82,12 @@ fn main() {
|
|||||||
virtual_display::close_device();
|
virtual_display::close_device();
|
||||||
println!("Close device done");
|
println!("Close device done");
|
||||||
}
|
}
|
||||||
'1' => plug_in(),
|
'1' => plug_in(0),
|
||||||
'4' => plug_out(),
|
'2' => plug_in(1),
|
||||||
|
'3' => plug_in(2),
|
||||||
|
'4' => plug_out(0),
|
||||||
|
'5' => plug_out(1),
|
||||||
|
'6' => plug_out(2),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,96 @@
|
|||||||
use hbb_common::{bail, ResultType};
|
use hbb_common::{anyhow, dlopen::symbor::Library, log, ResultType};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
const LIB_NAME_VIRTUAL_DISPLAY: &str = "dylib_virtual_display";
|
const LIB_NAME_VIRTUAL_DISPLAY: &str = "dylib_virtual_display";
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
pub type DWORD = ::std::os::raw::c_ulong;
|
||||||
static ref LIB_VIRTUAL_DISPLAY: Arc<Mutex<Result<libloading::Library, libloading::Error>>> = {
|
#[repr(C)]
|
||||||
Arc::new(Mutex::new(unsafe { libloading::Library::new(get_lib_name()) }))
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct _MonitorMode {
|
||||||
|
pub width: DWORD,
|
||||||
|
pub height: DWORD,
|
||||||
|
pub sync: DWORD,
|
||||||
|
}
|
||||||
|
pub type MonitorMode = _MonitorMode;
|
||||||
|
pub type PMonitorMode = *mut MonitorMode;
|
||||||
|
|
||||||
|
pub type GetDriverInstallPath = fn() -> &'static str;
|
||||||
|
pub type IsDeviceCreated = fn() -> bool;
|
||||||
|
pub type CloseDevice = fn();
|
||||||
|
pub type DownLoadDriver = fn() -> ResultType<()>;
|
||||||
|
pub type CreateDevice = fn() -> ResultType<()>;
|
||||||
|
pub type InstallUpdateDriver = fn(&mut bool) -> ResultType<()>;
|
||||||
|
pub type UninstallDriver = fn(&mut bool) -> ResultType<()>;
|
||||||
|
pub type PlugInMonitor = fn(u32) -> ResultType<()>;
|
||||||
|
pub type PlugOutMonitor = fn(u32) -> ResultType<()>;
|
||||||
|
pub type UpdateMonitorModes = fn(u32, u32, PMonitorMode) -> ResultType<()>;
|
||||||
|
|
||||||
|
macro_rules! make_lib_wrapper {
|
||||||
|
($($field:ident : $tp:ty),+) => {
|
||||||
|
struct LibWrapper {
|
||||||
|
_lib: Option<Library>,
|
||||||
|
$($field: Option<$tp>),+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibWrapper {
|
||||||
|
fn new() -> Self {
|
||||||
|
let lib = match Library::open(get_lib_name()) {
|
||||||
|
Ok(lib) => Some(lib),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e);
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$(let $field = if let Some(lib) = &lib {
|
||||||
|
match unsafe { lib.symbol::<$tp>(stringify!($field)) } {
|
||||||
|
Ok(m) => {
|
||||||
|
log::info!("method found {}", stringify!($field));
|
||||||
|
Some(*m)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to load func {}, {}", stringify!($field), e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};)+
|
||||||
|
|
||||||
|
Self {
|
||||||
|
_lib: lib,
|
||||||
|
$( $field ),+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LibWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_lib_wrapper!(
|
||||||
|
get_driver_install_path: GetDriverInstallPath,
|
||||||
|
is_device_created: IsDeviceCreated,
|
||||||
|
close_device: CloseDevice,
|
||||||
|
download_driver: DownLoadDriver,
|
||||||
|
create_device: CreateDevice,
|
||||||
|
install_update_driver: InstallUpdateDriver,
|
||||||
|
uninstall_driver: UninstallDriver,
|
||||||
|
plug_in_monitor: PlugInMonitor,
|
||||||
|
plug_out_monitor: PlugOutMonitor,
|
||||||
|
update_monitor_modes: UpdateMonitorModes
|
||||||
|
);
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref LIB_WRAPPER: Arc<Mutex<LibWrapper>> = Default::default();
|
||||||
|
static ref MONITOR_INDICES: Mutex<HashSet<u32>> = Mutex::new(HashSet::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -24,102 +108,90 @@ fn get_lib_name() -> String {
|
|||||||
format!("lib{}.dylib", LIB_NAME_VIRTUAL_DISPLAY)
|
format!("lib{}.dylib", LIB_NAME_VIRTUAL_DISPLAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_reload_lib() {
|
|
||||||
let mut lock = LIB_VIRTUAL_DISPLAY.lock().unwrap();
|
|
||||||
if lock.is_err() {
|
|
||||||
*lock = unsafe { libloading::Library::new(get_lib_name()) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn get_dirver_install_path() -> ResultType<&'static str> {
|
pub fn get_driver_install_path() -> Option<&'static str> {
|
||||||
try_reload_lib();
|
Some(LIB_WRAPPER.lock().unwrap().get_driver_install_path?())
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
|
||||||
Ok(lib) => unsafe {
|
|
||||||
match lib.get::<libloading::Symbol<fn() -> &'static str>>(b"get_dirver_install_path") {
|
|
||||||
Ok(func) => Ok(func()),
|
|
||||||
Err(e) => bail!("Failed to load func get_dirver_install_path, {}", e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_device_created() -> bool {
|
pub fn is_device_created() -> bool {
|
||||||
try_reload_lib();
|
LIB_WRAPPER
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
.lock()
|
||||||
Ok(lib) => unsafe {
|
.unwrap()
|
||||||
match lib.get::<libloading::Symbol<fn() -> bool>>(b"is_device_created") {
|
.is_device_created
|
||||||
Ok(func) => func(),
|
.map(|f| f())
|
||||||
Err(..) => false,
|
.unwrap_or(false)
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(..) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_device() {
|
pub fn close_device() {
|
||||||
try_reload_lib();
|
let _r = LIB_WRAPPER.lock().unwrap().close_device.map(|f| f());
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
|
||||||
Ok(lib) => unsafe {
|
|
||||||
match lib.get::<libloading::Symbol<fn()>>(b"close_device") {
|
|
||||||
Ok(func) => func(),
|
|
||||||
Err(..) => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(..) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! def_func_result {
|
pub fn download_driver() -> ResultType<()> {
|
||||||
($func:ident, $name: tt) => {
|
LIB_WRAPPER
|
||||||
pub fn $func() -> ResultType<()> {
|
.lock()
|
||||||
try_reload_lib();
|
.unwrap()
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
.download_driver
|
||||||
Ok(lib) => unsafe {
|
.ok_or(anyhow::Error::msg("download_driver method not found"))?()
|
||||||
match lib.get::<libloading::Symbol<fn() -> ResultType<()>>>($name.as_bytes()) {
|
}
|
||||||
Ok(func) => func(),
|
|
||||||
Err(e) => bail!("Failed to load func {}, {}", $name, e),
|
pub fn create_device() -> ResultType<()> {
|
||||||
}
|
LIB_WRAPPER
|
||||||
},
|
.lock()
|
||||||
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
|
.unwrap()
|
||||||
}
|
.create_device
|
||||||
}
|
.ok_or(anyhow::Error::msg("create_device method not found"))?()
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_update_driver(reboot_required: &mut bool) -> ResultType<()> {
|
pub fn install_update_driver(reboot_required: &mut bool) -> ResultType<()> {
|
||||||
try_reload_lib();
|
LIB_WRAPPER
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
.lock()
|
||||||
Ok(lib) => unsafe {
|
.unwrap()
|
||||||
match lib.get::<libloading::Symbol<fn(&mut bool) -> ResultType<()>>>(
|
.install_update_driver
|
||||||
b"install_update_driver",
|
.ok_or(anyhow::Error::msg("install_update_driver method not found"))?(reboot_required)
|
||||||
) {
|
|
||||||
Ok(func) => func(reboot_required),
|
|
||||||
Err(e) => bail!("Failed to load func install_update_driver, {}", e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uninstall_driver(reboot_required: &mut bool) -> ResultType<()> {
|
pub fn uninstall_driver(reboot_required: &mut bool) -> ResultType<()> {
|
||||||
try_reload_lib();
|
LIB_WRAPPER
|
||||||
match &*LIB_VIRTUAL_DISPLAY.lock().unwrap() {
|
.lock()
|
||||||
Ok(lib) => unsafe {
|
.unwrap()
|
||||||
match lib
|
.uninstall_driver
|
||||||
.get::<libloading::Symbol<fn(&mut bool) -> ResultType<()>>>(b"uninstall_driver")
|
.ok_or(anyhow::Error::msg("uninstall_driver method not found"))?(reboot_required)
|
||||||
{
|
|
||||||
Ok(func) => func(reboot_required),
|
|
||||||
Err(e) => bail!("Failed to load func uninstall_driver, {}", e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => bail!("Failed to load library {}, {}", LIB_NAME_VIRTUAL_DISPLAY, e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def_func_result!(download_driver, "download_driver");
|
#[cfg(windows)]
|
||||||
def_func_result!(create_device, "create_device");
|
pub fn plug_in_monitor(monitor_index: u32) -> ResultType<()> {
|
||||||
def_func_result!(plug_in_monitor, "plug_in_monitor");
|
let mut lock = MONITOR_INDICES.lock().unwrap();
|
||||||
def_func_result!(plug_out_monitor, "plug_out_monitor");
|
if lock.contains(&monitor_index) {
|
||||||
def_func_result!(update_monitor_modes, "update_monitor_modes");
|
return Ok(());
|
||||||
|
}
|
||||||
|
let f = LIB_WRAPPER
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.plug_in_monitor
|
||||||
|
.ok_or(anyhow::Error::msg("plug_in_monitor method not found"))?;
|
||||||
|
f(monitor_index)?;
|
||||||
|
lock.insert(monitor_index);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn plug_out_monitor(monitor_index: u32) -> ResultType<()> {
|
||||||
|
let f = LIB_WRAPPER
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.plug_out_monitor
|
||||||
|
.ok_or(anyhow::Error::msg("plug_out_monitor method not found"))?;
|
||||||
|
f(monitor_index)?;
|
||||||
|
MONITOR_INDICES.lock().unwrap().remove(&monitor_index);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn update_monitor_modes(monitor_index: u32, modes: &[MonitorMode]) -> ResultType<()> {
|
||||||
|
let f = LIB_WRAPPER
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.update_monitor_modes
|
||||||
|
.ok_or(anyhow::Error::msg("update_monitor_modes method not found"))?;
|
||||||
|
f(monitor_index, modes.len() as _, modes.as_ptr() as _)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ arch=('x86_64')
|
|||||||
url=""
|
url=""
|
||||||
license=('AGPL-3.0')
|
license=('AGPL-3.0')
|
||||||
groups=()
|
groups=()
|
||||||
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'curl' 'libva' 'libvdpau' 'libappindicator-gtk3')
|
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'curl' 'libva' 'libvdpau' 'libappindicator-gtk3' 'pam' 'gst-plugins-base' 'gst-plugin-pipewire')
|
||||||
makedepends=()
|
makedepends=()
|
||||||
checkdepends=()
|
checkdepends=()
|
||||||
optdepends=()
|
optdepends=()
|
||||||
|
|||||||
5
res/pam.d/rustdesk.debian
Normal file
5
res/pam.d/rustdesk.debian
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
@include common-auth
|
||||||
|
@include common-account
|
||||||
|
@include common-session
|
||||||
|
@include common-password
|
||||||
5
res/pam.d/rustdesk.suse
Normal file
5
res/pam.d/rustdesk.suse
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
auth include common-auth
|
||||||
|
account include common-account
|
||||||
|
session include common-session
|
||||||
|
password include common-password
|
||||||
@@ -3,7 +3,7 @@ Version: 1.2.0
|
|||||||
Release: 0
|
Release: 0
|
||||||
Summary: RPM package
|
Summary: RPM package
|
||||||
License: GPL-3.0
|
License: GPL-3.0
|
||||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libappindicator-gtk3 libvdpau1 libva2
|
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libappindicator-gtk3 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Version: 1.2.0
|
|||||||
Release: 0
|
Release: 0
|
||||||
Summary: RPM package
|
Summary: RPM package
|
||||||
License: GPL-3.0
|
License: GPL-3.0
|
||||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator-gtk3 libvdpau libva
|
Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator-gtk3 libvdpau libva pam gstreamer1-plugins-base
|
||||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit)
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Version: 1.1.9
|
|||||||
Release: 0
|
Release: 0
|
||||||
Summary: RPM package
|
Summary: RPM package
|
||||||
License: GPL-3.0
|
License: GPL-3.0
|
||||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libayatana-appindicator3-1 libvdpau1 libva2
|
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libayatana-appindicator3-1 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||||
|
|
||||||
%description
|
%description
|
||||||
The best open-source remote desktop client software, written in Rust.
|
The best open-source remote desktop client software, written in Rust.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Version: 1.2.0
|
|||||||
Release: 0
|
Release: 0
|
||||||
Summary: RPM package
|
Summary: RPM package
|
||||||
License: GPL-3.0
|
License: GPL-3.0
|
||||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator libvdpau1 libva2
|
Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator libvdpau1 libva2 pam gstreamer1-plugins-base
|
||||||
|
|
||||||
%description
|
%description
|
||||||
The best open-source remote desktop client software, written in Rust.
|
The best open-source remote desktop client software, written in Rust.
|
||||||
|
|||||||
130
res/startwm.sh
Executable file
130
res/startwm.sh
Executable file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script is derived from https://github.com/neutrinolabs/xrdp/sesman/startwm.sh.
|
||||||
|
|
||||||
|
#
|
||||||
|
# This script is an example. You might need to edit this script
|
||||||
|
# depending on your distro if it doesn't work for you.
|
||||||
|
#
|
||||||
|
# Uncomment the following line for debug:
|
||||||
|
# exec xterm
|
||||||
|
|
||||||
|
|
||||||
|
# Execution sequence for interactive login shell - pseudocode
|
||||||
|
#
|
||||||
|
# IF /etc/profile is readable THEN
|
||||||
|
# execute ~/.bash_profile
|
||||||
|
# END IF
|
||||||
|
# IF ~/.bash_profile is readable THEN
|
||||||
|
# execute ~/.bash_profile
|
||||||
|
# ELSE
|
||||||
|
# IF ~/.bash_login is readable THEN
|
||||||
|
# execute ~/.bash_login
|
||||||
|
# ELSE
|
||||||
|
# IF ~/.profile is readable THEN
|
||||||
|
# execute ~/.profile
|
||||||
|
# END IF
|
||||||
|
# END IF
|
||||||
|
# END IF
|
||||||
|
pre_start()
|
||||||
|
{
|
||||||
|
if [ -r /etc/profile ]; then
|
||||||
|
. /etc/profile
|
||||||
|
fi
|
||||||
|
if [ -r ~/.bash_profile ]; then
|
||||||
|
. ~/.bash_profile
|
||||||
|
else
|
||||||
|
if [ -r ~/.bash_login ]; then
|
||||||
|
. ~/.bash_login
|
||||||
|
else
|
||||||
|
if [ -r ~/.profile ]; then
|
||||||
|
. ~/.profile
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# When loging out from the interactive shell, the execution sequence is:
|
||||||
|
#
|
||||||
|
# IF ~/.bash_logout exists THEN
|
||||||
|
# execute ~/.bash_logout
|
||||||
|
# END IF
|
||||||
|
post_start()
|
||||||
|
{
|
||||||
|
if [ -r ~/.bash_logout ]; then
|
||||||
|
. ~/.bash_logout
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#start the window manager
|
||||||
|
wm_start()
|
||||||
|
{
|
||||||
|
if [ -r /etc/default/locale ]; then
|
||||||
|
. /etc/default/locale
|
||||||
|
export LANG LANGUAGE
|
||||||
|
fi
|
||||||
|
|
||||||
|
# debian
|
||||||
|
if [ -r /etc/X11/Xsession ]; then
|
||||||
|
pre_start
|
||||||
|
. /etc/X11/Xsession
|
||||||
|
post_start
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# alpine
|
||||||
|
# Don't use /etc/X11/xinit/Xsession - it doesn't work
|
||||||
|
if [ -f /etc/alpine-release ]; then
|
||||||
|
if [ -f /etc/X11/xinit/xinitrc ]; then
|
||||||
|
pre_start
|
||||||
|
/etc/X11/xinit/xinitrc
|
||||||
|
post_start
|
||||||
|
else
|
||||||
|
echo "** xinit package isn't installed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# el
|
||||||
|
if [ -r /etc/X11/xinit/Xsession ]; then
|
||||||
|
pre_start
|
||||||
|
. /etc/X11/xinit/Xsession
|
||||||
|
post_start
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# suse
|
||||||
|
if [ -r /etc/X11/xdm/Xsession ]; then
|
||||||
|
# since the following script run a user login shell,
|
||||||
|
# do not execute the pseudo login shell scripts
|
||||||
|
. /etc/X11/xdm/Xsession
|
||||||
|
exit 0
|
||||||
|
elif [ -r /usr/etc/X11/xdm/Xsession ]; then
|
||||||
|
. /usr/etc/X11/xdm/Xsession
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
pre_start
|
||||||
|
xterm
|
||||||
|
post_start
|
||||||
|
}
|
||||||
|
|
||||||
|
#. /etc/environment
|
||||||
|
#export PATH=$PATH
|
||||||
|
#export LANG=$LANG
|
||||||
|
|
||||||
|
# change PATH to be what your environment needs usually what is in
|
||||||
|
# /etc/environment
|
||||||
|
#PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
|
||||||
|
#export PATH=$PATH
|
||||||
|
|
||||||
|
# for PATH and LANG from /etc/environment
|
||||||
|
# pam will auto process the environment file if /etc/pam.d/xrdp-sesman
|
||||||
|
# includes
|
||||||
|
# auth required pam_env.so readenv=1
|
||||||
|
|
||||||
|
wm_start
|
||||||
|
|
||||||
|
exit 1
|
||||||
30
res/xorg.conf
Normal file
30
res/xorg.conf
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Section "Monitor"
|
||||||
|
Identifier "Dummy Monitor"
|
||||||
|
|
||||||
|
# Default HorizSync 31.50 - 48.00 kHz
|
||||||
|
HorizSync 5.0 - 150.0
|
||||||
|
# Default VertRefresh 50.00 - 70.00 Hz
|
||||||
|
VertRefresh 5.0 - 100.0
|
||||||
|
|
||||||
|
# Taken from https://www.xpra.org/xorg.conf
|
||||||
|
Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135
|
||||||
|
Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757
|
||||||
|
EndSection
|
||||||
|
|
||||||
|
Section "Device"
|
||||||
|
Identifier "Dummy VideoCard"
|
||||||
|
Driver "dummy"
|
||||||
|
# Default VideoRam 4096
|
||||||
|
# (1920 * 1080 * 4) / 1024 = 8100
|
||||||
|
VideoRam 8100
|
||||||
|
EndSection
|
||||||
|
|
||||||
|
Section "Screen"
|
||||||
|
Identifier "Dummy Screen"
|
||||||
|
Device "Dummy VideoCard"
|
||||||
|
Monitor "Dummy Monitor"
|
||||||
|
SubSectionSub "Display"
|
||||||
|
Depth 24
|
||||||
|
Modes "1920x1080" "1280x720"
|
||||||
|
EndSubSection
|
||||||
|
EndSection
|
||||||
87
src/api.rs
Normal file
87
src/api.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::ffi::{c_char};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
flutter::{FlutterHandler, SESSIONS},
|
||||||
|
plugins::PLUGIN_REGISTRAR,
|
||||||
|
ui_session_interface::Session,
|
||||||
|
};
|
||||||
|
|
||||||
|
// API provided by RustDesk.
|
||||||
|
pub type LoadPluginFunc = fn(*const c_char) -> i32;
|
||||||
|
pub type UnloadPluginFunc = fn(*const c_char) -> i32;
|
||||||
|
pub type AddSessionFunc = fn(session_id: String) -> bool;
|
||||||
|
pub type RemoveSessionFunc = fn(session_id: &String) -> bool;
|
||||||
|
pub type AddSessionHookFunc = fn(session_id: String, key: String, hook: SessionHook) -> bool;
|
||||||
|
pub type RemoveSessionHookFunc = fn(session_id: String, key: &String) -> bool;
|
||||||
|
|
||||||
|
/// Hooks for session.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SessionHook {
|
||||||
|
OnSessionRgba(fn(String, Vec<i8>) -> Vec<i8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[repr(C)]
|
||||||
|
pub struct RustDeskApiTable {
|
||||||
|
pub(crate) load_plugin: LoadPluginFunc,
|
||||||
|
pub(crate) unload_plugin: UnloadPluginFunc,
|
||||||
|
pub add_session: AddSessionFunc,
|
||||||
|
pub remove_session: RemoveSessionFunc,
|
||||||
|
pub add_session_hook: AddSessionHookFunc,
|
||||||
|
pub remove_session_hook: RemoveSessionHookFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_plugin(path: *const c_char) -> i32 {
|
||||||
|
PLUGIN_REGISTRAR.load_plugin(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unload_plugin(path: *const c_char) -> i32 {
|
||||||
|
PLUGIN_REGISTRAR.unload_plugin(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_session(session_id: String) -> bool {
|
||||||
|
// let mut sessions = SESSIONS.write().unwrap();
|
||||||
|
// if sessions.contains_key(&session.id) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// let _ = sessions.insert(session.id.to_owned(), session);
|
||||||
|
// true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_session(session_id: &String) -> bool {
|
||||||
|
let mut sessions = SESSIONS.write().unwrap();
|
||||||
|
if !sessions.contains_key(session_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let _ = sessions.remove(session_id);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_session_hook(session_id: String, key: String, hook: SessionHook) -> bool {
|
||||||
|
let sessions = SESSIONS.read().unwrap();
|
||||||
|
if let Some(session) = sessions.get(&session_id) {
|
||||||
|
return session.add_session_hook(key, hook);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_session_hook(session_id: String, key: &String) -> bool {
|
||||||
|
let sessions = SESSIONS.read().unwrap();
|
||||||
|
if let Some(session) = sessions.get(&session_id) {
|
||||||
|
return session.remove_session_hook(key);
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RustDeskApiTable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
load_plugin,
|
||||||
|
unload_plugin,
|
||||||
|
add_session,
|
||||||
|
remove_session,
|
||||||
|
add_session_hook,
|
||||||
|
remove_session_hook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user