mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-18 06:21:30 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a23822074e | ||
|
|
3d17bf4990 | ||
|
|
fd67be4a16 | ||
|
|
e6edf39305 | ||
|
|
34d2c62781 | ||
|
|
bd0a33e467 | ||
|
|
b8d36b6558 | ||
|
|
f38d89aaee | ||
|
|
773b9d6645 | ||
|
|
dea99ffb3a | ||
|
|
3251045e22 | ||
|
|
dc58c85e30 | ||
|
|
5a2a94d2cc | ||
|
|
f330953f4f | ||
|
|
8d4c86fe7f | ||
|
|
f8c2713c5b | ||
|
|
082a66b282 | ||
|
|
743b0ce8ce | ||
|
|
9d9b67aca5 | ||
|
|
d60b5a6ca0 | ||
|
|
e0ed6ee986 | ||
|
|
d3f0c80e94 | ||
|
|
b32ff87c6e | ||
|
|
b91b49229a | ||
|
|
afc8bb71dc | ||
|
|
4f86169f7f | ||
|
|
2cf43042e6 | ||
|
|
734fb8d6f7 | ||
|
|
3c7f6d3127 |
2
.github/workflows/bridge.yml
vendored
2
.github/workflows/bridge.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
- name: Install flutter rust bridge deps
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
||||
|
||||
- name: Run flutter rust bridge
|
||||
|
||||
23
.github/workflows/flutter-build.yml
vendored
23
.github/workflows/flutter-build.yml
vendored
@@ -33,8 +33,8 @@ env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2024.07.12
|
||||
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
|
||||
VERSION: "1.3.3"
|
||||
NDK_VERSION: "r27b"
|
||||
VERSION: "1.3.4"
|
||||
NDK_VERSION: "r27c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
|
||||
@@ -107,7 +107,6 @@ jobs:
|
||||
|
||||
# https://github.com/flutter/flutter/issues/155685
|
||||
- name: Replace engine with rustdesk custom flutter engine
|
||||
if: false
|
||||
run: |
|
||||
flutter doctor -v
|
||||
flutter precache --windows
|
||||
@@ -157,6 +156,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
- name: Build rustdesk
|
||||
@@ -317,6 +317,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
- name: Build rustdesk
|
||||
@@ -520,6 +521,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -638,6 +640,7 @@ jobs:
|
||||
os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel
|
||||
extra-build-args: "",
|
||||
arch: x86_64,
|
||||
vcpkg-triplet: x64-osx,
|
||||
}
|
||||
- {
|
||||
target: aarch64-apple-darwin,
|
||||
@@ -645,6 +648,7 @@ jobs:
|
||||
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
|
||||
extra-build-args: "--screencapturekit",
|
||||
arch: aarch64,
|
||||
vcpkg-triplet: arm64-osx,
|
||||
}
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
@@ -715,7 +719,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd "$(dirname "$(which flutter)")"
|
||||
# https://github.com/flutter/flutter/issues/133533
|
||||
# https://github.com/flutter/flutter/issues/1.3.43
|
||||
sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart
|
||||
grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart
|
||||
|
||||
@@ -756,6 +760,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
|
||||
- name: Show version information (Rust, cargo, Clang)
|
||||
shell: bash
|
||||
@@ -932,7 +937,6 @@ jobs:
|
||||
libpam0g-dev \
|
||||
libpulse-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
@@ -1033,7 +1037,7 @@ jobs:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
rustup target add ${{ matrix.job.target }}
|
||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }}
|
||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked
|
||||
case ${{ matrix.job.target }} in
|
||||
aarch64-linux-android)
|
||||
./flutter/ndk_arm64.sh
|
||||
@@ -1209,7 +1213,6 @@ jobs:
|
||||
libpam0g-dev \
|
||||
libpulse-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
@@ -1441,7 +1444,7 @@ jobs:
|
||||
- name: Install vcpkg dependencies
|
||||
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
sudo apt install -y libva-dev libvdpau-dev
|
||||
sudo apt install -y libva-dev && apt show libva-dev
|
||||
if ! $VCPKG_ROOT/vcpkg \
|
||||
install \
|
||||
--triplet ${{ matrix.job.vcpkg-triplet }} \
|
||||
@@ -1455,6 +1458,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
- name: Restore bridge files
|
||||
@@ -1499,7 +1503,6 @@ jobs:
|
||||
libpam0g-dev \
|
||||
libpulse-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
@@ -1772,7 +1775,6 @@ jobs:
|
||||
libpam0g-dev \
|
||||
libpulse-dev \
|
||||
libva-dev \
|
||||
libvdpau-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
@@ -1860,6 +1862,7 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
# build rustdesk
|
||||
python3 ./res/inline-sciter.py
|
||||
export CARGO_INCREMENTAL=0
|
||||
|
||||
8
.github/workflows/playground.yml
vendored
8
.github/workflows/playground.yml
vendored
@@ -18,7 +18,7 @@ env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2024.06.15
|
||||
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
|
||||
VERSION: "1.3.3"
|
||||
VERSION: "1.3.4"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i '' 's/3.1.0/2.17.0/g' flutter/pubspec.yaml;
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ matrix.job.bridge }} --features "uuid"
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ matrix.job.bridge }} --features "uuid" --locked
|
||||
# below works for mac to make buildable on 3.13.9
|
||||
# pushd flutter/lib; find . -name "*.dart" | xargs -I{} sed -i '' 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' {}; popd;
|
||||
pushd flutter && flutter pub get && popd
|
||||
@@ -302,7 +302,7 @@ jobs:
|
||||
- name: Install flutter rust bridge deps
|
||||
run: |
|
||||
git config --global core.longpaths true
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml
|
||||
pushd flutter/lib; find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'; popd;
|
||||
pushd flutter ; flutter pub get ; popd
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
run: |
|
||||
rustup target add ${{ matrix.job.target }}
|
||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }}
|
||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked
|
||||
case ${{ matrix.job.target }} in
|
||||
aarch64-linux-android)
|
||||
./flutter/ndk_arm64.sh
|
||||
|
||||
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -3064,8 +3064,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hwcodec"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/rustdesk-org/hwcodec#da7dab48df19edb5a7138ff9e01bf9f148b523da"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/rustdesk-org/hwcodec#835e599ed229e4e01b6fa3566e02ea45c73e2e9c"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
@@ -3519,7 +3519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5219,7 +5219,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rdev"
|
||||
version = "0.5.0-2"
|
||||
source = "git+https://github.com/rustdesk-org/rdev#961d25cc00c6b3ef80f444e6a7bed9872e2c35ea"
|
||||
source = "git+https://github.com/rustdesk-org/rdev#01ac3ec8009f04f7615842b9152338844b806184"
|
||||
dependencies = [
|
||||
"cocoa 0.24.1",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -5494,7 +5494,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
dependencies = [
|
||||
"android-wakelock",
|
||||
"android_logger",
|
||||
@@ -5594,7 +5594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs 5.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.3.3
|
||||
version: 1.3.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
@@ -47,9 +47,9 @@ AppDir:
|
||||
- libasound2
|
||||
- libsystemd0
|
||||
- curl
|
||||
- libva2
|
||||
- libva-drm2
|
||||
- libva-x11-2
|
||||
- libvdpau1
|
||||
- libgstreamer-plugins-base1.0-0
|
||||
- gstreamer1.0-pipewire
|
||||
- libwayland-client0
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.3.3
|
||||
version: 1.3.4
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
@@ -50,9 +50,9 @@ AppDir:
|
||||
- libasound2
|
||||
- libsystemd0
|
||||
- curl
|
||||
- libva2
|
||||
- libva-drm2
|
||||
- libva-x11-2
|
||||
- libvdpau1
|
||||
- libgstreamer-plugins-base1.0-0
|
||||
- gstreamer1.0-pipewire
|
||||
- libwayland-client0
|
||||
|
||||
4
build.py
4
build.py
@@ -111,7 +111,7 @@ def make_parser():
|
||||
'--hwcodec',
|
||||
action='store_true',
|
||||
help='Enable feature hwcodec' + (
|
||||
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
||||
'' if windows or osx else ', need libva-dev.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--vram',
|
||||
@@ -298,7 +298,7 @@ Version: %s
|
||||
Architecture: %s
|
||||
Maintainer: rustdesk <info@rustdesk.com>
|
||||
Homepage: https://rustdesk.com
|
||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||
Recommends: libayatana-appindicator3-1
|
||||
Description: A remote control software.
|
||||
|
||||
|
||||
@@ -135,8 +135,8 @@ docker build -t "rustdesk-builder" . # 构建容器
|
||||
```
|
||||
在Dockerfile的RUN apt update之前插入两行:
|
||||
|
||||
RUN sed -i "s/deb.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
||||
RUN sed -i "s/security.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
||||
RUN sed -i "s|deb.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list && \
|
||||
sed -i "s|security.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list
|
||||
```
|
||||
|
||||
2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码:
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<!-- https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs -->
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="RustDesk"
|
||||
|
||||
@@ -68,6 +68,7 @@ function build {
|
||||
pushd "$SCRIPTDIR/.."
|
||||
$VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed"
|
||||
popd
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-$VCPKG_TARGET-rel-out.log" || true
|
||||
echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies"
|
||||
|
||||
if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
|
||||
#
|
||||
# Script to build F-Droid release of RustDesk
|
||||
#
|
||||
@@ -23,6 +21,43 @@ set -x
|
||||
# + build: perform actual build of APK file
|
||||
#
|
||||
|
||||
# Start of functions
|
||||
|
||||
# Install Flutter of version `VERSION` from Github repository
|
||||
# into directory `FLUTTER_DIR` and apply patches if needed
|
||||
|
||||
prepare_flutter() {
|
||||
VERSION="${1}"
|
||||
FLUTTER_DIR="${2}"
|
||||
|
||||
if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then
|
||||
git clone https://github.com/flutter/flutter "${FLUTTER_DIR}"
|
||||
fi
|
||||
|
||||
pushd "${FLUTTER_DIR}"
|
||||
|
||||
git restore .
|
||||
git checkout "${VERSION}"
|
||||
|
||||
# Patch flutter
|
||||
|
||||
if dpkg --compare-versions "${VERSION}" ge "3.24.4"; then
|
||||
git apply "${ROOTDIR}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff"
|
||||
fi
|
||||
|
||||
flutter config --no-analytics
|
||||
|
||||
popd # ${FLUTTER_DIR}
|
||||
}
|
||||
|
||||
# Start of script
|
||||
|
||||
set -x
|
||||
|
||||
# Note current working directory as root dir for patches
|
||||
|
||||
ROOTDIR="${PWD}"
|
||||
|
||||
# Parse command-line arguments
|
||||
|
||||
VERNAME="${1}"
|
||||
@@ -82,21 +117,6 @@ export PATH="${PATH}:${HOME}/flutter/bin:${HOME}/depot_tools"
|
||||
|
||||
export VCPKG_ROOT="${HOME}/vcpkg"
|
||||
|
||||
prepare_Flutter() {
|
||||
version="${1}"
|
||||
|
||||
pushd "${HOME}"
|
||||
if [ ! -f "${HOME}/flutter/bin/flutter" ]; then
|
||||
git clone https://github.com/flutter/flutter
|
||||
fi
|
||||
pushd flutter
|
||||
git restore .
|
||||
git checkout "${version}"
|
||||
flutter config --no-analytics
|
||||
popd # flutter
|
||||
popd # ${HOME}
|
||||
}
|
||||
|
||||
# Now act depending on build step
|
||||
|
||||
# NOTE: F-Droid maintainers require explicit declaration of dependencies
|
||||
@@ -116,15 +136,20 @@ prebuild)
|
||||
.env.CARGO_NDK_VERSION \
|
||||
.github/workflows/flutter-build.yml)"
|
||||
|
||||
# Flutter used to compile main Rustdesk library
|
||||
|
||||
FLUTTER_VERSION="$(yq -r \
|
||||
.env.ANDROID_FLUTTER_VERSION \
|
||||
.github/workflows/flutter-build.yml)"
|
||||
|
||||
if [ -z "${FLUTTER_VERSION}" ]; then
|
||||
FLUTTER_VERSION="$(yq -r \
|
||||
.env.FLUTTER_VERSION \
|
||||
.github/workflows/flutter-build.yml)"
|
||||
fi
|
||||
|
||||
# Flutter used to compile Flutter<->Rust bridge files
|
||||
|
||||
FLUTTER_BRIDGE_VERSION="$(yq -r \
|
||||
.env.FLUTTER_VERSION \
|
||||
.github/workflows/bridge.yml)"
|
||||
@@ -207,14 +232,16 @@ prebuild)
|
||||
|
||||
cargo install \
|
||||
cargo-ndk \
|
||||
--version "${CARGO_NDK_VERSION}"
|
||||
--version "${CARGO_NDK_VERSION}" \
|
||||
--locked
|
||||
|
||||
# Install rust bridge generator
|
||||
|
||||
cargo install cargo-expand
|
||||
cargo install flutter_rust_bridge_codegen \
|
||||
--version "${FLUTTER_RUST_BRIDGE_VERSION}" \
|
||||
--features "uuid"
|
||||
--features "uuid" \
|
||||
--locked
|
||||
|
||||
# Populate native vcpkg dependencies
|
||||
|
||||
@@ -277,46 +304,66 @@ prebuild)
|
||||
|
||||
git apply res/fdroid/patches/*.patch
|
||||
|
||||
# Backup .gclient file, for later restore
|
||||
# If Flutter version used to generate bridge files differs from Flutter
|
||||
# version used to compile Rustdesk library, generate bridge using the
|
||||
# `FLUTTER_BRIDGE_VERSION` an restore the pubspec later
|
||||
|
||||
cp flutter-sdk/.gclient flutter-sdk/.gclient.bak
|
||||
if [ "${FLUTTER_VERSION}" != "${FLUTTER_BRIDGE_VERSION}" ]; then
|
||||
# Install Flutter bridge version
|
||||
|
||||
# For FLUTTER_BRIDGE_VERSION
|
||||
sed \
|
||||
-i \
|
||||
-e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' \
|
||||
flutter/pubspec.yaml
|
||||
prepare_flutter "${FLUTTER_BRIDGE_VERSION}" "${HOME}/flutter"
|
||||
|
||||
# Install Flutter bridge version
|
||||
prepare_Flutter "${FLUTTER_BRIDGE_VERSION}"
|
||||
cp flutter-sdk/.gclient.bak flutter-sdk/.gclient
|
||||
sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_BRIDGE_VERSION}/" flutter-sdk/.gclient
|
||||
# Save changes
|
||||
|
||||
# Download Flutter dependencies
|
||||
pushd flutter
|
||||
flutter clean && flutter packages pub get
|
||||
popd # flutter
|
||||
git add .
|
||||
|
||||
# Generate FFI bindings
|
||||
flutter_rust_bridge_codegen \
|
||||
--rust-input ./src/flutter_ffi.rs \
|
||||
--dart-output ./flutter/lib/generated_bridge.dart
|
||||
# Edit pubspec to make flutter bridge version work
|
||||
|
||||
git restore flutter/pubspec.*
|
||||
sed \
|
||||
-i \
|
||||
-e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' \
|
||||
flutter/pubspec.yaml
|
||||
|
||||
# Install Flutter
|
||||
prepare_Flutter "${FLUTTER_VERSION}"
|
||||
cp flutter-sdk/.gclient.bak flutter-sdk/.gclient
|
||||
sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_VERSION}/" flutter-sdk/.gclient
|
||||
# Download Flutter dependencies
|
||||
|
||||
pushd flutter
|
||||
|
||||
flutter clean
|
||||
flutter packages pub get
|
||||
|
||||
popd # flutter
|
||||
|
||||
# Generate FFI bindings
|
||||
|
||||
flutter_rust_bridge_codegen \
|
||||
--rust-input ./src/flutter_ffi.rs \
|
||||
--dart-output ./flutter/lib/generated_bridge.dart
|
||||
|
||||
# Add bridge files to save-list
|
||||
|
||||
git add -f ./flutter/lib/generated_bridge.* ./src/bridge_generated.*
|
||||
|
||||
# Restore everything
|
||||
|
||||
git checkout '*'
|
||||
git clean -dffx
|
||||
git reset
|
||||
fi
|
||||
|
||||
# Install Flutter version for RustDesk library build
|
||||
|
||||
prepare_flutter "${FLUTTER_VERSION}" "${HOME}/flutter"
|
||||
|
||||
# gms is not in thoes files now, but we still keep the following line for future reference(maybe).
|
||||
|
||||
sed \
|
||||
-i \
|
||||
-e '/gms/d' \
|
||||
flutter/android/build.gradle \
|
||||
flutter/android/app/build.gradle
|
||||
|
||||
# `firebase_analytics`` is not in thoes files now, but we still keep the following lines.
|
||||
# `firebase_analytics` is not in these files now, but we still keep the following lines.
|
||||
|
||||
sed \
|
||||
-i \
|
||||
-e '/firebase_analytics/d' \
|
||||
@@ -343,9 +390,12 @@ build)
|
||||
# '.github/workflows/flutter-build.yml'
|
||||
#
|
||||
|
||||
# Flutter used to compile main Rustdesk library
|
||||
|
||||
FLUTTER_VERSION="$(yq -r \
|
||||
.env.ANDROID_FLUTTER_VERSION \
|
||||
.github/workflows/flutter-build.yml)"
|
||||
|
||||
if [ -z "${FLUTTER_VERSION}" ]; then
|
||||
FLUTTER_VERSION="$(yq -r \
|
||||
.env.FLUTTER_VERSION \
|
||||
@@ -381,7 +431,8 @@ build)
|
||||
|
||||
pushd flutter
|
||||
|
||||
flutter clean && flutter packages pub get
|
||||
flutter clean
|
||||
flutter packages pub get
|
||||
|
||||
popd # flutter
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:back_button_interceptor/back_button_interceptor.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
@@ -2730,30 +2731,6 @@ Future<bool> osxRequestAudio() async {
|
||||
return await kMacOSPermChannel.invokeMethod("requestRecordAudio");
|
||||
}
|
||||
|
||||
class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
|
||||
/// Creates scroll physics that does not let the user scroll.
|
||||
const DraggableNeverScrollableScrollPhysics({super.parent});
|
||||
|
||||
@override
|
||||
DraggableNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||
return DraggableNeverScrollableScrollPhysics(parent: buildParent(ancestor));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldAcceptUserOffset(ScrollMetrics position) {
|
||||
// TODO: find a better solution to check if the offset change is caused by the scrollbar.
|
||||
// Workaround: when dragging with the scrollbar, it always triggers an [IdleScrollActivity].
|
||||
if (position is ScrollPositionWithSingleContext) {
|
||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
return position.activity is IdleScrollActivity;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get allowImplicitScrolling => false;
|
||||
}
|
||||
|
||||
Widget futureBuilder(
|
||||
{required Future? future, required Widget Function(dynamic data) hasData}) {
|
||||
return FutureBuilder(
|
||||
@@ -3483,6 +3460,35 @@ Widget buildPresetPasswordWarning() {
|
||||
);
|
||||
}
|
||||
|
||||
bool get isLinuxMateDesktop =>
|
||||
isLinux &&
|
||||
(Platform.environment['XDG_CURRENT_DESKTOP']?.toLowerCase() == 'mate' ||
|
||||
Platform.environment['XDG_SESSION_DESKTOP']?.toLowerCase() == 'mate' ||
|
||||
Platform.environment['DESKTOP_SESSION']?.toLowerCase() == 'mate');
|
||||
|
||||
Map<String, dynamic>? _linuxOsDistro;
|
||||
|
||||
String getLinuxOsDistroId() {
|
||||
if (_linuxOsDistro == null) {
|
||||
String osInfo = bind.getOsDistroInfo();
|
||||
if (osInfo.isEmpty) {
|
||||
_linuxOsDistro = {};
|
||||
} else {
|
||||
try {
|
||||
_linuxOsDistro = jsonDecode(osInfo);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to parse os info: $e');
|
||||
// Don't call `bind.getOsDistroInfo()` again if failed to parse osInfo.
|
||||
_linuxOsDistro = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return (_linuxOsDistro?['id'] ?? '') as String;
|
||||
}
|
||||
|
||||
bool get isLinuxMint =>
|
||||
getLinuxOsDistroId().toLowerCase().contains('linuxmint');
|
||||
|
||||
// https://github.com/leanflutter/window_manager/blob/87dd7a50b4cb47a375b9fc697f05e56eea0a2ab3/lib/src/widgets/virtual_window_frame.dart#L44
|
||||
Widget buildVirtualWindowFrame(BuildContext context, Widget child) {
|
||||
boxShadow() => isMainDesktopWindow
|
||||
@@ -3627,3 +3633,20 @@ List<SubWindowResizeEdge>? get subWindowManagerEnableResizeEdges => isWindows
|
||||
void earlyAssert() {
|
||||
assert('\1' == '1');
|
||||
}
|
||||
|
||||
void checkUpdate() {
|
||||
if (isDesktop || isAndroid) {
|
||||
if (!bind.isCustomClient()) {
|
||||
platformFFI.registerEventHandler(
|
||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||
(Map<String, dynamic> evt) async {
|
||||
if (evt['url'] is String) {
|
||||
stateGlobal.updateUrl.value = evt['url'];
|
||||
}
|
||||
});
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
bind.mainGetSoftwareUpdateUrl();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
@@ -271,33 +270,24 @@ class _PeersViewState extends State<_PeersView>
|
||||
},
|
||||
)
|
||||
: peerCardUiType.value == PeerUiType.list
|
||||
? DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index], false)
|
||||
.marginOnly(
|
||||
right: space,
|
||||
top: index == 0 ? 0 : space / 2,
|
||||
bottom: space / 2);
|
||||
}),
|
||||
? ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index], false).marginOnly(
|
||||
right: space,
|
||||
top: index == 0 ? 0 : space / 2,
|
||||
bottom: space / 2);
|
||||
},
|
||||
)
|
||||
: DesktopScrollWrapper(
|
||||
scrollController: _scrollController,
|
||||
child: DynamicGridView.builder(
|
||||
controller: _scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2,
|
||||
crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index], false);
|
||||
}),
|
||||
));
|
||||
: DynamicGridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithWrapping(
|
||||
mainAxisSpacing: space / 2,
|
||||
crossAxisSpacing: space),
|
||||
itemCount: peers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildOnePeer(peers[index], false);
|
||||
}));
|
||||
|
||||
if (updateEvent == UpdateEvent.load) {
|
||||
_curPeers.clear();
|
||||
|
||||
@@ -244,10 +244,6 @@ const double kDesktopIconButtonSplashRadius = 20;
|
||||
/// [kMinCursorSize] indicates min cursor (w, h)
|
||||
const int kMinCursorSize = 12;
|
||||
|
||||
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
|
||||
const kDefaultScrollAmountMultiplier = 5.0;
|
||||
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
||||
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
|
||||
const kFullScreenEdgeSize = 0.0;
|
||||
const kMaximizeEdgeSize = 0.0;
|
||||
// Do not use kWindowResizeEdgeSize directly. Use `windowResizeEdgeSize` in `common.dart` instead.
|
||||
|
||||
@@ -12,9 +12,9 @@ import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/plugin/ui_manager.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -40,7 +40,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
var updateUrl = '';
|
||||
var systemError = '';
|
||||
StreamSubscription? _uniLinksSubscription;
|
||||
var svcStopped = false.obs;
|
||||
@@ -87,7 +86,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
if (!isOutgoingOnly) buildIDBoard(context),
|
||||
if (!isOutgoingOnly) buildPasswordBoard(context),
|
||||
FutureBuilder<Widget>(
|
||||
future: buildHelpCards(),
|
||||
future: Future.value(
|
||||
Obx(() => buildHelpCards(stateGlobal.updateUrl.value))),
|
||||
builder: (_, data) {
|
||||
if (data.hasData) {
|
||||
if (isIncomingOnly) {
|
||||
@@ -125,47 +125,43 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
child: Container(
|
||||
width: isIncomingOnly ? 280.0 : 200.0,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: DesktopScrollWrapper(
|
||||
scrollController: _leftPaneScrollController,
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
controller: _leftPaneScrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
child: Column(
|
||||
key: _childKey,
|
||||
children: children,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
controller: _leftPaneScrollController,
|
||||
child: Column(
|
||||
key: _childKey,
|
||||
children: children,
|
||||
),
|
||||
if (isOutgoingOnly)
|
||||
Positioned(
|
||||
bottom: 6,
|
||||
left: 12,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: InkWell(
|
||||
child: Obx(
|
||||
() => Icon(
|
||||
Icons.settings,
|
||||
color: _editHover.value
|
||||
? textColor
|
||||
: Colors.grey.withOpacity(0.5),
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
if (isOutgoingOnly)
|
||||
Positioned(
|
||||
bottom: 6,
|
||||
left: 12,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: InkWell(
|
||||
child: Obx(
|
||||
() => Icon(
|
||||
Icons.settings,
|
||||
color: _editHover.value
|
||||
? textColor
|
||||
: Colors.grey.withOpacity(0.5),
|
||||
size: 22,
|
||||
),
|
||||
onTap: () => {
|
||||
if (DesktopSettingPage.tabKeys.isNotEmpty)
|
||||
{
|
||||
DesktopSettingPage.switch2page(
|
||||
DesktopSettingPage.tabKeys[0])
|
||||
}
|
||||
},
|
||||
onHover: (value) => _editHover.value = value,
|
||||
),
|
||||
onTap: () => {
|
||||
if (DesktopSettingPage.tabKeys.isNotEmpty)
|
||||
{
|
||||
DesktopSettingPage.switch2page(
|
||||
DesktopSettingPage.tabKeys[0])
|
||||
}
|
||||
},
|
||||
onHover: (value) => _editHover.value = value,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -420,7 +416,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
Future<Widget> buildHelpCards() async {
|
||||
Widget buildHelpCards(String updateUrl) {
|
||||
if (!bind.isCustomClient() &&
|
||||
updateUrl.isNotEmpty &&
|
||||
!isCardClosed &&
|
||||
@@ -674,20 +670,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!bind.isCustomClient()) {
|
||||
platformFFI.registerEventHandler(
|
||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||
(Map<String, dynamic> evt) async {
|
||||
if (evt['url'] is String) {
|
||||
setState(() {
|
||||
updateUrl = evt['url'];
|
||||
});
|
||||
}
|
||||
});
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
bind.mainGetSoftwareUpdateUrl();
|
||||
});
|
||||
}
|
||||
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
||||
await gFFI.serverModel.fetchID();
|
||||
final error = await bind.mainGetError();
|
||||
|
||||
@@ -11,15 +11,16 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/plugin/manager.dart';
|
||||
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
|
||||
import '../../common/widgets/dialog.dart';
|
||||
import '../../common/widgets/login.dart';
|
||||
@@ -226,13 +227,11 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: DesktopScrollWrapper(
|
||||
scrollController: controller,
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: _children(),
|
||||
)),
|
||||
child: PageView(
|
||||
controller: controller,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: _children(),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
@@ -281,13 +280,10 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
|
||||
Widget _listView({required List<_TabInfo> tabs}) {
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: tabs.map((tab) => _listItem(tab: tab)).toList(),
|
||||
));
|
||||
return ListView(
|
||||
controller: scrollController,
|
||||
children: tabs.map((tab) => _listItem(tab: tab)).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _listItem({required _TabInfo tab}) {
|
||||
@@ -349,22 +345,19 @@ class _GeneralState extends State<_General> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: [
|
||||
if (!isWeb) service(),
|
||||
theme(),
|
||||
_Card(title: 'Language', children: [language()]),
|
||||
if (!isWeb) hwcodec(),
|
||||
if (!isWeb) audio(context),
|
||||
if (!isWeb) record(context),
|
||||
if (!isWeb) WaylandCard(),
|
||||
other()
|
||||
],
|
||||
).marginOnly(bottom: _kListViewBottomMargin));
|
||||
return ListView(
|
||||
controller: scrollController,
|
||||
children: [
|
||||
if (!isWeb) service(),
|
||||
theme(),
|
||||
_Card(title: 'Language', children: [language()]),
|
||||
if (!isWeb) hwcodec(),
|
||||
if (!isWeb) audio(context),
|
||||
if (!isWeb) record(context),
|
||||
if (!isWeb) WaylandCard(),
|
||||
other()
|
||||
],
|
||||
).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}
|
||||
|
||||
Widget theme() {
|
||||
@@ -705,29 +698,26 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
_lock(locked, 'Unlock Security Settings', () {
|
||||
locked = false;
|
||||
setState(() => {});
|
||||
}),
|
||||
AbsorbPointer(
|
||||
absorbing: locked,
|
||||
child: Column(children: [
|
||||
permissions(context),
|
||||
password(context),
|
||||
_Card(title: '2FA', children: [tfa()]),
|
||||
_Card(title: 'ID', children: [changeId()]),
|
||||
more(context),
|
||||
]),
|
||||
),
|
||||
],
|
||||
)).marginOnly(bottom: _kListViewBottomMargin));
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: Column(
|
||||
children: [
|
||||
_lock(locked, 'Unlock Security Settings', () {
|
||||
locked = false;
|
||||
setState(() => {});
|
||||
}),
|
||||
AbsorbPointer(
|
||||
absorbing: locked,
|
||||
child: Column(children: [
|
||||
permissions(context),
|
||||
password(context),
|
||||
_Card(title: '2FA', children: [tfa()]),
|
||||
_Card(title: 'ID', children: [changeId()]),
|
||||
more(context),
|
||||
]),
|
||||
),
|
||||
],
|
||||
)).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}
|
||||
|
||||
Widget tfa() {
|
||||
@@ -1374,112 +1364,82 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
bool get wantKeepAlive => true;
|
||||
bool locked = !isWeb && bind.mainIsInstalled();
|
||||
|
||||
final scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
bool enabled = !locked;
|
||||
final scrollController = ScrollController();
|
||||
final hideServer =
|
||||
bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
|
||||
// TODO: support web proxy
|
||||
final hideProxy =
|
||||
isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_lock(locked, 'Unlock Network Settings', () {
|
||||
locked = false;
|
||||
setState(() => {});
|
||||
}),
|
||||
AbsorbPointer(
|
||||
absorbing: locked,
|
||||
child: Column(children: [
|
||||
if (!hideServer) server(enabled),
|
||||
if (!hideProxy)
|
||||
_Card(title: 'Proxy', children: [
|
||||
_Button('Socks5/Http(s) Proxy', changeSocks5Proxy,
|
||||
enabled: enabled),
|
||||
]),
|
||||
]),
|
||||
),
|
||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
||||
return ListView(controller: scrollController, children: [
|
||||
_lock(locked, 'Unlock Network Settings', () {
|
||||
locked = false;
|
||||
setState(() => {});
|
||||
}),
|
||||
AbsorbPointer(
|
||||
absorbing: locked,
|
||||
child: Column(children: [
|
||||
network(context),
|
||||
]),
|
||||
),
|
||||
]).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}
|
||||
|
||||
server(bool enabled) {
|
||||
// Simple temp wrapper for PR check
|
||||
tmpWrapper() {
|
||||
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
||||
Map<String, dynamic> oldOptions = jsonDecode(bind.mainGetOptionsSync());
|
||||
old(String key) {
|
||||
return (oldOptions[key] ?? '').trim();
|
||||
}
|
||||
Widget network(BuildContext context) {
|
||||
final hideServer =
|
||||
bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
|
||||
final hideProxy =
|
||||
isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
|
||||
|
||||
RxString idErrMsg = ''.obs;
|
||||
RxString relayErrMsg = ''.obs;
|
||||
RxString apiErrMsg = ''.obs;
|
||||
var idController =
|
||||
TextEditingController(text: old('custom-rendezvous-server'));
|
||||
var relayController = TextEditingController(text: old('relay-server'));
|
||||
var apiController = TextEditingController(text: old('api-server'));
|
||||
var keyController = TextEditingController(text: old('key'));
|
||||
final controllers = [
|
||||
idController,
|
||||
relayController,
|
||||
apiController,
|
||||
keyController,
|
||||
];
|
||||
final errMsgs = [
|
||||
idErrMsg,
|
||||
relayErrMsg,
|
||||
apiErrMsg,
|
||||
];
|
||||
|
||||
submit() async {
|
||||
bool result = await setServerConfig(
|
||||
null,
|
||||
errMsgs,
|
||||
ServerConfig(
|
||||
idServer: idController.text,
|
||||
relayServer: relayController.text,
|
||||
apiServer: apiController.text,
|
||||
key: keyController.text));
|
||||
if (result) {
|
||||
setState(() {});
|
||||
showToast(translate('Successful'));
|
||||
} else {
|
||||
showToast(translate('Failed'));
|
||||
}
|
||||
}
|
||||
|
||||
bool secure = !enabled;
|
||||
return _Card(
|
||||
title: 'ID/Relay Server',
|
||||
title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs),
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Obx(() => _LabeledTextField(context, 'ID Server', idController,
|
||||
idErrMsg.value, enabled, secure)),
|
||||
if (!isWeb)
|
||||
Obx(() => _LabeledTextField(context, 'Relay Server',
|
||||
relayController, relayErrMsg.value, enabled, secure)),
|
||||
Obx(() => _LabeledTextField(context, 'API Server',
|
||||
apiController, apiErrMsg.value, enabled, secure)),
|
||||
_LabeledTextField(
|
||||
context, 'Key', keyController, '', enabled, secure),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [_Button('Apply', submit, enabled: enabled)],
|
||||
).marginOnly(top: 10),
|
||||
],
|
||||
)
|
||||
]);
|
||||
if (hideServer && hideProxy) {
|
||||
return Offstage();
|
||||
}
|
||||
|
||||
return tmpWrapper();
|
||||
return _Card(
|
||||
title: 'Network',
|
||||
children: [
|
||||
Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!hideServer)
|
||||
ListTile(
|
||||
leading: Icon(Icons.dns_outlined, color: _accentColor),
|
||||
title: Text(
|
||||
translate('ID/Relay Server'),
|
||||
style: TextStyle(fontSize: _kContentFontSize),
|
||||
),
|
||||
enabled: !locked,
|
||||
onTap: () => showServerSettings(gFFI.dialogManager),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||
minLeadingWidth: 0,
|
||||
horizontalTitleGap: 10,
|
||||
),
|
||||
if (!hideServer && !hideProxy)
|
||||
Divider(height: 1, indent: 16, endIndent: 16),
|
||||
if (!hideProxy)
|
||||
ListTile(
|
||||
leading:
|
||||
Icon(Icons.network_ping_outlined, color: _accentColor),
|
||||
title: Text(
|
||||
translate('Socks5/Http(s) Proxy'),
|
||||
style: TextStyle(fontSize: _kContentFontSize),
|
||||
),
|
||||
enabled: !locked,
|
||||
onTap: changeSocks5Proxy,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||
minLeadingWidth: 0,
|
||||
horizontalTitleGap: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1494,19 +1454,14 @@ class _DisplayState extends State<_Display> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
viewStyle(context),
|
||||
scrollStyle(context),
|
||||
imageQuality(context),
|
||||
codec(context),
|
||||
if (!isWeb) privacyModeImpl(context),
|
||||
other(context),
|
||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
||||
return ListView(controller: scrollController, children: [
|
||||
viewStyle(context),
|
||||
scrollStyle(context),
|
||||
imageQuality(context),
|
||||
codec(context),
|
||||
if (!isWeb) privacyModeImpl(context),
|
||||
other(context),
|
||||
]).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}
|
||||
|
||||
Widget viewStyle(BuildContext context) {
|
||||
@@ -1729,15 +1684,12 @@ class _AccountState extends State<_Account> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ListView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: [
|
||||
_Card(title: 'Account', children: [accountAction(), useInfo()]),
|
||||
],
|
||||
).marginOnly(bottom: _kListViewBottomMargin));
|
||||
return ListView(
|
||||
controller: scrollController,
|
||||
children: [
|
||||
_Card(title: 'Account', children: [accountAction(), useInfo()]),
|
||||
],
|
||||
).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}
|
||||
|
||||
Widget accountAction() {
|
||||
@@ -1834,18 +1786,14 @@ class _PluginState extends State<_Plugin> {
|
||||
Widget build(BuildContext context) {
|
||||
bind.pluginListReload();
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: ChangeNotifierProvider.value(
|
||||
value: pluginManager,
|
||||
child: Consumer<PluginManager>(builder: (context, model, child) {
|
||||
return ListView(
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
||||
).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}),
|
||||
),
|
||||
return ChangeNotifierProvider.value(
|
||||
value: pluginManager,
|
||||
child: Consumer<PluginManager>(builder: (context, model, child) {
|
||||
return ListView(
|
||||
controller: scrollController,
|
||||
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
||||
).marginOnly(bottom: _kListViewBottomMargin);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1897,75 +1845,72 @@ class _AboutState extends State<_About> {
|
||||
final fingerprint = data['fingerprint'].toString();
|
||||
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
||||
final scrollController = ScrollController();
|
||||
return DesktopScrollWrapper(
|
||||
scrollController: scrollController,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
physics: DraggableNeverScrollableScrollPhysics(),
|
||||
child: _Card(title: translate('About RustDesk'), children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Version')}: $version')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Build Date')}: $buildDate')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
if (!isWeb)
|
||||
SelectionArea(
|
||||
child: Text('${translate('Fingerprint')}: $fingerprint')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com/privacy.html');
|
||||
},
|
||||
child: Text(
|
||||
translate('Privacy Statement'),
|
||||
style: linkStyle,
|
||||
).marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com');
|
||||
},
|
||||
child: Text(
|
||||
translate('Website'),
|
||||
style: linkStyle,
|
||||
).marginSymmetric(vertical: 4.0)),
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
||||
child: SelectionArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
translate('Slogan_tip'),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white),
|
||||
)
|
||||
],
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: _Card(title: translate('About RustDesk'), children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Version')}: $version')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
SelectionArea(
|
||||
child: Text('${translate('Build Date')}: $buildDate')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
if (!isWeb)
|
||||
SelectionArea(
|
||||
child: Text('${translate('Fingerprint')}: $fingerprint')
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com/privacy.html');
|
||||
},
|
||||
child: Text(
|
||||
translate('Privacy Statement'),
|
||||
style: linkStyle,
|
||||
).marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com');
|
||||
},
|
||||
child: Text(
|
||||
translate('Website'),
|
||||
style: linkStyle,
|
||||
).marginSymmetric(vertical: 4.0)),
|
||||
Container(
|
||||
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
||||
child: SelectionArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
).marginSymmetric(vertical: 4.0)
|
||||
],
|
||||
).marginOnly(left: _kContentHMargin)
|
||||
]),
|
||||
));
|
||||
Text(
|
||||
translate('Slogan_tip'),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
).marginSymmetric(vertical: 4.0)
|
||||
],
|
||||
).marginOnly(left: _kContentHMargin)
|
||||
]),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2283,26 +2228,39 @@ _LabeledTextField(
|
||||
String errorText,
|
||||
bool enabled,
|
||||
bool secure) {
|
||||
return Row(
|
||||
return Table(
|
||||
columnWidths: const {
|
||||
0: FixedColumnWidth(150),
|
||||
1: FlexColumnWidth(),
|
||||
},
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 140),
|
||||
child: Text(
|
||||
'${translate(label)}:',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: disabledTextColor(context, enabled)),
|
||||
).marginOnly(right: 10)),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
TableRow(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Text(
|
||||
'${translate(label)}:',
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: disabledTextColor(context, enabled),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
obscureText: secure,
|
||||
autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
errorText: errorText.isNotEmpty ? errorText : null),
|
||||
errorText: errorText.isNotEmpty ? errorText : null,
|
||||
),
|
||||
style: TextStyle(
|
||||
color: disabledTextColor(context, enabled),
|
||||
)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).marginOnly(bottom: 8);
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
|
||||
import '../../consts.dart';
|
||||
@@ -742,12 +741,6 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
ScrollController horizontal,
|
||||
ScrollController vertical,
|
||||
) {
|
||||
final scrollConfig = CustomMouseWheelScrollConfig(
|
||||
scrollDuration: kDefaultScrollDuration,
|
||||
scrollCurve: Curves.linearToEaseOut,
|
||||
mouseWheelTurnsThrottleTimeMs:
|
||||
kDefaultMouseWheelThrottleDuration.inMilliseconds,
|
||||
scrollAmountMultiplier: kDefaultScrollAmountMultiplier);
|
||||
var widget = child;
|
||||
if (layoutSize.width < size.width) {
|
||||
widget = ScrollConfiguration(
|
||||
@@ -793,36 +786,26 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
);
|
||||
}
|
||||
if (layoutSize.width < size.width) {
|
||||
widget = ImprovedScrolling(
|
||||
scrollController: horizontal,
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: horizontal,
|
||||
thumbVisibility: false,
|
||||
trackVisibility: false,
|
||||
notificationPredicate: layoutSize.height < size.height
|
||||
? (notification) => notification.depth == 1
|
||||
: defaultScrollNotificationPredicate,
|
||||
child: widget,
|
||||
),
|
||||
widget = RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: horizontal,
|
||||
thumbVisibility: false,
|
||||
trackVisibility: false,
|
||||
notificationPredicate: layoutSize.height < size.height
|
||||
? (notification) => notification.depth == 1
|
||||
: defaultScrollNotificationPredicate,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
if (layoutSize.height < size.height) {
|
||||
widget = ImprovedScrolling(
|
||||
scrollController: vertical,
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: vertical,
|
||||
thumbVisibility: false,
|
||||
trackVisibility: false,
|
||||
child: widget,
|
||||
),
|
||||
widget = RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: vertical,
|
||||
thumbVisibility: false,
|
||||
trackVisibility: false,
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
|
||||
|
||||
class DesktopScrollWrapper extends StatelessWidget {
|
||||
final ScrollController scrollController;
|
||||
final Widget child;
|
||||
const DesktopScrollWrapper(
|
||||
{Key? key, required this.scrollController, required this.child})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ImprovedScrolling(
|
||||
scrollController: scrollController,
|
||||
enableCustomMouseWheelScrolling: true,
|
||||
// enableKeyboardScrolling: true, // strange behavior on mac
|
||||
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
|
||||
scrollDuration: kDefaultScrollDuration,
|
||||
scrollCurve: Curves.linearToEaseOut,
|
||||
mouseWheelTurnsThrottleTimeMs:
|
||||
kDefaultMouseWheelThrottleDuration.inMilliseconds,
|
||||
scrollAmountMultiplier: kDefaultScrollAmountMultiplier),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,7 @@ Future<void> initEnv(String appType) async {
|
||||
void runMainApp(bool startService) async {
|
||||
// register uni links
|
||||
await initEnv(kAppTypeMain);
|
||||
checkUpdate();
|
||||
// trigger connection status updater
|
||||
await bind.mainCheckConnectStatus();
|
||||
if (startService) {
|
||||
@@ -156,6 +157,7 @@ void runMainApp(bool startService) async {
|
||||
|
||||
void runMobileApp() async {
|
||||
await initEnv(kAppTypeMain);
|
||||
checkUpdate();
|
||||
if (isAndroid) androidChannelInit();
|
||||
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||
draggablePositions.load();
|
||||
@@ -483,7 +485,16 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
child = keyListenerBuilder(context, child);
|
||||
}
|
||||
if (isLinux) {
|
||||
child = buildVirtualWindowFrame(context, child);
|
||||
// `(!(isLinuxMateDesktop || isLinuxMint))` is not used here for clarity.
|
||||
// `isLinuxMint` will call ffi function.
|
||||
if (!isLinuxMateDesktop) {
|
||||
if (!isLinuxMint) {
|
||||
debugPrint(
|
||||
'Linux distro is not linuxmint, and desktop is not mate, '
|
||||
'so we build virtual window frame.');
|
||||
child = buildVirtualWindowFrame(context, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return child;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:auto_size_text_field/auto_size_text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:flutter_hbb/common/widgets/connection_page_title.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -40,8 +41,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
final _idController = IDTextEditingController();
|
||||
final RxBool _idEmpty = true.obs;
|
||||
|
||||
/// Update url. If it's not null, means an update is available.
|
||||
var _updateUrl = '';
|
||||
List<Peer> peers = [];
|
||||
|
||||
bool isPeersLoading = false;
|
||||
@@ -72,22 +71,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isAndroid) {
|
||||
if (!bind.isCustomClient()) {
|
||||
platformFFI.registerEventHandler(
|
||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||
(Map<String, dynamic> evt) async {
|
||||
if (evt['url'] is String) {
|
||||
setState(() {
|
||||
_updateUrl = evt['url'];
|
||||
});
|
||||
}
|
||||
});
|
||||
Timer(const Duration(seconds: 1), () async {
|
||||
bind.mainGetSoftwareUpdateUrl();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -97,7 +80,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
if (!bind.isCustomClient()) _buildUpdateUI(),
|
||||
if (!bind.isCustomClient())
|
||||
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
|
||||
_buildRemoteIDTextField(),
|
||||
])),
|
||||
SliverFillRemaining(
|
||||
@@ -116,13 +100,21 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
}
|
||||
|
||||
/// UI for software update.
|
||||
/// If [_updateUrl] is not empty, shows a button to update the software.
|
||||
Widget _buildUpdateUI() {
|
||||
return _updateUrl.isEmpty
|
||||
/// If _updateUrl] is not empty, shows a button to update the software.
|
||||
Widget _buildUpdateUI(String updateUrl) {
|
||||
return updateUrl.isEmpty
|
||||
? const SizedBox(height: 0)
|
||||
: InkWell(
|
||||
onTap: () async {
|
||||
final url = 'https://rustdesk.com/download';
|
||||
// https://pub.dev/packages/url_launcher#configuration
|
||||
// https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs
|
||||
//
|
||||
// `await launchUrl(Uri.parse(url))` can also run if skip
|
||||
// 1. The following check
|
||||
// 2. `<action android:name="android.support.customtabs.action.CustomTabsService" />` in AndroidManifest.xml
|
||||
//
|
||||
// But it is better to add the check.
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
}
|
||||
|
||||
@@ -872,6 +872,8 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
|
||||
|
||||
final pi = gFFI.ffiModel.pi;
|
||||
final isMac = pi.platform == kPeerPlatformMacOS;
|
||||
final isWin = pi.platform == kPeerPlatformWindows;
|
||||
final isLinux = pi.platform == kPeerPlatformLinux;
|
||||
final modifiers = <Widget>[
|
||||
wrap('Ctrl ', () {
|
||||
setState(() => inputModel.ctrl = !inputModel.ctrl);
|
||||
@@ -952,6 +954,28 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
|
||||
wrap('PgDn', () {
|
||||
inputModel.inputKey('VK_NEXT');
|
||||
}),
|
||||
// to-do: support PrtScr on Mac
|
||||
if (isWin || isLinux)
|
||||
wrap('PrtScr', () {
|
||||
inputModel.inputKey('VK_SNAPSHOT');
|
||||
}),
|
||||
if (isWin || isLinux)
|
||||
wrap('ScrollLock', () {
|
||||
inputModel.inputKey('VK_SCROLL');
|
||||
}),
|
||||
if (isWin || isLinux)
|
||||
wrap('Pause', () {
|
||||
inputModel.inputKey('VK_PAUSE');
|
||||
}),
|
||||
if (isWin || isLinux)
|
||||
// Maybe it's better to call it "Menu"
|
||||
// https://en.wikipedia.org/wiki/Menu_key
|
||||
wrap('Menu', () {
|
||||
inputModel.inputKey('Apps');
|
||||
}),
|
||||
wrap('Enter', () {
|
||||
inputModel.inputKey('VK_ENTER');
|
||||
}),
|
||||
SizedBox(width: 9999),
|
||||
wrap('', () {
|
||||
inputModel.inputKey('VK_LEFT');
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:settings_ui/settings_ui.dart';
|
||||
@@ -70,6 +71,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
false; //androidVersion >= 26; // remove because not work on every device
|
||||
var _ignoreBatteryOpt = false;
|
||||
var _enableStartOnBoot = false;
|
||||
var _checkUpdateOnStartup = false;
|
||||
var _floatingWindowDisabled = false;
|
||||
var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
|
||||
var _enableAbr = false;
|
||||
@@ -154,6 +156,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_enableStartOnBoot = enableStartOnBoot;
|
||||
}
|
||||
|
||||
var checkUpdateOnStartup =
|
||||
mainGetLocalBoolOptionSync(kOptionEnableCheckUpdate);
|
||||
if (checkUpdateOnStartup != _checkUpdateOnStartup) {
|
||||
update = true;
|
||||
_checkUpdateOnStartup = checkUpdateOnStartup;
|
||||
}
|
||||
|
||||
var floatingWindowDisabled =
|
||||
bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
|
||||
!await AndroidPermissionManager.check(kSystemAlertWindow);
|
||||
@@ -552,6 +561,22 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
||||
}));
|
||||
|
||||
if (!bind.isCustomClient()) {
|
||||
enhancementsTiles.add(
|
||||
SettingsTile.switchTile(
|
||||
initialValue: _checkUpdateOnStartup,
|
||||
title:
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(translate('Check for software update on startup')),
|
||||
]),
|
||||
onToggle: (bool toValue) async {
|
||||
await mainSetLocalBoolOption(kOptionEnableCheckUpdate, toValue);
|
||||
setState(() => _checkUpdateOnStartup = toValue);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
onFloatingWindowChanged(bool toValue) async {
|
||||
if (toValue) {
|
||||
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
||||
@@ -828,11 +853,6 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
void showServerSettings(OverlayDialogManager dialogManager) async {
|
||||
Map<String, dynamic> options = jsonDecode(await bind.mainGetOptions());
|
||||
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
|
||||
}
|
||||
|
||||
void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||
try {
|
||||
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||
@@ -146,6 +147,16 @@ void setTemporaryPasswordLengthDialog(
|
||||
}, backDismiss: true, clickMaskDismiss: true);
|
||||
}
|
||||
|
||||
void showServerSettings(OverlayDialogManager dialogManager) async {
|
||||
Map<String, dynamic> options = {};
|
||||
try {
|
||||
options = jsonDecode(await bind.mainGetOptions());
|
||||
} catch (e) {
|
||||
print("Invalid server config: $e");
|
||||
}
|
||||
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
|
||||
}
|
||||
|
||||
void showServerSettingsWithValue(
|
||||
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
||||
var isInProgress = false;
|
||||
@@ -184,6 +195,43 @@ void showServerSettingsWithValue(
|
||||
return ret;
|
||||
}
|
||||
|
||||
Widget buildField(
|
||||
String label, TextEditingController controller, String errorMsg,
|
||||
{String? Function(String?)? validator, bool autofocus = false}) {
|
||||
if (isDesktop || isWeb) {
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(label),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
errorText: errorMsg.isEmpty ? null : errorMsg,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
),
|
||||
validator: validator,
|
||||
autofocus: autofocus,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
errorText: errorMsg.isEmpty ? null : errorMsg,
|
||||
),
|
||||
validator: validator,
|
||||
);
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Row(
|
||||
children: [
|
||||
@@ -191,56 +239,45 @@ void showServerSettingsWithValue(
|
||||
...ServerConfigImportExportWidgets(controllers, errMsgs),
|
||||
],
|
||||
),
|
||||
content: Form(
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 500),
|
||||
child: Form(
|
||||
child: Obx(() => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
controller: idCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('ID Server'),
|
||||
errorText: idServerMsg.value.isEmpty
|
||||
? null
|
||||
: idServerMsg.value),
|
||||
)
|
||||
] +
|
||||
[
|
||||
if (isAndroid)
|
||||
TextFormField(
|
||||
controller: relayCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('Relay Server'),
|
||||
errorText: relayServerMsg.value.isEmpty
|
||||
? null
|
||||
: relayServerMsg.value),
|
||||
)
|
||||
] +
|
||||
[
|
||||
TextFormField(
|
||||
controller: apiCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('API Server'),
|
||||
),
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (v) {
|
||||
if (v != null && v.isNotEmpty) {
|
||||
if (!(v.startsWith('http://') ||
|
||||
v.startsWith("https://"))) {
|
||||
return translate("invalid_http");
|
||||
}
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
buildField(translate('ID Server'), idCtrl, idServerMsg.value,
|
||||
autofocus: true),
|
||||
SizedBox(height: 8),
|
||||
if (!isIOS && !isWeb) ...[
|
||||
buildField(translate('Relay Server'), relayCtrl,
|
||||
relayServerMsg.value),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
buildField(
|
||||
translate('API Server'),
|
||||
apiCtrl,
|
||||
apiServerMsg.value,
|
||||
validator: (v) {
|
||||
if (v != null && v.isNotEmpty) {
|
||||
if (!(v.startsWith('http://') ||
|
||||
v.startsWith("https://"))) {
|
||||
return translate("invalid_http");
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
buildField('Key', keyCtrl, ''),
|
||||
if (isInProgress)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 8),
|
||||
child: LinearProgressIndicator(),
|
||||
),
|
||||
TextFormField(
|
||||
controller: keyCtrl,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Key',
|
||||
),
|
||||
),
|
||||
// NOT use Offstage to wrap LinearProgressIndicator
|
||||
if (isInProgress) const LinearProgressIndicator(),
|
||||
]))),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
dialogButton('Cancel', onPressed: () {
|
||||
close();
|
||||
|
||||
@@ -648,6 +648,9 @@ class AbModel {
|
||||
}
|
||||
|
||||
Color getCurrentAbTagColor(String tag) {
|
||||
if (tag == kUntagged) {
|
||||
return MyTheme.accent;
|
||||
}
|
||||
int? colorValue = current.tagColors[tag];
|
||||
if (colorValue != null) {
|
||||
return Color(colorValue);
|
||||
|
||||
@@ -261,6 +261,27 @@ class FileModel {
|
||||
debugPrint("Failed to decode onSelectedFiles: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void sendEmptyDirs(dynamic obj) {
|
||||
late final List<dynamic> emptyDirs;
|
||||
try {
|
||||
emptyDirs = jsonDecode(obj['dirs'] as String);
|
||||
} catch (e) {
|
||||
debugPrint("Failed to decode sendEmptyDirs: $e");
|
||||
}
|
||||
final otherSideData = remoteController.directoryData();
|
||||
final toPath = otherSideData.directory.path;
|
||||
final isPeerWindows = otherSideData.options.isWindows;
|
||||
|
||||
final isLocalWindows = isWindows || isWebOnWindows;
|
||||
for (var dir in emptyDirs) {
|
||||
if (isLocalWindows != isPeerWindows) {
|
||||
dir = PathUtil.convert(dir, isLocalWindows, isPeerWindows);
|
||||
}
|
||||
var peerPath = PathUtil.join(toPath, dir, isPeerWindows);
|
||||
remoteController.createDirWithRemote(peerPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DirectoryData {
|
||||
@@ -502,8 +523,9 @@ class FileController {
|
||||
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
||||
}
|
||||
|
||||
if (!isLocal &&
|
||||
versionCmp(rootState.target!.ffiModel.pi.version, '1.3.3') < 0) {
|
||||
if (isWeb ||
|
||||
(!isLocal &&
|
||||
versionCmp(rootState.target!.ffiModel.pi.version, '1.3.3') < 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1506,6 +1528,12 @@ class PathUtil {
|
||||
return pathUtil.split(path);
|
||||
}
|
||||
|
||||
static String convert(String path, bool isMainWindows, bool isOtherWindows) {
|
||||
final mainPathUtil = isMainWindows ? windowsContext : posixContext;
|
||||
final otherPathUtil = isOtherWindows ? windowsContext : posixContext;
|
||||
return otherPathUtil.joinAll(mainPathUtil.split(path));
|
||||
}
|
||||
|
||||
static String dirname(String path, bool isWindows) {
|
||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||
return pathUtil.dirname(path);
|
||||
|
||||
@@ -1080,7 +1080,7 @@ class InputModel {
|
||||
onExit: true,
|
||||
);
|
||||
|
||||
static int tryGetNearestRange(int v, int min, int max, int n) {
|
||||
static double tryGetNearestRange(double v, double min, double max, double n) {
|
||||
if (v < min && v >= min - n) {
|
||||
v = min;
|
||||
}
|
||||
@@ -1138,8 +1138,8 @@ class InputModel {
|
||||
return;
|
||||
}
|
||||
evtValue = {
|
||||
'x': pos.x,
|
||||
'y': pos.y,
|
||||
'x': pos.x.toInt(),
|
||||
'y': pos.y.toInt(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1221,8 +1221,8 @@ class InputModel {
|
||||
evt['x'] = '0';
|
||||
evt['y'] = '0';
|
||||
} else {
|
||||
evt['x'] = '${pos.x}';
|
||||
evt['y'] = '${pos.y}';
|
||||
evt['x'] = '${pos.x.toInt()}';
|
||||
evt['y'] = '${pos.y.toInt()}';
|
||||
}
|
||||
|
||||
Map<int, String> mapButtons = {
|
||||
@@ -1362,31 +1362,27 @@ class InputModel {
|
||||
y = pos.dy;
|
||||
}
|
||||
|
||||
var evtX = 0;
|
||||
var evtY = 0;
|
||||
try {
|
||||
evtX = x.round();
|
||||
evtY = y.round();
|
||||
} catch (e) {
|
||||
debugPrintStack(label: 'canvas.scale value ${canvas.scale}, $e');
|
||||
return null;
|
||||
}
|
||||
|
||||
return InputModel.getPointInRemoteRect(
|
||||
true, peerPlatform, kind, evtType, evtX, evtY, rect,
|
||||
true, peerPlatform, kind, evtType, x, y, rect,
|
||||
buttons: buttons);
|
||||
}
|
||||
|
||||
static Point? getPointInRemoteRect(bool isLocalDesktop, String? peerPlatform,
|
||||
String kind, String evtType, int evtX, int evtY, Rect rect,
|
||||
static Point<double>? getPointInRemoteRect(
|
||||
bool isLocalDesktop,
|
||||
String? peerPlatform,
|
||||
String kind,
|
||||
String evtType,
|
||||
double evtX,
|
||||
double evtY,
|
||||
Rect rect,
|
||||
{int buttons = kPrimaryMouseButton}) {
|
||||
int minX = rect.left.toInt();
|
||||
double minX = rect.left;
|
||||
// https://github.com/rustdesk/rustdesk/issues/6678
|
||||
// For Windows, [0,maxX], [0,maxY] should be set to enable window snapping.
|
||||
int maxX = (rect.left + rect.width).toInt() -
|
||||
double maxX = (rect.left + rect.width) -
|
||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
int minY = rect.top.toInt();
|
||||
int maxY = (rect.top + rect.height).toInt() -
|
||||
double minY = rect.top;
|
||||
double maxY = (rect.top + rect.height) -
|
||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||
evtX = InputModel.tryGetNearestRange(evtX, minX, maxX, 5);
|
||||
evtY = InputModel.tryGetNearestRange(evtY, minY, maxY, 5);
|
||||
|
||||
@@ -402,6 +402,10 @@ class FfiModel with ChangeNotifier {
|
||||
if (isWeb) {
|
||||
parent.target?.fileModel.onSelectedFiles(evt);
|
||||
}
|
||||
} else if (name == "send_emptry_dirs") {
|
||||
if (isWeb) {
|
||||
parent.target?.fileModel.sendEmptyDirs(evt);
|
||||
}
|
||||
} else if (name == "record_status") {
|
||||
if (desktopType == DesktopType.remote || isMobile) {
|
||||
parent.target?.recordingModel.updateStatus(evt['start'] == 'true');
|
||||
@@ -1268,7 +1272,9 @@ class ImageModel with ChangeNotifier {
|
||||
rgba,
|
||||
rect?.width.toInt() ?? 0,
|
||||
rect?.height.toInt() ?? 0,
|
||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
||||
isWeb | isWindows | isLinux
|
||||
? ui.PixelFormat.rgba8888
|
||||
: ui.PixelFormat.bgra8888,
|
||||
);
|
||||
if (parent.target?.id != pid) return;
|
||||
await update(image);
|
||||
@@ -2184,7 +2190,7 @@ class CursorModel with ChangeNotifier {
|
||||
|
||||
if (dx == 0 && dy == 0) return;
|
||||
|
||||
Point? newPos;
|
||||
Point<double>? newPos;
|
||||
final rect = parent.target?.ffiModel.rect;
|
||||
if (rect == null) {
|
||||
// unreachable
|
||||
@@ -2195,8 +2201,8 @@ class CursorModel with ChangeNotifier {
|
||||
parent.target?.ffiModel.pi.platform,
|
||||
kPointerEventKindMouse,
|
||||
kMouseEventTypeDefault,
|
||||
(_x + dx).toInt(),
|
||||
(_y + dy).toInt(),
|
||||
_x + dx,
|
||||
_y + dy,
|
||||
rect,
|
||||
buttons: kPrimaryButton);
|
||||
if (newPos == null) {
|
||||
@@ -2204,8 +2210,8 @@ class CursorModel with ChangeNotifier {
|
||||
}
|
||||
dx = newPos.x - _x;
|
||||
dy = newPos.y - _y;
|
||||
_x = newPos.x.toDouble();
|
||||
_y = newPos.y.toDouble();
|
||||
_x = newPos.x;
|
||||
_y = newPos.y;
|
||||
if (tryMoveCanvasX && dx != 0) {
|
||||
parent.target?.canvasModel.panX(-dx * scale);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ class StateGlobal {
|
||||
|
||||
final isPortrait = false.obs;
|
||||
|
||||
final updateUrl = ''.obs;
|
||||
|
||||
String _inputSource = '';
|
||||
|
||||
// Use for desktop -> remote toolbar -> resolution
|
||||
|
||||
@@ -1801,6 +1801,26 @@ class RustdeskImpl {
|
||||
throw UnimplementedError("mainMaxEncryptLen");
|
||||
}
|
||||
|
||||
bool mainAudioSupportLoopback({dynamic hint}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<String> sessionReadLocalEmptyDirsRecursiveSync(
|
||||
{required UuidValue sessionId,
|
||||
required String path,
|
||||
required bool includeHidden,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("sessionReadLocalEmptyDirsRecursiveSync");
|
||||
}
|
||||
|
||||
Future<void> sessionReadRemoteEmptyDirsRecursiveSync(
|
||||
{required UuidValue sessionId,
|
||||
required String path,
|
||||
required bool includeHidden,
|
||||
dynamic hint}) {
|
||||
throw UnimplementedError("sessionReadRemoteEmptyDirsRecursiveSync");
|
||||
}
|
||||
|
||||
Future<void> sessionRenameFile(
|
||||
{required UuidValue sessionId,
|
||||
required int actId,
|
||||
@@ -1828,5 +1848,9 @@ class RustdeskImpl {
|
||||
throw UnimplementedError("sessionGetConnToken");
|
||||
}
|
||||
|
||||
String getOsDistroInfo({dynamic hint}) {
|
||||
return '';
|
||||
}
|
||||
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
extern bool gIsConnectionManager;
|
||||
|
||||
GtkWidget *find_gl_area(GtkWidget *widget);
|
||||
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view);
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
@@ -39,9 +42,10 @@ static void my_application_activate(GApplication* application) {
|
||||
// If running on Wayland assume the header bar will work (may need changing
|
||||
// if future cases occur).
|
||||
gboolean use_header_bar = TRUE;
|
||||
GdkScreen* screen = NULL;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
if (GDK_IS_X11_SCREEN(screen)) {
|
||||
screen = gtk_window_get_screen(window);
|
||||
if (screen != NULL && GDK_IS_X11_SCREEN(screen)) {
|
||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||
use_header_bar = FALSE;
|
||||
@@ -76,6 +80,8 @@ static void my_application_activate(GApplication* application) {
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
try_set_transparent(window, screen, view);
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
@@ -121,3 +127,106 @@ MyApplication* my_application_new() {
|
||||
"flags", G_APPLICATION_NON_UNIQUE,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
GtkWidget *find_gl_area(GtkWidget *widget)
|
||||
{
|
||||
if (GTK_IS_GL_AREA(widget)) {
|
||||
return widget;
|
||||
}
|
||||
|
||||
if (GTK_IS_CONTAINER(widget)) {
|
||||
GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
|
||||
for (GList *iter = children; iter != NULL; iter = g_list_next(iter)) {
|
||||
GtkWidget *child = GTK_WIDGET(iter->data);
|
||||
GtkWidget *gl_area = find_gl_area(child);
|
||||
if (gl_area != NULL) {
|
||||
g_list_free(children);
|
||||
return gl_area;
|
||||
}
|
||||
}
|
||||
g_list_free(children);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool is_linux_mint()
|
||||
{
|
||||
bool is_mint = false;
|
||||
char line[256];
|
||||
FILE *fp = fopen("/etc/os-release", "r");
|
||||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
while (fgets(line, sizeof(line), fp)) {
|
||||
if (strstr(line, "ID=linuxmint") != NULL) {
|
||||
is_mint = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
return is_mint;
|
||||
}
|
||||
|
||||
bool is_desktop_mate()
|
||||
{
|
||||
const char* desktop = NULL;
|
||||
desktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
printf("Linux desktop, XDG_CURRENT_DESKTOP: %s\n", desktop == NULL ? "" : desktop);
|
||||
if (desktop == NULL) {
|
||||
desktop = getenv("XDG_SESSION_DESKTOP");
|
||||
printf("Linux desktop, XDG_SESSION_DESKTOP: %s\n", desktop == NULL ? "" : desktop);
|
||||
}
|
||||
if (desktop == NULL) {
|
||||
desktop = getenv("DESKTOP_SESSION");
|
||||
printf("Linux desktop, DESKTOP_SESSION: %s\n", desktop == NULL ? "" : desktop);
|
||||
}
|
||||
if (desktop != NULL && strcasecmp(desktop, "mate") == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool skip_setting_transparent()
|
||||
{
|
||||
if (is_desktop_mate()) {
|
||||
printf("Linux desktop, MATE\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_linux_mint()) {
|
||||
printf("Linux desktop, Linux Mint\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://github.com/flutter/flutter/issues/152154
|
||||
// Remove this workaround when flutter version is updated.
|
||||
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view)
|
||||
{
|
||||
GtkWidget *gl_area = NULL;
|
||||
|
||||
if (skip_setting_transparent()) {
|
||||
printf("Skip setting transparent\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Try setting transparent\n");
|
||||
|
||||
gl_area = find_gl_area(GTK_WIDGET(view));
|
||||
if (gl_area != NULL) {
|
||||
gtk_gl_area_set_has_alpha(GTK_GL_AREA(gl_area), TRUE);
|
||||
}
|
||||
|
||||
if (screen != NULL) {
|
||||
GdkVisual *visual = NULL;
|
||||
gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
|
||||
visual = gdk_screen_get_rgba_visual(screen);
|
||||
if (visual != NULL && gdk_screen_is_composited(screen)) {
|
||||
gtk_widget_set_visual(GTK_WIDGET(window), visual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "519350f1f40746798299e94786197d058353bac9"
|
||||
resolved-ref: "4f562ab49d289cfa36bfda7cff12746ec0200033"
|
||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
@@ -530,15 +530,6 @@ packages:
|
||||
url: "https://github.com/rustdesk-org/flutter_gpu_texture_renderer"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
flutter_improved_scrolling:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "62f09545149f320616467c306c8c5f71714a18e6"
|
||||
url: "https://github.com/rustdesk-org/flutter_improved_scrolling"
|
||||
source: git
|
||||
version: "0.0.3"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1277,10 +1268,11 @@ packages:
|
||||
texture_rgba_renderer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: texture_rgba_renderer
|
||||
sha256: cb048abdd800468ca40749ca10d1db9d1e6a055d1cde6234c05191293f0c7d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "."
|
||||
ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||
resolved-ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||
url: "https://github.com/rustdesk-org/flutter_texture_rgba_renderer"
|
||||
source: git
|
||||
version: "0.0.16"
|
||||
timing:
|
||||
dependency: transitive
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.3.3+52
|
||||
version: 1.3.4+53
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
@@ -71,13 +71,6 @@ dependencies:
|
||||
debounce_throttle: ^2.0.0
|
||||
file_picker: ^5.1.0
|
||||
flutter_svg: ^2.0.5
|
||||
flutter_improved_scrolling:
|
||||
# currently, we use flutter 3.10.0+.
|
||||
#
|
||||
# for flutter 3.0.5, please use official version(just comment code below).
|
||||
# if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below).
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter_improved_scrolling
|
||||
uni_links:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/uni_links
|
||||
@@ -91,7 +84,10 @@ dependencies:
|
||||
password_strength: ^0.2.0
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
flutter_keyboard_visibility: ^5.4.0
|
||||
texture_rgba_renderer: ^0.0.16
|
||||
texture_rgba_renderer:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter_texture_rgba_renderer
|
||||
ref: 42797e0f03141dc2b585f76c64a13974508058b4
|
||||
percent_indicator: ^4.2.2
|
||||
dropdown_button2: ^2.0.0
|
||||
flutter_gpu_texture_renderer:
|
||||
|
||||
@@ -345,7 +345,7 @@ fn convert_to_tfc_key(key: Key) -> Option<TFC_Key> {
|
||||
Key::Numpad9 => TFC_Key::N9,
|
||||
Key::Decimal => TFC_Key::NumpadDecimal,
|
||||
Key::Clear => TFC_Key::NumpadClear,
|
||||
Key::Pause => TFC_Key::PlayPause,
|
||||
Key::Pause => TFC_Key::Pause,
|
||||
Key::Print => TFC_Key::Print,
|
||||
Key::Snapshot => TFC_Key::PrintScreen,
|
||||
Key::Insert => TFC_Key::Insert,
|
||||
|
||||
@@ -13,22 +13,35 @@ pub const XDG_CURRENT_DESKTOP: &str = "XDG_CURRENT_DESKTOP";
|
||||
|
||||
pub struct Distro {
|
||||
pub name: String,
|
||||
pub id: String,
|
||||
pub version_id: String,
|
||||
}
|
||||
|
||||
impl Distro {
|
||||
fn new() -> Self {
|
||||
// to-do:
|
||||
// 1. Remove `run_cmds`, read file once
|
||||
// 2. Add more distro infos
|
||||
let name = run_cmds("awk -F'=' '/^NAME=/ {print $2}' /etc/os-release")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
let id = run_cmds("awk -F'=' '/^ID=/ {print $2}' /etc/os-release")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
let version_id = run_cmds("awk -F'=' '/^VERSION_ID=/ {print $2}' /etc/os-release")
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.to_string();
|
||||
Self { name, version_id }
|
||||
Self {
|
||||
name,
|
||||
id,
|
||||
version_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
|
||||
@@ -193,15 +193,11 @@ impl EncoderApi for HwRamEncoder {
|
||||
}
|
||||
|
||||
fn support_abr(&self) -> bool {
|
||||
["qsv", "vaapi", "mediacodec", "videotoolbox"]
|
||||
.iter()
|
||||
.all(|&x| !self.config.name.contains(x))
|
||||
["qsv", "vaapi"].iter().all(|&x| !self.config.name.contains(x))
|
||||
}
|
||||
|
||||
fn support_changing_quality(&self) -> bool {
|
||||
["vaapi", "mediacodec", "videotoolbox"]
|
||||
.iter()
|
||||
.all(|&x| !self.config.name.contains(x))
|
||||
["vaapi"].iter().all(|&x| !self.config.name.contains(x))
|
||||
}
|
||||
|
||||
fn latency_free(&self) -> bool {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.3.3
|
||||
pkgver=1.3.4
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
@@ -7,7 +7,7 @@ arch=('x86_64')
|
||||
url=""
|
||||
license=('AGPL-3.0')
|
||||
groups=()
|
||||
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'libva' 'libvdpau' 'libappindicator-gtk3' 'pam' 'gst-plugins-base' 'gst-plugin-pipewire')
|
||||
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'libva' 'libappindicator-gtk3' 'pam' 'gst-plugins-base' 'gst-plugin-pipewire')
|
||||
makedepends=()
|
||||
checkdepends=()
|
||||
optdepends=()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: rustdesk
|
||||
Version: 1.3.3
|
||||
Version: 1.3.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_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)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: rustdesk
|
||||
Version: 1.3.3
|
||||
Version: 1.3.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libvdpau libva pam gstreamer1-plugins-base
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3
|
||||
Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_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)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Version: 1.1.9
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libvdpau1 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire
|
||||
Recommends: libayatana-appindicator3-1
|
||||
|
||||
%description
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Name: rustdesk
|
||||
Version: 1.3.3
|
||||
Version: 1.3.4
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
URL: https://rustdesk.com
|
||||
Vendor: rustdesk <info@rustdesk.com>
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libvdpau1 libva2 pam gstreamer1-plugins-base
|
||||
Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva2 pam gstreamer1-plugins-base
|
||||
Recommends: libayatana-appindicator-gtk3
|
||||
|
||||
%description
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
From 7f12898fe8fd12c1042c98b34825ab2eda89e54d Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Sun, 24 Nov 2024 12:58:39 +0800
|
||||
Subject: [PATCH 1/2] videotoolbox changing bitrate
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/videotoolboxenc.c | 39 ++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 39 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/videotoolboxenc.c b/libavcodec/videotoolboxenc.c
|
||||
index 5ea9afee22..89c927cdcc 100644
|
||||
--- a/libavcodec/videotoolboxenc.c
|
||||
+++ b/libavcodec/videotoolboxenc.c
|
||||
@@ -278,6 +278,8 @@ typedef struct VTEncContext {
|
||||
int max_slice_bytes;
|
||||
int power_efficient;
|
||||
int max_ref_frames;
|
||||
+
|
||||
+ int last_bit_rate;
|
||||
} VTEncContext;
|
||||
|
||||
static int vt_dump_encoder(AVCodecContext *avctx)
|
||||
@@ -1174,6 +1176,7 @@ static int vtenc_create_encoder(AVCodecContext *avctx,
|
||||
int64_t one_second_value = 0;
|
||||
void *nums[2];
|
||||
|
||||
+ vtctx->last_bit_rate = bit_rate;
|
||||
int status = VTCompressionSessionCreate(kCFAllocatorDefault,
|
||||
avctx->width,
|
||||
avctx->height,
|
||||
@@ -2618,6 +2621,41 @@ static int vtenc_send_frame(AVCodecContext *avctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void update_config(AVCodecContext *avctx)
|
||||
+{
|
||||
+ VTEncContext *vtctx = avctx->priv_data;
|
||||
+
|
||||
+ if (avctx->codec_id != AV_CODEC_ID_PRORES) {
|
||||
+ if (avctx->bit_rate != vtctx->last_bit_rate) {
|
||||
+ av_log(avctx, AV_LOG_INFO, "Setting bit rate to %d\n", avctx->bit_rate);
|
||||
+ vtctx->last_bit_rate = avctx->bit_rate;
|
||||
+ SInt32 bit_rate = avctx->bit_rate;
|
||||
+ CFNumberRef bit_rate_num = CFNumberCreate(kCFAllocatorDefault,
|
||||
+ kCFNumberSInt32Type,
|
||||
+ &bit_rate);
|
||||
+ if (!bit_rate_num) return;
|
||||
+
|
||||
+ if (vtctx->constant_bit_rate) {
|
||||
+ int status = VTSessionSetProperty(vtctx->session,
|
||||
+ compat_keys.kVTCompressionPropertyKey_ConstantBitRate,
|
||||
+ bit_rate_num);
|
||||
+ if (status == kVTPropertyNotSupportedErr) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Error: -constant_bit_rate true is not supported by the encoder.\n");
|
||||
+ }
|
||||
+ } else {
|
||||
+ int status = VTSessionSetProperty(vtctx->session,
|
||||
+ kVTCompressionPropertyKey_AverageBitRate,
|
||||
+ bit_rate_num);
|
||||
+ if (!status) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Error: cannot set average bit rate: %d\n", status);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ CFRelease(bit_rate_num);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static av_cold int vtenc_frame(
|
||||
AVCodecContext *avctx,
|
||||
AVPacket *pkt,
|
||||
@@ -2630,6 +2668,7 @@ static av_cold int vtenc_frame(
|
||||
CMSampleBufferRef buf = NULL;
|
||||
ExtraSEI *sei = NULL;
|
||||
|
||||
+ update_config(avctx);
|
||||
if (frame) {
|
||||
status = vtenc_send_frame(avctx, vtctx, frame);
|
||||
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
246
res/vcpkg/ffmpeg/patch/0005-mediacodec-changing-bitrate.patch
Normal file
246
res/vcpkg/ffmpeg/patch/0005-mediacodec-changing-bitrate.patch
Normal file
@@ -0,0 +1,246 @@
|
||||
From ed73f8f6494d74ae47218f9503c7e3de385d9253 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Sun, 24 Nov 2024 14:17:39 +0800
|
||||
Subject: [PATCH 1/2] mediacodec changing bitrate
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/mediacodec_wrapper.c | 97 +++++++++++++++++++++++++++++++++
|
||||
libavcodec/mediacodec_wrapper.h | 7 +++
|
||||
libavcodec/mediacodecenc.c | 18 ++++++
|
||||
3 files changed, 122 insertions(+)
|
||||
|
||||
diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c
|
||||
index 306359071e..7edb38a7d7 100644
|
||||
--- a/libavcodec/mediacodec_wrapper.c
|
||||
+++ b/libavcodec/mediacodec_wrapper.c
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "ffjni.h"
|
||||
#include "mediacodec_wrapper.h"
|
||||
|
||||
+#define PARAMETER_KEY_VIDEO_BITRATE "video-bitrate"
|
||||
+
|
||||
struct JNIAMediaCodecListFields {
|
||||
|
||||
jclass mediacodec_list_class;
|
||||
@@ -195,6 +197,8 @@ struct JNIAMediaCodecFields {
|
||||
jmethodID set_input_surface_id;
|
||||
jmethodID signal_end_of_input_stream_id;
|
||||
|
||||
+ jmethodID set_parameters_id;
|
||||
+
|
||||
jclass mediainfo_class;
|
||||
|
||||
jmethodID init_id;
|
||||
@@ -248,6 +252,8 @@ static const struct FFJniField jni_amediacodec_mapping[] = {
|
||||
{ "android/media/MediaCodec", "setInputSurface", "(Landroid/view/Surface;)V", FF_JNI_METHOD, OFFSET(set_input_surface_id), 0 },
|
||||
{ "android/media/MediaCodec", "signalEndOfInputStream", "()V", FF_JNI_METHOD, OFFSET(signal_end_of_input_stream_id), 0 },
|
||||
|
||||
+ { "android/media/MediaCodec", "setParameters", "(Landroid/os/Bundle;)V", FF_JNI_METHOD, OFFSET(set_parameters_id), 0 },
|
||||
+
|
||||
{ "android/media/MediaCodec$BufferInfo", NULL, NULL, FF_JNI_CLASS, OFFSET(mediainfo_class), 1 },
|
||||
|
||||
{ "android/media/MediaCodec.BufferInfo", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
||||
@@ -292,6 +298,24 @@ typedef struct FFAMediaCodecJni {
|
||||
|
||||
static const FFAMediaCodec media_codec_jni;
|
||||
|
||||
+struct JNIABundleFields
|
||||
+{
|
||||
+ jclass bundle_class;
|
||||
+ jmethodID init_id;
|
||||
+ jmethodID put_int_id;
|
||||
+};
|
||||
+
|
||||
+#define OFFSET(x) offsetof(struct JNIABundleFields, x)
|
||||
+static const struct FFJniField jni_abundle_mapping[] = {
|
||||
+ { "android/os/Bundle", NULL, NULL, FF_JNI_CLASS, OFFSET(bundle_class), 1 },
|
||||
+
|
||||
+ { "android/os/Bundle", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
||||
+ { "android/os/Bundle", "putInt", "(Ljava/lang/String;I)V", FF_JNI_METHOD, OFFSET(put_int_id), 1 },
|
||||
+
|
||||
+ { NULL }
|
||||
+};
|
||||
+#undef OFFSET
|
||||
+
|
||||
#define JNI_GET_ENV_OR_RETURN(env, log_ctx, ret) do { \
|
||||
(env) = ff_jni_get_env(log_ctx); \
|
||||
if (!(env)) { \
|
||||
@@ -1761,6 +1785,69 @@ static int mediacodec_jni_signalEndOfInputStream(FFAMediaCodec *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int mediacodec_jni_setParameter(FFAMediaCodec *ctx, const char* name, int value)
|
||||
+{
|
||||
+ JNIEnv *env = NULL;
|
||||
+ struct JNIABundleFields jfields = { 0 };
|
||||
+ jobject object = NULL;
|
||||
+ jstring key = NULL;
|
||||
+ FFAMediaCodecJni *codec = (FFAMediaCodecJni *)ctx;
|
||||
+ void *log_ctx = codec;
|
||||
+ int ret = -1;
|
||||
+
|
||||
+ JNI_GET_ENV_OR_RETURN(env, codec, AVERROR_EXTERNAL);
|
||||
+
|
||||
+ if (ff_jni_init_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx) < 0) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to init jfields\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ object = (*env)->NewObject(env, jfields.bundle_class, jfields.init_id);
|
||||
+ if (!object) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to create bundle object\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ key = ff_jni_utf_chars_to_jstring(env, name, log_ctx);
|
||||
+ if (!key) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to convert key to jstring\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ (*env)->CallVoidMethod(env, object, jfields.put_int_id, key, value);
|
||||
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ if (!codec->jfields.set_parameters_id) {
|
||||
+ av_log(log_ctx, AV_LOG_ERROR, "System doesn't support setParameters\n");
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ (*env)->CallVoidMethod(env, codec->object, codec->jfields.set_parameters_id, object);
|
||||
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
||||
+ goto fail;
|
||||
+ }
|
||||
+
|
||||
+ ret = 0;
|
||||
+
|
||||
+fail:
|
||||
+ if (key) {
|
||||
+ (*env)->DeleteLocalRef(env, key);
|
||||
+ }
|
||||
+ if (object) {
|
||||
+ (*env)->DeleteLocalRef(env, object);
|
||||
+ }
|
||||
+ ff_jni_reset_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int mediacodec_jni_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
||||
+{
|
||||
+ return mediacodec_jni_setParameter(ctx, PARAMETER_KEY_VIDEO_BITRATE, bitrate);
|
||||
+}
|
||||
+
|
||||
static const FFAMediaFormat media_format_jni = {
|
||||
.class = &amediaformat_class,
|
||||
|
||||
@@ -1820,6 +1907,8 @@ static const FFAMediaCodec media_codec_jni = {
|
||||
.getConfigureFlagEncode = mediacodec_jni_getConfigureFlagEncode,
|
||||
.cleanOutputBuffers = mediacodec_jni_cleanOutputBuffers,
|
||||
.signalEndOfInputStream = mediacodec_jni_signalEndOfInputStream,
|
||||
+
|
||||
+ .setDynamicBitrate = mediacodec_jni_setDynamicBitrate,
|
||||
};
|
||||
|
||||
typedef struct FFAMediaFormatNdk {
|
||||
@@ -2428,6 +2517,12 @@ static int mediacodec_ndk_signalEndOfInputStream(FFAMediaCodec *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static int mediacodec_ndk_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
||||
+{
|
||||
+ av_log(ctx, AV_LOG_ERROR, "ndk setDynamicBitrate unavailable\n");
|
||||
+ return -1;
|
||||
+}
|
||||
+
|
||||
static const FFAMediaFormat media_format_ndk = {
|
||||
.class = &amediaformat_ndk_class,
|
||||
|
||||
@@ -2489,6 +2584,8 @@ static const FFAMediaCodec media_codec_ndk = {
|
||||
.getConfigureFlagEncode = mediacodec_ndk_getConfigureFlagEncode,
|
||||
.cleanOutputBuffers = mediacodec_ndk_cleanOutputBuffers,
|
||||
.signalEndOfInputStream = mediacodec_ndk_signalEndOfInputStream,
|
||||
+
|
||||
+ .setDynamicBitrate = mediacodec_ndk_setDynamicBitrate,
|
||||
};
|
||||
|
||||
FFAMediaFormat *ff_AMediaFormat_new(int ndk)
|
||||
diff --git a/libavcodec/mediacodec_wrapper.h b/libavcodec/mediacodec_wrapper.h
|
||||
index 11a4260497..86c64556ad 100644
|
||||
--- a/libavcodec/mediacodec_wrapper.h
|
||||
+++ b/libavcodec/mediacodec_wrapper.h
|
||||
@@ -219,6 +219,8 @@ struct FFAMediaCodec {
|
||||
|
||||
// For encoder with FFANativeWindow as input.
|
||||
int (*signalEndOfInputStream)(FFAMediaCodec *);
|
||||
+
|
||||
+ int (*setDynamicBitrate)(FFAMediaCodec *codec, int bitrate);
|
||||
};
|
||||
|
||||
static inline char *ff_AMediaCodec_getName(FFAMediaCodec *codec)
|
||||
@@ -343,6 +345,11 @@ static inline int ff_AMediaCodec_signalEndOfInputStream(FFAMediaCodec *codec)
|
||||
return codec->signalEndOfInputStream(codec);
|
||||
}
|
||||
|
||||
+static inline int ff_AMediaCodec_setDynamicBitrate(FFAMediaCodec *codec, int bitrate)
|
||||
+{
|
||||
+ return codec->setDynamicBitrate(codec, bitrate);
|
||||
+}
|
||||
+
|
||||
int ff_Build_SDK_INT(AVCodecContext *avctx);
|
||||
|
||||
enum FFAMediaFormatColorRange {
|
||||
diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
|
||||
index d3bf27cb7f..621529d686 100644
|
||||
--- a/libavcodec/mediacodecenc.c
|
||||
+++ b/libavcodec/mediacodecenc.c
|
||||
@@ -73,6 +73,8 @@ typedef struct MediaCodecEncContext {
|
||||
int bitrate_mode;
|
||||
int level;
|
||||
int pts_as_dts;
|
||||
+
|
||||
+ int last_bit_rate;
|
||||
} MediaCodecEncContext;
|
||||
|
||||
enum {
|
||||
@@ -155,6 +157,8 @@ static av_cold int mediacodec_init(AVCodecContext *avctx)
|
||||
int ret;
|
||||
int gop;
|
||||
|
||||
+ s->last_bit_rate = avctx->bit_rate;
|
||||
+
|
||||
if (s->use_ndk_codec < 0)
|
||||
s->use_ndk_codec = !av_jni_get_java_vm(avctx);
|
||||
|
||||
@@ -515,12 +519,26 @@ static int mediacodec_send(AVCodecContext *avctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void update_config(AVCodecContext *avctx)
|
||||
+{
|
||||
+ MediaCodecEncContext *s = avctx->priv_data;
|
||||
+ if (avctx->bit_rate != s->last_bit_rate) {
|
||||
+ s->last_bit_rate = avctx->bit_rate;
|
||||
+ if (0 != ff_AMediaCodec_setDynamicBitrate(s->codec, avctx->bit_rate)) {
|
||||
+ av_log(avctx, AV_LOG_ERROR, "Failed to set bitrate to %d\n", avctx->bit_rate);
|
||||
+ } else {
|
||||
+ av_log(avctx, AV_LOG_INFO, "Set bitrate to %d\n", avctx->bit_rate);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int mediacodec_encode(AVCodecContext *avctx, AVPacket *pkt)
|
||||
{
|
||||
MediaCodecEncContext *s = avctx->priv_data;
|
||||
int ret;
|
||||
int got_packet = 0;
|
||||
|
||||
+ update_config(avctx);
|
||||
// Return on three case:
|
||||
// 1. Serious error
|
||||
// 2. Got a packet success
|
||||
--
|
||||
2.34.1
|
||||
|
||||
1993
res/vcpkg/ffmpeg/patch/0006-dlopen-libva.patch
Normal file
1993
res/vcpkg/ffmpeg/patch/0006-dlopen-libva.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,9 @@ vcpkg_from_github(
|
||||
patch/0001-avcodec-amfenc-add-query_timeout-option-for-h264-hev.patch
|
||||
patch/0002-libavcodec-amfenc-reconfig-when-bitrate-change.patch
|
||||
patch/0003-amf-colorspace.patch
|
||||
patch/0004-videotoolbox-changing-bitrate.patch
|
||||
patch/0005-mediacodec-changing-bitrate.patch
|
||||
patch/0006-dlopen-libva.patch
|
||||
)
|
||||
|
||||
if(SOURCE_PATH MATCHES " ")
|
||||
@@ -77,13 +80,15 @@ else()
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_IS_LINUX)
|
||||
string(APPEND OPTIONS "\
|
||||
string(APPEND OPTIONS "\
|
||||
--target-os=linux \
|
||||
--enable-pthreads \
|
||||
--disable-vdpau \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm")
|
||||
else()
|
||||
string(APPEND OPTIONS "\
|
||||
string(APPEND OPTIONS "\
|
||||
--enable-cuda \
|
||||
--enable-ffnvcodec \
|
||||
--enable-encoder=h264_nvenc \
|
||||
@@ -98,8 +103,9 @@ if(VCPKG_TARGET_IS_LINUX)
|
||||
--enable-encoder=h264_vaapi \
|
||||
--enable-encoder=hevc_vaapi \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
string(APPEND OPTIONS "\
|
||||
string(APPEND OPTIONS "\
|
||||
--enable-cuda_llvm \
|
||||
")
|
||||
endif()
|
||||
@@ -127,7 +133,8 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
--enable-libmfx \
|
||||
--enable-encoder=h264_qsv \
|
||||
--enable-encoder=hevc_qsv \
|
||||
")
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
|
||||
set(LIB_MACHINE_ARG /machine:x86)
|
||||
string(APPEND OPTIONS " --arch=i686 --enable-cross-compile")
|
||||
@@ -189,6 +196,7 @@ endif()
|
||||
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include\"")
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include\"")
|
||||
|
||||
if(VCPKG_TARGET_IS_WINDOWS)
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"")
|
||||
string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include/mfx\"")
|
||||
@@ -202,9 +210,11 @@ if(VCPKG_DETECTED_CMAKE_C_COMPILER)
|
||||
get_filename_component(CC_filename "${VCPKG_DETECTED_CMAKE_C_COMPILER}" NAME)
|
||||
set(ENV{CC} "${CC_filename}")
|
||||
string(APPEND OPTIONS " --cc=${CC_filename}")
|
||||
|
||||
if(VCPKG_HOST_IS_WINDOWS)
|
||||
string(APPEND OPTIONS " --host_cc=${CC_filename}")
|
||||
endif()
|
||||
|
||||
list(APPEND prog_env "${CC_path}")
|
||||
endif()
|
||||
|
||||
@@ -282,6 +292,7 @@ if(VCPKG_HOST_IS_WINDOWS)
|
||||
else()
|
||||
# find_program(SHELL bash)
|
||||
endif()
|
||||
|
||||
list(REMOVE_DUPLICATES prog_env)
|
||||
vcpkg_add_to_path(PREPEND ${prog_env})
|
||||
|
||||
|
||||
@@ -1188,9 +1188,15 @@ impl VideoHandler {
|
||||
pub fn new(format: CodecFormat, _display: usize) -> Self {
|
||||
let luid = Self::get_adapter_luid();
|
||||
log::info!("new video handler for display #{_display}, format: {format:?}, luid: {luid:?}");
|
||||
let rgba_format =
|
||||
if cfg!(feature = "flutter") && (cfg!(windows) || cfg!(target_os = "linux")) {
|
||||
ImageFormat::ABGR
|
||||
} else {
|
||||
ImageFormat::ARGB
|
||||
};
|
||||
VideoHandler {
|
||||
decoder: Decoder::new(format, luid),
|
||||
rgb: ImageRgb::new(ImageFormat::ARGB, crate::get_dst_align_rgba()),
|
||||
rgb: ImageRgb::new(rgba_format, crate::get_dst_align_rgba()),
|
||||
texture: Default::default(),
|
||||
recorder: Default::default(),
|
||||
record: false,
|
||||
@@ -3293,6 +3299,7 @@ lazy_static::lazy_static! {
|
||||
("VK_PRINT", Key::ControlKey(ControlKey::Print)),
|
||||
("VK_EXECUTE", Key::ControlKey(ControlKey::Execute)),
|
||||
("VK_SNAPSHOT", Key::ControlKey(ControlKey::Snapshot)),
|
||||
("VK_SCROLL", Key::ControlKey(ControlKey::Scroll)),
|
||||
("VK_INSERT", Key::ControlKey(ControlKey::Insert)),
|
||||
("VK_DELETE", Key::ControlKey(ControlKey::Delete)),
|
||||
("VK_HELP", Key::ControlKey(ControlKey::Help)),
|
||||
|
||||
@@ -19,6 +19,7 @@ use hbb_common::allow_err;
|
||||
use hbb_common::{
|
||||
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
|
||||
fs, lazy_static, log,
|
||||
message_proto::Hash,
|
||||
rendezvous_proto::ConnType,
|
||||
ResultType,
|
||||
};
|
||||
@@ -1417,7 +1418,8 @@ pub fn main_get_last_remote_id() -> String {
|
||||
}
|
||||
|
||||
pub fn main_get_software_update_url() {
|
||||
if get_local_option("enable-check-update".to_string()) != "N" {
|
||||
let opt = get_local_option(config::keys::OPTION_ENABLE_CHECK_UPDATE.to_string());
|
||||
if config::option2bool(config::keys::OPTION_ENABLE_CHECK_UPDATE, &opt) {
|
||||
crate::common::check_software_update();
|
||||
}
|
||||
}
|
||||
@@ -2341,6 +2343,25 @@ pub fn main_audio_support_loopback() -> SyncReturn<bool> {
|
||||
SyncReturn(is_surpport)
|
||||
}
|
||||
|
||||
pub fn get_os_distro_info() -> SyncReturn<String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let distro = &hbb_common::platform::linux::DISTRO;
|
||||
SyncReturn(
|
||||
serde_json::to_string(&HashMap::from([
|
||||
("name", distro.name.clone()),
|
||||
("id", distro.id.clone()),
|
||||
("version_id", distro.version_id.clone()),
|
||||
]))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
SyncReturn("".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use hbb_common::{config, log};
|
||||
|
||||
@@ -145,7 +145,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to make direct connection to remote desktop", "Impossible d'établir une connexion directe"),
|
||||
("Set Password", "Définir le mot de passe"),
|
||||
("OS Password", "Mot de passe du système d'exploitation"),
|
||||
("install_tip", "Vous utilisez une version non installée. En raison des restrictions UAC, en tant que terminal contrôlé, dans certains cas, il ne sera pas en mesure de contrôler la souris et le clavier ou d'enregistrer l'écran. Veuillez cliquer sur le bouton ci-dessous pour installer RustDesk au système pour éviter la question ci-dessus."),
|
||||
("install_tip", "RustDesk n'est pas installé, ce qui peut limiter son utilisation à cause de l'UAC. Cliquez ci-dessous pour l'installer."),
|
||||
("Click to upgrade", "Cliquer pour mettre à niveau"),
|
||||
("Click to download", "Cliquer pour télécharger"),
|
||||
("Click to update", "Cliquer pour mettre à jour"),
|
||||
|
||||
@@ -654,6 +654,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload files", "File upload"),
|
||||
("Clipboard is synchronized", "Gli appunti sono sincronizzati"),
|
||||
("Update client clipboard", "Aggiorna appunti client"),
|
||||
("Untagged", ""),
|
||||
("Untagged", "Senza tag"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -653,7 +653,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "폴더 업로드"),
|
||||
("Upload files", "파일 업로드"),
|
||||
("Clipboard is synchronized", "클립보드가 동기화됨"),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("Update client clipboard", "클라이언트 클립보드 업데이트"),
|
||||
("Untagged", "태그 없음"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -653,7 +653,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Augšupielādēt mapi"),
|
||||
("Upload files", "Augšupielādēt failus"),
|
||||
("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("Update client clipboard", "Atjaunināt klienta starpliktuvi"),
|
||||
("Untagged", "Neatzīmēts"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -654,6 +654,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload files", "Загрузить файлы"),
|
||||
("Clipboard is synchronized", "Буфер обмена синхронизирован"),
|
||||
("Update client clipboard", "Обновить буфер обмена клиента"),
|
||||
("Untagged", ""),
|
||||
("Untagged", "Без метки"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -653,7 +653,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "上傳資料夾"),
|
||||
("Upload files", "上傳檔案"),
|
||||
("Clipboard is synchronized", "剪貼簿已同步"),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("Update client clipboard", "更新客戶端的剪貼簿"),
|
||||
("Untagged", "無標籤"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -120,9 +120,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Оригінал"),
|
||||
("Shrink", "Зменшити"),
|
||||
("Stretch", "Розтягнути"),
|
||||
("Scrollbar", "Смуга прокрутки"),
|
||||
("ScrollAuto", "Автоматична прокрутка"),
|
||||
("Good image quality", "Хороша якість зображення"),
|
||||
("Scrollbar", "Смужка гортання"),
|
||||
("ScrollAuto", "Автоматичне гортання"),
|
||||
("Good image quality", "Гарна якість зображення"),
|
||||
("Balanced", "Збалансована"),
|
||||
("Optimize reaction time", "Оптимізувати час реакції"),
|
||||
("Custom", "Користувацька"),
|
||||
@@ -199,10 +199,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter the folder name", "Будь ласка, введіть назву для теки"),
|
||||
("Fix it", "Виправити"),
|
||||
("Warning", "Попередження"),
|
||||
("Login screen using Wayland is not supported", "Вхід в систему з використанням Wayland не підтримується"),
|
||||
("Login screen using Wayland is not supported", "Екран входу, який використовує Wayland, не підтримується"),
|
||||
("Reboot required", "Потрібне перезавантаження"),
|
||||
("Unsupported display server", "Графічний сервер не підтримується"),
|
||||
("x11 expected", "Очікується X11"),
|
||||
("x11 expected", "Потрібен X11"),
|
||||
("Port", "Порт"),
|
||||
("Settings", "Налаштування"),
|
||||
("Username", "Імʼя користувача"),
|
||||
@@ -220,21 +220,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Verification code", "Код підтвердження"),
|
||||
("verification_tip", "Код підтвердження надіслано на зареєстровану email-адресу, введіть код підтвердження для продовження авторизації."),
|
||||
("Logout", "Вийти"),
|
||||
("Tags", "Теги"),
|
||||
("Tags", "Мітки"),
|
||||
("Search ID", "Пошук за ID"),
|
||||
("whitelist_sep", "Відокремлення комою, крапкою з комою, пропуском або новим рядком"),
|
||||
("Add ID", "Додати ID"),
|
||||
("Add Tag", "Додати ключове слово"),
|
||||
("Unselect all tags", "Скасувати вибір усіх тегів"),
|
||||
("Add Tag", "Додати мітку"),
|
||||
("Unselect all tags", "Скасувати вибір усіх міток"),
|
||||
("Network error", "Помилка мережі"),
|
||||
("Username missed", "Імʼя користувача відсутнє"),
|
||||
("Password missed", "Пароль відсутній"),
|
||||
("Wrong credentials", "Неправильні дані"),
|
||||
("The verification code is incorrect or has expired", "Код підтвердження некоректний або протермінований"),
|
||||
("Edit Tag", "Редагувати тег"),
|
||||
("Edit Tag", "Редагувати мітку"),
|
||||
("Forget Password", "Не зберігати пароль"),
|
||||
("Favorites", "Вибране"),
|
||||
("Add to Favorites", "Додати в обране"),
|
||||
("Add to Favorites", "Додати до обраного"),
|
||||
("Remove from Favorites", "Видалити з обраного"),
|
||||
("Empty", "Пусто"),
|
||||
("Invalid folder name", "Неприпустима назва теки"),
|
||||
@@ -279,7 +279,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Screen Connection", "Підключення екрана"),
|
||||
("Do you accept?", "Ви згодні?"),
|
||||
("Open System Setting", "Відкрити налаштування системи"),
|
||||
("How to get Android input permission?", "Як отримати дозвіл на введення Android?"),
|
||||
("How to get Android input permission?", "Як отримати дозвіл на введення в Android?"),
|
||||
("android_input_permission_tip1", "Для того, щоб віддалений пристрій міг керувати вашим Android-пристроєм за допомогою миші або дотику, вам необхідно дозволити RustDesk використовувати службу \"Спеціальні можливості\"."),
|
||||
("android_input_permission_tip2", "Будь ласка, перейдіть на наступну сторінку системних налаштувань, знайдіть та увійдіть у [Встановлені служби], увімкніть службу [RustDesk Input]."),
|
||||
("android_new_connection_tip", "Отримано новий запит на керування вашим поточним пристроєм."),
|
||||
@@ -329,15 +329,15 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Display Settings", "Налаштування дисплею"),
|
||||
("Ratio", "Співвідношення"),
|
||||
("Image Quality", "Якість зображення"),
|
||||
("Scroll Style", "Стиль прокрутки"),
|
||||
("Scroll Style", "Стиль гортання"),
|
||||
("Show Toolbar", "Показати панель інструментів"),
|
||||
("Hide Toolbar", "Приховати панель інструментів"),
|
||||
("Direct Connection", "Пряме підключення"),
|
||||
("Relay Connection", "Ретрансльоване підключення"),
|
||||
("Secure Connection", "Безпечне підключення"),
|
||||
("Insecure Connection", "Небезпечне підключення"),
|
||||
("Scale original", "Оригінал масштабу"),
|
||||
("Scale adaptive", "Масштаб адаптивний"),
|
||||
("Scale original", "Оригінальний масштаб"),
|
||||
("Scale adaptive", "Адаптивний масштаб"),
|
||||
("General", "Загальні"),
|
||||
("Security", "Безпека"),
|
||||
("Theme", "Тема"),
|
||||
@@ -402,7 +402,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Hide connection management window", "Приховати вікно керування підключеннями"),
|
||||
("hide_cm_tip", "Дозволено приховати лише якщо сеанс підтверджується постійним паролем"),
|
||||
("wayland_experiment_tip", "Підтримка Wayland на експериментальній стадії, будь ласка, використовуйте X11, якщо необхідний автоматичний доступ."),
|
||||
("Right click to select tabs", "Правий клік для вибору вкладки"),
|
||||
("Right click to select tabs", "Вибір вкладок клацанням правою"),
|
||||
("Skipped", "Пропущено"),
|
||||
("Add to address book", "Додати IP до Адресної книги"),
|
||||
("Group", "Група"),
|
||||
@@ -513,7 +513,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop", "Зупинити"),
|
||||
("exceed_max_devices", "У вас максимальна кількість керованих пристроїв."),
|
||||
("Sync with recent sessions", "Синхронізація з нещодавніми сеансами"),
|
||||
("Sort tags", "Сортувати теги"),
|
||||
("Sort tags", "Сортувати мітки"),
|
||||
("Open connection in new tab", "Відкрити підключення в новій вкладці"),
|
||||
("Move tab to new window", "Перемістити вкладку до нового вікна"),
|
||||
("Can not be empty", "Не може бути порожнім"),
|
||||
@@ -524,7 +524,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Grid View", "Перегляд ґраткою"),
|
||||
("List View", "Перегляд списком"),
|
||||
("Select", "Вибрати"),
|
||||
("Toggle Tags", "Видимість тегів"),
|
||||
("Toggle Tags", "Видимість міток"),
|
||||
("pull_ab_failed_tip", "Не вдалося оновити адресну книгу"),
|
||||
("push_ab_failed_tip", "Не вдалося синхронізувати адресну книгу"),
|
||||
("synced_peer_readded_tip", "Пристрої з нещодавніх сеансів будуть синхронізовані з адресною книгою."),
|
||||
@@ -533,7 +533,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("HSV Color", "Колір HSV"),
|
||||
("Installation Successful!", "Успішне встановлення!"),
|
||||
("Installation failed!", "Невдале встановлення!"),
|
||||
("Reverse mouse wheel", "Зворотній напрям прокрутки"),
|
||||
("Reverse mouse wheel", "Зворотній напрям гортання"),
|
||||
("{} sessions", "{} сеансів"),
|
||||
("scam_title", "Вас можуть ОБМАНУТИ!"),
|
||||
("scam_text1", "Якщо ви розмовляєте по телефону з кимось, кого НЕ ЗНАЄТЕ чи кому НЕ ДОВІРЯЄТЕ, і ця особа хоче, щоб ви використали RustDesk та запустили службу, не робіть цього та негайно завершіть дзвінок."),
|
||||
@@ -654,6 +654,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload files", "Надіслати файли"),
|
||||
("Clipboard is synchronized", "Буфер обміну синхронізовано"),
|
||||
("Update client clipboard", "Оновити буфер обміну клієнта"),
|
||||
("Untagged", ""),
|
||||
("Untagged", "Без міток"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -222,6 +222,11 @@ extern "C"
|
||||
return IsWindowsServer();
|
||||
}
|
||||
|
||||
bool is_windows_10_or_greater()
|
||||
{
|
||||
return IsWindows10OrGreater();
|
||||
}
|
||||
|
||||
HANDLE LaunchProcessWin(LPCWSTR cmd, DWORD dwSessionId, BOOL as_user, DWORD *pDwTokenPid)
|
||||
{
|
||||
HANDLE hProcess = NULL;
|
||||
|
||||
@@ -479,6 +479,7 @@ extern "C" {
|
||||
fn selectInputDesktop() -> BOOL;
|
||||
fn inputDesktopSelected() -> BOOL;
|
||||
fn is_windows_server() -> BOOL;
|
||||
fn is_windows_10_or_greater() -> BOOL;
|
||||
fn handleMask(
|
||||
out: *mut u8,
|
||||
mask: *const u8,
|
||||
@@ -1559,6 +1560,11 @@ pub fn is_win_server() -> bool {
|
||||
unsafe { is_windows_server() > 0 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_win_10_or_greater() -> bool {
|
||||
unsafe { is_windows_10_or_greater() > 0 }
|
||||
}
|
||||
|
||||
pub fn bootstrap() {
|
||||
if let Ok(lic) = get_license_from_exe_name() {
|
||||
*config::EXE_RENDEZVOUS_SERVER.write().unwrap() = lic.host.clone();
|
||||
|
||||
@@ -239,7 +239,7 @@ pub mod service {
|
||||
(enigo::Key::Select, evdev::Key::KEY_SELECT),
|
||||
(enigo::Key::Print, evdev::Key::KEY_PRINT),
|
||||
// (enigo::Key::Execute, evdev::Key::KEY_EXECUTE),
|
||||
// (enigo::Key::Snapshot, evdev::Key::KEY_SNAPSHOT),
|
||||
(enigo::Key::Snapshot, evdev::Key::KEY_SYSRQ),
|
||||
(enigo::Key::Insert, evdev::Key::KEY_INSERT),
|
||||
(enigo::Key::Help, evdev::Key::KEY_HELP),
|
||||
(enigo::Key::Sleep, evdev::Key::KEY_SLEEP),
|
||||
@@ -247,7 +247,7 @@ pub mod service {
|
||||
(enigo::Key::Scroll, evdev::Key::KEY_SCROLLLOCK),
|
||||
(enigo::Key::NumLock, evdev::Key::KEY_NUMLOCK),
|
||||
(enigo::Key::RWin, evdev::Key::KEY_RIGHTMETA),
|
||||
(enigo::Key::Apps, evdev::Key::KEY_CONTEXT_MENU),
|
||||
(enigo::Key::Apps, evdev::Key::KEY_COMPOSE), // it's a little strange that the key is mapped to KEY_COMPOSE, not KEY_MENU
|
||||
(enigo::Key::Multiply, evdev::Key::KEY_KPASTERISK),
|
||||
(enigo::Key::Add, evdev::Key::KEY_KPPLUS),
|
||||
(enigo::Key::Subtract, evdev::Key::KEY_KPMINUS),
|
||||
|
||||
@@ -173,21 +173,36 @@ pub fn get_option<T: AsRef<str>>(key: T) -> String {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn use_texture_render() -> bool {
|
||||
cfg!(feature = "flutter") && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y"
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
return false;
|
||||
#[cfg(target_os = "ios")]
|
||||
return false;
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
pub fn use_texture_render() -> bool {
|
||||
cfg!(feature = "flutter") && LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
return cfg!(feature = "flutter")
|
||||
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
|
||||
#[inline]
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub fn use_texture_render() -> bool {
|
||||
false
|
||||
#[cfg(target_os = "linux")]
|
||||
return cfg!(feature = "flutter")
|
||||
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if !cfg!(feature = "flutter") {
|
||||
return false;
|
||||
}
|
||||
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
#[cfg(debug_assertions)]
|
||||
let default_texture = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let default_texture = crate::platform::is_win_10_or_greater();
|
||||
if default_texture {
|
||||
LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
|
||||
} else {
|
||||
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
Reference in New Issue
Block a user