Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
63e22b7685 | ||
|
a02d2bb4ac | ||
|
0f7d78c263 | ||
|
0e321bd845 | ||
|
875b738222 | ||
|
b39e851262 | ||
|
ec466d459f | ||
|
d4a712bb32 | ||
|
1c17fddf51 | ||
|
3c838e7a92 | ||
|
8f44787ba3 | ||
|
12e15b5a37 | ||
|
588103c6dc | ||
|
2ce9b108ed | ||
|
93e3107881 | ||
|
468bdd6cc6 | ||
|
d5c5825ffd | ||
|
fe4094777f | ||
|
f13ef48cec | ||
|
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.5"
|
||||
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.53
|
||||
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.5"
|
||||
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
|
||||
|
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1290,7 +1290,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.15.3"
|
||||
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#4d318ff778063ce14669fd4bd67a1673653fc6e5"
|
||||
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#7cb4ed0bd5546bf209edde0d0e9da5194753f2c0"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"cidre",
|
||||
@ -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.5"
|
||||
dependencies = [
|
||||
"android-wakelock",
|
||||
"android_logger",
|
||||
@ -5594,7 +5594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.3.3"
|
||||
version = "1.3.5"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs 5.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.3.3"
|
||||
version = "1.3.5"
|
||||
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.5
|
||||
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.5
|
||||
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.
|
||||
|
||||
|
@ -9,12 +9,12 @@
|
||||
<b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
|
||||
</p>
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
채팅하기: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
|
||||
Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [스스로 설정](https://rustdesk.com/server)하는 것도, [스스로 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [직접 설정](https://rustdesk.com/server)하거나 [직접 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||

|
||||
|
||||
@ -43,9 +43,9 @@ RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`docs/C
|
||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||
|
||||
- run `cargo run`
|
||||
- 실행 `cargo run`
|
||||
|
||||
## [Build](https://rustdesk.com/docs/en/dev/build/)
|
||||
## [빌드](https://rustdesk.com/docs/en/dev/build/)
|
||||
|
||||
## Linux에서 빌드 순서
|
||||
|
||||
@ -67,7 +67,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
|
||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||
```
|
||||
|
||||
### Install vcpkg
|
||||
### vcpkg 설치
|
||||
|
||||
```sh
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
@ -79,7 +79,7 @@ export VCPKG_ROOT=$HOME/vcpkg
|
||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||
```
|
||||
|
||||
### Fix libvpx (For Fedora)
|
||||
### libvpx 수정 (For Fedora용)
|
||||
|
||||
```sh
|
||||
cd vcpkg/buildtrees/libvpx/src
|
||||
@ -92,7 +92,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||
cd
|
||||
```
|
||||
|
||||
### Build
|
||||
### 빌드
|
||||
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
@ -107,7 +107,7 @@ VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||
|
||||
## Docker에 빌드하는 방법
|
||||
|
||||
레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
||||
리포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/rustdesk/rustdesk
|
||||
@ -115,13 +115,13 @@ cd rustdesk
|
||||
docker build -t "rustdesk-builder" .
|
||||
```
|
||||
|
||||
이후, 애플리케이션을 빌드할 필요가 있을 때마다, 이하의 커맨드를 실행합니다.
|
||||
이후, 애플리케이션을 빌드할 필요가 있을 때마다, 아래의의 명령을 실행합니다.
|
||||
|
||||
```sh
|
||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||
```
|
||||
|
||||
첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 커맨드에 다른 인수를 지정할 필요가 있다면, 커맨드 끝에 있는 `<OPTIONAL-ARGS>` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 커맨드 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 커맨드로 실행할 수 있습니다.
|
||||
첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 명령에 다른 인수를 지정할 필요가 있다면, 명령 끝에 있는 `<OPTIONAL-ARGS>` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 명령 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 명령으로 실행할 수 있습니다.
|
||||
|
||||
```sh
|
||||
target/debug/rustdesk
|
||||
@ -133,9 +133,9 @@ target/debug/rustdesk
|
||||
target/release/rustdesk
|
||||
```
|
||||
|
||||
커맨드를 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 서브커맨드는 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다.
|
||||
명령을 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 하위 명령은 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다.
|
||||
|
||||
## File Structure
|
||||
## 파일 구조
|
||||
|
||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 비디오 코덱, 설정, tcp/udp 랩퍼, protobuf, 파일 전송을 위한 fs 함수, 그 외 유틸리티 함수
|
||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡처
|
||||
@ -143,12 +143,12 @@ target/release/rustdesk
|
||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 오디오, 클립보드, 입력, 비디오 서비스 그리고 네트워크 연결
|
||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 피어 접속 시작
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트(TCP hole punching) 혹은 relayed 접속
|
||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트 (TCP hole punching) 혹은 relayed 접속
|
||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 플랫폼 고유의 코드
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client
|
||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 모바일용 Flutter 코드
|
||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter 웹 클라이언트용 자바스크립트
|
||||
|
||||
## Snapshot
|
||||
## 스냅샷
|
||||
|
||||

|
||||
|
||||
|
@ -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"
|
||||
|
@ -306,8 +306,8 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
||||
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
||||
// For host side, clipboard sync
|
||||
val idSyncClipboard = 1
|
||||
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isListening ?: false
|
||||
if (isClipboardListenerEnabled) {
|
||||
val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled()
|
||||
if (isServiceSyncEnabled) {
|
||||
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
||||
}
|
||||
val idStopService = 2
|
||||
|
@ -103,7 +103,6 @@ class MainActivity : FlutterActivity() {
|
||||
mainService?.let {
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
rdClipboardManager?.rustEnableServiceClipboard(false)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@ -221,6 +220,10 @@ class MainActivity : FlutterActivity() {
|
||||
result.success(true)
|
||||
|
||||
}
|
||||
"try_sync_clipboard" -> {
|
||||
rdClipboardManager?.syncClipboard(true)
|
||||
result.success(true)
|
||||
}
|
||||
GET_START_ON_BOOT_OPT -> {
|
||||
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
|
||||
@ -402,13 +405,4 @@ class MainActivity : FlutterActivity() {
|
||||
super.onStart()
|
||||
stopService(Intent(this, FloatingWindowService::class.java))
|
||||
}
|
||||
|
||||
// For client side
|
||||
// When swithing from other app to this app, try to sync clipboard.
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
rdClipboardManager?.syncClipboard(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +433,7 @@ class MainService : Service() {
|
||||
checkMediaPermission()
|
||||
_isStart = true
|
||||
FFI.setFrameRawEnable("video",true)
|
||||
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -441,6 +442,7 @@ class MainService : Service() {
|
||||
Log.d(logTag, "Stop Capture")
|
||||
FFI.setFrameRawEnable("video",false)
|
||||
_isStart = false
|
||||
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
|
||||
// release video
|
||||
if (reuseVirtualDisplay) {
|
||||
// The virtual display video projection can be paused by calling `setSurface(null)`.
|
||||
|
@ -36,19 +36,19 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
||||
// though the `lastUpdatedClipData` will be set to null once.
|
||||
private var lastUpdatedClipData: ClipData? = null
|
||||
private var isClientEnabled = true;
|
||||
private var _isListening = false;
|
||||
val isListening: Boolean
|
||||
get() = _isListening
|
||||
private var _isCaptureStarted = false;
|
||||
|
||||
fun checkPrimaryClip(isClient: Boolean, isSync: Boolean) {
|
||||
val isCaptureStarted: Boolean
|
||||
get() = _isCaptureStarted
|
||||
|
||||
fun checkPrimaryClip(isClient: Boolean) {
|
||||
val clipData = clipboardManager.primaryClip
|
||||
if (clipData != null && clipData.itemCount > 0) {
|
||||
// Only handle the first item in the clipboard for now.
|
||||
val clip = clipData.getItemAt(0)
|
||||
val isHostSync = !isClient && isSync
|
||||
// Ignore the `isClipboardDataEqual()` check if it's a host sync operation.
|
||||
// Because it's a action manually triggered by the user.
|
||||
if (!isHostSync) {
|
||||
// Ignore the `isClipboardDataEqual()` check if it's a host operation.
|
||||
// Because it's an action manually triggered by the user.
|
||||
if (isClient) {
|
||||
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
|
||||
Log.d(logTag, "Clipboard data is the same as last update, ignore")
|
||||
return
|
||||
@ -95,13 +95,6 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
||||
}
|
||||
}
|
||||
|
||||
private val clipboardListener = object : ClipboardManager.OnPrimaryClipChangedListener {
|
||||
override fun onPrimaryClipChanged() {
|
||||
Log.d(logTag, "onPrimaryClipChanged")
|
||||
checkPrimaryClip(true, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSupportedMimeType(mimeType: String): Boolean {
|
||||
return supportedMimeTypes.contains(mimeType)
|
||||
}
|
||||
@ -136,43 +129,23 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
||||
return true
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustEnableServiceClipboard(enable: Boolean) {
|
||||
Log.d(logTag, "rustEnableServiceClipboard: enable: $enable, _isListening: $_isListening")
|
||||
if (enable) {
|
||||
if (!_isListening) {
|
||||
clipboardManager.addPrimaryClipChangedListener(clipboardListener)
|
||||
_isListening = true
|
||||
}
|
||||
} else {
|
||||
if (_isListening) {
|
||||
clipboardManager.removePrimaryClipChangedListener(clipboardListener)
|
||||
_isListening = false
|
||||
lastUpdatedClipData = null
|
||||
}
|
||||
}
|
||||
fun setCaptureStarted(started: Boolean) {
|
||||
_isCaptureStarted = started
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustEnableClientClipboard(enable: Boolean) {
|
||||
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
|
||||
isClientEnabled = enable
|
||||
if (enable) {
|
||||
lastUpdatedClipData = clipboardManager.primaryClip
|
||||
} else {
|
||||
lastUpdatedClipData = null
|
||||
}
|
||||
lastUpdatedClipData = null
|
||||
}
|
||||
|
||||
fun syncClipboard(isClient: Boolean) {
|
||||
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled, _isListening: $_isListening")
|
||||
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled")
|
||||
if (isClient && !isClientEnabled) {
|
||||
return
|
||||
}
|
||||
if (!isClient && !_isListening) {
|
||||
return
|
||||
}
|
||||
checkPrimaryClip(isClient, true)
|
||||
checkPrimaryClip(isClient)
|
||||
}
|
||||
|
||||
@Keep
|
||||
|
@ -24,4 +24,5 @@ object FFI {
|
||||
external fun setCodecInfo(info: String)
|
||||
external fun getLocalOption(key: String): String
|
||||
external fun onClipboardUpdate(clips: ByteBuffer)
|
||||
external fun isServiceClipboardEnabled(): Boolean
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -2,4 +2,6 @@
|
||||
# https://docs.flutter.dev/deployment/ios
|
||||
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
|
||||
# no obfuscate, because no easy to check errors
|
||||
cd $(dirname $(dirname $(which flutter)))
|
||||
git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||
flutter build ipa --release
|
||||
|
@ -133,7 +133,7 @@ SPEC CHECKSUMS:
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@UIApplicationMain
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -1,2 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
cd $(dirname $(dirname $(which flutter)))
|
||||
git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||
|
@ -2730,30 +2730,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(
|
||||
@ -2833,7 +2809,7 @@ Widget buildRemoteBlock(
|
||||
onExit: (event) => block.value = false,
|
||||
child: Stack(children: [
|
||||
// scope block tab
|
||||
FocusScope(child: child, canRequestFocus: !block.value),
|
||||
preventMouseKeyBuilder(child: child, block: block.value),
|
||||
// mask block click, cm not block click and still use check_click_time to avoid block local click
|
||||
if (mask)
|
||||
Offstage(
|
||||
@ -2845,6 +2821,11 @@ Widget buildRemoteBlock(
|
||||
));
|
||||
}
|
||||
|
||||
Widget preventMouseKeyBuilder({required Widget child, required bool block}) {
|
||||
return ExcludeFocus(
|
||||
excluding: block, child: AbsorbPointer(child: child, absorbing: block));
|
||||
}
|
||||
|
||||
Widget unreadMessageCountBuilder(RxInt? count,
|
||||
{double? size, double? fontSize}) {
|
||||
return Obx(() => Offstage(
|
||||
@ -3627,3 +3608,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.
|
||||
@ -579,4 +575,4 @@ extension WindowsTargetExt on int {
|
||||
WindowsTarget get windowsVersion => getWindowsTarget(this);
|
||||
}
|
||||
|
||||
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';
|
||||
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';
|
||||
|
@ -179,6 +179,9 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
|
||||
stateGlobal.svcStatus.value = SvcStatus.notReady;
|
||||
}
|
||||
_svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
|
||||
try {
|
||||
stateGlobal.videoConnCount.value = status['video_conn_count'] as int;
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,7 +362,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
||||
);
|
||||
}
|
||||
String textToFind = textEditingValue.text.toLowerCase();
|
||||
_autocompleteOpts = peers
|
||||
_autocompleteOpts = peers
|
||||
.where((peer) =>
|
||||
peer.id.toLowerCase().contains(textToFind) ||
|
||||
peer.username
|
||||
|
@ -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';
|
||||
@ -35,12 +35,11 @@ class DesktopHomePage extends StatefulWidget {
|
||||
const borderColor = Color(0xFF2F65BA);
|
||||
|
||||
class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
|
||||
final _leftPaneScrollController = ScrollController();
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
var updateUrl = '';
|
||||
var systemError = '';
|
||||
StreamSubscription? _uniLinksSubscription;
|
||||
var svcStopped = false.obs;
|
||||
@ -52,6 +51,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
bool isCardClosed = false;
|
||||
|
||||
final RxBool _editHover = false.obs;
|
||||
final RxBool _block = false.obs;
|
||||
|
||||
final GlobalKey _childKey = GlobalKey();
|
||||
|
||||
@ -59,14 +59,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final isIncomingOnly = bind.isIncomingOnly();
|
||||
return Row(
|
||||
return _buildBlock(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildLeftPane(context),
|
||||
if (!isIncomingOnly) const VerticalDivider(width: 1),
|
||||
if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
|
||||
],
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildBlock({required Widget child}) {
|
||||
return buildRemoteBlock(
|
||||
block: _block, mask: true, use: canBeBlocked, child: child);
|
||||
}
|
||||
|
||||
Widget buildLeftPane(BuildContext context) {
|
||||
@ -87,7 +93,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 +132,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,14 +423,14 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
Future<Widget> buildHelpCards() async {
|
||||
Widget buildHelpCards(String updateUrl) {
|
||||
if (!bind.isCustomClient() &&
|
||||
updateUrl.isNotEmpty &&
|
||||
!isCardClosed &&
|
||||
bind.mainUriPrefixSync().contains('rustdesk')) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
|
||||
"${translate("new-version-of-{${bind.mainGetAppNameSync()}}-tip")} (${bind.mainGetNewVersion()}).",
|
||||
"Click to download", () async {
|
||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||
await launchUrl(url);
|
||||
@ -674,20 +677,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();
|
||||
@ -823,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
_updateWindowSize();
|
||||
});
|
||||
}
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
_updateWindowSize() {
|
||||
@ -848,9 +838,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
platformFFI.unregisterEventHandler(
|
||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
|
||||
}
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
shouldBeBlocked(_block, canBeBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildPluginEntry() {
|
||||
final entries = PluginUiManager.instance.entries.entries;
|
||||
return Offstage(
|
||||
|
@ -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';
|
||||
@ -106,13 +107,20 @@ class DesktopSettingPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||
with
|
||||
TickerProviderStateMixin,
|
||||
AutomaticKeepAliveClientMixin,
|
||||
WidgetsBindingObserver {
|
||||
late PageController controller;
|
||||
late Rx<SettingsTabKey> selectedTab;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
final RxBool _block = false.obs;
|
||||
final RxBool _canBeBlocked = false.obs;
|
||||
Timer? _videoConnTimer;
|
||||
|
||||
_DesktopSettingPageState(SettingsTabKey initialTabkey) {
|
||||
var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey);
|
||||
if (initialIndex == -1) {
|
||||
@ -132,11 +140,34 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
shouldBeBlocked(_block, canBeBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_videoConnTimer =
|
||||
periodic_immediate(Duration(milliseconds: 1000), () async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_canBeBlocked.value = await canBeBlocked();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
||||
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_videoConnTimer?.cancel();
|
||||
}
|
||||
|
||||
List<_TabInfo> _settingTabs() {
|
||||
@ -206,12 +237,35 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
return children;
|
||||
}
|
||||
|
||||
Widget _buildBlock({required List<Widget> children}) {
|
||||
// check both mouseMoveTime and videoConnCount
|
||||
return Obx(() {
|
||||
final videoConnBlock =
|
||||
_canBeBlocked.value && stateGlobal.videoConnCount > 0;
|
||||
return Stack(children: [
|
||||
buildRemoteBlock(
|
||||
block: _block,
|
||||
mask: false,
|
||||
use: canBeBlocked,
|
||||
child: preventMouseKeyBuilder(
|
||||
child: Row(children: children),
|
||||
block: videoConnBlock,
|
||||
),
|
||||
),
|
||||
if (videoConnBlock)
|
||||
Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
)
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
body: Row(
|
||||
body: _buildBlock(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: _kTabWidth,
|
||||
@ -226,13 +280,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 +333,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 +398,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 +751,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(() => {});
|
||||
}),
|
||||
preventMouseKeyBuilder(
|
||||
block: 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 +1417,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(() => {});
|
||||
}),
|
||||
preventMouseKeyBuilder(
|
||||
block: 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 +1507,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 +1737,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 +1839,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 +1898,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 +2281,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);
|
||||
|
@ -37,13 +37,9 @@ class DesktopTabPage extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _DesktopTabPageState extends State<DesktopTabPage>
|
||||
with WidgetsBindingObserver {
|
||||
class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
final tabController = DesktopTabController(tabType: DesktopTabType.main);
|
||||
|
||||
final RxBool _block = false.obs;
|
||||
// bool mouseIn = false;
|
||||
|
||||
_DesktopTabPageState() {
|
||||
RemoteCountState.init();
|
||||
Get.put<DesktopTabController>(tabController);
|
||||
@ -69,19 +65,10 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
shouldBeBlocked(_block, canBeBlocked);
|
||||
} else if (state == AppLifecycleState.inactive) {}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -97,7 +84,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
||||
@override
|
||||
void dispose() {
|
||||
// HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
Get.delete<DesktopTabController>();
|
||||
|
||||
super.dispose();
|
||||
@ -119,7 +105,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
||||
isClose: false,
|
||||
),
|
||||
),
|
||||
blockTab: _block,
|
||||
)));
|
||||
return isMacOS || kUseCompatibleUiMode
|
||||
? tabWidget
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,8 @@ class ConnectionManager extends StatefulWidget {
|
||||
|
||||
class ConnectionManagerState extends State<ConnectionManager>
|
||||
with WidgetsBindingObserver {
|
||||
final RxBool _block = false.obs;
|
||||
final RxBool _controlPageBlock = false.obs;
|
||||
final RxBool _sidePageBlock = false.obs;
|
||||
|
||||
ConnectionManagerState() {
|
||||
gFFI.serverModel.tabController.onSelected = (client_id_str) {
|
||||
@ -139,7 +140,8 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
if (!allowRemoteCMModification()) {
|
||||
shouldBeBlocked(_block, null);
|
||||
shouldBeBlocked(_controlPageBlock, null);
|
||||
shouldBeBlocked(_sidePageBlock, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,7 +194,6 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
selectedBorderColor: MyTheme.accent,
|
||||
maxLabelWidth: 100,
|
||||
tail: null, //buildScrollJumper(),
|
||||
blockTab: allowRemoteCMModification() ? null : _block,
|
||||
tabBuilder: (key, icon, label, themeConf) {
|
||||
final client = serverModel.clients
|
||||
.firstWhereOrNull((client) => client.id.toString() == key);
|
||||
@ -237,13 +238,20 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
? buildSidePage()
|
||||
: buildRemoteBlock(
|
||||
child: buildSidePage(),
|
||||
block: _block,
|
||||
block: _sidePageBlock,
|
||||
mask: true),
|
||||
)),
|
||||
SizedBox(
|
||||
width: realClosedWidth,
|
||||
child:
|
||||
SizedBox(width: realClosedWidth, child: pageView)),
|
||||
child: SizedBox(
|
||||
width: realClosedWidth,
|
||||
child: allowRemoteCMModification()
|
||||
? pageView
|
||||
: buildRemoteBlock(
|
||||
child: _buildKeyEventBlock(pageView),
|
||||
block: _controlPageBlock,
|
||||
mask: false,
|
||||
))),
|
||||
]);
|
||||
return Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
@ -268,6 +276,10 @@ class ConnectionManagerState extends State<ConnectionManager>
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildKeyEventBlock(Widget child) {
|
||||
return ExcludeFocus(child: child, excluding: true);
|
||||
}
|
||||
|
||||
Widget buildTitleBar() {
|
||||
return SizedBox(
|
||||
height: kDesktopRemoteTabBarHeight,
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -246,7 +246,6 @@ class DesktopTab extends StatefulWidget {
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
final Color? selectedBorderColor;
|
||||
final RxBool? blockTab;
|
||||
|
||||
final DesktopTabController controller;
|
||||
|
||||
@ -272,7 +271,6 @@ class DesktopTab extends StatefulWidget {
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
this.selectedBorderColor,
|
||||
this.blockTab,
|
||||
}) : super(key: key);
|
||||
|
||||
static RxString tablabelGetter(String peerId) {
|
||||
@ -311,7 +309,6 @@ class _DesktopTabState extends State<DesktopTab>
|
||||
Color? get unSelectedTabBackgroundColor =>
|
||||
widget.unSelectedTabBackgroundColor;
|
||||
Color? get selectedBorderColor => widget.selectedBorderColor;
|
||||
RxBool? get blockTab => widget.blockTab;
|
||||
DesktopTabController get controller => widget.controller;
|
||||
RxList<String> get invisibleTabKeys => widget.invisibleTabKeys;
|
||||
Debouncer get _scrollDebounce => widget._scrollDebounce;
|
||||
@ -533,21 +530,9 @@ class _DesktopTabState extends State<DesktopTab>
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _buildBlock({required Widget child}) {
|
||||
if (blockTab != null) {
|
||||
return buildRemoteBlock(
|
||||
child: child,
|
||||
block: blockTab!,
|
||||
use: canBeBlocked,
|
||||
mask: tabType == DesktopTabType.main);
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _tabWidgets = [];
|
||||
Widget _buildPageView() {
|
||||
final child = _buildBlock(
|
||||
final child = Container(
|
||||
child: Obx(() => PageView(
|
||||
controller: state.value.pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -147,6 +147,19 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||
gFFI.chatModel.onVoiceCallClosed("End connetion");
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
trySyncClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
// For client side
|
||||
// When swithing from other app to this app, try to sync clipboard.
|
||||
void trySyncClipboard() {
|
||||
gFFI.invokeMethod("try_sync_clipboard");
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
|
||||
@ -872,6 +885,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 +967,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);
|
||||
}
|
||||
|
@ -323,10 +323,10 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
toggleClipboard() async {
|
||||
_clipboardOk = !_clipboardOk;
|
||||
_clipboardOk = !clipboardOk;
|
||||
bind.mainSetOption(
|
||||
key: kOptionEnableClipboard,
|
||||
value: _clipboardOk ? defaultOptionYes : 'N');
|
||||
value: clipboardOk ? defaultOptionYes : 'N');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ class StateGlobal {
|
||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||
final RxBool showRemoteToolBar = false.obs;
|
||||
final svcStatus = SvcStatus.notReady.obs;
|
||||
final RxInt videoConnCount = 0.obs;
|
||||
final RxBool isFocused = false.obs;
|
||||
// for mobile and web
|
||||
bool isInMainPage = true;
|
||||
@ -25,6 +26,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,
|
||||
|
@ -14,8 +14,13 @@ struct _MyApplication {
|
||||
|
||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||
|
||||
GtkWidget *find_gl_area(GtkWidget *widget);
|
||||
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view);
|
||||
|
||||
extern bool gIsConnectionManager;
|
||||
|
||||
GtkWidget *find_gl_area(GtkWidget *widget);
|
||||
|
||||
// Implements GApplication::activate.
|
||||
static void my_application_activate(GApplication* application) {
|
||||
MyApplication* self = MY_APPLICATION(application);
|
||||
@ -39,9 +44,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;
|
||||
@ -66,16 +72,19 @@ static void my_application_activate(GApplication* application) {
|
||||
height = 490;
|
||||
}
|
||||
gtk_window_set_default_size(window, width, height); // <-- comment this line
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
// gtk_widget_show(GTK_WIDGET(window));
|
||||
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
try_set_transparent(window, gtk_window_get_screen(window), view);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
@ -121,3 +130,48 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
@ -1372,13 +1364,13 @@ packages:
|
||||
source: hosted
|
||||
version: "6.2.2"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.4"
|
||||
version: "6.3.2"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -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.5+54
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
@ -36,6 +36,7 @@ dependencies:
|
||||
#firebase_analytics: ^9.1.5
|
||||
package_info_plus: ^4.2.0
|
||||
url_launcher: ^6.2.1
|
||||
url_launcher_ios: ^6.3.2
|
||||
toggle_switch: ^2.1.0
|
||||
dash_chat_2:
|
||||
git:
|
||||
@ -71,13 +72,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 +85,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,
|
||||
|
@ -284,7 +284,9 @@ enum ControlKey {
|
||||
}
|
||||
|
||||
message KeyEvent {
|
||||
// `down` indicates the key's state(down or up).
|
||||
bool down = 1;
|
||||
// `press` indicates a click event(down and up).
|
||||
bool press = 2;
|
||||
oneof union {
|
||||
ControlKey control_key = 3;
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.3.3"
|
||||
version = "1.3.5"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
|
@ -371,14 +371,6 @@ pub fn call_clipboard_manager_update_clipboard(data: &[u8]) -> JniResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_clipboard_manager_enable_service_clipboard(enable: bool) -> JniResult<()> {
|
||||
_call_clipboard_manager(
|
||||
"rustEnableServiceClipboard",
|
||||
"(Z)V",
|
||||
&[JValue::Bool(jboolean::from(enable))],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_clipboard_manager_enable_client_clipboard(enable: bool) -> JniResult<()> {
|
||||
_call_clipboard_manager(
|
||||
"rustEnableClientClipboard",
|
||||
|
@ -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.5
|
||||
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.5
|
||||
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.5
|
||||
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.5
|
||||
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})
|
||||
|
||||
|
@ -1002,8 +1002,13 @@ impl AudioHandler {
|
||||
let sample_format = config.sample_format();
|
||||
log::info!("Default output format: {:?}", config);
|
||||
log::info!("Remote input format: {:?}", format0);
|
||||
#[allow(unused_mut)]
|
||||
let mut config: StreamConfig = config.into();
|
||||
config.buffer_size = cpal::BufferSize::Fixed(64);
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
{
|
||||
// this makes ios audio output not work
|
||||
config.buffer_size = cpal::BufferSize::Fixed(64);
|
||||
}
|
||||
|
||||
self.sample_rate = (format0.sample_rate, config.sample_rate.0);
|
||||
let mut build_output_stream = |config: StreamConfig| match sample_format {
|
||||
@ -1188,9 +1193,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,
|
||||
@ -2742,6 +2753,7 @@ fn _input_os_password(p: String, activate: bool, interface: impl Interface) {
|
||||
return;
|
||||
}
|
||||
let mut key_event = KeyEvent::new();
|
||||
key_event.mode = KeyboardMode::Legacy.into();
|
||||
key_event.press = true;
|
||||
let mut msg_out = Message::new();
|
||||
key_event.set_seq(p);
|
||||
@ -3293,6 +3305,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)),
|
||||
|
@ -831,30 +831,22 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
|
||||
SyncReturn(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "android")]
|
||||
fn enable_server_clipboard(keyboard_enabled: &str, clip_enabled: &str) {
|
||||
use scrap::android::ffi::call_clipboard_manager_enable_service_clipboard;
|
||||
let keyboard_enabled =
|
||||
config::option2bool(config::keys::OPTION_ENABLE_KEYBOARD, &keyboard_enabled);
|
||||
let clip_enabled = config::option2bool(config::keys::OPTION_ENABLE_CLIPBOARD, &clip_enabled);
|
||||
crate::ui_cm_interface::switch_permission_all("clipboard".to_owned(), clip_enabled);
|
||||
let _ = call_clipboard_manager_enable_service_clipboard(keyboard_enabled && clip_enabled);
|
||||
}
|
||||
|
||||
pub fn main_set_option(key: String, value: String) {
|
||||
#[cfg(target_os = "android")]
|
||||
if key.eq(config::keys::OPTION_ENABLE_KEYBOARD) {
|
||||
crate::ui_cm_interface::notify_input_control(config::option2bool(
|
||||
config::keys::OPTION_ENABLE_KEYBOARD,
|
||||
&value,
|
||||
));
|
||||
enable_server_clipboard(&value, &get_option(config::keys::OPTION_ENABLE_CLIPBOARD));
|
||||
crate::ui_cm_interface::switch_permission_all(
|
||||
"keyboard".to_owned(),
|
||||
config::option2bool(&key, &value),
|
||||
);
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
if key.eq(config::keys::OPTION_ENABLE_CLIPBOARD) {
|
||||
enable_server_clipboard(&get_option(config::keys::OPTION_ENABLE_KEYBOARD), &value);
|
||||
crate::ui_cm_interface::switch_permission_all(
|
||||
"clipboard".to_owned(),
|
||||
config::option2bool(&key, &value),
|
||||
);
|
||||
}
|
||||
|
||||
if key.eq("custom-rendezvous-server") {
|
||||
set_option(key, value.clone());
|
||||
#[cfg(target_os = "android")]
|
||||
@ -1417,7 +1409,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();
|
||||
}
|
||||
}
|
||||
@ -2347,7 +2340,7 @@ pub mod server_side {
|
||||
use jni::{
|
||||
errors::{Error as JniError, Result as JniResult},
|
||||
objects::{JClass, JObject, JString},
|
||||
sys::jstring,
|
||||
sys::{jboolean, jstring},
|
||||
JNIEnv,
|
||||
};
|
||||
|
||||
@ -2420,4 +2413,12 @@ pub mod server_side {
|
||||
};
|
||||
return env.new_string(res).unwrap_or_default().into_raw();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "system" fn Java_ffi_FFI_isServiceClipboardEnabled(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
) -> jboolean {
|
||||
jboolean::from(crate::server::is_clipboard_service_ok())
|
||||
}
|
||||
}
|
||||
|
@ -217,8 +217,6 @@ pub enum Data {
|
||||
MouseMoveTime(i64),
|
||||
Authorize,
|
||||
Close,
|
||||
#[cfg(target_os = "android")]
|
||||
InputControl(bool),
|
||||
#[cfg(windows)]
|
||||
SAS,
|
||||
UserSid(Option<u32>),
|
||||
@ -268,6 +266,7 @@ pub enum Data {
|
||||
ControlledSessionCount(usize),
|
||||
CmErr(String),
|
||||
CheckHwcodec,
|
||||
#[cfg(feature = "flutter")]
|
||||
VideoConnCount(Option<usize>),
|
||||
// Although the key is not neccessary, it is used to avoid hardcoding the key.
|
||||
WaylandScreencastRestoreToken((String, String)),
|
||||
@ -457,6 +456,7 @@ async fn handle(data: Data, stream: &mut Connection) {
|
||||
log::info!("socks updated");
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "flutter")]
|
||||
Data::VideoConnCount(None) => {
|
||||
let n = crate::server::AUTHED_CONNS
|
||||
.lock()
|
||||
|
@ -417,6 +417,17 @@ pub fn is_modifier(key: &rdev::Key) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_modifier_code(evt: &KeyEvent) -> bool {
|
||||
match evt.union {
|
||||
Some(key_event::Union::Chr(code)) => {
|
||||
let key = rdev::linux_key_from_code(code);
|
||||
is_modifier(&key)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_numpad_rdev_key(key: &rdev::Key) -> bool {
|
||||
matches!(
|
||||
@ -869,24 +880,11 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
|
||||
events
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn map_keyboard_mode(_peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
|
||||
match _map_keyboard_mode(_peer, event, key_event) {
|
||||
Some(key_event) => {
|
||||
if _peer == OS_LOWER_LINUX {
|
||||
if let EventType::KeyPress(k) = &event.event_type {
|
||||
#[cfg(target_os = "ios")]
|
||||
let try_workaround = true;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let try_workaround = !is_modifier(k);
|
||||
if try_workaround {
|
||||
return try_workaround_linux_long_press(key_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
vec![key_event]
|
||||
}
|
||||
None => Vec::new(),
|
||||
}
|
||||
_map_keyboard_mode(_peer, event, key_event)
|
||||
.map(|e| vec![e])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
|
||||
@ -958,14 +956,6 @@ fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Op
|
||||
Some(key_event)
|
||||
}
|
||||
|
||||
// https://github.com/rustdesk/rustdesk/issues/6793
|
||||
#[inline]
|
||||
fn try_workaround_linux_long_press(key_event: KeyEvent) -> Vec<KeyEvent> {
|
||||
let mut key_event_up = key_event.clone();
|
||||
key_event_up.down = false;
|
||||
vec![key_event, key_event_up]
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
fn try_fill_unicode(_peer: &str, event: &Event, key_event: &KeyEvent, events: &mut Vec<KeyEvent>) {
|
||||
match &event.unicode {
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", "剪贴板已同步"),
|
||||
("Update client clipboard", "更新客户端的粘贴板"),
|
||||
("Untagged", "无标签"),
|
||||
("new-version-of-{}-tip", "{} 版本更新"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -654,6 +654,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload files", "Dateien hochladen"),
|
||||
("Clipboard is synchronized", "Zwischenablage ist synchronisiert"),
|
||||
("Update client clipboard", "Client-Zwischenablage aktualisieren"),
|
||||
("Untagged", ""),
|
||||
("Untagged", "Unmarkiert"),
|
||||
("new-version-of-{}-tip", "Es ist eine neue Version von {} verfügbar"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -236,5 +236,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("enable-trusted-devices-tip", "Skip 2FA verification on trusted devices"),
|
||||
("one-way-file-transfer-tip", "One-way file transfer is enabled on the controlled side."),
|
||||
("web_id_input_tip", "You can input an ID in the same server, direct IP access is not supported in web client.\nIf you want to access a device on another server, please append the server address (<id>@<server_address>?key=<key_value>), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"<id>@public\", the key is not needed for public server."),
|
||||
("new-version-of-{}-tip", "There is a new version of {} available"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", "Portapapeles sincronizado"),
|
||||
("Update client clipboard", "Actualizar portapapeles del cliente"),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -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"),
|
||||
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", "A vágólap szinkronizálva van"),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -654,6 +654,7 @@ 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"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,7 +653,8 @@ 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", "태그 없음"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,7 +653,8 @@ 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"),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", "Klembord is gesynchroniseerd"),
|
||||
("Update client clipboard", "Klembord van client bijwerken"),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", "Schowek jest zsynchronizowany"),
|
||||
("Update client clipboard", "Uaktualnij schowek klienta"),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -654,6 +654,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload files", "Загрузить файлы"),
|
||||
("Clipboard is synchronized", "Буфер обмена синхронизирован"),
|
||||
("Update client clipboard", "Обновить буфер обмена клиента"),
|
||||
("Untagged", ""),
|
||||
("Untagged", "Без метки"),
|
||||
("new-version-of-{}-tip", "Доступна новая версия {}"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -655,5 +655,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
("Untagged", ""),
|
||||
("new-version-of-{}-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user