Compare commits

...

48 Commits

Author SHA1 Message Date
rustdesk
63e22b7685 fix build error with latest xcode 2024-12-09 18:49:02 +08:00
21pages
a02d2bb4ac
fix ios audio output (#10235)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-09 15:41:49 +08:00
solokot
0f7d78c263
Update ru.rs (#10233) 2024-12-09 15:11:09 +08:00
XLion
0e321bd845
Update tw.rs (#10231) 2024-12-09 12:30:50 +08:00
Mr-Update
875b738222
Update de.rs (#10228) 2024-12-09 12:30:32 +08:00
21pages
b39e851262
fix typo (#10227)
newer version -> new version

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-08 20:09:10 +08:00
21pages
ec466d459f
add version update translation (#10225)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-08 18:27:45 +08:00
21pages
d4a712bb32
always block desktop settings page if video connection exists (#10224)
1. Always block desktop settings page if video connection exists, both mouse event and key event are blocked..
2. Server control page always block key event.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-08 18:26:55 +08:00
fufesou
1c17fddf51
fix: android clipboard permission (#10223)
* fix: android clipboard permission

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: Android, clipboard, floating ball

Call rust to check if clipboard is enabled.

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-07 22:34:54 +08:00
fufesou
3c838e7a92
fix: Android, try sync clipboard on connecting (#10218)
* fix: Android, try sync clipboard on connecting

Signed-off-by: fufesou <linlong1266@gmail.com>

* Android, clipboard, more clear skip check

Signed-off-by: fufesou <linlong1266@gmail.com>

* comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* comment todo: Android clipboard listener, callback twice

Signed-off-by: fufesou <linlong1266@gmail.com>

* Android, clipboard, remove listner

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-07 15:12:15 +08:00
VenusGirl❤
8f44787ba3
Update README-KR.md (#10217) 2024-12-07 10:24:32 +08:00
fufesou
12e15b5a37
fix: linux, weak network, repeated keys (#10211)
Use `press` as the `click` flag on Linux to avoid repeated keys, like
the Legacy mode.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-06 20:01:11 +08:00
rustdesk
588103c6dc 1.3.5 2024-12-05 18:38:39 +08:00
fufesou
2ce9b108ed
fix: linux, transparent window (#10192)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-05 17:26:34 +08:00
RustDesk
93e3107881
Revert "Revert "fix: workaround, linux window, transparent rounded corner (#1…" (#10191)
This reverts commit 468bdd6cc6f18f5b07a38e82f4b0d1c97acd051d.
2024-12-05 17:07:23 +08:00
RustDesk
468bdd6cc6
Revert "fix: workaround, linux window, transparent rounded corner (#10128)" (#10186)
This reverts commit 8d4c86fe7ff96e38c526c910e8381d697ab66446.
2024-12-05 11:08:58 +08:00
Dmytro Zozulia
d5c5825ffd
Update uk.rs (#10174) 2024-12-05 11:04:10 +08:00
RustDesk
fe4094777f
Revert "fix: linux, window, workaround, mint, mate (#10146)" (#10184)
This reverts commit bd0a33e46796a9f24bba6fabb2534490fe1757a0.
2024-12-05 10:44:37 +08:00
fufesou
f13ef48cec
fix: macos, aarch64, try fix running on 12.3 (#10183)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-05 10:38:22 +08:00
fufesou
a23822074e
feat: Android, opt, check update on startup (#10165)
* feat: Android, opt, check update on startup

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: check update only on startup

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Android, "Download new version"

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-04 17:10:32 +08:00
21pages
3d17bf4990
linux dynamic load libva (#10171)
1. Linux dynamic load libva, which can fix lack of libva dependency for
   appimage or flatpak, also fix libva version mismatch between build
and run.
2. Remove libvdpau, it's not used, and add libva2 explicitly for deb and
   appimage
3. Print FFmpeg configure log to know the actual codecs.

Test
*  ubuntu 22.04 x64
  - [x] deb
  - [x] flatpak
  - [x] appimage
* ubuntu 18.04
  * deb: fcntl64 not found
  - [x]:appimage
  - [ ]: platpak
hwcodec example:
  - [x]: combination of lacking any of libva2, libva-x11-2, libva-drm2,
    intel-media-va-driver
- [ ] federa
- [ ] arch
- [ ] arm64: my ci can't finish arm64 building

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-04 17:10:10 +08:00
Yevhen Popok
fd67be4a16
Update uk.rs (#10162) 2024-12-04 10:31:13 +08:00
fufesou
e6edf39305
fix: support emptry folder transfer for web (#10151)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-03 14:22:20 +08:00
21pages
34d2c62781
set id/relay server with a dialog (#10150)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-03 14:14:29 +08:00
fufesou
bd0a33e467
fix: linux, window, workaround, mint, mate (#10146)
* refact: linux, window, workaround, mint, mate

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: case insensitive

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-03 01:02:41 +08:00
rustdesk
b8d36b6558 revert multi-window plugin 2024-12-02 22:29:37 +08:00
solokot
f38d89aaee
Update ru.rs (#10143) 2024-12-02 18:30:24 +08:00
21pages
773b9d6645
win7 uses soft rendering by default (#10139)
win7 vm got black screen on remote window with texture rendering

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-02 17:10:34 +08:00
rustdesk
dea99ffb3a fix rustdesk exit crash 2024-12-02 16:11:12 +08:00
jkh0kr
3251045e22
Update ko.rs (#10138)
Update ko.rs
2024-12-02 15:47:23 +08:00
21pages
dc58c85e30
try fix mac textedit of server config (#10135)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-02 12:35:44 +08:00
fufesou
5a2a94d2cc
fix: macos, input (#10133)
1. Workaround sticky `Fn` for more keys.
2. Workaround stikey `Help`.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-02 00:09:03 +08:00
rustdesk
f330953f4f bump to 1.3.4 2024-12-01 18:49:24 +08:00
fufesou
8d4c86fe7f
fix: workaround, linux window, transparent rounded corner (#10128)
* fix: linux window, rounded corner

Signed-off-by: fufesou <linlong1266@gmail.com>

* Update my_application.cc

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-12-01 17:19:21 +08:00
Kleofass
f8c2713c5b
Update lv.rs (#10124) 2024-12-01 00:27:39 +08:00
fufesou
082a66b282
refact: remove flutter_improved_scrolling (#10120)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-30 15:01:44 +08:00
21pages
743b0ce8ce
fix mediacodec patch (#10119)
ensure set_parameters_id is not null

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-30 13:29:03 +08:00
21pages
9d9b67aca5
update flutter texture rgba renderer plugin, remove switch rgba (#10070)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-30 12:19:42 +08:00
21pages
d60b5a6ca0
videotoolbox/mediacodec support changing bitrate dynamically (#10117)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-30 11:44:51 +08:00
Vasyl Gello
e0ed6ee986
Fix F-Droid build and bump Android NDK to r27c (#10105)
* Fix fdroid build

* Refactor recent @fufesou edits to reflect the fact that
  .gclient file is needed only on x86 to build jit-release
  version of flutter-engine and `flutter-sdk` directory is
  not affected by flutter version checkouts

* Install cargo-ndk and flutter-rust-codegen with `--locked`
  argument to avoid bumping `cargo-platform` to require
  newer Rust toolchain

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>

* Bump Android NDK to r27c

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-11-30 09:24:45 +08:00
21pages
d3f0c80e94
"Untagged" tag uses the theme accent color (#10111)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-30 09:24:05 +08:00
fufesou
b32ff87c6e
fix: android, pan, canvas, remove toInt() (#10103)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-29 22:39:07 +08:00
rustdesk
b91b49229a enable our engine to fix dart supporting win7, https://github.com/rustdesk/rustdesk/issues/10085#issuecomment-2506485955 2024-11-29 19:10:57 +08:00
fufesou
afc8bb71dc
feat: mobile, key help tool, more keys (#10068)
* feat: mobile, key help tool, vk_enter

Signed-off-by: fufesou <linlong1266@gmail.com>

* Mobile, add more function keys

Signed-off-by: fufesou <linlong1266@gmail.com>

* Mobile, more virtual function keys

Signed-off-by: fufesou <linlong1266@gmail.com>

* uinput, menu maps key_compose

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-29 00:56:38 +08:00
XLion
4f86169f7f
Update tw.rs (#10076) 2024-11-28 00:10:47 +08:00
Leo Mozoloa
2cf43042e6
Update fr.rs (#10075) 2024-11-27 23:00:41 +08:00
BoyChai
734fb8d6f7
Update README-ZH.md (#10069)
Modify Alibaba Cloud apt source
2024-11-27 10:47:09 +08:00
bovirus
3c7f6d3127
Italian language update (#10067) 2024-11-27 07:37:49 +08:00
118 changed files with 3501 additions and 937 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](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).
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
@ -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
## 스냅샷
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)

View File

@ -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`后插入下面代码:

View File

@ -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"

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)`.

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
import UIKit
import Flutter
@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,

View File

@ -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

View File

@ -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();
});
}
}
}

View File

@ -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();

View File

@ -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';

View File

@ -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

View File

@ -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(

View File

@ -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);

View File

@ -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

View File

@ -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,
);
}

View File

@ -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,

View File

@ -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,
);
}
}

View File

@ -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(),

View File

@ -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();

View File

@ -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));
}

View File

@ -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');

View File

@ -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>;

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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

View File

@ -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,

View File

@ -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);
}
}
}

View File

@ -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:

View File

@ -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:

View File

@ -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,

View File

@ -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;

View File

@ -1,6 +1,6 @@
[package]
name = "rustdesk-portable-packer"
version = "1.3.3"
version = "1.3.5"
edition = "2021"
description = "RustDesk Remote Desktop"

View File

@ -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",

View File

@ -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 {

View File

@ -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=()

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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})

View File

@ -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)),

View File

@ -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())
}
}

View File

@ -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()

View File

@ -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 {

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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