Compare commits

...

196 Commits
1.3.2 ... 1.3.7

Author SHA1 Message Date
21pages
1f02bc9d3e bump to 1.3.7 (#10548)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-20 23:12:00 +08:00
21pages
5fa8c25e65 opt qos (#10459)
* Adjust bitrate and fps based on TestDelay messages.
* Bitrate is adjusted every 3 seconds, fps is adjusted every second and when receiving test lag.
* Latency optimized at high resolutions. However, when the network is poor, the delay when just connecting or sliding static pages is still obvious.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-20 17:59:36 +08:00
21pages
c44803f5b0 replace hbb_common with submodule (#10543)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-20 17:33:41 +08:00
Samuel FORESTIER
4b066b1fba fix(debian): makes postinst/prerm scripts idempotent (#10541)
* fix(debian): makes `postinst` script idempotent

* fix(debian): makes `prerm` script idempotent
2025-01-20 07:59:40 +08:00
fufesou
dd004f1a2d fix: clipboard, client side, update is required on conn (#10464)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-01-17 02:27:20 +08:00
fufesou
222dbf12cd fix: mobile, don't reset canvas on metrics changed (#10463)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-01-15 18:24:50 +08:00
是果宝呐
b5d54debce Fix a translation error (#10500) 2025-01-14 22:16:17 +08:00
flusheDData
08cdf7134d Update es.rs (#10468) 2025-01-10 21:14:59 +08:00
add-uos
be5037bd03 fix: [translations] Add the translation in tw.rs (#10452)
Add the translation in tw.rs

Log: Add the translation in tw.rs
2025-01-08 14:16:16 +08:00
rustdesk
f9915df926 update readme 2025-01-08 00:23:17 +08:00
rustdesk
f96c759cf5 fix https://github.com/rustdesk/rustdesk/issues/10440 2025-01-07 11:52:43 +08:00
Xiaobo Liu
8f329ebc1a scrap: style (#10445) 2025-01-07 11:21:43 +08:00
Xiaobo Liu
4a3c11e711 scrap: fixed build warnning (#10442)
```shell
warning: elided lifetime has a name
   --> src/common/mod.rs:192:21
    |
187 |     pub fn to<'a>(
    |               -- lifetime `'a` declared here
...
192 |     ) -> ResultType<EncodeInput> {
    |                     ^^^^^^^^^^^ this elided lifetime gets resolved as `'a`
    |
    = note: `#[warn(elided_named_lifetimes)]` on by default
```
2025-01-07 11:14:20 +08:00
Xiaobo Liu
0dbd3094ec hbb_common: simplify is_compressed_file (#10436)
* hbb_common: simplify is_compressed_file

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>

* `exts` rename to `compressed_exts`

---------

Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com>
2025-01-06 18:20:18 +08:00
21pages
40999c3211 fix ffmpeg videotoolbox wrong log (#10413)
* Fix ffmpeg videotoolbox wrong log when changing bitrate
* Let qsv support abr, and it's safe for qsv to changing bitrate.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-02 22:19:30 +08:00
rustdesk
7c2d62237f missed file 2025-01-01 23:11:38 +08:00
rustdesk
ef90ab2bd4 compelete fix https://github.com/rustdesk/rustdesk/discussions/10210
rather than the awful workaround
2025-01-01 23:05:52 +08:00
Dimitris Apostolou
4f3b821883 fix: fix crate vulnerabilities (#10407) 2025-01-01 10:15:57 +08:00
21pages
98b00cdb3d Fix image blur occurring at the moment of changing quality (#10399)
1. Fix this issue occurs on FFmepg qsv, FFmpeg nvenc and SDK mfx, other
   codecs don't have this problem. Clear cache is needed.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-30 11:51:36 +08:00
rustdesk
8e4127b6a0 remove all stupid canLaunchUrl 2024-12-29 23:43:31 +08:00
21pages
b1f54acf90 fix andriod update button cannot be clicked (#10394)
1. Remove `canLaunchUrl`, which fix the issue
2. Remove `unregisterEventHandler` of `kCheckSoftwareUpdateFinish` when
   connection page dispose, it's registered on main.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-29 23:37:52 +08:00
rustdesk
39a430f96f upgrade url_launch 2024-12-28 22:03:34 +08:00
Kleofass
a9f2e14091 Update lv.rs (#10381) 2024-12-27 14:47:01 +08:00
Vasyl Gello
77baba3122 Fix missing locked arg in cargo install (#10374)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-12-26 15:54:46 +08:00
fufesou
1c62a28ef3 fix: build (#10364)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-25 16:36:13 +08:00
fufesou
9ed2499666 fix: file clipboard, init disabled (#10361)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-25 15:18:06 +08:00
XLion
06bc554216 Fix: DEBIAN Control md5sums (#10356)
* Fix: DEBIAN Control md5 sums

* I forgot import
2024-12-25 00:04:34 +08:00
Jernej Simončič
090f5b65ac Update sl.rs (#10346) 2024-12-24 14:15:22 +08:00
Integral
49dabd3533 refactor: replace &PathBuf with &Path to enhance generality (#10332) 2024-12-23 20:28:04 +08:00
RustDesk
7289dbc80f Update flutter-build.yml (#10337) 2024-12-22 11:35:55 +08:00
rustdesk
72f5184ee0 unused 2024-12-22 11:20:38 +08:00
fufesou
e9c5e0d26b fix: android, mouse mode, right menu, unexpected click (#10330)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-21 15:09:03 +08:00
rustdesk
03999d900e 1.3.6 2024-12-21 15:00:16 +08:00
fufesou
b24551da7b refact: linux, move rustdesk into /usr/share (#10327)
* refact: linux, move rustdesk into /usr/share

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

* linux, upgrade, try remove old empty folders

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-21 14:53:28 +08:00
21pages
bc461fe99b Revert "Revert "revert linux use cpal "" (#10326)
* Revert "Revert "revert linux use cpal (#10260)" (#10262)"

This reverts commit 827b5f6a4c.

* update Cargo.lock

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-20 22:46:42 +08:00
rustdesk
25e438a663 crate 2024-12-20 22:24:53 +08:00
jkh0kr
1f5aeda41d Update ko.rs (#10320) 2024-12-20 15:09:33 +08:00
fufesou
9114743577 fix: linux, flutter, workaround freeze (#10324)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-20 09:24:08 +08:00
fufesou
7830a9e9f3 refact: linux, install path (#10316)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-19 15:05:24 +08:00
fufesou
5fa8485130 fix: macos, show remote cursor (#10314)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-18 21:17:04 +08:00
Iacopo Modica
ed9cb37283 Fix translation issues in Italian language file (#10312)
Corrected multiple translation errors and typos in the Italian language resource file.
2024-12-18 21:02:53 +08:00
21pages
e4b270a581 update hwcodec (#10306)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-17 21:52:17 +08:00
21pages
9dd9c45afc fix ci (#10305)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-17 15:01:01 +08:00
21pages
e163b75407 update hwcodec (#10304)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-17 12:07:34 +08:00
princeyogesh
10ff3e6937 Fix for compilation due to minimum Cmake version update and arm based compilation of vcpkg (#10297) 2024-12-17 10:37:57 +08:00
21pages
acae6d6558 try fix FFmpeg amf encode hang (#10283)
* Possible Causes
  * GPU API Call Hangs: This could occur, though it's less likely.
  * Infinite Loop: If `QueryOutput` always fails and `hwsurfaces_in_queue_max` is zero, the loop will continue indefinitely.
* Proposed Solution
  * A query_timeout patch has been added to FFmpeg with a value of 1000ms, which exceeds the time required to encode a single frame. This allows us to remove the loop.
* Test
  * After removing the loop, no frame encoding failures were encountered during testing. A single call to QueryOutput is sufficient, as it typically consumes about 12ms on a 2K screen.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-16 19:40:48 +08:00
fufesou
d025ca1d81 refact: linux, chcon, bin_t (#10293)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-16 19:01:12 +08:00
21pages
e5aa31eb4c Fix auto record outgoing sessions ignore record permission (#10294)
1. Fix auto record outgoing sessions ignore record permission
2. Stop record if record permission changed
3. Update hwcodec
4. Make video thread finish faster when connection closed

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-16 17:13:48 +08:00
Dmitry Beskov
771cc565ab Flathub badge in the README (#10288)
* new flathub badge in readme

* replacing badge with svg
2024-12-16 16:04:53 +08:00
summoner001
db3bdb16a1 Update hu.rs (#10287)
* Update hu.rs

Fixes and corrections

* Update hu.rs

more fixes

* Update hu.rs

Minor fixes

* Update hu.rs

Fixing typo
2024-12-16 15:48:17 +08:00
Dmitry Beskov
c06e1d74b4 changes for flatpak build (#10273) 2024-12-15 16:55:43 +08:00
21pages
b544a2889b update vcpkg to 2024.11.16 (#10272)
1. version changes:
* vcpkg: 2024.07.12 -> 2024.11.16
* aom (except linux sciter): 3.9.1 -> 3.11.0
* libvpx: 1.14.1 -> 1.15.0
* libyuv: not update because compiled failed on arm64, and didn't apply
  different version on different archs
* opus: already the latest version
* ffmpeg: 7.0.2 -> 7.1

2. other changes:
* android 5.0 required, otherwise crash when start, because FFmpeg 7.1 link to mediandk directly

3. Tests:
* Except arm, arm64, linux amf, ios, all the other codecs are tested
* Compile on arm32 linux is not tested, ci is failed before vcpkg
  install
* Tested windows FFmpeg qsv, still no memory leak

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-13 13:28:48 +08:00
Yevhen Popok
9c45636875 Update uk.rs (#10265) 2024-12-12 11:13:50 +08:00
RustDesk
827b5f6a4c Revert "revert linux use cpal (#10260)" (#10262)
This reverts commit b0791ba183.
2024-12-11 13:42:25 +08:00
21pages
b0791ba183 revert linux use cpal (#10260)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-12-11 13:35:50 +08:00
fufesou
b24b381575 fix: macos, keyboard, translate mode, capslock and deadkeys (#10248)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-10 13:03:00 +08:00
fufesou
0751005073 Fix/windows empty file clipboard on disconn (#10242)
* fix: windows, empty file clipboard on disconn

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

* refact: Don't send files copied before the conn

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

* refact: windows, file clipboard

Empty clipboard if no `Ctrl+C` is pressed, but
`CliprdrDataObject_GetData()` is called.
`CliprdrDataObject_GetData()` is only called in the clipboard object set
by RustDesk.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-10 11:01:34 +08:00
bovirus
fe06cf77da Italian language update (#10245) 2024-12-10 10:48:38 +08:00
Alex Rijckaert
d57cf204c8 Update nl.rs (#10243) 2024-12-10 10:48:29 +08:00
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 468bdd6cc6.
2024-12-05 17:07:23 +08:00
RustDesk
468bdd6cc6 Revert "fix: workaround, linux window, transparent rounded corner (#10128)" (#10186)
This reverts commit 8d4c86fe7f.
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 bd0a33e467.
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
21pages
b99c540210 add "Untagged" to filter addressbook peers without tags (#10063)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-26 20:35:17 +08:00
fufesou
84dab0e96f Fix/android keyboard map mode workaround (#10064)
* fix: Android, keyboard, map mode, workaround

The `KeyEvent.physicalKey.usbHidUsage` are wrong if using Microsoft
SwiftKey keyboard.

`window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)` is a workaround for this issue.

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

* fix: clear callback on first image

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

* Android disable soft keyboard in remote page if not editing.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-26 20:33:54 +08:00
fufesou
458a88fb89 fix: mobile autocomplete options (#10060)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-26 16:24:51 +08:00
Yevhen Popok
8a70932cd6 Update Ukrainian UI translation (#10056) 2024-11-26 09:51:27 +08:00
flusheDData
edfae98a01 Update es.rs (#10055)
New term added
2024-11-25 23:46:28 +08:00
fufesou
d61c99b105 fix: Android 29, crash on restart and reconn (#10054)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-25 21:49:09 +08:00
21pages
30a11bfe0a android wait 3s for isStart flag (#10053)
The normal process is that `startCapture` and `VideoService::run` run in parallel,  the `run` function waits for startCapture to complete, then sets the scale, and subsequently calls `stopCapture` and `startCapture`. If the `run` function does not wait long enough, `startCapture` initializes the surface with the original width and height, but the `start` flag is still false, meaning it can't call `stopCapture` and `startCapture`. This results in only capturing the upper-left portion of the virtual display.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-25 21:45:58 +08:00
fufesou
9d2bdfefb1 feat: update build_fdroid.sh (#10040)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-25 11:12:43 +08:00
21pages
34b93c6f83 fix aom pts (#10042)
the old pts/duration is wrong, use timebase 1/1000 like other codecs, not found any differences.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-25 11:09:17 +08:00
21pages
152d0ce74b install vaapi dependencies before vcpkg installs ffmpeg (#10035)
linux vaapi encoding/decoding lost since installing ffmpeg with
vcpkg

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-24 21:07:03 +08:00
rustdesk
32a3bcdc4f no custom engine 2024-11-24 10:05:00 +09:00
zuiyu
314c93b210 Create empty dir on send files in local (#9993)
* feat: Add empty dirs on sendfiles

* Update connection.rs

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-11-23 23:09:11 +08:00
rustdesk
b64f6271e2 typo 2024-11-24 00:08:39 +09:00
RustDesk
ac044c4049 Update config.toml (#10029) 2024-11-23 22:54:43 +08:00
fufesou
0973f51df9 feat: macos, audio, loopback (#10025)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-23 19:41:27 +09:00
21pages
02b046bdbf fix hwcodec ram quality change not work (#10024)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-23 16:36:13 +08:00
Andrzej Rudnik
02c274aeb6 Updated Polish translation (#10019)
* Update pl.rs

* Update README-PL.md
2024-11-23 08:41:25 +08:00
fufesou
ab6a6ca17d fix: ci macos (#10020)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-23 08:38:30 +08:00
21pages
28d38cd71d avoid invalid recording files and fix removing little recording file (#10012)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-22 17:19:22 +08:00
rustdesk
b487f297b8 flutter 3.24.5 2024-11-22 16:58:08 +09:00
21pages
64654ee7cf seperate video decoding thread for each display (#9968)
* seperate video decoding thread for each display

1. Separate Video Decoding Thread for Each Display
2. Fix Decode Errors When Clearing the Queue
Previously, on-flight frames after clearing the queue could not be decoded successfully. This issue can be resolved by setting a discard_queue flag when sending a refresh message. The flag will be reset upon receiving a keyframe.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* update video format along with fps to flutter

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Fix keyframe interval when auto record outgoing sessions

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-22 00:02:25 +08:00
Alex Rijckaert
1c99eb5500 Update nl.rs (#9997) 2024-11-21 19:12:24 +08:00
zyl
74dd0c8fa0 fix mis-align problem when converting &[u8] to &[f32] (#9986)
* fix: windows, improve audio buffer (#9770)

* .

* fix statics does not record

and avoid channel changing when drio audio when audio is stero

* add some commence

* fix mis-align problem when converting &[u8] to &[f32]

* add safety commence

* revert client.rs

* avoid tmp lifetime extends

* avoid move in loop

* avoid use after drop

* another use after free

* another use after free

* make code more reasonable

---------

Co-authored-by: zylthinking <zhaoyulong@qianxin.com>
2024-11-21 13:36:11 +08:00
rustdesk
d26fea41ee upgrade The-Fat-Controller 2024-11-21 02:26:51 +09:00
21pages
bc211c8031 A=b, A case insensitive (#9976)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-20 19:44:24 +08:00
Mr-Update
d4cb7d68c5 Update de.rs (#9975) 2024-11-20 19:41:43 +08:00
KAYUII
608d7d55d5 add env VCPKG_INSTALLED_ROOT (#9985) 2024-11-20 19:41:30 +08:00
fufesou
4a49fbe4a6 fix: privacy mode 2 (#9972)
Do not change the resolutions when setting the new primary display.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-19 17:29:28 +08:00
fufesou
f760e21ff8 fix: android w&h, refresh when no connection (#9966)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-19 15:03:00 +08:00
solokot
251e1a3487 Update ru.rs (#9962) 2024-11-19 08:15:50 +08:00
bovirus
b990ff3782 Update Italian language (#9961) 2024-11-19 08:15:37 +08:00
21pages
c5426b0fbc Fix hevc decode error "Could not find ref with POC" (#9960)
1. Dropping frames can cause this error, reset encoder when this
   happens.
2. There are some logic error for clear video queue, because video queue
   message is not cleared. This need to be fixed.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-18 23:03:27 +08:00
fufesou
8b710f62c8 feat: android clipboard, multi-formats (#9950)
* feat: android clipboard, multi-formats

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

* Chore

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

* Remove unused code

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-18 15:43:41 +08:00
21pages
0707e791e8 opt av1 test data (#9954)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-18 15:05:23 +08:00
XLion
a07392e6b8 Add URL and Vendor to RPM spec (#9947) 2024-11-17 21:07:00 +08:00
21pages
9125a68f81 remove flutter install cache flag (#9944)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-17 11:35:53 +08:00
21pages
9ee77a9b92 fix last commit (#9939)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-17 09:21:34 +08:00
21pages
304e0e465d if av1 is not slow in test, av1 takes precedence over vp9 (#9938)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-16 22:31:28 +08:00
RustDesk
5d2bb9c995 Update bug_report.yaml (#9935) 2024-11-16 16:27:51 +08:00
RustDesk
ddc172bdfa Update bug_report.yaml (#9934) 2024-11-16 16:26:26 +08:00
21pages
06c7bc137f linux android use cpal (#9914)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-14 21:01:41 +08:00
rustdesk
9e4cc91a14 use linux for bridge because macos runner network problem 2024-11-13 17:00:58 +08:00
zyl
0a28d09ff8 fix: windows, improve audio buffer (#9770) (#9893)
* fix: windows, improve audio buffer (#9770)

* .

* fix statics does not record

and avoid channel changing when drio audio when audio is stero

* add some commence

---------

Co-authored-by: zylthinking <zhaoyulong@qianxin.com>
2024-11-13 15:35:23 +08:00
fufesou
ab89d84a8f refact: ci, bridge (#9899)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-12 22:10:56 +08:00
HanaKuru
0aa98eac6d Use base64Url encoding for server configuration to ensure compatibility with the command line --config option. (#9897) 2024-11-12 20:15:34 +08:00
fufesou
d4aa2b7ce4 fix: virtual display, headless, wait plug in done (#9895)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-12 20:12:05 +08:00
21pages
35b4535ebc fix aarch64 nightly build (#9881)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-11 16:28:25 +08:00
fufesou
f0be80c253 fix: macos, workaround app close (#9880)
* fix: macos, workaround app close

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

* Update common.dart

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-11-11 13:06:23 +08:00
21pages
a79a9f697b fix "Add to addresssBook" dropdown menu (#9878)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-11 08:39:54 +08:00
fufesou
6082bb2754 fix: save load window rect, Windows, ignore dpi (#9875)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-11 00:35:41 +08:00
rustdesk
4e6a43288e https://github.com/rustdesk/rustdesk/issues/9877 https://developer.apple.com/documentation/security/ksecusedataprotectionkeychain 2024-11-10 23:57:25 +08:00
fufesou
72a1f1161e refact: flutter 3.24.4 (#9874)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-10 11:18:08 +08:00
RustDesk
912f5265f1 Revert "Refact/flutter 3.24.4 (#9870)" (#9871)
This reverts commit 5eb2c31207.
2024-11-09 23:32:18 +08:00
fufesou
5eb2c31207 Refact/flutter 3.24.4 (#9870)
* Update pubspec.lock

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

* refact: flutter 3.24.3

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

* fix: workaround Autocomplete options

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

* Replace engine with rustdesk custom flutter engine

* Update flutter-build.yml to use RustDesk flutter engine

* Fix the problem of missing extraction file directory windows-x64-release

* Update pubspec.lock.3.22.3

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

* remove pubspec.lock.3.22.3

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

* upgrade flutter android to 3.24.4

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: vitoway <vitoway@email.com>
Co-authored-by: vitoway <167743630+vitoway@users.noreply.github.com>
2024-11-09 20:51:47 +08:00
Lee Jong Mun
062c8d582c kor translation update (#9866) 2024-11-09 11:37:54 +08:00
XLion
68b07505ab Update tw.rs (#9863) 2024-11-08 22:18:30 +08:00
fufesou
d3efcd4223 fix: mobile, soft keyboard (#9860)
Switching the input method, don't affect the canvas.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-08 15:01:36 +08:00
rustdesk
a277b022ff bump to 1.3.3 2024-11-08 15:00:49 +08:00
21pages
740c5358ab rotate ID3D11Texture2D (#9772)
* Rotate ID3D11Texture2D after duplication with d3d11 video processor.
* If display is not rotated, nothing will be created; If the rotation
  fails, it will use the old fallback logic

TODO:
* If changing from Landscape to Landscape(flipped) during capture, the resolution is
  not changed, video service fallback to gdi directly.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-08 12:12:10 +08:00
fufesou
7978e0301d fix: input mobile -> Android (#9767)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-08 12:11:56 +08:00
fufesou
0f070b0108 revert: 9644, iOS, Korean input (#9857)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-07 22:54:14 +08:00
fufesou
6f0cb3b8c2 fix: mobile, two fingers cale, no tapdown (#9856)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-07 22:36:56 +08:00
fufesou
d0ef52e418 fix: touch input, ensure message orders (#9855)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-07 21:23:41 +08:00
fufesou
69277dd16b fix: mobile, don't adjust canvas on gesture help show up (#9846)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-07 14:58:10 +08:00
fufesou
faf97c770c fix: mobile, cursor mode, don't reset canvas (#9843)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-06 21:59:23 +08:00
notlin4
78088360ca Fix traditional Chinese localization (#9833) 2024-11-06 07:59:12 +08:00
fufesou
5cfd1701fb fix: mobile input, touch mode, in display (#9827)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-05 17:55:38 +08:00
21pages
a4bd23c9de fix missing window focus service on windows/macos (#9824)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-11-05 11:28:27 +08:00
fufesou
12c1337b7b fix: mobile mouse mode, cursor range (#9811)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-04 22:37:21 +08:00
fufesou
040253b319 fix: mobile cursor focus (#9803)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-01 15:40:57 +08:00
fufesou
44fa83d080 fix: android input, soft keyboard, mouse mode (#9797)
Cursor movement in the remote screen.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-11-01 11:25:38 +08:00
21pages
4f7e10bac6 Revert "Change the minimum value of the bitrate slider to 5" (#9795)
* Revert "Change the minimum value of the bitrate slider to 5 (#9480)"

This reverts commit beb1084e87.

* Revert "Change the value of kMinQuality to 5  (#9508)"

This reverts commit d563372a91.
2024-10-31 22:57:39 +08:00
jkh0kr
d1fdcf1b16 Update ko.rs (#9792) 2024-10-31 12:01:37 +08:00
fufesou
4c12b83068 fix: android input do not handle composing (#9790)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-31 10:20:57 +08:00
21pages
f86c88b3d8 refresh icon not visible when not using one-time password (#9791)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-31 10:11:42 +08:00
fufesou
697dd87383 Refact/mobile remove adjust 4 soft keyabord (#9787)
* refact: remove adjust for soft keyboard

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

* mobile, do not set the view style after scale end

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-31 10:11:00 +08:00
fufesou
0b8cccd8be fix: mobile view style, on conn (#9786)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-30 19:00:07 +08:00
Kleofass
32dbc0c8fb Update lv.rs (#9785) 2024-10-30 17:08:32 +08:00
fufesou
bae4a2c710 Fix/android check normal usbhid usage (#9784)
* fix: android check normal usbhid usage

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

* fix: android input, ignore composing if is deleting

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-30 15:29:52 +08:00
RustDesk
711ed28846 Revert "revert: #9769 (#9780)" (#9783)
This reverts commit 0f5f9f6524.
2024-10-30 13:12:18 +08:00
fufesou
e6c5064ce5 fix: android input, Backspace and Enter (#9782)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-30 13:05:25 +08:00
fufesou
0f5f9f6524 revert: #9769 (#9780)
Not sure TextEditingController.addListener() can handle all composing
changes.

https://github.com/rustdesk/rustdesk/issues/7727#issuecomment-2445721499

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-30 12:17:13 +08:00
21pages
ce7867c1c0 fix wrong display of custom clients when approval mode is not set (#9779)
when approve-mode is not set, the approve mode option shows as
password, it's `both` approve mode in rust, so only ui is wrong.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-30 11:29:39 +08:00
rustdesk
415d2c5c60 OPTION_VERIFICATION_METHOD 2024-10-29 22:52:46 +08:00
fufesou
a289eae07c fix: mobile -> mobile, long press (#9775)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-29 19:57:29 +08:00
RustDesk
1c9b456456 Update bug_report.yaml (#9771) 2024-10-29 15:33:12 +08:00
fufesou
eba19e67ff fix: mobile input (#9769)
1. Map mode. Check if the KeyEvent's usbHidUsage is correct.
2. Korean input, use listener to handle composing state.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-29 14:19:16 +08:00
John Fowler
adc5a7be51 Update hu.rs (#9762)
Making the Hungarian language file complete. Adding tips in Hungarian.
2024-10-29 13:55:13 +08:00
pppanghu77
26d23d588a fix: [translations] Add the translation in cn.rs (#9768)
Add the translation in cn.rs

Log: Add the translation in cn.rs
2024-10-29 13:54:16 +08:00
252 changed files with 8498 additions and 11608 deletions

View File

@@ -12,3 +12,5 @@ rustflags = [
#rustflags = [
# "-C", "link-args=-Wl,-Bstatic -lc -Wl,-Bdynamic"
#]
[net]
git-fetch-with-cli = true

View File

@@ -26,8 +26,8 @@ body:
- type: input
id: os
attributes:
label: Operating system(s) on local side and remote side
description: What operating system(s) do you see this bug on? local side -> remote side.
label: Operating system(s) on local (controlling) side and remote (controlled) side
description: What operating system(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
placeholder: |
Windows 10 -> osx
validations:
@@ -35,8 +35,8 @@ body:
- type: input
id: version
attributes:
label: RustDesk Version(s) on local side and remote side
description: What RustDesk version(s) do you see this bug on? local side -> remote side.
label: RustDesk Version(s) on local (controlling) side and remote (controlled) side
description: What RustDesk version(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
placeholder: |
1.1.9 -> 1.1.8
validations:

View File

@@ -0,0 +1,42 @@
diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart
index 7e634cd2aa..c1e9acc295 100644
--- a/packages/flutter/lib/src/material/dropdown_menu.dart
+++ b/packages/flutter/lib/src/material/dropdown_menu.dart
@@ -475,7 +475,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
final GlobalKey _leadingKey = GlobalKey();
late List<GlobalKey> buttonItemKeys;
final MenuController _controller = MenuController();
- late bool _enableFilter;
+ bool _enableFilter = false;
late List<DropdownMenuEntry<T>> filteredEntries;
List<Widget>? _initialMenu;
int? currentHighlight;
@@ -524,6 +524,11 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
}
_localTextEditingController = widget.controller ?? TextEditingController();
}
+ if (oldWidget.enableFilter != widget.enableFilter) {
+ if (!widget.enableFilter) {
+ _enableFilter = false;
+ }
+ }
if (oldWidget.enableSearch != widget.enableSearch) {
if (!widget.enableSearch) {
currentHighlight = null;
@@ -663,6 +668,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
);
currentHighlight = widget.enableSearch ? i : null;
widget.onSelected?.call(entry.value);
+ _enableFilter = false;
}
: null,
requestFocusOnHover: false,
@@ -735,6 +741,8 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
if (_enableFilter) {
filteredEntries = widget.filterCallback?.call(filteredEntries, _localTextEditingController!.text)
?? filter(widget.dropdownMenuEntries, _localTextEditingController!);
+ } else {
+ filteredEntries = widget.dropdownMenuEntries;
}
if (widget.enableSearch) {

View File

@@ -6,7 +6,7 @@ on:
workflow_call:
env:
FLUTTER_VERSION: "3.19.6"
FLUTTER_VERSION: "3.22.3"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
@@ -25,6 +25,8 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install prerequisites
run: |
@@ -73,12 +75,13 @@ jobs:
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
- name: Run flutter rust bridge
run: |
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
cp ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h
- name: Upload Artifact
uses: actions/upload-artifact@master
@@ -89,3 +92,5 @@ jobs:
./src/bridge_generated.io.rs
./flutter/lib/generated_bridge.dart
./flutter/lib/generated_bridge.freezed.dart
./flutter/macos/Runner/bridge_generated.h
./flutter/ios/Runner/bridge_generated.h

View File

@@ -4,9 +4,9 @@ env:
# MIN_SUPPORTED_RUST_VERSION: "1.46.0"
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.06.15
# vcpkg version: 2024.11.16
# for multiarch gcc compatibility
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
on:
workflow_dispatch:
@@ -45,6 +45,8 @@ jobs:
# steps:
# - name: Checkout source code
# uses: actions/checkout@v3
# with:
# submodules: recursive
# - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
# uses: actions-rs/toolchain@v1
@@ -92,6 +94,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install prerequisites
shell: bash

View File

@@ -20,21 +20,21 @@ on:
env:
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
MAC_RUST_VERSION: "1.81" # 1.81 is requred for macos, because of https://github.com/yury/cidre requires 1.81
CARGO_NDK_VERSION: "3.1.2"
SCITER_ARMV7_CMAKE_VERSION: "3.29.7"
SCITER_NASM_DEBVERSION: "2.14-1"
LLVM_VERSION: "15.0.6"
FLUTTER_VERSION: "3.19.6"
ANDROID_FLUTTER_VERSION: "3.13.9" # >= 3.16 is very slow on my android phone, but work well on most of others. We may switch to new flutter after changing to texture rendering (I believe it can solve my problem).
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
FLUTTER_VERSION: "3.24.5"
ANDROID_FLUTTER_VERSION: "3.24.5"
# for arm64 linux because official Dart SDK does not work
FLUTTER_ELINUX_VERSION: "3.16.9"
TAG_NAME: "${{ inputs.upload-tag }}"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.07.12
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
VERSION: "1.3.2"
NDK_VERSION: "r27b"
# vcpkg version: 2024.11.16
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
VERSION: "1.3.7"
NDK_VERSION: "r27c"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
@@ -46,6 +46,9 @@ env:
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
jobs:
generate-bridge:
uses: ./.github/workflows/bridge.yml
build-RustDeskTempTopMostWindow:
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
with:
@@ -59,7 +62,7 @@ jobs:
build-for-windows-flutter:
name: ${{ matrix.job.target }}
needs: [build-RustDeskTempTopMostWindow]
needs: [build-RustDeskTempTopMostWindow, generate-bridge]
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
@@ -84,6 +87,14 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Restore bridge files
uses: actions/download-artifact@master
with:
name: bridge-artifact
path: ./
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
@@ -95,7 +106,22 @@ jobs:
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
# https://github.com/flutter/flutter/issues/155685
- name: Replace engine with rustdesk custom flutter engine
run: |
flutter doctor -v
flutter precache --windows
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
Expand-Archive -Path windows-x64-release.zip -DestinationPath windows-x64-release
mv -Force windows-x64-release/*  C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
- name: Patch flutter
shell: bash
run: |
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
@@ -108,13 +134,6 @@ jobs:
with:
prefix-key: ${{ matrix.job.os }}
- 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"
Push-Location flutter ; flutter pub get ; Pop-Location
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
- name: Setup vcpkg with Github Actions binary cache
uses: lukka/run-vcpkg@v11
with:
@@ -139,6 +158,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
@@ -258,6 +278,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install LLVM and Clang
uses: rustdesk-org/install-llvm-action-32bit@master
@@ -299,6 +321,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
@@ -374,6 +397,7 @@ jobs:
# use build-for-macOS instead
if: false
runs-on: [self-hosted, macOS, ARM64]
needs: [generate-bridge]
steps:
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
@@ -384,13 +408,14 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
- name: Restore bridge files
uses: actions/download-artifact@master
with:
name: bridge-artifact
path: ./
- name: Build rustdesk
run: |
@@ -446,6 +471,7 @@ jobs:
if: ${{ inputs.upload-artifact }}
name: build rustdesk ios ipa
runs-on: ${{ matrix.job.os }}
needs: [generate-bridge]
strategy:
fail-fast: false
matrix:
@@ -469,12 +495,20 @@ jobs:
brew install nasm yasm
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Patch flutter
run: |
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
- name: Setup vcpkg with Github Actions binary cache
uses: lukka/run-vcpkg@v11
with:
@@ -496,6 +530,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
@@ -510,17 +545,22 @@ jobs:
prefix-key: rustdesk-lib-cache-ios
key: ${{ matrix.job.target }}
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
- name: Restore bridge files
uses: actions/download-artifact@master
with:
name: bridge-artifact
path: ./
- name: Build rustdesk lib
run: |
rustup target add ${{ matrix.job.target }}
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
- name: Upload liblibrustdesk.a Artifacts
uses: actions/upload-artifact@master
with:
name: liblibrustdesk.a
path: target/aarch64-apple-ios/release/liblibrustdesk.a
- name: Build rustdesk
shell: bash
@@ -550,6 +590,7 @@ jobs:
#if: ${{ inputs.upload-artifact }}
if: false
runs-on: [self-hosted, macOS, ARM64]
needs: [generate-bridge]
strategy:
fail-fast: false
steps:
@@ -562,15 +603,16 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
- name: Restore bridge files
uses: actions/download-artifact@master
with:
name: bridge-artifact
path: ./
- name: Build rustdesk lib
run: |
@@ -605,6 +647,7 @@ jobs:
build-for-macOS:
name: ${{ matrix.job.target }}
runs-on: ${{ matrix.job.os }}
needs: [generate-bridge]
strategy:
fail-fast: false
matrix:
@@ -614,13 +657,15 @@ 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,
os: macos-latest,
# 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: "",
extra-build-args: "--screencapturekit",
arch: aarch64,
vcpkg-triplet: arm64-osx,
}
steps:
- name: Export GitHub Actions cache environment variables
@@ -632,6 +677,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Import the codesign cert
if: env.MACOS_P12_BASE64 != null
@@ -668,7 +715,13 @@ jobs:
- name: Install build runtime
run: |
brew install llvm create-dmg nasm cmake gcc wget ninja pkg-config
brew install llvm create-dmg nasm cmake gcc wget ninja
# pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner
if command -v pkg-config &>/dev/null; then
echo "pkg-config is already installed"
else
brew install pkg-config
fi
- name: Install flutter
uses: subosito/flutter-action@v2
@@ -676,6 +729,11 @@ jobs:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Patch flutter
run: |
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
- name: Workaround for flutter issue
shell: bash
run: |
@@ -687,7 +745,7 @@ jobs:
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ env.RUST_VERSION }}
toolchain: ${{ env.MAC_RUST_VERSION }}
targets: ${{ matrix.job.target }}
components: "rustfmt"
@@ -695,12 +753,11 @@ jobs:
with:
prefix-key: ${{ matrix.job.os }}
- name: Install flutter rust bridge deps
shell: bash
run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
- name: Restore bridge files
uses: actions/download-artifact@master
with:
name: bridge-artifact
path: ./
- name: Setup vcpkg with Github Actions binary cache
uses: lukka/run-vcpkg@v11
@@ -722,6 +779,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
@@ -735,6 +793,13 @@ jobs:
- name: Build rustdesk
run: |
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then
MIN_MACOS_VERSION="12.3"
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py
sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj
fi
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: create unsigned dmg
@@ -822,11 +887,8 @@ jobs:
tag_name: ${{ env.TAG_NAME }}
files: rustdesk-${{ env.VERSION }}-unsigned.tar.gz
generate-bridge-linux:
uses: ./.github/workflows/bridge.yml
build-rustdesk-android:
needs: [generate-bridge-linux]
needs: [generate-bridge]
name: build rustdesk android apk ${{ matrix.job.target }}
runs-on: ${{ matrix.job.os }}
strategy:
@@ -894,7 +956,6 @@ jobs:
libpam0g-dev \
libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
@@ -903,18 +964,27 @@ jobs:
llvm-10-dev \
nasm \
ninja-build \
openjdk-11-jdk-headless \
openjdk-17-jdk-headless \
pkg-config \
tree \
wget
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
- name: Patch flutter
run: |
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
@@ -974,7 +1044,7 @@ jobs:
key: ${{ matrix.job.target }}
- name: fix android for flutter 3.13
if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
run: |
cd flutter
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
@@ -989,7 +1059,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
@@ -1022,9 +1092,9 @@ jobs:
- name: Build rustdesk
shell: bash
env:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
run: |
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH
# temporary use debug sign config
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
case ${{ matrix.job.target }} in
@@ -1069,6 +1139,14 @@ jobs:
mkdir -p signed-apk; pushd signed-apk
mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk .
# https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075
- name: Setup sign tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
echo Last build tool version is: $BUILD_TOOL_VERSION
- uses: r0adkll/sign-android-release@v1
name: Sign app APK
if: env.ANDROID_SIGNING_KEY != null
@@ -1080,8 +1158,8 @@ jobs:
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "30.0.2"
# env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable"
BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }}
- name: Upload Artifacts
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
@@ -1157,7 +1235,6 @@ jobs:
libpam0g-dev \
libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
@@ -1166,19 +1243,27 @@ jobs:
llvm-10-dev \
nasm \
ninja-build \
openjdk-11-jdk-headless \
openjdk-17-jdk-headless \
pkg-config \
tree \
wget
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
- name: Patch flutter
run: |
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
- name: Restore bridge files
uses: actions/download-artifact@master
with:
@@ -1211,7 +1296,7 @@ jobs:
path: ./flutter/android/app/src/main/jniLibs/x86
- name: fix android for flutter 3.13
if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
run: |
cd flutter
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
@@ -1223,9 +1308,9 @@ jobs:
- name: Build rustdesk
shell: bash
env:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
run: |
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH
# temporary use debug sign config
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
mv ./flutter/android/app/src/main/jniLibs/arm64-v8a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
@@ -1245,6 +1330,14 @@ jobs:
mkdir -p signed-apk
mv ./flutter/build/app/outputs/flutter-apk/app-${{ env.reltype }}.apk signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
# https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075
- name: Setup sign tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
echo Last build tool version is: $BUILD_TOOL_VERSION
- uses: r0adkll/sign-android-release@v1
name: Sign app APK
if: env.ANDROID_SIGNING_KEY != null
@@ -1256,8 +1349,8 @@ jobs:
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "30.0.2"
# env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable"
BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }}
- name: Upload Artifacts
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
@@ -1285,7 +1378,7 @@ jobs:
signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
build-rustdesk-linux:
needs: [generate-bridge-linux]
needs: [generate-bridge]
name: build rustdesk linux ${{ matrix.job.target }}
runs-on: ${{ matrix.job.on }}
strategy:
@@ -1328,6 +1421,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set Swap Space
if: ${{ matrix.job.arch == 'x86_64' }}
@@ -1376,6 +1471,7 @@ jobs:
- name: Install vcpkg dependencies
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
run: |
sudo apt install -y libva-dev && apt show libva-dev
if ! $VCPKG_ROOT/vcpkg \
install \
--triplet ${{ matrix.job.vcpkg-triplet }} \
@@ -1389,6 +1485,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
@@ -1433,7 +1530,6 @@ jobs:
libpam0g-dev \
libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
@@ -1520,6 +1616,19 @@ jobs:
;;
esac
if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then
case ${{ matrix.job.arch }} in
aarch64)
pushd /opt/flutter-elinux/flutter
;;
x86_64)
pushd /opt/flutter
;;
esac
git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
popd
fi
# build flutter
pushd /workspace
export CARGO_INCREMENTAL=0
@@ -1642,6 +1751,8 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Free Space
run: |
@@ -1693,7 +1804,6 @@ jobs:
libpam0g-dev \
libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
@@ -1771,6 +1881,8 @@ jobs:
cat ~/.cargo/config
# install dependencies from vcpkg
export VCPKG_ROOT=/opt/artifacts/vcpkg
# remove this when support higher version
export USE_AOM_391=1
if ! $VCPKG_ROOT/vcpkg install --triplet ${{ matrix.job.vcpkg-triplet }} --x-install-root="$VCPKG_ROOT/installed"; then
find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do
echo "$_1:"
@@ -1781,6 +1893,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
@@ -1830,6 +1943,8 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Download Binary
uses: actions/download-artifact@master
@@ -1902,6 +2017,8 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Download Binary
uses: actions/download-artifact@master
@@ -1927,36 +2044,17 @@ jobs:
shell: /bin/bash
install: |
apt-get update -y
apt-get install -y \
curl \
git \
rpm \
wget
apt-get install -y git flatpak flatpak-builder
run: |
# disable git safe.directory
git config --global --add safe.directory "*"
pushd /workspace
# install
apt-get update -y
apt-get install -y \
cmake \
curl \
flatpak \
flatpak-builder \
gcc \
git \
g++ \
libgtk-3-dev \
nasm \
wget
# flatpak deps
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08
flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08
flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
# package
pushd flatpak
git clone https://github.com/flathub/shared-modules.git --depth=1
flatpak-builder --user --force-clean --repo=repo ./build ./rustdesk.json
flatpak-builder --user --install-deps-from=flathub -y --force-clean --repo=repo ./build ./rustdesk.json
flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak com.rustdesk.RustDesk
- name: Publish flatpak package
@@ -1978,6 +2076,8 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Prepare env
run: |
@@ -1989,7 +2089,12 @@ jobs:
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Patch flutter
shell: bash
run: |
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
# https://rustdesk.com/docs/en/dev/build/web/
- name: Build web

View File

@@ -16,9 +16,9 @@ env:
FLUTTER_ELINUX_VERSION: "3.16.9"
TAG_NAME: "nightly"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.06.15
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
VERSION: "1.3.2"
# vcpkg version: 2024.11.16
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
VERSION: "1.3.7"
NDK_VERSION: "r26d"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
@@ -90,7 +90,8 @@ jobs:
uses: actions/checkout@v3
with:
ref: ${{ matrix.job.ref }}
submodules: recursive
- name: Import the codesign cert
if: env.MACOS_P12_BASE64 != null
uses: apple-actions/import-codesign-certs@v1
@@ -149,7 +150,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
@@ -250,6 +251,7 @@ jobs:
uses: actions/checkout@v3
with:
ref: ${{ matrix.job.ref }}
submodules: recursive
- name: Install dependencies
run: |
@@ -302,7 +304,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 +349,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

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "libs/hbb_common"]
path = libs/hbb_common
url = https://github.com/rustdesk/hbb_common

94
Cargo.lock generated
View File

@@ -723,9 +723,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.16.1"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
@@ -735,9 +735,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.6.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
dependencies = [
"serde 1.0.203",
]
@@ -893,6 +893,20 @@ dependencies = [
"regex",
]
[[package]]
name = "cidre"
version = "0.4.0"
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
dependencies = [
"cidre-macros",
"parking_lot",
]
[[package]]
name = "cidre-macros"
version = "0.1.0"
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
[[package]]
name = "cipher"
version = "0.4.4"
@@ -1276,10 +1290,10 @@ dependencies = [
[[package]]
name = "cpal"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#6b374bcaed076750ca8fce6da518ab39b882e14a"
dependencies = [
"alsa",
"cidre",
"core-foundation-sys 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
"coreaudio-rs",
"dasp_sample",
@@ -1567,6 +1581,16 @@ dependencies = [
"windows 0.32.0",
]
[[package]]
name = "default_net"
version = "0.1.0"
source = "git+https://github.com/rustdesk-org/default_net#a831d47bcacb4615b394968287697924a8f62be1"
dependencies = [
"anyhow",
"regex",
"winapi 0.3.9",
]
[[package]]
name = "deranged"
version = "0.3.11"
@@ -1683,7 +1707,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading 0.8.4",
"libloading 0.7.4",
]
[[package]]
@@ -2237,9 +2261,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -2247,9 +2271,9 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
@@ -2264,9 +2288,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
@@ -2298,9 +2322,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2 1.0.86",
"quote 1.0.36",
@@ -2309,21 +2333,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
@@ -2887,6 +2911,7 @@ dependencies = [
"bytes",
"chrono",
"confy",
"default_net",
"directories-next",
"dirs-next",
"dlopen",
@@ -2911,6 +2936,7 @@ dependencies = [
"serde 1.0.203",
"serde_derive",
"serde_json 1.0.118",
"sha2",
"socket2 0.3.19",
"sodiumoxide",
"sysinfo",
@@ -3050,8 +3076,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hwcodec"
version = "0.7.0"
source = "git+https://github.com/rustdesk-org/hwcodec#8bbd05bb300ad07cc345356ad85570f9ea99fbfa"
version = "0.7.1"
source = "git+https://github.com/rustdesk-org/hwcodec#c4d6b1c5c4ddc7548868306004cf5d4eb614a36f"
dependencies = [
"bindgen 0.59.2",
"cc",
@@ -4149,7 +4175,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro-crate 1.3.1",
"proc-macro2 1.0.86",
"quote 1.0.36",
"syn 2.0.68",
@@ -4368,9 +4394,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.64"
version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags 2.6.0",
"cfg-if 1.0.0",
@@ -4400,9 +4426,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
@@ -5205,7 +5231,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#f9b60b1dd0f3300a1b797d7a74c116683cd232c8"
dependencies = [
"cocoa 0.24.1",
"core-foundation 0.9.4",
@@ -5452,7 +5478,7 @@ dependencies = [
[[package]]
name = "rust-pulsectl"
version = "0.2.12"
source = "git+https://github.com/open-trade/pulsectl#5e68f4c2b7c644fa321984688602d71e8ad0bba3"
source = "git+https://github.com/rustdesk-org/pulsectl#aa34dde499aa912a3abc5289cc0b547bd07dd6e2"
dependencies = [
"libpulse-binding",
]
@@ -5480,7 +5506,7 @@ dependencies = [
[[package]]
name = "rustdesk"
version = "1.3.2"
version = "1.3.7"
dependencies = [
"android-wakelock",
"android_logger",
@@ -5580,7 +5606,7 @@ dependencies = [
[[package]]
name = "rustdesk-portable-packer"
version = "1.3.2"
version = "1.3.7"
dependencies = [
"brotli",
"dirs 5.0.1",
@@ -5799,7 +5825,7 @@ dependencies = [
[[package]]
name = "sciter-rs"
version = "0.5.57"
source = "git+https://github.com/open-trade/rust-sciter?branch=dyn#5322f3a755a0e6bf999fbc60d1efc35246c0f821"
source = "git+https://github.com/rustdesk-org/rust-sciter?branch=dyn#5322f3a755a0e6bf999fbc60d1efc35246c0f821"
dependencies = [
"lazy_static",
"libc",
@@ -6445,7 +6471,7 @@ dependencies = [
[[package]]
name = "tfc"
version = "0.7.0"
source = "git+https://github.com/rustdesk-org/The-Fat-Controller?branch=history/rebase_upstream_20240722#de9c8ba480f166a9fc90aaa47bb0e84b443ea9c6"
source = "git+https://github.com/rustdesk-org/The-Fat-Controller?branch=history/rebase_upstream_20240722#78bb80a8e596e4c14ae57c8448f5fca75f91f2b0"
dependencies = [
"anyhow",
"core-graphics 0.23.2",

View File

@@ -1,6 +1,6 @@
[package]
name = "rustdesk"
version = "1.3.2"
version = "1.3.7"
authors = ["rustdesk <info@rustdesk.com>"]
edition = "2021"
build= "build.rs"
@@ -16,6 +16,10 @@ crate-type = ["cdylib", "staticlib", "rlib"]
name = "naming"
path = "src/naming.rs"
[[bin]]
name = "service"
path = "src/service.rs"
[features]
inline = []
cli = []
@@ -36,6 +40,7 @@ unix-file-copy-paste = [
"dep:once_cell",
"clipboard/unix-file-copy-paste",
]
screencapturekit = ["cpal/screencapturekit"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -78,13 +83,14 @@ zip = "0.6"
shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.15"
[target.'cfg(not(target_os = "linux"))'.dependencies]
# https://github.com/rustdesk/rustdesk/discussions/10197, not use cpal on linux
cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" }
ringbuf = "0.3"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1"
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
sciter-rs = { git = "https://github.com/rustdesk-org/rust-sciter", branch = "dyn" }
sys-locale = "0.3"
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
clipboard = { path = "libs/clipboard" }
@@ -150,7 +156,7 @@ reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocki
[target.'cfg(target_os = "linux")'.dependencies]
psimple = { package = "libpulse-simple-binding", version = "2.27" }
pulse = { package = "libpulse-binding", version = "2.27" }
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
rust-pulsectl = { git = "https://github.com/rustdesk-org/pulsectl" }
async-process = "1.7"
evdev = { git="https://github.com/rustdesk-org/evdev" }
dbus = "0.9"

View File

@@ -2,6 +2,7 @@ FROM debian:bullseye-slim
WORKDIR /
ARG DEBIAN_FRONTEND=noninteractive
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
RUN apt update -y && \
apt install --yes --no-install-recommends \
g++ \
@@ -21,7 +22,8 @@ RUN apt update -y && \
libpam0g-dev \
libpulse-dev \
make \
cmake \
wget \
libssl-dev \
unzip \
zip \
sudo \
@@ -31,6 +33,13 @@ RUN apt update -y && \
ninja-build && \
rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.6/cmake-3.30.6.tar.gz --no-check-certificate && \
tar xzf cmake-3.30.6.tar.gz && \
cd cmake-3.30.6 && \
./configure --prefix=/usr/local && \
make && \
make install
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom

View File

@@ -25,9 +25,12 @@ RustDesk welcomes contribution from everyone. See [CONTRIBUTING.md](docs/CONTRIB
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
[<img src="https://flathub.org/api/badge?svg&locale=en"
alt="Get it on Flathub"
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
## Dependencies
@@ -172,6 +175,3 @@ Please ensure that you are running these commands from the root of the RustDesk
![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
## [Public Servers](#public-servers)
RustDesk is supported by a free EU server, graciously provided by [Codext GmbH](https://codext.link/rustdesk?utm_source=github)

View File

@@ -18,8 +18,8 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.3.2
exec: usr/lib/rustdesk/rustdesk
version: 1.3.7
exec: usr/share/rustdesk/rustdesk
exec_args: $@
apt:
arch:
@@ -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
@@ -77,7 +77,7 @@ AppDir:
env:
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules
GDK_BACKEND: x11
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/aarch64-linux-gnu:$APPDIR/usr/lib/aarch64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/aarch64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/aarch64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/aarch64-linux-gnu/pulseaudio:$APPDIR/usr/lib/aarch64-linux-gnu/sasl2:$APPDIR/usr/lib/aarch64-linux-gnu/vdpau:$APPDIR/usr/lib/rustdesk/lib:$APPDIR/lib/aarch64
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/aarch64-linux-gnu:$APPDIR/usr/lib/aarch64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/aarch64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/aarch64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/aarch64-linux-gnu/pulseaudio:$APPDIR/usr/lib/aarch64-linux-gnu/sasl2:$APPDIR/usr/lib/aarch64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/aarch64
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
test:

View File

@@ -18,8 +18,8 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.3.2
exec: usr/lib/rustdesk/rustdesk
version: 1.3.7
exec: usr/share/rustdesk/rustdesk
exec_args: $@
apt:
arch:
@@ -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
@@ -80,7 +80,7 @@ AppDir:
env:
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/x86_64-linux-gnu/gio/modules:$APPDIR/usr/lib/x86_64-linux-gnu/gio/modules
GDK_BACKEND: x11
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/x86_64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/x86_64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/x86_64-linux-gnu/pulseaudio:$APPDIR/usr/lib/x86_64-linux-gnu/sasl2:$APPDIR/usr/lib/x86_64-linux-gnu/vdpau:$APPDIR/usr/lib/rustdesk/lib:$APPDIR/lib/x86_64
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/x86_64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/x86_64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/x86_64-linux-gnu/pulseaudio:$APPDIR/usr/lib/x86_64-linux-gnu/sasl2:$APPDIR/usr/lib/x86_64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/x86_64
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
test:

View File

@@ -9,6 +9,7 @@ import shutil
import hashlib
import argparse
import sys
from pathlib import Path
windows = platform.platform().startswith('Windows')
osx = platform.platform().startswith(
@@ -111,7 +112,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',
@@ -143,6 +144,12 @@ def make_parser():
"--package",
type=str
)
if osx:
parser.add_argument(
'--screencapturekit',
action='store_true',
help='Enable feature screencapturekit'
)
return parser
@@ -274,6 +281,9 @@ def get_features(args):
features.append('flutter')
if args.unix_file_copy_paste:
features.append('unix-file-copy-paste')
if osx:
if args.screencapturekit:
features.append('screencapturekit')
print("features:", features)
return features
@@ -289,7 +299,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.
@@ -312,7 +322,7 @@ def build_flutter_deb(version, features):
os.chdir('flutter')
system2('flutter build linux --release')
system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mkdir -p tmpdeb/etc/rustdesk/')
system2('mkdir -p tmpdeb/etc/pam.d/')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
@@ -322,7 +332,7 @@ def build_flutter_deb(version, features):
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true')
system2(
f'cp -r {flutter_build_dir}/* tmpdeb/usr/lib/rustdesk/')
f'cp -r {flutter_build_dir}/* tmpdeb/usr/share/rustdesk/')
system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2(
@@ -345,7 +355,7 @@ def build_flutter_deb(version, features):
system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file_folder("tmpdeb/")
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
system2('/bin/rm -rf tmpdeb/')
@@ -357,7 +367,7 @@ def build_flutter_deb(version, features):
def build_deb_from_folder(version, binary_folder):
os.chdir('flutter')
system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
@@ -365,7 +375,7 @@ def build_deb_from_folder(version, binary_folder):
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true')
system2(
f'cp -r ../{binary_folder}/* tmpdeb/usr/lib/rustdesk/')
f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/')
system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2(
@@ -382,7 +392,7 @@ def build_deb_from_folder(version, binary_folder):
system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file_folder("tmpdeb/")
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
system2('/bin/rm -rf tmpdeb/')
@@ -395,12 +405,13 @@ def build_flutter_dmg(version, features):
if not skip_cargo:
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
system2(
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --release')
# copy dylib
system2(
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
os.chdir('flutter')
system2('flutter build macos --release')
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
'''
system2(
"create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
@@ -612,21 +623,24 @@ def main():
os.system('mkdir -p tmpdeb/etc/pam.d/')
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2('strip tmpdeb/usr/bin/rustdesk')
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('etc/rustdesk/startwm.sh')
md5_file('etc/X11/rustdesk/xorg.conf')
md5_file('etc/pam.d/rustdesk')
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/')
system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/')
md5_file_folder("tmpdeb/")
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
def md5_file(fn):
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest()
system2('echo "%s %s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
system2('echo "%s /%s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
def md5_file_folder(base_dir):
base_path = Path(base_dir)
for file in base_path.rglob('*'):
if file.is_file() and 'DEBIAN' not in file.parts:
relative_path = file.relative_to(base_path)
md5_file(str(relative_path))
if __name__ == "__main__":

View File

@@ -1,7 +1,7 @@
#[cfg(windows)]
fn build_windows() {
let file = "src/platform/windows.cc";
let file2 = "src/platform/windows_delete_test_cert.cc";
let file2 = "src/platform/windows_delete_test_cert.cc";
cc::Build::new().file(file).file(file2).compile("windows");
println!("cargo:rustc-link-lib=WtsApi32");
println!("cargo:rerun-if-changed={}", file);
@@ -61,7 +61,11 @@ fn install_android_deps() {
let target = format!("{}-android", target_arch);
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
let mut path: std::path::PathBuf = vcpkg_root.into();
path.push("installed");
if let Ok(vcpkg_root) = std::env::var("VCPKG_INSTALLED_ROOT") {
path = vcpkg_root.into();
} else {
path.push("installed");
}
path.push(target);
println!(
"{}",
@@ -72,7 +76,6 @@ fn install_android_deps() {
);
println!("cargo:rustc-link-lib=ndk_compat");
println!("cargo:rustc-link-lib=oboe");
println!("cargo:rustc-link-lib=oboe_wrapper");
println!("cargo:rustc-link-lib=c++");
println!("cargo:rustc-link-lib=OpenSLES");
}

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

@@ -164,3 +164,4 @@ Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium Ru
![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png)
![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png)

View File

@@ -172,6 +172,3 @@ target/release/rustdesk
![Тунелювання TCP](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
## [Публічні сервери](#публічні-сервери)
RustDesk підтримується безкоштовним європейським сервером, любʼязно наданим [Codext GmbH](https://codext.link/rustdesk?utm_source=github)

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

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.rustdesk.RustDesk</id>
<developer id="com.rustdesk">
<name>RustDesk</name>
</developer>
<launchable type="desktop-id">com.rustdesk.RustDesk.desktop</launchable>
<metadata_license>CC0-1.0</metadata_license>
<project_license>AGPL-3.0-only</project_license>
<name>RustDesk</name>
<summary>Secure remote desktop access</summary>
<description>
<p>
RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration.
</p>
<ul>
<li> Works on Windows, macOS, Linux, iOS, Android, Web. </li>
<li> Supports VP8 / VP9 / AV1 software codecs, and H264 / H265 hardware codecs. </li>
<li> Own your data, easily set up self-hosting solution on your infrastructure. </li>
<li> P2P connection with end-to-end encryption based on NaCl. </li>
<li> No administrative privileges or installation needed for Windows, elevate priviledge locally or from remote on demand. </li>
<li> We like to keep things simple and will strive to make simpler where possible. </li>
</ul>
<p>
For self-hosting setup instructions please go to our home page.
</p>
</description>
<categories>
<category>Utility</category>
</categories>
<screenshots>
<screenshot type="default">
<caption>Remote desktop session</caption>
<image>https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png</image>
</screenshot>
</screenshots>
<branding>
<color type="primary" scheme_preference="light">#d9eaf8</color>
<color type="primary" scheme_preference="dark">#0160ee</color>
</branding>
<url type="homepage">https://rustdesk.com</url>
<url type="bugtracker">https://github.com/rustdesk/rustdesk/issues</url>
<url type="faq">https://github.com/rustdesk/rustdesk/wiki/FAQ</url>
<url type="help">https://rustdesk.com/docs</url>
<url type="donation">https://ko-fi.com/rustdesk</url>
<url type="vcs-browser">https://github.com/rustdesk/rustdesk</url>
<url type="translate">https://github.com/rustdesk/rustdesk/tree/master/src/lang</url>
<url type="contribute">https://github.com/rustdesk/rustdesk/blob/master/docs/CONTRIBUTING.md</url>
<url type="contact">https://rustdesk.com/docs/en/technical-support</url>
<requires>
<display_length compare="ge">600</display_length>
<internet>always</internet>
</requires>
<supports>
<control>keyboard</control>
<control>pointing</control>
</supports>
<content_rating type="oars-1.1"/>
</component>

View File

@@ -1,19 +1,30 @@
{
"id": "com.rustdesk.RustDesk",
"runtime": "org.freedesktop.Platform",
"runtime-version": "23.08",
"runtime-version": "24.08",
"sdk": "org.freedesktop.Sdk",
"command": "rustdesk",
"icon": "share/icons/hicolor/scalable/apps/rustdesk.svg",
"cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"],
"rename-desktop-file": "rustdesk.desktop",
"rename-icon": "rustdesk",
"modules": [
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
"xdotool.json",
{
"name": "pam",
"buildsystem": "simple",
"build-commands": [
"./configure --disable-selinux --prefix=/app && make -j4 install"
],
"name": "xdotool",
"no-autogen": true,
"make-install-args": ["PREFIX=${FLATPAK_DEST}"],
"sources": [
{
"type": "archive",
"url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz",
"sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada"
}
]
},
{
"name": "pam",
"buildsystem": "autotools",
"config-opts": ["--disable-selinux"],
"sources": [
{
"type": "archive",
@@ -26,32 +37,24 @@
"name": "rustdesk",
"buildsystem": "simple",
"build-commands": [
"bsdtar -zxvf rustdesk.deb",
"tar -xvf ./data.tar.xz",
"cp -r ./usr/* /app/",
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
"mv /app/share/applications/rustdesk.desktop /app/share/applications/com.rustdesk.RustDesk.desktop",
"mv /app/share/applications/rustdesk-link.desktop /app/share/applications/com.rustdesk.RustDesk-link.desktop",
"sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/*.desktop",
"mv /app/share/icons/hicolor/scalable/apps/rustdesk.svg /app/share/icons/hicolor/scalable/apps/com.rustdesk.RustDesk.svg",
"for size in 16 24 32 48 64 128 256 512; do\n rsvg-convert -w $size -h $size -f png -o $size.png scalable.svg\n install -Dm644 $size.png /app/share/icons/hicolor/${size}x${size}/apps/com.rustdesk.RustDesk.png\n done"
"bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -",
"cp -r usr/* /app/",
"mkdir -p /app/bin && ln -s /app/share/rustdesk/rustdesk /app/bin/rustdesk"
],
"cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"],
"sources": [
{
"type": "file",
"path": "./rustdesk.deb"
"path": "rustdesk.deb"
},
{
"type": "file",
"path": "../res/scalable.svg"
"path": "com.rustdesk.RustDesk.metainfo.xml"
}
]
}
],
"finish-args": [
"--share=ipc",
"--socket=x11",
"--socket=fallback-x11",
"--socket=wayland",
"--share=network",
@@ -60,4 +63,4 @@
"--socket=pulseaudio",
"--talk-name=org.freedesktop.Flatpak"
]
}
}

View File

@@ -1,15 +0,0 @@
{
"name": "xdotool",
"buildsystem": "simple",
"build-commands": [
"make -j4 && PREFIX=./build make install",
"cp -r ./build/* /app/"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz",
"sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada"
}
]
}

View File

@@ -1,6 +1,9 @@
import com.google.protobuf.gradle.*
plugins {
id "com.google.protobuf" version "0.9.4"
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def keystoreProperties = new Properties()
@@ -17,11 +20,6 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
@@ -32,10 +30,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
dependencies {
implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
}
@@ -57,7 +51,7 @@ protobuf {
}
android {
compileSdkVersion 33
compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -105,7 +99,6 @@ flutter {
dependencies {
implementation "androidx.media:media:1.6.0"
implementation 'com.github.getActivity:XXPermissions:18.5'
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("1.9.10") } }
implementation 'com.caverock:androidsvg-aar:1.4'
}

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

@@ -304,7 +304,13 @@ class FloatingWindowService : Service(), View.OnTouchListener {
val popupMenu = PopupMenu(this, floatingView)
val idShowRustDesk = 0
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
val idStopService = 1
// For host side, clipboard sync
val idSyncClipboard = 1
val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled()
if (isServiceSyncEnabled) {
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
}
val idStopService = 2
popupMenu.menu.add(0, idStopService, 0, translate("Stop service"))
popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
@@ -312,6 +318,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
openMainActivity()
true
}
idSyncClipboard -> {
syncClipboard()
true
}
idStopService -> {
stopMainService()
true
@@ -340,6 +350,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
}
}
private fun syncClipboard() {
MainActivity.rdClipboardManager?.syncClipboard(false)
}
private fun stopMainService() {
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
}

View File

@@ -280,20 +280,20 @@ class InputService : AccessibilityService() {
var textToCommit: String? = null
if (keyboardMode == KeyboardMode.Legacy) {
if (keyEvent.hasChr() && keyEvent.getDown()) {
// [down] indicates the key's state(down or up).
// [press] indicates a click event(down and up).
// https://github.com/rustdesk/rustdesk/blob/3a7594755341f023f56fa4b6a43b60d6b47df88d/flutter/lib/models/input_model.dart#L688
if (keyEvent.hasSeq()) {
textToCommit = keyEvent.getSeq()
} else if (keyboardMode == KeyboardMode.Legacy) {
if (keyEvent.hasChr() && (keyEvent.getDown() || keyEvent.getPress())) {
val chr = keyEvent.getChr()
if (chr != null) {
textToCommit = String(Character.toChars(chr))
}
}
} else if (keyboardMode == KeyboardMode.Translate) {
if (keyEvent.hasSeq() && keyEvent.getDown()) {
val seq = keyEvent.getSeq()
if (seq != null) {
textToCommit = seq
}
}
} else {
}
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit")
@@ -320,6 +320,10 @@ class InputService : AccessibilityService() {
} else {
ke?.let { event ->
inputConnection.sendKeyEvent(event)
if (keyEvent.getPress()) {
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
inputConnection.sendKeyEvent(actionUpEvent)
}
}
}
}
@@ -333,6 +337,10 @@ class InputService : AccessibilityService() {
for (item in possibleNodes) {
val success = trySendKeyEvent(event, item, textToCommit)
if (success) {
if (keyEvent.getPress()) {
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
trySendKeyEvent(actionUpEvent, item, textToCommit)
}
break
}
}

View File

@@ -31,14 +31,12 @@ object KeyEventConverter {
}
var action = 0
if (keyEventProto.getDown()) {
if (keyEventProto.getDown() || keyEventProto.getPress()) {
action = KeyEvent.ACTION_DOWN
} else {
action = KeyEvent.ACTION_UP
}
// FIXME: The last parameter is the repeat count, not modifiers ?
// https://developer.android.com/reference/android/view/KeyEvent#KeyEvent(long,%20long,%20int,%20int,%20int)
return KeyEvent(0, 0, action, chrValue, 0, modifiers)
}

View File

@@ -13,6 +13,8 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.ClipboardManager
import android.os.Bundle
import android.os.Build
import android.os.IBinder
import android.util.Log
@@ -36,6 +38,9 @@ import kotlin.concurrent.thread
class MainActivity : FlutterActivity() {
companion object {
var flutterMethodChannel: MethodChannel? = null
private var _rdClipboardManager: RdClipboardManager? = null
val rdClipboardManager: RdClipboardManager?
get() = _rdClipboardManager;
}
private val channelTag = "mChannel"
@@ -85,6 +90,14 @@ class MainActivity : FlutterActivity() {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (_rdClipboardManager == null) {
_rdClipboardManager = RdClipboardManager(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
FFI.setClipboardManager(_rdClipboardManager!!)
}
}
override fun onDestroy() {
Log.e(logTag, "onDestroy")
mainService?.let {
@@ -207,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))

View File

@@ -302,6 +302,8 @@ class MainService : Service() {
stopCapture()
FFI.refreshScreen()
startCapture()
} else {
FFI.refreshScreen()
}
}
@@ -431,6 +433,7 @@ class MainService : Service() {
checkMediaPermission()
_isStart = true
FFI.setFrameRawEnable("video",true)
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
return true
}
@@ -439,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

@@ -0,0 +1,197 @@
package com.carriez.flutter_hbb
import java.nio.ByteBuffer
import java.util.Timer
import java.util.TimerTask
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.util.Log
import androidx.annotation.Keep
import hbb.MessageOuterClass.ClipboardFormat
import hbb.MessageOuterClass.Clipboard
import hbb.MessageOuterClass.MultiClipboards
import ffi.FFI
class RdClipboardManager(private val clipboardManager: ClipboardManager) {
private val logTag = "RdClipboardManager"
private val supportedMimeTypes = arrayOf(
ClipDescription.MIMETYPE_TEXT_PLAIN,
ClipDescription.MIMETYPE_TEXT_HTML
)
// 1. Avoid listening to the same clipboard data updated by `rustUpdateClipboard`.
// 2. Avoid sending the clipboard data before enabling client clipboard.
// 1) Disable clipboard
// 2) Copy text "a"
// 3) Enable clipboard
// 4) Switch to another app
// 5) Switch back to the app
// 6) "a" should not be sent to the client, because it's copied before enabling clipboard
//
// It's okay to that `rustEnableClientClipboard(false)` is called after `rustUpdateClipboard`,
// though the `lastUpdatedClipData` will be set to null once.
private var lastUpdatedClipData: ClipData? = null
private var isClientEnabled = true;
private var _isCaptureStarted = false;
val isCaptureStarted: Boolean
get() = _isCaptureStarted
fun checkPrimaryClip(isClient: Boolean) {
val clipData = clipboardManager.primaryClip
if (clipData != null && clipData.itemCount > 0) {
// Only handle the first item in the clipboard for now.
val clip = clipData.getItemAt(0)
// Ignore the `isClipboardDataEqual()` check if it's a host operation.
// Because it's an action manually triggered by the user.
if (isClient) {
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
Log.d(logTag, "Clipboard data is the same as last update, ignore")
return
}
}
val mimeTypeCount = clipData.description.getMimeTypeCount()
val mimeTypes = mutableListOf<String>()
for (i in 0 until mimeTypeCount) {
mimeTypes.add(clipData.description.getMimeType(i))
}
var text: CharSequence? = null;
var html: String? = null;
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
text = clip?.text
}
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) {
text = clip?.text
html = clip?.htmlText
}
var count = 0
val clips = MultiClipboards.newBuilder()
if (text != null) {
val content = com.google.protobuf.ByteString.copyFromUtf8(text.toString())
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Text).setContent(content).build())
count++
}
if (html != null) {
val content = com.google.protobuf.ByteString.copyFromUtf8(html)
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Html).setContent(content).build())
count++
}
if (count > 0) {
val clipsBytes = clips.build().toByteArray()
val isClientFlag = if (isClient) 1 else 0
val clipsBuf = ByteBuffer.allocateDirect(clipsBytes.size + 1).apply {
put(isClientFlag.toByte())
put(clipsBytes)
}
clipsBuf.flip()
lastUpdatedClipData = clipData
Log.d(logTag, "${if (isClient) "client" else "host"}, send clipboard data to the remote")
FFI.onClipboardUpdate(clipsBuf)
}
}
}
private fun isSupportedMimeType(mimeType: String): Boolean {
return supportedMimeTypes.contains(mimeType)
}
private fun isClipboardDataEqual(left: ClipData, right: ClipData): Boolean {
if (left.description.getMimeTypeCount() != right.description.getMimeTypeCount()) {
return false
}
val mimeTypeCount = left.description.getMimeTypeCount()
for (i in 0 until mimeTypeCount) {
if (left.description.getMimeType(i) != right.description.getMimeType(i)) {
return false
}
}
if (left.itemCount != right.itemCount) {
return false
}
for (i in 0 until left.itemCount) {
val mimeType = left.description.getMimeType(i)
if (!isSupportedMimeType(mimeType)) {
continue
}
val leftItem = left.getItemAt(i)
val rightItem = right.getItemAt(i)
if (mimeType == ClipDescription.MIMETYPE_TEXT_PLAIN || mimeType == ClipDescription.MIMETYPE_TEXT_HTML) {
if (leftItem.text != rightItem.text || leftItem.htmlText != rightItem.htmlText) {
return false
}
}
}
return true
}
fun setCaptureStarted(started: Boolean) {
_isCaptureStarted = started
}
@Keep
fun rustEnableClientClipboard(enable: Boolean) {
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
isClientEnabled = enable
lastUpdatedClipData = null
}
fun syncClipboard(isClient: Boolean) {
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled")
if (isClient && !isClientEnabled) {
return
}
checkPrimaryClip(isClient)
}
@Keep
fun rustUpdateClipboard(clips: ByteArray) {
val clips = MultiClipboards.parseFrom(clips)
var mimeTypes = mutableListOf<String>()
var text: String? = null
var html: String? = null
for (clip in clips.getClipboardsList()) {
when (clip.format) {
ClipboardFormat.Text -> {
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_PLAIN)
text = String(clip.content.toByteArray(), Charsets.UTF_8)
}
ClipboardFormat.Html -> {
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_HTML)
html = String(clip.content.toByteArray(), Charsets.UTF_8)
}
ClipboardFormat.ImageRgba -> {
}
ClipboardFormat.ImagePng -> {
}
else -> {
Log.e(logTag, "Unsupported clipboard format: ${clip.format}")
}
}
}
val clipDescription = ClipDescription("clipboard", mimeTypes.toTypedArray())
var item: ClipData.Item? = null
if (text == null) {
Log.e(logTag, "No text content in clipboard")
return
} else {
if (html == null) {
item = ClipData.Item(text)
} else {
item = ClipData.Item(text, html)
}
}
if (item == null) {
Log.e(logTag, "No item in clipboard")
return
}
val clipData = ClipData(clipDescription, item)
lastUpdatedClipData = clipData
clipboardManager.setPrimaryClip(clipData)
}
}

View File

@@ -5,12 +5,15 @@ package ffi
import android.content.Context
import java.nio.ByteBuffer
import com.carriez.flutter_hbb.RdClipboardManager
object FFI {
init {
System.loadLibrary("rustdesk")
}
external fun init(ctx: Context)
external fun setClipboardManager(clipboardManager: RdClipboardManager)
external fun startServer(app_dir: String, custom_client_config: String)
external fun startService()
external fun onVideoFrameUpdate(buf: ByteBuffer)
@@ -20,4 +23,6 @@ object FFI {
external fun setFrameRawEnable(name: String, value: Boolean)
external fun setCodecInfo(info: String)
external fun getLocalOption(key: String): String
}
external fun onClipboardUpdate(clips: ByteBuffer)
external fun isServiceClipboardEnabled(): Boolean
}

View File

@@ -1,18 +1,3 @@
buildscript {
ext.kotlin_version = '1.9.10'
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.14'
}
}
allprojects {
repositories {
google()
@@ -24,6 +9,8 @@ allprojects {
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip

View File

@@ -1,11 +1,25 @@
include ':app'
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
}
include ":app"

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}"
@@ -101,18 +136,27 @@ 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)"
FLUTTER_RUST_BRIDGE_VERSION="$(yq -r \
.env.FLUTTER_RUST_BRIDGE_VERSION \
.github/workflows/flutter-build.yml)"
.github/workflows/bridge.yml)"
NDK_VERSION="$(yq -r \
.env.NDK_VERSION \
@@ -127,6 +171,7 @@ prebuild)
.github/workflows/flutter-build.yml)"
if [ -z "${CARGO_NDK_VERSION}" ] || [ -z "${FLUTTER_VERSION}" ] ||
[ -z "${FLUTTER_BRIDGE_VERSION}" ] ||
[ -z "${FLUTTER_RUST_BRIDGE_VERSION}" ] ||
[ -z "${NDK_VERSION}" ] || [ -z "${RUST_VERSION}" ] ||
[ -z "${VCPKG_COMMIT_ID}" ]; then
@@ -163,24 +208,6 @@ prebuild)
sdkmanager --install "ndk;${NDK_VERSION}"
fi
# Install Flutter
if [ ! -f "${HOME}/flutter/bin/flutter" ]; then
pushd "${HOME}"
git clone https://github.com/flutter/flutter
pushd flutter
git reset --hard "${FLUTTER_VERSION}"
flutter config --no-analytics
popd # flutter
popd # ${HOME}
fi
# Install Rust
if [ ! -f "${HOME}/rustup/rustup-init.sh" ]; then
@@ -205,14 +232,18 @@ prebuild)
cargo install \
cargo-ndk \
--version "${CARGO_NDK_VERSION}"
--version "${CARGO_NDK_VERSION}" \
--locked
# Install rust bridge generator
cargo install cargo-expand
cargo install \
cargo-expand \
--locked
cargo install flutter_rust_bridge_codegen \
--version "${FLUTTER_RUST_BRIDGE_VERSION}" \
--features "uuid"
--features "uuid" \
--locked
# Populate native vcpkg dependencies
@@ -275,12 +306,66 @@ prebuild)
git apply res/fdroid/patches/*.patch
# If Flutter version used to generate bridge files differs from Flutter
# version used to compile Rustdesk library, generate bridge using the
# `FLUTTER_BRIDGE_VERSION` an restore the pubspec later
if [ "${FLUTTER_VERSION}" != "${FLUTTER_BRIDGE_VERSION}" ]; then
# Install Flutter bridge version
prepare_flutter "${FLUTTER_BRIDGE_VERSION}" "${HOME}/flutter"
# Save changes
git add .
# Edit pubspec to make flutter bridge version work
sed \
-i \
-e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' \
flutter/pubspec.yaml
# Download Flutter dependencies
pushd flutter
flutter clean
flutter packages pub get
popd # flutter
# Generate FFI bindings
flutter_rust_bridge_codegen \
--rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart
# 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 these files now, but we still keep the following lines.
sed \
-i \
-e '/firebase_analytics/d' \
@@ -296,34 +381,6 @@ prebuild)
-e '/firebase/Id' \
flutter/lib/main.dart
if [ "${FLUTTER_VERSION}" = "3.13.9" ]; then
# Fix for android 3.13.9
# https://github.com/rustdesk/rustdesk/blob/285e974d1a52c891d5fcc28e963d724e085558bc/.github/workflows/flutter-build.yml#L862
sed \
-i \
-e 's/extended_text: .*/extended_text: 11.1.0/' \
-e 's/uni_links_desktop/#uni_links_desktop/g' \
flutter/pubspec.yaml
set --
while read -r _1; do
set -- "$@" "${_1}"
done 0<<.a
$(find flutter/lib/ -type f -name "*dart*")
.a
sed \
-i \
-e 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' \
"$@"
set --
fi
sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_VERSION}/" flutter-sdk/.gclient
;;
build)
# build: perform actual build of APK file
@@ -335,9 +392,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 \
@@ -373,16 +433,11 @@ build)
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
# Build host android deps
bash flutter/build_android_deps.sh "${ANDROID_ABI}"

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

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
cd build/web/
python3 -c 'x=open("./main.dart.js", "rt").read();import re;y=re.search("https://.*canvaskit-wasm@([\d\.]+)/bin/",x);dirname="canvaskit@"+y.groups()[0];z=x.replace(y.group(),"/"+dirname+"/");f=open("./main.dart.js", "wt");f.write(z);import os;os.system("ln -s canvaskit " + dirname);'
mv jds/dist/index.js ./
mv jds/dist/vendor.js ./
/bin/rm -rf js
python3 -c 'import hashlib;x=hashlib.sha1(open("./main.dart.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("main.dart.js", "main.dart.js?v="+x);open("index.html","wt").write(y)'
python3 -c 'import hashlib;x=hashlib.sha1(open("./index.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("js/dist/index.js", "index.js?v="+x);open("index.html","wt").write(y)'
python3 -c 'import hashlib;x=hashlib.sha1(open("./vendor.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("js/dist/vendor.js", "vendor.js?v="+x);open("index.html","wt").write(y)'
tar czf x *
scp x sg:/tmp/
ssh sg "sudo tar xzf /tmp/x -C /var/www/html/web.rustdesk.com/ && /bin/rm /tmp/x && sudo chown www-data:www-data /var/www/html/web.rustdesk.com/ -R"
/bin/rm x
cd -

View File

@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
</dict>
</plist>

View File

@@ -1,10 +1,7 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
platform :ios, '12.0'
platform :ios, '13.0'
project 'Runner', {
'Debug' => :debug,

View File

@@ -133,10 +133,10 @@ SPEC CHECKSUMS:
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156
PODFILE CHECKSUM: 83d1b0fb6fc8613d8312a03b8e1540d37cfc5d2c
COCOAPODS: 1.15.2

View File

@@ -347,7 +347,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -491,7 +491,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -541,7 +541,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;

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

@@ -64,6 +64,9 @@ int androidVersion = 0;
// So we need to use this flag to enable/disable resizable.
bool _linuxWindowResizable = true;
// Only used on Windows(window manager).
bool _ignoreDevicePixelRatio = true;
/// only available for Windows target
int windowsBuildNumber = 0;
DesktopType? desktopType;
@@ -1611,12 +1614,6 @@ Widget getPlatformImage(String platform, {double size = 50}) {
return SvgPicture.asset('assets/$platform.svg', height: size, width: size);
}
class OffsetDevicePixelRatio {
Offset offset;
final double devicePixelRatio;
OffsetDevicePixelRatio(this.offset, this.devicePixelRatio);
}
class LastWindowPosition {
double? width;
double? height;
@@ -1699,8 +1696,10 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
if (isFullscreen || isMaximized) {
setPreFrame();
} else {
position = await windowManager.getPosition();
sz = await windowManager.getSize();
position = await windowManager.getPosition(
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
sz = await windowManager.getSize(
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
}
break;
default:
@@ -1818,7 +1817,7 @@ bool isPointInRect(Offset point, Rect rect) {
}
/// return null means center
Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
Future<Offset?> _adjustRestoreMainWindowOffset(
double? left,
double? top,
double? width,
@@ -1832,13 +1831,9 @@ Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
double? frameTop;
double? frameRight;
double? frameBottom;
double devicePixelRatio = 1.0;
if (isDesktop || isWebDesktop) {
for (final screen in await window_size.getScreenList()) {
if (isPointInRect(Offset(left, top), screen.visibleFrame)) {
devicePixelRatio = screen.scaleFactor;
}
frameLeft = frameLeft == null
? screen.visibleFrame.left
: min(screen.visibleFrame.left, frameLeft);
@@ -1872,7 +1867,7 @@ Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
top < frameTop!) {
return null;
} else {
return OffsetDevicePixelRatio(Offset(left, top), devicePixelRatio);
return Offset(left, top);
}
}
@@ -1932,47 +1927,23 @@ Future<bool> restoreWindowPosition(WindowType type,
}
final size = await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offsetDevicePixelRatio = await _adjustRestoreMainWindowOffset(
final offsetLeftTop = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth,
lpos.offsetHeight,
size.width,
size.height,
);
debugPrint(
"restore lpos: ${size.width}/${size.height}, offset:${offsetDevicePixelRatio?.offset.dx}/${offsetDevicePixelRatio?.offset.dy}, devicePixelRatio:${offsetDevicePixelRatio?.devicePixelRatio}, isMaximized: ${lpos.isMaximized}, isFullscreen: ${lpos.isFullscreen}");
"restore lpos: ${size.width}/${size.height}, offset:${offsetLeftTop?.dx}/${offsetLeftTop?.dy}, isMaximized: ${lpos.isMaximized}, isFullscreen: ${lpos.isFullscreen}");
switch (type) {
case WindowType.Main:
// https://github.com/rustdesk/rustdesk/issues/8038
// `setBounds()` in `window_manager` will use the current devicePixelRatio.
// So we need to adjust the offset by the scale factor.
// https://github.com/rustdesk-org/window_manager/blob/f19acdb008645366339444a359a45c3257c8b32e/windows/window_manager.cpp#L701
if (isWindows) {
double? curDevicePixelRatio;
Offset curPos = await windowManager.getPosition();
for (final screen in await window_size.getScreenList()) {
if (isPointInRect(curPos, screen.visibleFrame)) {
curDevicePixelRatio = screen.scaleFactor;
}
}
if (curDevicePixelRatio != null &&
curDevicePixelRatio != 0 &&
offsetDevicePixelRatio != null) {
if (offsetDevicePixelRatio.devicePixelRatio != 0) {
final scale =
offsetDevicePixelRatio.devicePixelRatio / curDevicePixelRatio;
offsetDevicePixelRatio.offset =
offsetDevicePixelRatio.offset.scale(scale, scale);
debugPrint(
"restore new offset: ${offsetDevicePixelRatio.offset.dx}/${offsetDevicePixelRatio.offset.dy}, scale:$scale");
}
}
}
restorePos() async {
if (offsetDevicePixelRatio == null) {
if (offsetLeftTop == null) {
await windowManager.center();
} else {
await windowManager.setPosition(offsetDevicePixelRatio.offset);
await windowManager.setPosition(offsetLeftTop,
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
}
}
if (lpos.isMaximized == true) {
@@ -1981,20 +1952,39 @@ Future<bool> restoreWindowPosition(WindowType type,
await windowManager.maximize();
}
} else {
if (!bind.isIncomingOnly() || bind.isOutgoingOnly()) {
await windowManager.setSize(size);
final storeSize = !bind.isIncomingOnly() || bind.isOutgoingOnly();
if (isWindows) {
if (storeSize) {
// We need to set the window size first to avoid the incorrect size in some special cases.
// E.g. There are two monitors, the left one is 100% DPI and the right one is 175% DPI.
// The window belongs to the left monitor, but if it is moved a little to the right, it will belong to the right monitor.
// After restoring, the size will be incorrect.
// See known issue in https://github.com/rustdesk/rustdesk/pull/9840
await windowManager.setSize(size,
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
}
await restorePos();
if (storeSize) {
await windowManager.setSize(size,
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
}
} else {
if (storeSize) {
await windowManager.setSize(size,
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
}
await restorePos();
}
await restorePos();
}
return true;
default:
final wc = WindowController.fromWindowId(windowId!);
restoreFrame() async {
if (offsetDevicePixelRatio == null) {
if (offsetLeftTop == null) {
await wc.center();
} else {
final frame = Rect.fromLTWH(offsetDevicePixelRatio.offset.dx,
offsetDevicePixelRatio.offset.dy, size.width, size.height);
final frame = Rect.fromLTWH(
offsetLeftTop.dx, offsetLeftTop.dy, size.width, size.height);
await wc.setFrame(frame);
}
}
@@ -2255,7 +2245,10 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
}
}
var key = uri.queryParameters["key"];
var queryParameters =
uri.queryParameters.map((k, v) => MapEntry(k.toLowerCase(), v));
var key = queryParameters["key"];
if (id != null) {
if (key != null) {
id = "$id?key=$key";
@@ -2264,7 +2257,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
if (isMobile) {
if (id != null) {
final forceRelay = uri.queryParameters["relay"] != null;
final forceRelay = queryParameters["relay"] != null;
connect(Get.context!, id, forceRelay: forceRelay);
return null;
}
@@ -2274,7 +2267,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
if (command != null && id != null) {
args.add(command);
args.add(id);
var param = uri.queryParameters;
var param = queryParameters;
String? password = param["password"];
if (password != null) args.addAll(['--password', password]);
String? switch_uuid = param["switch_uuid"];
@@ -2508,9 +2501,20 @@ Future<void> onActiveWindowChanged() async {
// But the app will not close.
//
// No idea why we need to delay here, `terminate()` itself is also an async function.
Future.delayed(Duration.zero, () {
RdPlatformChannel.instance.terminate();
});
//
// A quick workaround, use `Timer.periodic` to avoid the app not closing.
// Because `await windowManager.close()` and `RdPlatformChannel.instance.terminate()`
// may not work since `Flutter 3.24.4`, see the following logs.
// A delay will allow the app to close.
//
//```
// embedder.cc (2725): 'FlutterPlatformMessageCreateResponseHandle' returned 'kInvalidArguments'. Engine handle was invalid.
// 2024-11-11 11:41:11.546 RustDesk[90272:2567686] Failed to create a FlutterPlatformMessageResponseHandle (2)
// embedder.cc (2672): 'FlutterEngineSendPlatformMessage' returned 'kInvalidArguments'. Invalid engine handle.
// 2024-11-11 11:41:11.565 RustDesk[90272:2567686] Failed to send message to Flutter engine on channel 'flutter/lifecycle' (2).
// ```
periodic_immediate(
Duration(milliseconds: 30), RdPlatformChannel.instance.terminate);
}
}
}
@@ -2603,7 +2607,7 @@ class ServerConfig {
config['relay'] = relayServer.trim();
config['api'] = apiServer.trim();
config['key'] = key.trim();
return base64Encode(Uint8List.fromList(jsonEncode(config).codeUnits))
return base64UrlEncode(Uint8List.fromList(jsonEncode(config).codeUnits))
.split('')
.reversed
.join();
@@ -2726,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(
@@ -2829,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(
@@ -2841,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(
@@ -3623,3 +3608,33 @@ List<SubWindowResizeEdge>? get subWindowManagerEnableResizeEdges => isWindows
void earlyAssert() {
assert('\1' == '1');
}
void checkUpdate() {
if (!isWeb) {
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();
});
}
}
}
// https://github.com/flutter/flutter/issues/153560#issuecomment-2497160535
// For TextField, TextFormField
extension WorkaroundFreezeLinuxMint on Widget {
Widget workaroundFreezeLinuxMint() {
// No need to check if is Linux Mint, because this workaround is harmless on other platforms.
if (isLinux) {
return ExcludeSemantics(child: this);
} else {
return this;
}
}
}

View File

@@ -1,5 +1,6 @@
import 'dart:math';
import 'package:bot_toast/bot_toast.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:dynamic_layouts/dynamic_layouts.dart';
import 'package:flutter/material.dart';
@@ -285,7 +286,7 @@ class _AddressBookState extends State<AddressBook> {
borderRadius: BorderRadius.circular(8),
),
),
),
).workaroundFreezeLinuxMint(),
),
searchMatchFn: (item, searchValue) {
return item.value
@@ -316,13 +317,14 @@ class _AddressBookState extends State<AddressBook> {
Widget _buildTags() {
return Obx(() {
final List tags;
List tags;
if (gFFI.abModel.sortTags.value) {
tags = gFFI.abModel.currentAbTags.toList();
tags.sort();
} else {
tags = gFFI.abModel.currentAbTags;
tags = gFFI.abModel.currentAbTags.toList();
}
tags = [kUntagged, ...tags].toList();
final editPermission = gFFI.abModel.current.canWrite();
tagBuilder(String e) {
return AddressBookTag(
@@ -554,7 +556,7 @@ class _AddressBookState extends State<AddressBook> {
: translate('ID'),
errorText: errorMsg,
errorMaxLines: 5),
))),
).workaroundFreezeLinuxMint())),
row(
lable: Text(
translate('Alias'),
@@ -567,7 +569,7 @@ class _AddressBookState extends State<AddressBook> {
? null
: translate('Alias'),
),
)),
).workaroundFreezeLinuxMint()),
),
if (isCurrentAbShared)
row(
@@ -596,7 +598,7 @@ class _AddressBookState extends State<AddressBook> {
},
),
),
),
).workaroundFreezeLinuxMint(),
)),
if (gFFI.abModel.currentAbTags.isNotEmpty)
Align(
@@ -669,6 +671,14 @@ class _AddressBookState extends State<AddressBook> {
} else {
final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
field = tags.join(',');
for (var t in [kUntagged, translate(kUntagged)]) {
if (tags.contains(t)) {
BotToast.showText(
contentColor: Colors.red, text: 'Tag name cannot be "$t"');
isInProgress = false;
return;
}
}
gFFI.abModel.addTags(tags);
// final currentPeers
}
@@ -694,7 +704,7 @@ class _AddressBookState extends State<AddressBook> {
),
controller: controller,
autofocus: true,
),
).workaroundFreezeLinuxMint(),
),
],
),
@@ -741,12 +751,14 @@ class AddressBookTag extends StatelessWidget {
}
const double radius = 8;
final isUnTagged = name == kUntagged;
final showAction = showActionMenu && !isUnTagged;
return GestureDetector(
onTap: onTap,
onTapDown: showActionMenu ? setPosition : null,
onSecondaryTapDown: showActionMenu ? setPosition : null,
onSecondaryTap: showActionMenu ? () => _showMenu(context, pos) : null,
onLongPress: showActionMenu ? () => _showMenu(context, pos) : null,
onTapDown: showAction ? setPosition : null,
onSecondaryTapDown: showAction ? setPosition : null,
onSecondaryTap: showAction ? () => _showMenu(context, pos) : null,
onLongPress: showAction ? () => _showMenu(context, pos) : null,
child: Obx(() => Container(
decoration: BoxDecoration(
color: tags.contains(name)
@@ -758,17 +770,18 @@ class AddressBookTag extends StatelessWidget {
child: IntrinsicWidth(
child: Row(
children: [
Container(
width: radius,
height: radius,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: tags.contains(name)
? Colors.white
: gFFI.abModel.getCurrentAbTagColor(name)),
).marginOnly(right: radius / 2),
if (!isUnTagged)
Container(
width: radius,
height: radius,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: tags.contains(name)
? Colors.white
: gFFI.abModel.getCurrentAbTagColor(name)),
).marginOnly(right: radius / 2),
Expanded(
child: Text(name,
child: Text(isUnTagged ? translate(name) : name,
style: TextStyle(
overflow: TextOverflow.ellipsis,
color: tags.contains(name) ? Colors.white : null)),

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
const _kWindowsSystemSound = 'System Sound';
const _kSystemSound = 'System Sound';
typedef AudioINputSetDevice = void Function(String device);
typedef AudioInputBuilder = Widget Function(
@@ -21,7 +21,7 @@ class AudioInput extends StatelessWidget {
: super(key: key);
static String getDefault() {
if (isWindows) return translate('System Sound');
if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound);
return '';
}
@@ -55,8 +55,8 @@ class AudioInput extends StatelessWidget {
static Future<Map<String, Object>> getDevicesInfo(
bool isCm, bool isVoiceCall) async {
List<String> devices = (await bind.mainGetSoundInputs()).toList();
if (isWindows) {
devices.insert(0, translate(_kWindowsSystemSound));
if (bind.mainAudioSupportLoopback()) {
devices.insert(0, translate(_kSystemSound));
}
String current = await getValue(isCm, isVoiceCall);
return {'devices': devices, 'current': current};

View File

@@ -167,7 +167,7 @@ class ChatPage extends StatelessWidget implements PageShape {
);
},
),
);
).workaroundFreezeLinuxMint();
return SelectionArea(child: chat);
}),
],

View File

@@ -140,7 +140,7 @@ void changeIdDialog() {
msg = '';
});
},
),
).workaroundFreezeLinuxMint(),
const SizedBox(
height: 8.0,
),
@@ -201,13 +201,14 @@ void changeWhiteList({Function()? callback}) async {
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
enabled: !isOptFixed,
autofocus: true),
maxLines: null,
decoration: InputDecoration(
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
enabled: !isOptFixed,
autofocus: true)
.workaroundFreezeLinuxMint(),
),
],
),
@@ -287,22 +288,23 @@ Future<String> changeDirectAccessPort(
children: [
Expanded(
child: TextField(
maxLines: null,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '21118',
isCollapsed: true,
prefix: Text('$currentIP : '),
suffix: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
controller: controller,
autofocus: true),
maxLines: null,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '21118',
isCollapsed: true,
prefix: Text('$currentIP : '),
suffix: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
controller: controller,
autofocus: true)
.workaroundFreezeLinuxMint(),
),
],
),
@@ -335,21 +337,22 @@ Future<String> changeAutoDisconnectTimeout(String old) async {
children: [
Expanded(
child: TextField(
maxLines: null,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '10',
isCollapsed: true,
suffix: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
controller: controller,
autofocus: true),
maxLines: null,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '10',
isCollapsed: true,
suffix: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
],
controller: controller,
autofocus: true)
.workaroundFreezeLinuxMint(),
),
],
),
@@ -427,7 +430,7 @@ class DialogTextField extends StatelessWidget {
keyboardType: keyboardType,
inputFormatters: inputFormatters,
maxLength: maxLength,
),
).workaroundFreezeLinuxMint(),
),
],
).paddingSymmetric(vertical: 4.0);
@@ -1501,7 +1504,7 @@ showAuditDialog(FFI ffi) async {
maxLength: 256,
controller: controller,
focusNode: focusNode,
)),
).workaroundFreezeLinuxMint()),
actions: [
dialogButton('Cancel', onPressed: close, isOutline: true),
dialogButton('OK', onPressed: submit)
@@ -1748,7 +1751,7 @@ void renameDialog(
autofocus: true,
decoration: InputDecoration(labelText: translate('Name')),
validator: validator,
),
).workaroundFreezeLinuxMint(),
),
),
// NOT use Offstage to wrap LinearProgressIndicator
@@ -1808,7 +1811,7 @@ void changeBot({Function()? callback}) async {
decoration: InputDecoration(
hintText: translate('Token'),
),
);
).workaroundFreezeLinuxMint();
return CustomAlertDialog(
title: Text(translate("Telegram bot")),
@@ -2178,7 +2181,7 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
},
),
),
),
).workaroundFreezeLinuxMint(),
if (!gFFI.abModel.current.isPersonal())
Row(children: [
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),

View File

@@ -86,7 +86,7 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
// end
switch (_currentState) {
case GestureState.oneFingerPan:
debugPrint("TwoFingerState.pan onEnd");
debugPrint("OneFingerState.pan onEnd");
if (onOneFingerPanEnd != null) {
onOneFingerPanEnd!(_getDragEndDetails(d));
}

View File

@@ -678,7 +678,7 @@ Future<bool?> verificationCodeDialog(
labelText: "Email", prefixIcon: Icon(Icons.email)),
readOnly: true,
controller: TextEditingController(text: user?.email),
)),
).workaroundFreezeLinuxMint()),
isEmailVerification ? const SizedBox(height: 8) : const Offstage(),
codeField,
/*

View File

@@ -145,7 +145,7 @@ class _MyGroupState extends State<MyGroup> {
border: InputBorder.none,
isDense: true,
),
)),
).workaroundFreezeLinuxMint()),
],
);
}

View File

@@ -1257,7 +1257,7 @@ void _rdpDialog(String id) async {
hintText: '3389'),
controller: portController,
autofocus: true,
),
).workaroundFreezeLinuxMint(),
),
],
).marginOnly(bottom: isDesktop ? 8 : 0),
@@ -1277,7 +1277,7 @@ void _rdpDialog(String id) async {
labelText:
isDesktop ? null : translate('Username')),
controller: userController,
),
).workaroundFreezeLinuxMint(),
),
],
).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)),
@@ -1305,7 +1305,7 @@ void _rdpDialog(String id) async {
? Icons.visibility_off
: Icons.visibility))),
controller: passwordController,
)),
).workaroundFreezeLinuxMint()),
),
],
))

View File

@@ -743,7 +743,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
border: InputBorder.none,
isDense: true,
),
),
).workaroundFreezeLinuxMint(),
),
// Icon(Icons.close),
IconButton(

View File

@@ -5,7 +5,7 @@ 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';
import 'package:get/get.dart';
@@ -270,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();
@@ -532,15 +523,22 @@ class AddressBookPeersView extends BasePeersView {
if (selectedTags.isEmpty) {
return true;
}
// The result of a no-tag union with normal tags, still allows normal tags to perform union or intersection operations.
final selectedNormalTags =
selectedTags.where((tag) => tag != kUntagged).toList();
if (selectedTags.contains(kUntagged)) {
if (idents.isEmpty) return true;
if (selectedNormalTags.isEmpty) return false;
}
if (gFFI.abModel.filterByIntersection.value) {
for (final tag in selectedTags) {
for (final tag in selectedNormalTags) {
if (!idents.contains(tag)) {
return false;
}
}
return true;
} else {
for (final tag in selectedTags) {
for (final tag in selectedNormalTags) {
if (idents.contains(tag)) {
return true;
}

View File

@@ -84,8 +84,17 @@ class _RawTouchGestureDetectorRegionState
double _mouseScrollIntegral = 0; // mouse scroll speed controller
double _scale = 1;
// Workaround tap down event when two fingers are used to scale(mobile)
TapDownDetails? _lastTapDownDetails;
PointerDeviceKind? lastDeviceKind;
// For touch mode, onDoubleTap
// `onDoubleTap()` does not provide the position of the tap event.
Offset _lastPosOfDoubleTapDown = Offset.zero;
bool _touchModePanStarted = false;
Offset _doubleFinerTapPosition = Offset.zero;
FFI get ffi => widget.ffi;
FfiModel get ffiModel => widget.ffiModel;
InputModel get inputModel => widget.inputModel;
@@ -100,152 +109,190 @@ class _RawTouchGestureDetectorRegionState
);
}
onTapDown(TapDownDetails d) {
onTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
_lastPosOfDoubleTapDown = d.localPosition;
// Desktop or mobile "Touch mode"
if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) {
inputModel.tapDown(MouseButtons.left);
}
_lastTapDownDetails = d;
}
}
onTapUp(TapUpDetails d) {
onTapUp(TapUpDetails d) async {
final TapDownDetails? lastTapDownDetails = _lastTapDownDetails;
_lastTapDownDetails = null;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) {
inputModel.tapUp(MouseButtons.left);
final isMoved =
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
if (isMoved) {
if (lastTapDownDetails != null) {
await inputModel.tapDown(MouseButtons.left);
}
await inputModel.tapUp(MouseButtons.left);
}
}
}
onTap() {
onTap() async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (!handleTouch) {
// Mobile, "Mouse mode"
inputModel.tap(MouseButtons.left);
await inputModel.tap(MouseButtons.left);
}
}
onDoubleTapDown(TapDownDetails d) {
onDoubleTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
_lastPosOfDoubleTapDown = d.localPosition;
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
}
}
onDoubleTap() {
onDoubleTap() async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) {
return;
}
inputModel.tap(MouseButtons.left);
inputModel.tap(MouseButtons.left);
if (handleTouch &&
!ffi.cursorModel.isInRemoteRect(_lastPosOfDoubleTapDown)) {
return;
}
await inputModel.tap(MouseButtons.left);
await inputModel.tap(MouseButtons.left);
}
onLongPressDown(LongPressDownDetails d) {
onLongPressDown(LongPressDownDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
_lastPosOfDoubleTapDown = d.localPosition;
_cacheLongPressPosition = d.localPosition;
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
return;
}
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
}
}
onLongPressUp() {
onLongPressUp() async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
inputModel.tapUp(MouseButtons.left);
await inputModel.tapUp(MouseButtons.left);
}
}
// for mobiles
onLongPress() {
onLongPress() async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
ffi.cursorModel
final isMoved = await ffi.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
if (!isMoved) {
return;
}
}
if (!ffi.ffiModel.isPeerMobile) {
await inputModel.tap(MouseButtons.right);
}
inputModel.tap(MouseButtons.right);
}
onDoubleFinerTapDown(TapDownDetails d) {
onDoubleFinerTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
_doubleFinerTapPosition = d.localPosition;
// ignore for desktop and mobile
}
onDoubleFinerTap(TapDownDetails d) {
onDoubleFinerTap(TapDownDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if ((isDesktop || isWebDesktop) || !ffiModel.touchMode) {
inputModel.tap(MouseButtons.right);
// mobile mouse mode or desktop touch screen
final isMobileMouseMode = isMobile && !ffiModel.touchMode;
// We can't use `d.localPosition` here because it's always (0, 0) on desktop.
final isDesktopInRemoteRect = (isDesktop || isWebDesktop) &&
ffi.cursorModel.isInRemoteRect(_doubleFinerTapPosition);
if (isMobileMouseMode || isDesktopInRemoteRect) {
await inputModel.tap(MouseButtons.right);
}
}
onHoldDragStart(DragStartDetails d) {
onHoldDragStart(DragStartDetails d) async {
lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (!handleTouch) {
inputModel.sendMouse('down', MouseButtons.left);
await inputModel.sendMouse('down', MouseButtons.left);
}
}
onHoldDragUpdate(DragUpdateDetails d) {
onHoldDragUpdate(DragUpdateDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (!handleTouch) {
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
}
}
onHoldDragEnd(DragEndDetails d) {
onHoldDragEnd(DragEndDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (!handleTouch) {
inputModel.sendMouse('up', MouseButtons.left);
await inputModel.sendMouse('up', MouseButtons.left);
}
}
onOneFingerPanStart(BuildContext context, DragStartDetails d) {
onOneFingerPanStart(BuildContext context, DragStartDetails d) async {
final TapDownDetails? lastTapDownDetails = _lastTapDownDetails;
_lastTapDownDetails = null;
lastDeviceKind = d.kind ?? lastDeviceKind;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) {
if (lastTapDownDetails != null) {
await ffi.cursorModel.move(lastTapDownDetails.localPosition.dx,
lastTapDownDetails.localPosition.dy);
}
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
return;
}
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
return;
}
_touchModePanStarted = true;
if (isDesktop || isWebDesktop) {
ffi.cursorModel.trySetRemoteWindowCoords();
}
// Workaround for the issue that the first pan event is sent a long time after the start event.
// If the time interval between the start event and the first pan event is less than 500ms,
// we consider to use the long press position as the start position.
@@ -253,11 +300,11 @@ class _RawTouchGestureDetectorRegionState
// TODO: We should find a better way to send the first pan event as soon as possible.
if (DateTime.now().millisecondsSinceEpoch - _cacheLongPressPositionTs <
500) {
ffi.cursorModel
await ffi.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
}
inputModel.sendMouse('down', MouseButtons.left);
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
await inputModel.sendMouse('down', MouseButtons.left);
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
} else {
final offset = ffi.cursorModel.offset;
final cursorX = offset.dx;
@@ -266,39 +313,46 @@ class _RawTouchGestureDetectorRegionState
ffi.cursorModel.getVisibleRect().inflate(1); // extend edges
final size = MediaQueryData.fromView(View.of(context)).size;
if (!visible.contains(Offset(cursorX, cursorY))) {
ffi.cursorModel.move(size.width / 2, size.height / 2);
await ffi.cursorModel.move(size.width / 2, size.height / 2);
}
}
}
onOneFingerPanUpdate(DragUpdateDetails d) {
onOneFingerPanUpdate(DragUpdateDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
return;
}
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
if (handleTouch && !_touchModePanStarted) {
return;
}
await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
}
onOneFingerPanEnd(DragEndDetails d) {
onOneFingerPanEnd(DragEndDetails d) async {
_touchModePanStarted = false;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (isDesktop || isWebDesktop) {
ffi.cursorModel.clearRemoteWindowCoords();
}
inputModel.sendMouse('up', MouseButtons.left);
if (handleTouch) {
await inputModel.sendMouse('up', MouseButtons.left);
}
}
// scale + pan event
onTwoFingerScaleStart(ScaleStartDetails d) {
_lastTapDownDetails = null;
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
}
onTwoFingerScaleUpdate(ScaleUpdateDetails d) {
onTwoFingerScaleUpdate(ScaleUpdateDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
@@ -307,7 +361,7 @@ class _RawTouchGestureDetectorRegionState
_scale = d.scale;
if (scale != 0) {
bind.sessionSendPointer(
await bind.sessionSendPointer(
sessionId: sessionId,
msg: json.encode(
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
@@ -322,21 +376,22 @@ class _RawTouchGestureDetectorRegionState
}
}
onTwoFingerScaleEnd(ScaleEndDetails d) {
onTwoFingerScaleEnd(ScaleEndDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if ((isDesktop || isWebDesktop)) {
bind.sessionSendPointer(
await bind.sessionSendPointer(
sessionId: sessionId,
msg: json.encode(
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
} else {
// mobile
_scale = 1;
bind.sessionSetViewStyle(sessionId: sessionId, value: "");
// No idea why we need to set the view style to "" here.
// bind.sessionSetViewStyle(sessionId: sessionId, value: "");
}
inputModel.sendMouse('up', MouseButtons.left);
await inputModel.sendMouse('up', MouseButtons.left);
}
get onHoldDragCancel => null;

View File

@@ -169,6 +169,13 @@ const int kWindowMainId = 0;
const String kPointerEventKindTouch = "touch";
const String kPointerEventKindMouse = "mouse";
const String kMouseEventTypeDefault = "";
const String kMouseEventTypePanStart = "pan_start";
const String kMouseEventTypePanUpdate = "pan_update";
const String kMouseEventTypePanEnd = "pan_end";
const String kMouseEventTypeDown = "down";
const String kMouseEventTypeUp = "up";
const String kKeyFlutterKey = "flutter_key";
const String kKeyShowDisplaysAsIndividualWindows =
@@ -202,7 +209,7 @@ const double kMinFps = 5;
const double kDefaultFps = 30;
const double kMaxFps = 120;
const double kMinQuality = 5;
const double kMinQuality = 10;
const double kDefaultQuality = 50;
const double kMaxQuality = 100;
const double kMaxMoreQuality = 2000;
@@ -237,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.

View File

@@ -39,7 +39,7 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
double? get height => bind.isIncomingOnly() ? null : em * 3;
void onUsePublicServerGuide() {
const url = "https://rustdesk.com/pricing.html";
const url = "https://rustdesk.com/pricing";
canLaunchUrlString(url).then((can) {
if (can) {
launchUrlString(url);
@@ -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 (_) {}
}
}
@@ -203,6 +206,8 @@ class _ConnectionPageState extends State<ConnectionPage>
bool isPeersLoading = false;
bool isPeersLoaded = false;
// https://github.com/flutter/flutter/issues/157244
Iterable<Peer> _autocompleteOpts = [];
@override
void initState() {
@@ -330,7 +335,7 @@ class _ConnectionPageState extends State<ConnectionPage>
child: Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
_autocompleteOpts = const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
@@ -346,7 +351,7 @@ class _ConnectionPageState extends State<ConnectionPage>
rdpUsername: '',
loginName: '',
);
return [emptyPeer];
_autocompleteOpts = [emptyPeer];
} else {
String textWithoutSpaces =
textEditingValue.text.replaceAll(" ", "");
@@ -357,8 +362,7 @@ class _ConnectionPageState extends State<ConnectionPage>
);
}
String textToFind = textEditingValue.text.toLowerCase();
return peers
_autocompleteOpts = peers
.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username
@@ -370,6 +374,7 @@ class _ConnectionPageState extends State<ConnectionPage>
peer.alias.toLowerCase().contains(textToFind))
.toList();
}
return _autocompleteOpts;
},
fieldViewBuilder: (
BuildContext context,
@@ -419,7 +424,7 @@ class _ConnectionPageState extends State<ConnectionPage>
onSubmitted: (_) {
onConnect();
},
));
).workaroundFreezeLinuxMint());
},
onSelected: (option) {
setState(() {
@@ -430,6 +435,7 @@ class _ConnectionPageState extends State<ConnectionPage>
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Peer> onSelected,
Iterable<Peer> options) {
options = _autocompleteOpts;
double maxHeight = options.length * 50;
if (options.length == 1) {
maxHeight = 52;

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,
),
)
],
),
),
)
],
),
),
);
@@ -234,7 +237,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
style: TextStyle(
fontSize: 22,
),
),
).workaroundFreezeLinuxMint(),
),
)
],
@@ -272,10 +275,21 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
buildPasswordBoard(BuildContext context) {
final model = gFFI.serverModel;
return ChangeNotifierProvider.value(
value: gFFI.serverModel,
child: Consumer<ServerModel>(
builder: (context, model, child) {
return buildPasswordBoard2(context, model);
},
));
}
buildPasswordBoard2(BuildContext context, ServerModel model) {
RxBool refreshHover = false.obs;
RxBool editHover = false.obs;
final textColor = Theme.of(context).textTheme.titleLarge?.color;
final showOneTime = model.approveMode != 'click' &&
model.verificationMethod != kUsePermanentPassword;
return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row(
@@ -304,8 +318,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Expanded(
child: GestureDetector(
onDoubleTap: () {
if (model.verificationMethod !=
kUsePermanentPassword) {
if (showOneTime) {
Clipboard.setData(
ClipboardData(text: model.serverPasswd.text));
showToast(translate("Copied"));
@@ -320,25 +333,26 @@ class _DesktopHomePageState extends State<DesktopHomePage>
EdgeInsets.only(top: 14, bottom: 10),
),
style: TextStyle(fontSize: 15),
),
).workaroundFreezeLinuxMint(),
),
),
AnimatedRotationWidget(
onPressed: () => bind.mainUpdateTemporaryPassword(),
child: Tooltip(
message: translate('Refresh Password'),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
))),
),
onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
if (showOneTime)
AnimatedRotationWidget(
onPressed: () => bind.mainUpdateTemporaryPassword(),
child: Tooltip(
message: translate('Refresh Password'),
child: Obx(() => RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.refresh,
color: refreshHover.value
? textColor
: Color(0xFFDDDDDD),
size: 22,
))),
),
onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
if (!bind.isDisableSettings())
InkWell(
child: Tooltip(
@@ -409,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);
@@ -663,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();
@@ -812,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_updateWindowSize();
});
}
WidgetsBinding.instance.addObserver(this);
}
_updateWindowSize() {
@@ -833,13 +834,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_uniLinksSubscription?.cancel();
Get.delete<RxBool>(tag: 'stop-service');
_updateTimer?.cancel();
if (!bind.isCustomClient()) {
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(
@@ -930,7 +936,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
});
},
maxLength: maxLength,
),
).workaroundFreezeLinuxMint(),
),
],
),
@@ -957,7 +963,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
});
},
maxLength: maxLength,
),
).workaroundFreezeLinuxMint(),
),
],
),

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() {
@@ -561,7 +607,6 @@ class _GeneralState extends State<_General> {
bool user_dir_exists = await Directory(user_dir).exists();
bool root_dir_exists =
showRootDir ? await Directory(root_dir).exists() : false;
// canLaunchUrl blocked on windows portable, user SYSTEM
return {
'user_dir': user_dir,
'root_dir': root_dir,
@@ -705,29 +750,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() {
@@ -1029,7 +1071,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
translate('Accept sessions via both'),
];
var modeInitialKey = model.approveMode;
if (!modeKeys.contains(modeInitialKey)) modeInitialKey = '';
if (!modeKeys.contains(modeInitialKey)) {
modeInitialKey = defaultOptionApproveMode;
}
final usePassword = model.approveMode != 'click';
final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
@@ -1144,7 +1188,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
contentPadding:
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
),
).marginOnly(right: 15),
).workaroundFreezeLinuxMint().marginOnly(right: 15),
),
Obx(() => ElevatedButton(
onPressed: applyEnabled.value &&
@@ -1301,7 +1345,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
contentPadding:
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
),
).marginOnly(right: 15),
).workaroundFreezeLinuxMint().marginOnly(right: 15),
),
Obx(() => ElevatedButton(
onPressed:
@@ -1372,112 +1416,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,
),
],
),
),
],
);
}
}
@@ -1492,19 +1506,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) {
@@ -1727,15 +1736,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() {
@@ -1832,18 +1838,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);
}),
);
}
@@ -1895,75 +1897,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)
]),
);
});
}
}
@@ -2281,26 +2280,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),
)),
),
).workaroundFreezeLinuxMint(),
],
),
],
).marginOnly(bottom: 8);
@@ -2478,7 +2490,7 @@ void changeSocks5Proxy() async {
controller: proxyController,
autofocus: true,
enabled: !isOptFixed,
),
).workaroundFreezeLinuxMint(),
),
],
).marginOnly(bottom: 8),
@@ -2498,7 +2510,7 @@ void changeSocks5Proxy() async {
labelText: isMobile ? translate('Username') : null,
),
enabled: !isOptFixed,
),
).workaroundFreezeLinuxMint(),
),
],
).marginOnly(bottom: 8),
@@ -2524,7 +2536,7 @@ void changeSocks5Proxy() async {
controller: pwdController,
enabled: !isOptFixed,
maxLength: bind.mainMaxEncryptLen(),
)),
).workaroundFreezeLinuxMint()),
),
],
),

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

@@ -768,7 +768,7 @@ class _FileManagerViewState extends State<FileManagerView> {
),
controller: name,
autofocus: true,
),
).workaroundFreezeLinuxMint(),
],
),
actions: [
@@ -1657,7 +1657,7 @@ class _FileManagerViewState extends State<FileManagerView> {
onChanged: _locationStatus.value == LocationStatus.fileSearchBar
? (searchText) => onSearchText(searchText, isLocal)
: null,
),
).workaroundFreezeLinuxMint(),
)
],
);

View File

@@ -147,7 +147,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0.75 * em),
),
).marginOnly(right: 10),
).workaroundFreezeLinuxMint().marginOnly(right: 10),
),
Obx(
() => OutlinedButton.icon(

View File

@@ -238,7 +238,7 @@ class _PortForwardPageState extends State<PortForwardPage>
inputFormatters: inputFormatters,
decoration: InputDecoration(
hintText: hint,
))),
)).workaroundFreezeLinuxMint()),
);
}

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

@@ -1495,7 +1495,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
);
}
TextField _resolutionInput(TextEditingController controller) {
Widget _resolutionInput(TextEditingController controller) {
return TextField(
decoration: InputDecoration(
border: InputBorder.none,
@@ -1509,7 +1509,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
controller: controller,
);
).workaroundFreezeLinuxMint();
}
List<Widget> _supportedResolutionMenuButtons() => resolutions

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,14 +41,15 @@ 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;
bool isPeersLoaded = false;
StreamSubscription? _uniLinksSubscription;
// https://github.com/flutter/flutter/issues/157244
Iterable<Peer> _autocompleteOpts = [];
_ConnectionPageState() {
if (!isWeb) _uniLinksSubscription = listenUniLinks();
_idController.addListener(() {
@@ -69,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
@@ -94,7 +80,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
slivers: [
SliverList(
delegate: SliverChildListDelegate([
if (!bind.isCustomClient()) _buildUpdateUI(),
if (!bind.isCustomClient() && !isIOS)
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
_buildRemoteIDTextField(),
])),
SliverFillRemaining(
@@ -113,16 +100,22 @@ 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';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
// 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.
await launchUrl(Uri.parse(url));
},
child: Container(
alignment: AlignmentDirectional.center,
@@ -166,7 +159,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Peer>.empty();
_autocompleteOpts = const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) {
Peer emptyPeer = Peer(
id: '',
@@ -182,7 +175,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
rdpUsername: '',
loginName: '',
);
return [emptyPeer];
_autocompleteOpts = [emptyPeer];
} else {
String textWithoutSpaces =
textEditingValue.text.replaceAll(" ", "");
@@ -194,7 +187,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
}
String textToFind = textEditingValue.text.toLowerCase();
return peers
_autocompleteOpts = peers
.where((peer) =>
peer.id.toLowerCase().contains(textToFind) ||
peer.username
@@ -206,6 +199,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
peer.alias.toLowerCase().contains(textToFind))
.toList();
}
return _autocompleteOpts;
},
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
@@ -274,6 +268,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Peer> onSelected,
Iterable<Peer> options) {
options = _autocompleteOpts;
double maxHeight = options.length * 50;
if (options.length == 1) {
maxHeight = 52;
@@ -373,10 +368,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
if (Get.isRegistered<TextEditingController>()) {
Get.delete<TextEditingController>();
}
if (!bind.isCustomClient()) {
platformFFI.unregisterEventHandler(
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
}
super.dispose();
}
}

View File

@@ -225,7 +225,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
errorText: errorText,
),
controller: name,
),
).workaroundFreezeLinuxMint(),
],
),
actions: [

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -25,6 +26,19 @@ import '../widgets/dialog.dart';
final initText = '1' * 1024;
// Workaround for Android (default input method, Microsoft SwiftKey keyboard) when using physical keyboard.
// When connecting a physical keyboard, `KeyEvent.physicalKey.usbHidUsage` are wrong is using Microsoft SwiftKey keyboard.
// https://github.com/flutter/flutter/issues/159384
// https://github.com/flutter/flutter/issues/159383
void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
if (isAndroid) {
if (isKeyboardVisible != true) {
// `enable_soft_keyboard` will be set to `true` when clicking the keyboard icon, in `openKeyboard()`.
gFFI.invokeMethod("enable_soft_keyboard", false);
}
}
}
class RemotePage extends StatefulWidget {
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword})
: super(key: key);
@@ -37,12 +51,15 @@ class RemotePage extends StatefulWidget {
State<RemotePage> createState() => _RemotePageState(id);
}
class _RemotePageState extends State<RemotePage> {
class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
Timer? _timer;
bool _showBar = !isWebDesktop;
bool _showGestureHelp = false;
String _value = '';
Orientation? _currentOrientation;
double _viewInsetsBottom = 0;
Timer? _timerDidChangeMetrics;
final _blockableOverlayState = BlockableOverlayState();
@@ -57,9 +74,6 @@ class _RemotePageState extends State<RemotePage> {
final TextEditingController _textController =
TextEditingController(text: initText);
// This timer is used to check the composing status of the soft keyboard.
// It is used for Android, Korean(and other similar) input method.
Timer? _composingTimer;
_RemotePageState(String id) {
initSharedStates(id);
@@ -98,11 +112,15 @@ class _RemotePageState extends State<RemotePage> {
if (gFFI.recordingModel.start) {
showToast(translate('Automatically record outgoing sessions'));
}
_disableAndroidSoftKeyboard(
isKeyboardVisible: keyboardVisibilityController.isVisible);
});
WidgetsBinding.instance.addObserver(this);
}
@override
Future<void> dispose() async {
WidgetsBinding.instance.removeObserver(this);
// https://github.com/flutter/flutter/issues/64935
super.dispose();
gFFI.dialogManager.hideMobileActionsOverlay(store: false);
@@ -114,7 +132,7 @@ class _RemotePageState extends State<RemotePage> {
_physicalFocusNode.dispose();
await gFFI.close();
_timer?.cancel();
_composingTimer?.cancel();
_timerDidChangeMetrics?.cancel();
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
overlays: SystemUiOverlay.values);
@@ -129,6 +147,39 @@ class _RemotePageState extends State<RemotePage> {
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)
// Don't try reset the view style and focus the cursor.
if (gFFI.cursorModel.lastKeyboardIsVisible &&
gFFI.canvasModel.isMobileCanvasChanged) {
return;
}
final newBottom = MediaQueryData.fromView(ui.window).viewInsets.bottom;
_timerDidChangeMetrics?.cancel();
_timerDidChangeMetrics = Timer(Duration(milliseconds: 100), () async {
// We need this comparation because poping up the floating action will also trigger `didChangeMetrics()`.
if (newBottom != _viewInsetsBottom) {
gFFI.canvasModel.mobileFocusCanvasCursor();
_viewInsetsBottom = newBottom;
}
});
}
// to-do: It should be better to use transparent color instead of the bgColor.
// But for now, the transparent color will cause the canvas to be white.
// I'm sure that the white color is caused by the Overlay widget in BlockableOverlay.
@@ -150,7 +201,6 @@ class _RemotePageState extends State<RemotePage> {
gFFI.ffiModel.pi.version.isNotEmpty) {
gFFI.invokeMethod("enable_soft_keyboard", false);
}
_composingTimer?.cancel();
} else {
_timer?.cancel();
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
@@ -214,13 +264,6 @@ class _RemotePageState extends State<RemotePage> {
}
void _handleNonIOSSoftKeyboardInput(String newValue) {
_composingTimer?.cancel();
if (_textController.value.isComposingRangeValid) {
_composingTimer = Timer(Duration(milliseconds: 25), () {
_handleNonIOSSoftKeyboardInput(_textController.value.text);
});
return;
}
var oldValue = _value;
_value = newValue;
if (oldValue.isNotEmpty &&
@@ -261,14 +304,10 @@ class _RemotePageState extends State<RemotePage> {
}
}
Future<void> handleSoftKeyboardInput(String newValue) async {
// handle mobile virtual keyboard
void handleSoftKeyboardInput(String newValue) {
if (isIOS) {
// fix: TextFormField onChanged event triggered multiple times when Korean input
// https://github.com/rustdesk/rustdesk/pull/9644
await Future.delayed(const Duration(milliseconds: 10));
if (newValue != _textController.text) return;
_handleIOSSoftKeyboardInput(_textController.text);
_handleIOSSoftKeyboardInput(newValue);
} else {
_handleNonIOSSoftKeyboardInput(newValue);
}
@@ -534,7 +573,9 @@ class _RemotePageState extends State<RemotePage> {
right: 10,
child: QualityMonitor(gFFI.qualityMonitorModel),
),
KeyHelpTools(requestShow: (keyboardIsVisible || _showGestureHelp)),
KeyHelpTools(
keyboardIsVisible: keyboardIsVisible,
showGestureHelp: _showGestureHelp),
SizedBox(
width: 0,
height: 0,
@@ -554,8 +595,16 @@ class _RemotePageState extends State<RemotePage> {
controller: _textController,
// trick way to make backspace work always
keyboardType: TextInputType.multiline,
// `onChanged` may be called depending on the input method if this widget is wrapped in
// `Focus(onKeyEvent: ..., child: ...)`
// For `Backspace` button in the soft keyboard:
// en/fr input method:
// 1. The button will not trigger `onKeyEvent` if the text field is not empty.
// 2. The button will trigger `onKeyEvent` if the text field is empty.
// ko/zh/ja input method: the button will trigger `onKeyEvent`
// and the event will not popup if `KeyEventResult.handled` is returned.
onChanged: handleSoftKeyboardInput,
),
).workaroundFreezeLinuxMint(),
),
];
if (showCursorPaint) {
@@ -763,10 +812,14 @@ class _RemotePageState extends State<RemotePage> {
}
class KeyHelpTools extends StatefulWidget {
/// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
final bool requestShow;
final bool keyboardIsVisible;
final bool showGestureHelp;
KeyHelpTools({required this.requestShow});
/// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
bool get requestShow => keyboardIsVisible || showGestureHelp;
KeyHelpTools(
{required this.keyboardIsVisible, required this.showGestureHelp});
@override
State<KeyHelpTools> createState() => _KeyHelpToolsState();
@@ -811,7 +864,8 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
final size = renderObject.size;
Offset pos = renderObject.localToGlobal(Offset.zero);
gFFI.cursorModel.keyHelpToolsVisibilityChanged(
Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height));
Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height),
widget.keyboardIsVisible);
}
}
@@ -823,13 +877,16 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
inputModel.command;
if (!_pin && !hasModifierOn && !widget.requestShow) {
gFFI.cursorModel.keyHelpToolsVisibilityChanged(null);
gFFI.cursorModel
.keyHelpToolsVisibilityChanged(null, widget.keyboardIsVisible);
return Offstage();
}
final size = MediaQuery.of(context).size;
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);
@@ -910,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');
@@ -960,11 +1039,11 @@ class ImagePaint extends StatelessWidget {
Widget build(BuildContext context) {
final m = Provider.of<ImageModel>(context);
final c = Provider.of<CanvasModel>(context);
final adjust = gFFI.cursorModel.adjustForKeyboard();
var s = c.scale;
final adjust = c.getAdjustY();
return CustomPaint(
painter: ImagePainter(
image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s),
image: m.image, x: c.x / s, y: (c.y + adjust) / s, scale: s),
);
}
}
@@ -978,7 +1057,6 @@ class CursorPaint extends StatelessWidget {
final m = Provider.of<CursorModel>(context);
final c = Provider.of<CanvasModel>(context);
final ffiModel = Provider.of<FfiModel>(context);
final adjust = gFFI.cursorModel.adjustForKeyboard();
final s = c.scale;
double hotx = m.hotx;
double hoty = m.hoty;
@@ -1010,11 +1088,12 @@ class CursorPaint extends StatelessWidget {
factor = s / mins;
}
final s2 = s < mins ? mins : s;
final adjust = c.getAdjustY();
return CustomPaint(
painter: ImagePainter(
image: image,
x: (m.x - hotx) * factor + c.x / s2,
y: (m.y - hoty) * factor + (c.y - adjust) / s2,
y: (m.y - hoty) * factor + (c.y + adjust) / s2,
scale: s2),
);
}
@@ -1217,7 +1296,9 @@ void showOptions(
toggles +
[privacyModeWidget]),
);
}, clickMaskDismiss: true, backDismiss: true);
}, clickMaskDismiss: true, backDismiss: true).then((value) {
_disableAndroidSoftKeyboard();
});
}
TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) {
@@ -1236,7 +1317,9 @@ TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) {
children: children,
),
);
}, clickMaskDismiss: true, backDismiss: true);
}, clickMaskDismiss: true, backDismiss: true).then((value) {
_disableAndroidSoftKeyboard();
});
},
);
}
@@ -1278,7 +1361,9 @@ TTextMenu? getResolutionMenu(FFI ffi, String id) {
children: children,
),
);
}, clickMaskDismiss: true, backDismiss: true);
}, clickMaskDismiss: true, backDismiss: true).then((value) {
_disableAndroidSoftKeyboard();
});
},
);
}

View File

@@ -446,7 +446,6 @@ class ServerInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isPermanent = model.verificationMethod == kUsePermanentPassword;
final serverModel = Provider.of<ServerModel>(context);
const Color colorPositive = Colors.green;
@@ -486,6 +485,8 @@ class ServerInfo extends StatelessWidget {
}
}
final showOneTime = serverModel.approveMode != 'click' &&
serverModel.verificationMethod != kUsePermanentPassword;
return PaddingCard(
title: translate('Your Device'),
child: Column(
@@ -523,10 +524,10 @@ class ServerInfo extends StatelessWidget {
]),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(
isPermanent ? '-' : model.serverPasswd.value.text,
!showOneTime ? '-' : model.serverPasswd.value.text,
style: textStyleValue,
),
isPermanent
!showOneTime
? SizedBox.shrink()
: Row(children: [
IconButton(
@@ -595,7 +596,9 @@ class _PermissionCheckerState extends State<PermissionChecker> {
translate("android_version_audio_tip"),
style: const TextStyle(color: MyTheme.darkGray),
))
])
]),
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
serverModel.toggleClipboard),
]));
}
}

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)) {
@@ -757,9 +782,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
tiles: [
SettingsTile(
onPressed: (context) async {
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
await launchUrl(Uri.parse(url));
},
title: Text(translate("Version: ") + version),
value: Padding(
@@ -828,11 +851,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>;
@@ -908,9 +926,7 @@ void showAbout(OverlayDialogManager dialogManager) {
InkWell(
onTap: () async {
const url = 'https://rustdesk.com/';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
}
await launchUrl(Uri.parse(url));
},
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8),

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';
@@ -65,7 +66,7 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
? null
: translate('Too short, at least 6 characters.');
},
),
).workaroundFreezeLinuxMint(),
TextFormField(
obscureText: true,
keyboardType: TextInputType.visiblePassword,
@@ -84,7 +85,7 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
? null
: translate('The confirmation is not identical.');
},
),
).workaroundFreezeLinuxMint(),
])),
onCancel: close,
onSubmit: (validateLength && validateSame) ? submit : null,
@@ -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,
).workaroundFreezeLinuxMint(),
),
],
);
}
return TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: label,
errorText: errorMsg.isEmpty ? null : errorMsg,
),
validator: validator,
).workaroundFreezeLinuxMint();
}
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

@@ -33,6 +33,8 @@ bool filterAbTagByIntersection() {
const _personalAddressBookName = "My address book";
const _legacyAddressBookName = "Legacy address book";
const kUntagged = "Untagged";
enum ForcePullAb {
listAndCurrent,
current,
@@ -424,6 +426,7 @@ class AbModel {
// #region tags
Future<bool> addTags(List<String> tagList) async {
tagList.removeWhere((e) => e == kUntagged);
final ret = await current.addTags(tagList, {});
await pullNonLegacyAfterChange();
_saveCache();
@@ -645,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

@@ -100,6 +100,10 @@ class FileModel {
fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
}
void receiveEmptyDirs(Map<String, dynamic> evt) {
fileFetcher.tryCompleteEmptyDirsTask(evt['value'], evt['is_local']);
}
Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
evtLoop.pushEvent(
_FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
@@ -257,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 {
@@ -470,7 +495,8 @@ class FileController {
}
/// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
void sendFiles(SelectedItems items, DirectoryData otherSideData) {
Future<void> sendFiles(
SelectedItems items, DirectoryData otherSideData) async {
/// ignore wrong items side status
if (items.isLocal != isLocal) {
return;
@@ -496,6 +522,43 @@ class FileController {
debugPrint(
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
}
if (isWeb ||
(!isLocal &&
versionCmp(rootState.target!.ffiModel.pi.version, '1.3.3') < 0)) {
return;
}
final List<Entry> entrys = items.items.toList();
var isRemote = isLocal == true ? true : false;
await Future.forEach(entrys, (Entry item) async {
if (!item.isDirectory) {
return;
}
final List<String> paths = [];
final emptyDirs =
await fileFetcher.readEmptyDirs(item.path, isLocal, showHidden);
if (emptyDirs.isEmpty) {
return;
} else {
for (var dir in emptyDirs) {
paths.add(dir.path);
}
}
final dirs = paths.map((path) {
return PathUtil.getOtherSidePath(directory.value.path, path,
options.value.isWindows, toPath, isWindows);
});
for (var dir in dirs) {
createDirWithRemote(dir, isRemote);
}
});
}
bool _removeCheckboxRemember = false;
@@ -689,12 +752,16 @@ class FileController {
sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal);
}
Future<void> createDir(String path) async {
Future<void> createDirWithRemote(String path, bool isRemote) async {
bind.sessionCreateDir(
sessionId: sessionId,
actId: JobController.jobID.next(),
path: path,
isRemote: !isLocal);
isRemote: isRemote);
}
Future<void> createDir(String path) async {
await createDirWithRemote(path, !isLocal);
}
Future<void> renameAction(Entry item, bool isLocal) async {
@@ -1064,6 +1131,7 @@ class JobResultListener<T> {
class FileFetcher {
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
Map<String, Completer<FileDirectory>> remoteTasks = {};
Map<String, Completer<List<FileDirectory>>> remoteEmptyDirsTasks = {};
Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
final GetSessionID getSessionID;
@@ -1071,6 +1139,24 @@ class FileFetcher {
FileFetcher(this.getSessionID);
Future<List<FileDirectory>> registerReadEmptyDirsTask(
bool isLocal, String path) {
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
final tasks = remoteEmptyDirsTasks; // bypass now
if (tasks.containsKey(path)) {
throw "Failed to registerReadEmptyDirsTask, already have same read job";
}
final c = Completer<List<FileDirectory>>();
tasks[path] = c;
Timer(Duration(seconds: 2), () {
tasks.remove(path);
if (c.isCompleted) return;
c.completeError("Failed to read empty dirs, timeout");
});
return c.future;
}
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
final tasks = remoteTasks; // bypass now
@@ -1104,6 +1190,25 @@ class FileFetcher {
return c.future;
}
tryCompleteEmptyDirsTask(String? msg, String? isLocalStr) {
if (msg == null || isLocalStr == null) return;
late final Map<String, Completer<List<FileDirectory>>> tasks;
try {
final map = jsonDecode(msg);
final String path = map["path"];
final List<dynamic> fdJsons = map["empty_dirs"];
final List<FileDirectory> fds =
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
tasks = remoteEmptyDirsTasks;
final completer = tasks.remove(path);
completer?.complete(fds);
} catch (e) {
debugPrint("tryCompleteJob err: $e");
}
}
tryCompleteTask(String? msg, String? isLocalStr) {
if (msg == null || isLocalStr == null) return;
late final Map<Object, Completer<FileDirectory>> tasks;
@@ -1127,6 +1232,28 @@ class FileFetcher {
}
}
Future<List<FileDirectory>> readEmptyDirs(
String path, bool isLocal, bool showHidden) async {
try {
if (isLocal) {
final res = await bind.sessionReadLocalEmptyDirsRecursiveSync(
sessionId: sessionId, path: path, includeHidden: showHidden);
final List<dynamic> fdJsons = jsonDecode(res);
final List<FileDirectory> fds =
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
return fds;
} else {
await bind.sessionReadRemoteEmptyDirsRecursiveSync(
sessionId: sessionId, path: path, includeHidden: showHidden);
return registerReadEmptyDirsTask(isLocal, path);
}
} catch (e) {
return Future.error(e);
}
}
Future<FileDirectory> fetchDirectory(
String path, bool isLocal, bool showHidden) async {
try {
@@ -1373,6 +1500,24 @@ class PathUtil {
static final windowsContext = path.Context(style: path.Style.windows);
static final posixContext = path.Context(style: path.Style.posix);
static String getOtherSidePath(String mainRootPath, String mainPath,
bool isMainWindows, String otherRootPath, bool isOtherWindows) {
final mainPathUtil = isMainWindows ? windowsContext : posixContext;
final relativePath = mainPathUtil.relative(mainPath, from: mainRootPath);
final names = mainPathUtil.split(relativePath);
final otherPathUtil = isOtherWindows ? windowsContext : posixContext;
String path = otherRootPath;
for (var name in names) {
path = otherPathUtil.join(path, name);
}
return path;
}
static String join(String path1, String path2, bool isWindows) {
final pathUtil = isWindows ? windowsContext : posixContext;
return pathUtil.join(path1, path2);
@@ -1383,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

@@ -544,7 +544,40 @@ class InputModel {
handleKeyDownEventModifiers(e);
}
if (isMobile || (isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) {
bool isMobileAndMapMode = false;
if (isMobile) {
// Do not use map mode if mobile -> Android. Android does not support map mode for now.
// Because simulating the physical key events(uhid) which requires root permission is not supported.
if (peerPlatform != kPeerPlatformAndroid) {
if (isIOS) {
isMobileAndMapMode = true;
} else {
// The physicalKey.usbHidUsage may be not correct for soft keyboard on Android.
// iOS does not have this issue.
// 1. Open the soft keyboard on Android
// 2. Switch to input method like zh/ko/ja
// 3. Click Backspace and Enter on the soft keyboard or physical keyboard
// 4. The physicalKey.usbHidUsage is not correct.
// PhysicalKeyboardKey#8ac83(usbHidUsage: "0x1100000042", debugName: "Key with ID 0x1100000042")
// LogicalKeyboardKey#2604c(keyId: "0x10000000d", keyLabel: "Enter", debugName: "Enter")
//
// The correct PhysicalKeyboardKey should be
// PhysicalKeyboardKey#e14a9(usbHidUsage: "0x00070028", debugName: "Enter")
// https://github.com/flutter/flutter/issues/157771
// We cannot use the debugName to determine the key is correct or not, because it's null in release mode.
// The normal `usbHidUsage` for keyboard shoud be between [0x00000010, 0x000c029f]
// https://github.com/flutter/flutter/blob/c051b69e2a2224300e20d93dbd15f4b91e8844d1/packages/flutter/lib/src/services/keyboard_key.g.dart#L5332 - 5600
final isNormalHsbHidUsage = (e.physicalKey.usbHidUsage >> 20) == 0;
isMobileAndMapMode = isNormalHsbHidUsage &&
// No need to check `!['Backspace', 'Enter'].contains(e.logicalKey.keyLabel)`
// But we still add it for more reliability.
!['Backspace', 'Enter'].contains(e.logicalKey.keyLabel);
}
}
}
final isDesktopAndMapMode =
isDesktop || (isWebDesktop && keyboardMode == kKeyMapMode);
if (isMobileAndMapMode || isDesktopAndMapMode) {
// FIXME: e.character is wrong for dead keys, eg: ^ in de
newKeyboardMode(
e.character ?? '',
@@ -735,22 +768,22 @@ class InputModel {
}
/// Send a mouse tap event(down and up).
void tap(MouseButtons button) {
sendMouse('down', button);
sendMouse('up', button);
Future<void> tap(MouseButtons button) async {
await sendMouse('down', button);
await sendMouse('up', button);
}
void tapDown(MouseButtons button) {
sendMouse('down', button);
Future<void> tapDown(MouseButtons button) async {
await sendMouse('down', button);
}
void tapUp(MouseButtons button) {
sendMouse('up', button);
Future<void> tapUp(MouseButtons button) async {
await sendMouse('up', button);
}
/// Send scroll event with scroll distance [y].
void scroll(int y) {
bind.sessionSendMouse(
Future<void> scroll(int y) async {
await bind.sessionSendMouse(
sessionId: sessionId,
msg: json
.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
@@ -771,9 +804,9 @@ class InputModel {
}
/// Send mouse press event.
void sendMouse(String type, MouseButtons button) {
Future<void> sendMouse(String type, MouseButtons button) async {
if (!keyboardPerm) return;
bind.sessionSendMouse(
await bind.sessionSendMouse(
sessionId: sessionId,
msg: json.encode(modify({'type': type, 'buttons': button.value})));
}
@@ -797,11 +830,11 @@ class InputModel {
}
/// Send mouse movement event with distance in [x] and [y].
void moveMouse(double x, double y) {
Future<void> moveMouse(double x, double y) async {
if (!keyboardPerm) return;
var x2 = x.toInt();
var y2 = y.toInt();
bind.sessionSendMouse(
await bind.sessionSendMouse(
sessionId: sessionId,
msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
}
@@ -823,7 +856,7 @@ class InputModel {
_stopFling = true;
if (isViewOnly) return;
if (peerPlatform == kPeerPlatformAndroid) {
handlePointerEvent('touch', 'pan_start', e.position);
handlePointerEvent('touch', kMouseEventTypePanStart, e.position);
}
}
@@ -866,8 +899,8 @@ class InputModel {
}
if (x != 0 || y != 0) {
if (peerPlatform == kPeerPlatformAndroid) {
handlePointerEvent(
'touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
handlePointerEvent('touch', kMouseEventTypePanUpdate,
Offset(x.toDouble(), y.toDouble()));
} else {
bind.sessionSendMouse(
sessionId: sessionId,
@@ -929,7 +962,7 @@ class InputModel {
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
if (peerPlatform == kPeerPlatformAndroid) {
handlePointerEvent('touch', 'pan_end', e.position);
handlePointerEvent('touch', kMouseEventTypePanEnd, e.position);
return;
}
@@ -1047,7 +1080,7 @@ class InputModel {
onExit: true,
);
int trySetNearestRange(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;
}
@@ -1087,13 +1120,13 @@ class InputModel {
// to-do: handle mouse events
late final dynamic evtValue;
if (type == 'pan_update') {
if (type == kMouseEventTypePanUpdate) {
evtValue = {
'x': x.toInt(),
'y': y.toInt(),
};
} else {
final isMoveTypes = ['pan_start', 'pan_end'];
final isMoveTypes = [kMouseEventTypePanStart, kMouseEventTypePanEnd];
final pos = handlePointerDevicePos(
kPointerEventKindTouch,
x,
@@ -1105,8 +1138,8 @@ class InputModel {
return;
}
evtValue = {
'x': pos.x,
'y': pos.y,
'x': pos.x.toInt(),
'y': pos.y.toInt(),
};
}
@@ -1148,14 +1181,14 @@ class InputModel {
return;
}
var type = '';
var type = kMouseEventTypeDefault;
var isMove = false;
switch (evt['type']) {
case _kMouseEventDown:
type = 'down';
type = kMouseEventTypeDown;
break;
case _kMouseEventUp:
type = 'up';
type = kMouseEventTypeUp;
break;
case _kMouseEventMove:
_pointerMovedAfterEnter = true;
@@ -1166,7 +1199,7 @@ class InputModel {
}
evt['type'] = type;
if (type == 'down' && !_pointerMovedAfterEnter) {
if (type == kMouseEventTypeDown && !_pointerMovedAfterEnter) {
// Move mouse to the position of the down event first.
lastMousePos = ui.Offset(x, y);
refreshMousePos();
@@ -1188,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 = {
@@ -1329,33 +1362,56 @@ 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, x, y, rect,
buttons: buttons);
}
int minX = rect.left.toInt();
static Point<double>? getPointInRemoteRect(
bool isLocalDesktop,
String? peerPlatform,
String kind,
String evtType,
double evtX,
double evtY,
Rect rect,
{int buttons = kPrimaryMouseButton}) {
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 = trySetNearestRange(evtX, minX, maxX, 5);
evtY = trySetNearestRange(evtY, minY, maxY, 5);
if (kind == kPointerEventKindMouse) {
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
// If left mouse up, no early return.
if (!(buttons == kPrimaryMouseButton && evtType == 'up')) {
return null;
evtX = InputModel.tryGetNearestRange(evtX, minX, maxX, 5);
evtY = InputModel.tryGetNearestRange(evtY, minY, maxY, 5);
if (isLocalDesktop) {
if (kind == kPointerEventKindMouse) {
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
// If left mouse up, no early return.
if (!(buttons == kPrimaryMouseButton &&
evtType == kMouseEventTypeUp)) {
return null;
}
}
}
} else {
bool evtXInRange = evtX >= minX && evtX <= maxX;
bool evtYInRange = evtY >= minY && evtY <= maxY;
if (!(evtXInRange || evtYInRange)) {
return null;
}
if (evtX < minX) {
evtX = minX;
} else if (evtX > maxX) {
evtX = maxX;
}
if (evtY < minY) {
evtY = minY;
} else if (evtY > maxY) {
evtY = maxY;
}
}
return Point(evtX, evtY);

View File

@@ -6,8 +6,10 @@ import 'dart:ui' as ui;
import 'package:bot_toast/bot_toast.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/ab_model.dart';
@@ -142,6 +144,7 @@ class FfiModel with ChangeNotifier {
bool get touchMode => _touchMode;
bool get isPeerAndroid => _pi.platform == kPeerPlatformAndroid;
bool get isPeerMobile => isPeerAndroid;
bool get viewOnly => _viewOnly;
@@ -306,6 +309,8 @@ class FfiModel with ChangeNotifier {
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
} else if (name == 'file_dir') {
parent.target?.fileModel.receiveFileDir(evt);
} else if (name == 'empty_dirs') {
parent.target?.fileModel.receiveEmptyDirs(evt);
} else if (name == 'job_progress') {
parent.target?.fileModel.jobController.tryUpdateJobProgress(evt);
} else if (name == 'job_done') {
@@ -397,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');
@@ -1263,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);
@@ -1274,13 +1285,6 @@ class ImageModel with ChangeNotifier {
if (isDesktop || isWebDesktop) {
await parent.target?.canvasModel.updateViewStyle();
await parent.target?.canvasModel.updateScrollStyle();
} else {
final size = MediaQueryData.fromWindow(ui.window).size;
final canvasWidth = size.width;
final canvasHeight = size.height;
final xscale = canvasWidth / image.width;
final yscale = canvasHeight / image.height;
parent.target?.canvasModel.scale = min(xscale, yscale);
}
if (parent.target != null) {
await initializeCursorAndCanvas(parent.target!);
@@ -1292,20 +1296,18 @@ class ImageModel with ChangeNotifier {
}
// mobile only
// for desktop, height should minus tabbar height
double get maxScale {
if (_image == null) return 1.5;
final size = MediaQueryData.fromWindow(ui.window).size;
final size = parent.target!.canvasModel.getSize();
final xscale = size.width / _image!.width;
final yscale = size.height / _image!.height;
return max(1.5, max(xscale, yscale));
}
// mobile only
// for desktop, height should minus tabbar height
double get minScale {
if (_image == null) return 1.5;
final size = MediaQueryData.fromWindow(ui.window).size;
final size = parent.target!.canvasModel.getSize();
final xscale = size.width / _image!.width;
final yscale = size.height / _image!.height;
return min(xscale, yscale) / 1.5;
@@ -1428,6 +1430,12 @@ class CanvasModel with ChangeNotifier {
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
Timer? _timerMobileFocusCanvasCursor;
// `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method
// after showing the soft keyboard.
bool isMobileCanvasChanged = false;
final ScrollController _horizontal = ScrollController();
final ScrollController _vertical = ScrollController();
@@ -1470,21 +1478,37 @@ class CanvasModel with ChangeNotifier {
static double get bottomToEdge =>
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.bottom : 0;
updateViewStyle({refreshMousePos = true}) async {
Size getSize() {
final size = MediaQueryData.fromWindow(ui.window).size;
// If minimized, w or h may be negative here.
double w = size.width - leftToEdge - rightToEdge;
double h = size.height - topToEdge - bottomToEdge;
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
Size getSize() {
final mediaData = MediaQueryData.fromView(ui.window);
final size = mediaData.size;
// If minimized, w or h may be negative here.
double w = size.width - leftToEdge - rightToEdge;
double h = size.height - topToEdge - bottomToEdge;
if (isMobile) {
h = h -
mediaData.viewInsets.bottom -
(parent.target?.cursorModel.keyHelpToolsRectToAdjustCanvas?.bottom ??
0);
}
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
}
// mobile only
double getAdjustY() {
final bottom =
parent.target?.cursorModel.keyHelpToolsRectToAdjustCanvas?.bottom ?? 0;
return max(bottom - MediaQueryData.fromView(ui.window).padding.top, 0);
}
updateSize() => _size = getSize();
updateViewStyle({refreshMousePos = true, notify = true}) async {
final style = await bind.sessionGetViewStyle(sessionId: sessionId);
if (style == null) {
return;
}
_size = getSize();
updateSize();
final displayWidth = getDisplayWidth();
final displayHeight = getDisplayHeight();
final viewStyle = ViewStyle(
@@ -1507,16 +1531,25 @@ class CanvasModel with ChangeNotifier {
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
_scale = 1.0 / _devicePixelRatio;
}
_x = (size.width - displayWidth * _scale) / 2;
_y = (size.height - displayHeight * _scale) / 2;
_resetCanvasOffset(displayWidth, displayHeight);
_imageOverflow.value = _x < 0 || y < 0;
notifyListeners();
if (refreshMousePos) {
if (notify) {
notifyListeners();
}
if (!isMobile && refreshMousePos) {
parent.target?.inputModel.refreshMousePos();
}
tryUpdateScrollStyle(Duration.zero, style);
}
_resetCanvasOffset(int displayWidth, int displayHeight) {
_x = (size.width - displayWidth * _scale) / 2;
_y = (size.height - displayHeight * _scale) / 2;
if (isMobile) {
_moveToCenterCursor();
}
}
tryUpdateScrollStyle(Duration duration, String? style) async {
if (_scrollStyle != ScrollStyle.scrollbar) return;
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
@@ -1620,6 +1653,9 @@ class CanvasModel with ChangeNotifier {
panX(double dx) {
_x += dx;
if (isMobile) {
isMobileCanvasChanged = true;
}
notifyListeners();
}
@@ -1627,17 +1663,20 @@ class CanvasModel with ChangeNotifier {
if (isWebDesktop) {
updateViewStyle();
} else {
_x = (size.width - getDisplayWidth() * _scale) / 2;
_y = (size.height - getDisplayHeight() * _scale) / 2;
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
}
notifyListeners();
}
panY(double dy) {
_y += dy;
if (isMobile) {
isMobileCanvasChanged = true;
}
notifyListeners();
}
// mobile only
updateScale(double v, Offset focalPoint) {
if (parent.target?.imageModel.image == null) return;
final s = _scale;
@@ -1649,13 +1688,13 @@ class CanvasModel with ChangeNotifier {
// (focalPoint.dx - _x_1) / s1 + displayOriginX = (focalPoint.dx - _x_2) / s2 + displayOriginX
// _x_2 = focalPoint.dx - (focalPoint.dx - _x_1) / s1 * s2
_x = focalPoint.dx - (focalPoint.dx - _x) / s * _scale;
final adjustForKeyboard =
parent.target?.cursorModel.adjustForKeyboard() ?? 0.0;
// (focalPoint.dy - _y_1 + adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 + adjust) / s2 + displayOriginY
// _y_2 = focalPoint.dy + adjust - (focalPoint.dy - _y_1 + adjust) / s1 * s2
_y = focalPoint.dy +
adjustForKeyboard -
(focalPoint.dy - _y + adjustForKeyboard) / s * _scale;
final adjust = getAdjustY();
// (focalPoint.dy - _y_1 - adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 - adjust) / s2 + displayOriginY
// _y_2 = focalPoint.dy - adjust - (focalPoint.dy - _y_1 - adjust) / s1 * s2
_y = focalPoint.dy - adjust - (focalPoint.dy - _y - adjust) / s * _scale;
if (isMobile) {
isMobileCanvasChanged = true;
}
notifyListeners();
}
@@ -1666,10 +1705,7 @@ class CanvasModel with ChangeNotifier {
if (kIgnoreDpi && _lastViewStyle.style == kRemoteViewStyleOriginal) {
_scale = 1.0 / _devicePixelRatio;
}
final displayWidth = getDisplayWidth();
final displayHeight = getDisplayHeight();
_x = (size.width - displayWidth * _scale) / 2;
_y = (size.height - displayHeight * _scale) / 2;
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
bind.sessionSetViewStyle(sessionId: sessionId, value: _lastViewStyle.style);
notifyListeners();
}
@@ -1678,6 +1714,8 @@ class CanvasModel with ChangeNotifier {
_x = 0;
_y = 0;
_scale = 1.0;
_lastViewStyle = ViewStyle.defaultViewStyle();
_timerMobileFocusCanvasCursor?.cancel();
}
updateScrollPercent() {
@@ -1695,6 +1733,43 @@ class CanvasModel with ChangeNotifier {
: 0.0;
setScrollPercent(percentX, percentY);
}
void mobileFocusCanvasCursor() {
_timerMobileFocusCanvasCursor?.cancel();
_timerMobileFocusCanvasCursor =
Timer(Duration(milliseconds: 100), () async {
updateSize();
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
notifyListeners();
});
}
// mobile only
// Move the canvas to make the cursor visible(center) on the screen.
void _moveToCenterCursor() {
Rect? imageRect = parent.target?.ffiModel.rect;
if (imageRect == null) {
// unreachable
return;
}
final maxX = 0.0;
final minX = _size.width + (imageRect.left - imageRect.right) * _scale;
final maxY = 0.0;
final minY = _size.height + (imageRect.top - imageRect.bottom) * _scale;
Offset offsetToCenter =
parent.target?.cursorModel.getCanvasOffsetToCenterCursor() ??
Offset.zero;
if (minX < 0) {
_x = min(max(offsetToCenter.dx, minX), maxX);
} else {
// _size.width > (imageRect.right, imageRect.left) * _scale, we should not change _x
}
if (minY < 0) {
_y = min(max(offsetToCenter.dy, minY), maxY);
} else {
// _size.height > (imageRect.bottom - imageRect.top) * _scale, , we should not change _y
}
}
}
// data for cursor
@@ -1888,9 +1963,13 @@ class CursorModel with ChangeNotifier {
// `lastIsBlocked` is only used in common/widgets/remote_input.dart -> _RawTouchGestureDetectorRegionState -> onDoubleTap()
// Because onDoubleTap() doesn't have the `event` parameter, we can't get the touch event's position.
bool _lastIsBlocked = false;
double _yForKeyboardAdjust = 0;
bool _lastKeyboardIsVisible = false;
keyHelpToolsVisibilityChanged(Rect? r) {
bool get lastKeyboardIsVisible => _lastKeyboardIsVisible;
Rect? get keyHelpToolsRectToAdjustCanvas =>
_lastKeyboardIsVisible ? _keyHelpToolsRect : null;
keyHelpToolsVisibilityChanged(Rect? r, bool keyboardIsVisible) {
_keyHelpToolsRect = r;
if (r == null) {
_lastIsBlocked = false;
@@ -1900,7 +1979,11 @@ class CursorModel with ChangeNotifier {
// `lastIsBlocked` will be set when the cursor is moving or touch somewhere else.
_lastIsBlocked = true;
}
_yForKeyboardAdjust = _y;
if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) {
parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.isMobileCanvasChanged = false;
}
_lastKeyboardIsVisible = keyboardIsVisible;
}
get lastIsBlocked => _lastIsBlocked;
@@ -1939,8 +2022,10 @@ class CursorModel with ChangeNotifier {
addKey(String key) => _cacheKeys.add(key);
// remote physical display coordinate
// For update pan (mobile), onOneFingerPanStart, onOneFingerPanUpdate, onHoldDragUpdate
Rect getVisibleRect() {
final size = MediaQueryData.fromWindow(ui.window).size;
final size = parent.target?.canvasModel.getSize() ??
MediaQueryData.fromView(ui.window).size;
final xoffset = parent.target?.canvasModel.x ?? 0;
final yoffset = parent.target?.canvasModel.y ?? 0;
final scale = parent.target?.canvasModel.scale ?? 1;
@@ -1949,22 +2034,22 @@ class CursorModel with ChangeNotifier {
return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale);
}
get keyboardHeight => MediaQueryData.fromWindow(ui.window).viewInsets.bottom;
get scale => parent.target?.canvasModel.scale ?? 1.0;
double adjustForKeyboard() {
if (keyboardHeight < 100) {
return 0.0;
}
final m = MediaQueryData.fromWindow(ui.window);
final size = m.size;
final thresh = (size.height - keyboardHeight) / 2;
final h = (_yForKeyboardAdjust - getVisibleRect().top) *
scale; // local physical display height
return h - thresh;
Offset getCanvasOffsetToCenterCursor() {
// Cursor should be at the center of the visible rect.
// _x = rect.left + rect.width / 2
// _y = rect.right + rect.height / 2
// See `getVisibleRect()`
// _x = _displayOriginX - xoffset / scale + size.width / scale * 0.5;
// _y = _displayOriginY - yoffset / scale + size.height / scale * 0.5;
final size = parent.target?.canvasModel.getSize() ??
MediaQueryData.fromView(ui.window).size;
final xoffset = (_displayOriginX - _x) * scale + size.width * 0.5;
final yoffset = (_displayOriginY - _y) * scale + size.height * 0.5;
return Offset(xoffset, yoffset);
}
get scale => parent.target?.canvasModel.scale ?? 1.0;
// mobile Soft keyboard, block touch event from the KeyHelpTools
shouldBlock(double x, double y) {
if (!(parent.target?.ffiModel.touchMode ?? false)) {
@@ -1979,22 +2064,63 @@ class CursorModel with ChangeNotifier {
return false;
}
move(double x, double y) {
// For touch mode
Future<bool> move(double x, double y) async {
if (shouldBlock(x, y)) {
_lastIsBlocked = true;
return false;
}
_lastIsBlocked = false;
moveLocal(x, y, adjust: adjustForKeyboard());
parent.target?.inputModel.moveMouse(_x, _y);
if (!_moveLocalIfInRemoteRect(x, y)) {
return false;
}
await parent.target?.inputModel.moveMouse(_x, _y);
return true;
}
bool isInRemoteRect(Offset offset) {
return getRemotePosInRect(offset) != null;
}
Offset? getRemotePosInRect(Offset offset) {
final adjust = parent.target?.canvasModel.getAdjustY() ?? 0;
final newPos = _getNewPos(offset.dx, offset.dy, adjust);
final visibleRect = getVisibleRect();
if (!isPointInRect(newPos, visibleRect)) {
return null;
}
final rect = parent.target?.ffiModel.rect;
if (rect != null) {
if (!isPointInRect(newPos, rect)) {
return null;
}
}
return newPos;
}
Offset _getNewPos(double x, double y, double adjust) {
final xoffset = parent.target?.canvasModel.x ?? 0;
final yoffset = parent.target?.canvasModel.y ?? 0;
final newX = (x - xoffset) / scale + _displayOriginX;
final newY = (y - yoffset - adjust) / scale + _displayOriginY;
return Offset(newX, newY);
}
bool _moveLocalIfInRemoteRect(double x, double y) {
final newPos = getRemotePosInRect(Offset(x, y));
if (newPos == null) {
return false;
}
_x = newPos.dx;
_y = newPos.dy;
notifyListeners();
return true;
}
moveLocal(double x, double y, {double adjust = 0}) {
final xoffset = parent.target?.canvasModel.x ?? 0;
final yoffset = parent.target?.canvasModel.y ?? 0;
_x = (x - xoffset) / scale + _displayOriginX;
_y = (y - yoffset + adjust) / scale + _displayOriginY;
final newPos = _getNewPos(x, y, adjust);
_x = newPos.dx;
_y = newPos.dy;
notifyListeners();
}
@@ -2006,9 +2132,9 @@ class CursorModel with ChangeNotifier {
notifyListeners();
}
updatePan(Offset delta, Offset localPosition, bool touchMode) {
updatePan(Offset delta, Offset localPosition, bool touchMode) async {
if (touchMode) {
_handleTouchMode(delta, localPosition);
await _handleTouchMode(delta, localPosition);
return;
}
double dx = delta.dx;
@@ -2066,13 +2192,34 @@ class CursorModel with ChangeNotifier {
}
if (dx == 0 && dy == 0) return;
_x += dx;
_y += dy;
Point<double>? newPos;
final rect = parent.target?.ffiModel.rect;
if (rect == null) {
// unreachable
return;
}
newPos = InputModel.getPointInRemoteRect(
false,
parent.target?.ffiModel.pi.platform,
kPointerEventKindMouse,
kMouseEventTypeDefault,
_x + dx,
_y + dy,
rect,
buttons: kPrimaryButton);
if (newPos == null) {
return;
}
dx = newPos.x - _x;
dy = newPos.y - _y;
_x = newPos.x;
_y = newPos.y;
if (tryMoveCanvasX && dx != 0) {
parent.target?.canvasModel.panX(-dx);
parent.target?.canvasModel.panX(-dx * scale);
}
if (tryMoveCanvasY && dy != 0) {
parent.target?.canvasModel.panY(-dy);
parent.target?.canvasModel.panY(-dy * scale);
}
parent.target?.inputModel.moveMouse(_x, _y);
@@ -2085,7 +2232,7 @@ class CursorModel with ChangeNotifier {
return x >= 0 && y >= 0 && x <= w && y <= h;
}
_handleTouchMode(Offset delta, Offset localPosition) {
_handleTouchMode(Offset delta, Offset localPosition) async {
bool isMoved = false;
if (_remoteWindowCoords.isNotEmpty &&
_windowRect != null &&
@@ -2101,15 +2248,50 @@ class CursorModel with ChangeNotifier {
coords.canvas.scale;
x2 += coords.cursor.offset.dx;
y2 += coords.cursor.offset.dy;
parent.target?.inputModel.moveMouse(x2, y2);
await parent.target?.inputModel.moveMouse(x2, y2);
isMoved = true;
}
}
if (!isMoved) {
final rect = parent.target?.ffiModel.rect;
if (rect == null) {
// unreachable
return;
}
Offset? movementInRect(double x, double y, Rect r) {
final isXInRect = x >= r.left && x <= r.right;
final isYInRect = y >= r.top && y <= r.bottom;
if (!(isXInRect || isYInRect)) {
return null;
}
if (x < r.left) {
x = r.left;
} else if (x > r.right) {
x = r.right;
}
if (y < r.top) {
y = r.top;
} else if (y > r.bottom) {
y = r.bottom;
}
return Offset(x, y);
}
final scale = parent.target?.canvasModel.scale ?? 1.0;
_x += delta.dx / scale;
_y += delta.dy / scale;
parent.target?.inputModel.moveMouse(_x, _y);
var movement =
movementInRect(_x + delta.dx / scale, _y + delta.dy / scale, rect);
if (movement == null) {
return;
}
movement = movementInRect(movement.dx, movement.dy, getVisibleRect());
if (movement == null) {
return;
}
_x = movement.dx;
_y = movement.dy;
await parent.target?.inputModel.moveMouse(_x, _y);
}
notifyListeners();
}
@@ -2686,6 +2868,7 @@ class FFI {
canvasModel.scale,
ffiModel.pi.currentDisplay);
}
imageModel.callbacksOnFirstImage.clear();
await imageModel.update(null);
cursorModel.clear();
ffiModel.clear();

View File

@@ -30,6 +30,7 @@ class ServerModel with ChangeNotifier {
bool _inputOk = false;
bool _audioOk = false;
bool _fileOk = false;
bool _clipboardOk = false;
bool _showElevation = false;
bool hideCm = false;
int _connectStatus = 0; // Rendezvous Server status
@@ -59,6 +60,8 @@ class ServerModel with ChangeNotifier {
bool get fileOk => _fileOk;
bool get clipboardOk => _clipboardOk;
bool get showElevation => _showElevation;
int get connectStatus => _connectStatus;
@@ -209,6 +212,10 @@ class ServerModel with ChangeNotifier {
_fileOk = fileOption != 'N';
}
// clipboard
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
_clipboardOk = clipOption != 'N';
notifyListeners();
}
@@ -315,6 +322,14 @@ class ServerModel with ChangeNotifier {
notifyListeners();
}
toggleClipboard() async {
_clipboardOk = !clipboardOk;
bind.mainSetOption(
key: kOptionEnableClipboard,
value: clipboardOk ? defaultOptionYes : 'N');
notifyListeners();
}
toggleInput() async {
if (clients.isNotEmpty) {
await showClientsMayNotBeChangedAlert(parent.target);

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

@@ -95,17 +95,17 @@ SPEC CHECKSUMS:
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
texture_rgba_renderer: cbed959a3c127122194a364e14b8577bd62dc8f2
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

View File

@@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
var launched = false;
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {

View File

@@ -37,10 +37,10 @@ packages:
dependency: transitive
description:
name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev"
source: hosted
version: "3.4.10"
version: "3.6.1"
args:
dependency: transitive
description:
@@ -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"
@@ -384,10 +384,10 @@ packages:
dependency: "direct main"
description:
name: extended_text
sha256: "7f382de3af12992e34bd72ddd36becf90c4720900af126cb9859f0189af71ffe"
sha256: "38c1cac571d6eaf406f4b80040c1f88561e7617ad90795aac6a1be0a8d0bb676"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.0.0"
extended_text_library:
dependency: transitive
description:
@@ -408,10 +408,10 @@ packages:
dependency: "direct main"
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.3"
ffigen:
dependency: "direct dev"
description:
@@ -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:
@@ -745,10 +736,10 @@ packages:
dependency: "direct main"
description:
name: image_picker
sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
url: "https://pub.dev"
source: hosted
version: "0.8.9"
version: "1.1.2"
image_picker_android:
dependency: transitive
description:
@@ -793,10 +784,10 @@ packages:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
url: "https://pub.dev"
source: hosted
version: "2.9.3"
version: "2.10.0"
image_picker_windows:
dependency: transitive
description:
@@ -865,18 +856,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.15.0"
mime:
dependency: transitive
description:
@@ -1037,14 +1028,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
pool:
dependency: transitive
description:
@@ -1285,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
@@ -1325,10 +1309,11 @@ packages:
uni_links:
dependency: "direct main"
description:
name: uni_links
sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e"
url: "https://pub.dev"
source: hosted
path: uni_links
ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
resolved-ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
url: "https://github.com/rustdesk-org/uni_links"
source: git
version: "0.5.1"
uni_links_desktop:
dependency: "direct main"
@@ -1366,26 +1351,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.3.14"
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:
@@ -1558,24 +1543,24 @@ packages:
dependency: "direct main"
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.5.4"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.5"
window_manager:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: f19acdb008645366339444a359a45c3257c8b32e
resolved-ref: "85789bfe6e4cfaf4ecc00c52857467fdb7f26879"
url: "https://github.com/rustdesk-org/window_manager"
source: git
version: "0.3.6"
@@ -1583,8 +1568,8 @@ packages:
dependency: "direct main"
description:
path: "plugins/window_size"
ref: a738913c8ce2c9f47515382d40827e794a334274
resolved-ref: a738913c8ce2c9f47515382d40827e794a334274
ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
url: "https://github.com/google/flutter-desktop-embedding.git"
source: git
version: "0.1.0"
@@ -1629,5 +1614,5 @@ packages:
source: hosted
version: "0.2.1"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.24.0"

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.2+51
version: 1.3.7+56
environment:
sdk: '^3.1.0'
@@ -35,7 +35,8 @@ dependencies:
wakelock_plus: ^1.1.3
#firebase_analytics: ^9.1.5
package_info_plus: ^4.2.0
url_launcher: ^6.2.1
url_launcher: ^6.3.1
url_launcher_ios: ^6.3.2
toggle_switch: ^2.1.0
dash_chat_2:
git:
@@ -46,7 +47,7 @@ dependencies:
http: ^1.1.0
qr_code_scanner: ^1.0.0
zxing2: ^0.2.0
image_picker: ^0.8.5
image_picker: ^1.1.2
image: ^4.0.17
back_button_interceptor: ^6.0.1
flutter_rust_bridge: "1.80.1"
@@ -62,7 +63,7 @@ dependencies:
git:
url: https://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size
ref: a738913c8ce2c9f47515382d40827e794a334274
ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
get: ^4.6.5
visibility_detector: ^0.4.0+2
contextmenu: ^3.0.0
@@ -71,14 +72,11 @@ 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).
uni_links:
git:
url: https://github.com/rustdesk-org/flutter_improved_scrolling
uni_links: ^0.5.1
url: https://github.com/rustdesk-org/uni_links
path: uni_links
ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
uni_links_desktop: ^0.1.6 # use 0.1.6 to make flutter 3.13 works
path: ^1.8.1
auto_size_text: ^3.0.0
@@ -87,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:
@@ -104,7 +105,7 @@ dependencies:
pull_down_button: ^0.9.3
device_info_plus: ^9.1.0
qr_flutter: ^4.1.0
extended_text: 13.0.0
extended_text: 14.0.0
dev_dependencies:
icons_launcher: ^2.0.4
@@ -186,3 +187,4 @@ flutter:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

View File

@@ -3,7 +3,7 @@ use std::{
fs::File,
io::{BufRead, BufReader, Read, Seek},
os::unix::prelude::PermissionsExt,
path::PathBuf,
path::{Path, PathBuf},
sync::atomic::{AtomicU64, Ordering},
time::SystemTime,
};
@@ -51,7 +51,7 @@ pub(super) struct LocalFile {
}
impl LocalFile {
pub fn try_open(path: &PathBuf) -> Result<Self, CliprdrError> {
pub fn try_open(path: &Path) -> Result<Self, CliprdrError> {
let mt = std::fs::metadata(path).map_err(|e| CliprdrError::FileError {
path: path.clone(),
err: e,
@@ -219,7 +219,7 @@ impl LocalFile {
pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, CliprdrError> {
fn constr_file_lst(
path: &PathBuf,
path: &Path,
file_list: &mut Vec<LocalFile>,
visited: &mut HashSet<PathBuf>,
) -> Result<(), CliprdrError> {

View File

@@ -1,5 +1,5 @@
use std::{
path::PathBuf,
path::{Path, PathBuf},
sync::{mpsc::Sender, Arc},
time::Duration,
};
@@ -74,7 +74,7 @@ trait SysClipboard: Send + Sync {
}
#[cfg(target_os = "linux")]
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
fn get_sys_clipboard(ignore_path: &Path) -> Result<Box<dyn SysClipboard>, CliprdrError> {
#[cfg(feature = "wayland")]
{
unimplemented!()
@@ -88,7 +88,7 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, Cli
}
#[cfg(target_os = "macos")]
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
fn get_sys_clipboard(ignore_path: &Path) -> Result<Box<dyn SysClipboard>, CliprdrError> {
use ns_clipboard::*;
let ns_pb = NsPasteboard::new(ignore_path)?;
Ok(Box::new(ns_pb) as Box<_>)

View File

@@ -1,4 +1,7 @@
use std::{collections::BTreeSet, path::PathBuf};
use std::{
collections::BTreeSet,
path::{Path, PathBuf},
};
use cacao::pasteboard::{Pasteboard, PasteboardName};
use hbb_common::log;
@@ -30,7 +33,7 @@ pub struct NsPasteboard {
}
impl NsPasteboard {
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
pub fn new(ignore_path: &Path) -> Result<Self, CliprdrError> {
Ok(Self {
ignore_path: ignore_path.to_owned(),
former_file_list: Mutex::new(vec![]),

View File

@@ -7,7 +7,7 @@ use crate::CliprdrError;
// url encode and decode is needed
const ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS.add(b' ').remove(b'/');
pub(super) fn encode_path_to_uri(path: &PathBuf) -> io::Result<String> {
pub(super) fn encode_path_to_uri(path: &Path) -> io::Result<String> {
let encoded =
percent_encoding::percent_encode(path.to_str()?.as_bytes(), &ENCODE_SET).to_string();
format!("file://{}", encoded)

View File

@@ -1,4 +1,7 @@
use std::{collections::BTreeSet, path::PathBuf};
use std::{
collections::BTreeSet,
path::{Path, PathBuf},
};
use hbb_common::log;
use once_cell::sync::OnceCell;
@@ -26,7 +29,7 @@ pub struct X11Clipboard {
}
impl X11Clipboard {
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
pub fn new(ignore_path: &Path) -> Result<Self, CliprdrError> {
let clipboard = get_clip()?;
let text_uri_list = clipboard
.setter

View File

@@ -211,6 +211,11 @@ struct wf_clipboard
BOOL sync;
UINT32 capabilities;
// This flag is not really needed,
// but we can use it to double confirm that files can only be pasted after `Ctrl+C`.
// Not sure `is_file_descriptor_from_remote()` is engough to check all cases on all Windows.
BOOL copied;
size_t map_size;
size_t map_capacity;
formatMapping *format_mappings;
@@ -263,6 +268,8 @@ static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 con
ULONG index, UINT32 flag, DWORD positionhigh,
DWORD positionlow, ULONG request);
static BOOL is_file_descriptor_from_remote();
static void CliprdrDataObject_Delete(CliprdrDataObject *instance);
static CliprdrEnumFORMATETC *CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC *pFormatEtc);
@@ -712,6 +719,15 @@ static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject *This, FO
if (!clipboard)
return E_INVALIDARG;
// If `Ctrl+C` is not pressed yet, do not handle the file paste, and empty the clipboard.
if (!clipboard->copied) {
if (try_open_clipboard(clipboard->hwnd)) {
EmptyClipboard();
CloseClipboard();
}
return E_UNEXPECTED;
}
if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1)
{
// empty clipboard here?
@@ -1479,6 +1495,8 @@ static UINT cliprdr_send_format_list(wfClipboard *clipboard, UINT32 connID)
// send
rc = clipboard->context->ClientFormatList(clipboard->context, &formatList);
// No need to check `rc`, `copied` is only used to indicate `Ctrl+C` is pressed.
clipboard->copied = TRUE;
for (index = 0; index < numFormats; index++)
{
@@ -2274,7 +2292,9 @@ static UINT wf_cliprdr_monitor_ready(CliprdrClientContext *context,
if (rc != CHANNEL_RC_OK)
return rc;
return cliprdr_send_format_list(clipboard, monitorReady->connID);
return rc;
// Don't send format list here, because we don't want to paste files copied before the connection.
// return cliprdr_send_format_list(clipboard, monitorReady->connID);
}
/**
@@ -2321,11 +2341,20 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context,
UINT32 i;
formatMapping *mapping;
CLIPRDR_FORMAT *format;
wfClipboard *clipboard = (wfClipboard *)context->Custom;
wfClipboard *clipboard = NULL;
if (!context || !formatList)
return ERROR_INTERNAL_ERROR;
clipboard = (wfClipboard *)context->Custom;
if (!clipboard)
return ERROR_INTERNAL_ERROR;
if (!clear_format_map(clipboard))
return ERROR_INTERNAL_ERROR;
clipboard->copied = TRUE;
for (i = 0; i < formatList->numFormats; i++)
{
format = &(formatList->formats[i]);
@@ -3060,6 +3089,19 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
return rc;
}
BOOL is_file_descriptor_from_remote()
{
UINT fsid = 0;
if (IsClipboardFormatAvailable(CF_HDROP)) {
return FALSE;
}
fsid = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
if (IsClipboardFormatAvailable(fsid)) {
return TRUE;
}
return FALSE;
}
BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
{
if (!clipboard || !cliprdr)
@@ -3071,6 +3113,7 @@ BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
clipboard->map_size = 0;
clipboard->hUser32 = LoadLibraryA("user32.dll");
clipboard->data_obj = NULL;
clipboard->copied = FALSE;
if (clipboard->hUser32)
{
@@ -3126,14 +3169,18 @@ BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
if (!clipboard || !cliprdr)
return FALSE;
clipboard->copied = FALSE;
cliprdr->Custom = NULL;
/* discard all contexts in clipboard */
if (try_open_clipboard(clipboard->hwnd))
{
if (!EmptyClipboard())
if (is_file_descriptor_from_remote())
{
DEBUG_CLIPRDR("EmptyClipboard failed with 0x%x", GetLastError());
if (!EmptyClipboard())
{
DEBUG_CLIPRDR("EmptyClipboard failed with 0x%x", GetLastError());
}
}
if (!CloseClipboard())
{
@@ -3227,6 +3274,8 @@ BOOL wf_do_empty_cliprdr(wfClipboard *clipboard)
return FALSE;
}
clipboard->copied = FALSE;
if (WaitForSingleObject(clipboard->data_obj_mutex, INFINITE) != WAIT_OBJECT_0)
{
return FALSE;
@@ -3248,10 +3297,14 @@ BOOL wf_do_empty_cliprdr(wfClipboard *clipboard)
break;
}
if (!EmptyClipboard())
if (is_file_descriptor_from_remote())
{
rc = FALSE;
if (!EmptyClipboard())
{
rc = FALSE;
}
}
if (!CloseClipboard())
{
// critical error!!!

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,

1
libs/hbb_common Submodule

Submodule libs/hbb_common added at 49c6b24a7a

View File

@@ -1,3 +0,0 @@
/target
**/*.rs.bk
Cargo.lock

View File

@@ -1,65 +0,0 @@
[package]
name = "hbb_common"
version = "0.1.0"
authors = ["open-trade <info@opentradesolutions.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flexi_logger = { version = "0.27", features = ["async"] }
protobuf = { version = "3.4", features = ["with-bytes"] }
tokio = { version = "1.38", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] }
futures = "0.3"
bytes = { version = "1.6", features = ["serde"] }
log = "0.4"
env_logger = "0.10"
socket2 = { version = "0.3", features = ["reuseport"] }
zstd = "0.13"
anyhow = "1.0"
futures-util = "0.3"
directories-next = "2.0"
rand = "0.8"
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
lazy_static = "1.4"
confy = { git = "https://github.com/rustdesk-org/confy" }
dirs-next = "2.0"
filetime = "0.2"
sodiumoxide = "0.2"
regex = "1.8"
tokio-socks = { git = "https://github.com/rustdesk-org/tokio-socks" }
chrono = "0.4"
backtrace = "0.3"
libc = "0.2"
dlopen = "0.1"
toml = "0.7"
uuid = { version = "1.3", features = ["v4"] }
# new sysinfo issue: https://github.com/rustdesk/rustdesk/pull/6330#issuecomment-2270871442
sysinfo = { git = "https://github.com/rustdesk-org/sysinfo", branch = "rlim_max" }
thiserror = "1.0"
httparse = "1.5"
base64 = "0.22"
url = "2.2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1"
machine-uid = { git = "https://github.com/rustdesk-org/machine-uid" }
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
tokio-rustls = { version = "0.26", features = ["logging", "tls12", "ring"], default-features = false }
rustls-platform-verifier = "0.3.1"
rustls-pki-types = "1.4"
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
tokio-native-tls ="0.3"
[build-dependencies]
protobuf-codegen = { version = "3.4" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi", "sysinfoapi"] }
[target.'cfg(target_os = "macos")'.dependencies]
osascript = "0.3"

View File

@@ -1,14 +0,0 @@
fn main() {
let out_dir = format!("{}/protos", std::env::var("OUT_DIR").unwrap());
std::fs::create_dir_all(&out_dir).unwrap();
protobuf_codegen::Codegen::new()
.pure()
.out_dir(out_dir)
.inputs(["protos/rendezvous.proto", "protos/message.proto"])
.include("protos")
.customize(protobuf_codegen::Customize::default().tokio_bytes(true))
.run()
.expect("Codegen failed.");
}

Some files were not shown because too many files have changed in this diff Show More