Compare commits

..

1036 Commits

Author SHA1 Message Date
princeyogesh e191d11f74 before docker run command added git submoulde update command in docker build Updated README.md (#10878)
there is dependency on submodule libs/hbb_common, 
we need to initialize submodule after cloning repo and before running container
2025-02-22 13:17:11 +08:00
fufesou fc396d2166 fix: check text editing controlling, if selection is valid (#10868)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-21 11:00:19 +08:00
Theofanis Sarmidis 2575e14811 Update el.rs (#10866) 2025-02-21 10:42:12 +08:00
fufesou 0b9a6a280e fix: remote id, update text and reserve selection (#10867)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-21 10:41:57 +08:00
fufesou 343f12b380 fix: load local peers, called two times on select tab (#10859)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-20 22:47:42 +08:00
fufesou ce1e4863cb fix: load peers, always push event data (#10856)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-20 22:44:18 +08:00
fufesou f631c1c28d refact: Remote ID editor, only select text on focus (#10854)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-20 19:35:04 +08:00
fufesou 8b9a7a3506 refact: optimize, ID search peers (#10853)
* refact: optimize, preload peers

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

* Update dialogs.dart

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2025-02-20 18:31:12 +08:00
fufesou 055b351164 refact: optimize, loading recent peers (#10847)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-20 11:53:36 +08:00
fufesou 2e89a33210 fix: android, back function (#10843)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-20 01:02:24 +08:00
Alex Rijckaert 965cc6af26 Update nl.rs (#10834) 2025-02-20 00:25:23 +08:00
rustdesk 16e191f913 fix de 2025-02-20 00:24:24 +08:00
fufesou 1d1e79c802 revert, peers card, sort by online status (#10829)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-19 14:14:02 +08:00
Mr-Update 9ffe516f54 Update de.rs (#10827) 2025-02-19 08:42:15 +08:00
solokot ac20d2fb56 Update ru.rs (#10794) 2025-02-19 01:11:36 +08:00
bovirus 2217152216 Italian language update (#10796) 2025-02-19 01:11:22 +08:00
XLion ee288280b3 Update tw.rs (#10799) 2025-02-19 01:11:08 +08:00
John Fowler cccdb2f289 Update hu.rs (#10804)
Translate new strings.
Clarification of some translations.
2025-02-19 01:10:52 +08:00
ANB5Dev 1ddab27c0e NL lang further improvements (#10813) 2025-02-19 01:10:41 +08:00
Alex Rijckaert 451b6dc651 Update nl.rs (#10812) 2025-02-19 01:10:32 +08:00
rustdesk 86b327ee41 they always forget to remove :21114 for https, so I remove for them 2025-02-18 16:18:41 +08:00
rustdesk 6e305d4865 improve sysinfo update 2025-02-18 16:09:25 +08:00
21pages 77af6c4ce1 clear selected device group or user when search text changes (#10815)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-02-18 09:08:38 +08:00
21pages fa49c72835 fix, accessible peers filter considering device group name (#10809)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-02-17 17:36:47 +08:00
rustdesk c150143d86 device_group_name in devices.py 2025-02-17 16:28:47 +08:00
fufesou 023d46b48c refact: android, handle right click (#10806)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-16 20:45:09 +08:00
rustdesk 356adbcd8c more about allow - 2025-02-15 19:00:43 +08:00
rustdesk 33b47dd6e3 allow dash in id 2025-02-15 18:51:30 +08:00
fufesou a548e9c94d fix: android, controlled side, gesture (#10792)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-15 18:33:26 +08:00
21pages cefda0dec1 device group (#10781)
1. Rename `Group` tab to `Accessible devices`
2. Add accessible device groups at the top of search list
3. option `preset-device-group-name` and command line `--assign --device_group_name`

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-02-15 12:13:11 +08:00
rustdesk 8f545491a2 verify_login, but not eable yet 2025-02-14 16:39:09 +08:00
rustdesk 263bbfc66f missed clear 2025-02-12 17:04:56 +08:00
fufesou a039741e5a fix: win10, border (#10753)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2025-02-10 13:50:28 +08:00
fufesou 2a0e8c109b fix: macos, main window, dark theme, border (#10749)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-02-10 00:25:11 +08:00
rustdesk 9614bf266a update READEME 2025-02-08 16:03:04 +08:00
rustdesk aa63ebc7e5 Misuse Disclaimer 2025-02-05 15:28:30 +08:00
fufesou fbba8f0b34 refact: file copy&paste, cross platform (no macOS) (#10671)
* feat: unix, file copy&paste

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

* refact: unix file c&p, check peer version

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

* Update pubspec.yaml

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2025-02-04 20:33:02 +08:00
ANB5Dev a27fa43081 Update NL translation: spelling, capitalization, missing entries (#10661) 2025-02-03 22:18:20 +08:00
rustdesk 7a5941de98 remove devcontainer since not maintained yet 2025-02-02 20:31:44 +08:00
rustdesk db3ca6a373 remove useless code 2025-02-01 13:07:27 +08:00
rustdesk ce5f0d513f 2024 -> 2025 2025-01-31 16:54:57 +08:00
rustdesk 05b0f95b79 restore entrypoint.sh 2025-01-30 13:53:02 +08:00
rustdesk 8b24b195a2 remove useless files 2025-01-30 13:49:37 +08:00
rustdesk 5fc8e8c428 remove PUBLIC_RS_PUB_KE 2025-01-29 16:57:28 +08:00
RustDesk 25f917a7b4 misused by bad guys (#10614) 2025-01-28 16:16:00 +08:00
fufesou 55005f8129 fix: win, file clipboard, try empty (#10609)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-01-27 16:16:44 +08:00
fufesou f08cb0412d fix: windows, dll, pre-loading attack (#10608)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-01-26 19:39:38 +08:00
XLion fc2e27bcf0 Create dependabot.yml (#10593) 2025-01-26 14:18:26 +08:00
Theofanis Sarmidis 7aa4592669 Update and fixes el.rs (#10600) 2025-01-25 16:39:16 +08:00
Y-Ploni d656ae2956 Update he.rs (#10594) 2025-01-24 15:09:36 +08:00
RustDesk e4f00361f6 Update README.md (#10587) 2025-01-23 13:24:14 +08:00
RustDesk 1b49d49df2 Update README.md (#10586) 2025-01-23 13:23:20 +08:00
bjoernp 80f759c1ed norwegian translation (#10579)
Signed-off-by: bjoernp116 <bjoernpollen@gmail.com>
2025-01-23 13:22:25 +08:00
21pages da80f3352a fix vaapi create 2 times at first (#10576)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-22 20:27:00 +08:00
Vasyl Gello ec3ba5be8e Fix issues spotted during 1.3.7 fdroid build (#10570)
* bridge.yml: Explicitly install cargo-expand of certain version

@linsui spotted this trying to fix the build failure of 1.3.7
on f-droid:

https://gitlab.com/fdroid/fdroiddata/-/merge_requests/18766

* flutter-build.yml: drop workarounds for flutter 3.13

@fufesou has removed them from build_fdroid.sh in #10040 but
forgot to remove them in main flutter_build.yml. flutter 3.13
is not used anymore, and those who want to build the old version
using flutter 3.13 can happily check out the appropriate commit
from Git history.

* Bump vcpkg baseline to 2025.01.13

@linsui addressed the missing vcpkg-tools.json file inside vcpkg
revision (microsoft side, not rustdesk's!) by updating the vcpkg
baseline.
2025-01-22 09:26:03 +08:00
21pages d04756ad70 replace self-hosted arm64 linux with ubuntu-22.04-arm (#10555)
https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/

Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-21 17:09:24 +08:00
21pages 0eba939cd6 fix windows crash (#10562)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-01-21 16:57:07 +08:00
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
fufesou f0450db203 refact: mobile reset canvas (#9766)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-29 09:49:04 +08:00
21pages 3a75947553 add some missing web bridge (#9763)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-28 09:40:16 +08:00
fufesou c565849062 fix: Function "LockScreen" on macOS since "ignore_flags" in enigo is introduced. (#9757)
1. LockScreen after connection is established.
2. LockScreen after "Map mode" or "Translate mode" keys are sent.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-27 20:36:21 +08:00
21pages 40e8f0d307 revert missing retry and opt keep session (#9755)
* Revert "fix missing retry (#8750)"

If `hasRetry` is true: there is a retry timeout;
If `hasRetry` is false: there is no retry button;

In
https://github.com/rustdesk/rustdesk/discussions/8748#discussioncomment-10081038,when
doesn't want inactive to retry,
https://github.com/rustdesk/rustdesk/blob/cf0e3ec303990a48e0b3a6beedd3587079a6526c/flutter/lib/models/model.dart#L444,
1.2.3 always show retry no matter what `hasRetry` is.

This reverts commit c3c99ba107.

* not keep session if there is no remote connection left.

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-26 22:05:54 +08:00
bovirus 129f6c869b Update Italian language (#9752) 2024-10-26 16:06:29 +08:00
Tobias Degen 924aa515c6 fix german translation (#9742) 2024-10-25 08:21:42 +08:00
solokot c51771c854 Update ru.rs (#9741) 2024-10-25 08:08:02 +08:00
21pages c8b9031996 refactor session insert, update if already exists (#9739)
* All share the same last_receive_time
* Not second port forward

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-24 23:14:43 +08:00
rustdesk 4da584055d fix ci 2024-10-24 22:52:06 +08:00
rustdesk bd22b01370 fix "insert ctrl+alt+del" 2024-10-24 22:27:51 +08:00
Tobias Degen b35b48086a Add translation string for better translationj capability (#9736)
* Add new translation für sending Ctrl+Alt+Del

* Add new translation string for sending Ctrl+Alt+Del
2024-10-24 21:52:39 +08:00
21pages 445e9ac285 no password required for file transfer action in remote control menu (#9731)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-24 17:20:48 +08:00
fufesou 7a3e1fe648 fix: ->macos, mouse events, key flags (#9733)
* fix: win->macos, mouse events,  key flags

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-24 14:33:37 +08:00
Alex Rijckaert dfa9519d58 Update nl.rs (#9726) 2024-10-23 18:13:05 +08:00
fufesou cc6f919080 feat: mobile map mode (#9717)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-23 09:28:39 +08:00
bovirus 2cdaca0fa3 Update Italian language (#9718) 2024-10-22 16:20:35 +08:00
21pages 6159449eba move option video-save-directory and allow-auto-record-outgoing to local (#9715)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-22 08:24:52 +08:00
Andrzej Rudnik 6088920f8d Update pl.rs (#9713)
* Update pl.rs

* Update pl.rs

Updated quota chars.
2024-10-21 19:55:34 +08:00
21pages e8187588c1 auto record outgoing (#9711)
* Add option auto record outgoing session
* In the same connection, all displays and all windows share the same
  recording state.

todo:

Android check external storage permission

Known issue:

* Sciter old issue, stop the process directly without stop record, the record file can't play.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-21 14:34:06 +08:00
solokot 289076aa70 Update ru.rs (#9712) 2024-10-21 14:12:58 +08:00
fufesou 547da31095 fix: windows, exit fullscreen, remove redraw (#9700)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-19 19:41:55 +08:00
Mr-Update 1bf4ef1f46 Update de.rs (#9699) 2024-10-19 19:41:14 +08:00
21pages 1212d9fa2d web uni link (#9697)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-19 15:32:17 +08:00
fufesou 8c8a643cce fix: workaround physical display rotation (#9696)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-19 10:57:42 +08:00
Kleofass 675ffe0381 Update lv.rs (#9694) 2024-10-18 22:51:17 +08:00
bovirus 844caf8c15 Update Italian language (#9692) 2024-10-18 16:07:49 +08:00
flusheDData 0f6d28def7 Update es.rs (#9687)
New terms added
2024-10-18 08:55:39 +08:00
fufesou 0d3243e6dd fix: android, Korean input (#9667)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-18 08:55:18 +08:00
21pages 53d11e99d7 web only decode the latest image (#9689)
1. web only decode the latest image
2. web/ios remove relay server config when import

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-18 08:45:16 +08:00
21pages defb3e6c73 fix gtk-sudo in non-English linux (#9680)
change LC_ALL from C.UTF-8 to C

Signed-off-by: 21pages <pages21@163.com>
2024-10-17 20:05:13 +08:00
fufesou ae8dfe84a0 feat, web toast (#9686)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-17 17:23:09 +08:00
rustdesk 5e920f0fd0 fix ci 2024-10-16 22:37:40 +08:00
XLion 1a0814b201 Update tw.rs (#9672) 2024-10-16 19:33:27 +08:00
shleyZ ace98d98ad fix: TextFormField onChanged event triggered multiple times when Korean input (#9644)
* fix: TextFormField onChanged event triggered multiple times when Korean input

* logic fix for iOS

* add comments

* move 'onChanged' logic to handleSoftKeyboardInput
2024-10-16 19:25:27 +08:00
Mr-Update 09083b3afa Update de.rs (#9668) 2024-10-16 11:23:12 +08:00
bovirus 36e11c61a9 Update Italian language (#9650) 2024-10-14 21:26:55 +08:00
21pages 55187e9243 fix theme radio update (#9647)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-14 17:46:38 +08:00
21pages ae1c1a56e6 add missing code of last commit (#9646)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-14 15:51:51 +08:00
fufesou cdd58e77eb fix: flickers child screen when resizing window (#9645)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-14 15:48:56 +08:00
21pages ce924cc0d3 combine upload files/folder button (#9643)
* combine upload files/folder button

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

* web compress cache

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-14 15:46:21 +08:00
fufesou 498b8ba3d6 fix: wayland CapsLock (#9641)
* fix: wayland CapsLock

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

* unformat for less changes

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

* refact: input, LockModes, do not check evt.down

Remove `evt.down`(revert the change) to avoid potential bugs.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-14 11:46:28 +08:00
21pages af610b2408 web (#9640)
resolution, image quality, tab name, last remote id

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-14 11:15:52 +08:00
Yevhen Popok 6cdbcfc082 Update README-UA.md (#9639) 2024-10-14 11:02:57 +08:00
Yevhen Popok 9c7f51bc76 Update uk.rs (#9638) 2024-10-14 11:02:44 +08:00
fufesou 65683cc3e6 refact: remove redundant escape (#9634)
* refact: remove redundant escape

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

* flutter, early assert

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-13 01:07:47 +08:00
21pages eb1ef0969c web file transfer (#9587)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-12 09:03:13 +08:00
21pages 29b01e9cef remove shift & tap enable multiselect (#9625)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-11 16:47:08 +08:00
21pages cde7620eda fix web peer card tap (#9622)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-11 16:35:15 +08:00
Ibnul Mutaki 844b853074 update ID trans (#9621) 2024-10-11 10:43:22 +08:00
21pages 97f02ed25e Web password source (#9618)
* ensure window init finish

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

* web password source

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-11 09:52:09 +08:00
Ibnul Mutaki 22c84bbbd1 Update ID translation (#9609)
* update id trans

* update id trans

* update inv

* update ind trans

* update ind trans

* update ind trans
2024-10-10 10:12:24 +08:00
Jernej Simončič 227f154ee7 Add missing Slovenian translations (#9606) 2024-10-10 07:35:46 +08:00
Ibnul Mutaki 59d7bf1e86 Update indonesian translate (#9601)
* update id trans

* update id trans

* update inv

* update ind trans
2024-10-09 10:44:20 +08:00
rustdesk 38fcf4e039 typo 2024-10-09 10:19:24 +08:00
James 4b3b31147e Update eo.rs (#9600) 2024-10-09 10:01:54 +08:00
fufesou e6d4067f48 refact: remote toolbar style (#9597)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-08 21:16:07 +08:00
fufesou 507de628c9 fix: web, 2fa, trust (#9594)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-08 18:35:25 +08:00
fufesou 2591d4f044 fix: web chat (#9588)
* fix: web chat

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

* add missing svg

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-08 15:23:00 +08:00
XLion 9bcd0d1b03 Add Priority: to Debian control (#9586) 2024-10-07 22:08:17 +08:00
RustDesk 5555ba6b2f Revert "manifest updates from the Flathub's PR (#9581)" (#9585)
This reverts commit e06f456bbd.
2024-10-07 21:50:01 +08:00
XLion 28b6bc186f Add Section to Debian control (#9584) 2024-10-07 21:46:25 +08:00
fufesou 00d38260e1 fix: web send audit note (#9582)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-07 21:37:43 +08:00
Dmitry Beskov e06f456bbd manifest updates from the Flathub's PR (#9581)
Signed-off-by: dmitry <43372966+besdar@users.noreply.github.com>
2024-10-07 18:55:25 +08:00
rustdesk cc860b2906 https://github.com/flathub/flathub/pull/5670#issuecomment-2395337173 2024-10-07 15:26:41 +08:00
fufesou 839e8180e0 refact: web, no minimize btn on fullscreen (#9578)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-07 12:00:56 +08:00
fufesou 83aba804d0 fix: web fullscreen (#9577)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-07 00:01:14 +08:00
21pages 560c1effe8 remove mobile web setting, remove web/ios relay server setting (#9575)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-10-06 16:20:37 +08:00
rustdesk e7353be0cd fix https://github.com/flathub/flathub/pull/5670#issuecomment-2395067890 2024-10-06 09:25:40 +08:00
fufesou ba832362a7 fix: installed, copy&paste, special format (#9570)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-06 08:32:04 +08:00
Ibnul Mutaki 9ea09c1515 Update id (Indonesia) translation on some part (#9566)
* update id trans

* update id trans
2024-10-05 10:54:17 +08:00
fufesou 3a97b63e95 fix: windows, multi-window, move between monitors (#9562)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-04 22:05:38 +08:00
fufesou dec3cde9b3 fix: deb, build, prerm (#9552)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-03 17:56:21 +08:00
rustdesk c4d0b02478 fix ci 2024-10-03 15:38:55 +08:00
rustdesk 306dd77b81 fix https://github.com/rustdesk/rustdesk/issues/9537 2024-10-03 15:34:40 +08:00
BennyBeat fd62751cb8 Improved Catalan translation (#9546)
* Improved Catalan translation

Improved Catalan translation

* Update ca.rs

Improved Catalan translation
2024-10-03 15:02:24 +08:00
hms5232 b0edfb8f70 fix wrong terms in tw lang (#9541)
"port" is "通訊埠", "連接埠" or just "埠" in Taiwan, "端口" is used in China.
2024-10-03 15:01:53 +08:00
fufesou 334526026c fix: web/mobile, skip querying onlines, if not in main page (#9535)
* fix: web, skip querying onlines, if not in main page

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

* fix: web/mobile, skip querying onlines

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

* Set isInMainPage to false after router is changed.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-01 15:25:59 +08:00
rustdesk b5414ec002 fix https://github.com/rustdesk/rustdesk/issues/9527 2024-10-01 07:09:57 +08:00
fufesou 4eca8b9447 fix: web, forget password (#9524)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-01 07:04:43 +08:00
rustdesk 1ebc726acd fix ci 2024-09-30 10:54:05 +08:00
hashiguchi d563372a91 Change the value of kMinQuality to 5 (#9508)
* Change the minimum value of the bitrate slider to 5

Signed-off-by: hashiguchi <hashiguchi@mtrx.jp>

* Change the value of kMinQuality to 5

Signed-off-by: hashiguchi <hashiguchi@mtrx.jp>

---------

Signed-off-by: hashiguchi <hashiguchi@mtrx.jp>
2024-09-28 21:52:05 +08:00
Alex Rijckaert 4a745d82f6 Update nl.rs (#9507) 2024-09-28 20:07:55 +08:00
fufesou 2f5f701dc7 fix: windows, subwindow, scale (#9506)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-28 19:43:28 +08:00
Iraccib 60a0099ba0 Update it.rs (#9503)
Changed "Scehrmo" to "Schermo"
2024-09-28 18:04:31 +08:00
rustdesk 30a7847100 fix ci 2024-09-28 15:53:39 +08:00
RustDesk 1e822fa135 Update nl.rs (#9501) 2024-09-28 15:49:11 +08:00
Alex Rijckaert f6261883e8 Update nl.rs (#9500) 2024-09-28 15:33:08 +08:00
bovirus 3365844def Update Italian language (#9493) 2024-09-28 09:26:05 +08:00
solokot 769bbf1e1c Update ru.rs (#9495) 2024-09-28 09:25:52 +08:00
Mr-Update 81b999cfbe Update de.rs (#9496) 2024-09-28 09:25:35 +08:00
fufesou 9959217cc3 chore (#9491)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-27 23:10:51 +08:00
fufesou 3e6938bec6 refact: web desktop, web_id_input_tip (#9490)
* refact: web desktop, web_id_input_tip

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

* Update en.rs

* Update cn.rs

* Update en.rs

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-09-27 21:56:10 +08:00
fufesou 4459406578 fix: windows, window, restore from minimized state (#9482)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-27 14:19:25 +08:00
hashiguchi beb1084e87 Change the minimum value of the bitrate slider to 5 (#9480)
Signed-off-by: hashiguchi <hashiguchi@mtrx.jp>
2024-09-27 10:14:46 +08:00
rustdesk d4184fd865 bump to 1.3.2 2024-09-26 23:07:53 +08:00
21pages ffc73f86a0 fix ab peers view, all peer tab use global peers model (#9475)
Use ChangeNotifierProvider<Peers>.value, and each peer tab has a global unique `Peers` model, then `load peers` and `build
peers` will always be the same one.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-26 22:08:32 +08:00
RustDesk c74bdcdfdb Revert "fix peers view ChangeNotifierProvider update (#9459)" (#9471)
This reverts commit 7c55e3266b.
2024-09-26 14:20:37 +08:00
Kássio Oliveira 6d8b5b289f Refactor ScanPage for better performance and memory management (#9464)
- Added null checks in `reassemble` method to avoid potential null pointer exceptions when pausing/resuming the camera.
- Refactored image picking and QR code decoding process to use async/await, avoiding UI blocking with synchronous file reads.
- Improved exception handling by making it more specific to QR code reading errors.
- Introduced `StreamSubscription` for the QR scan listener and ensured proper cancellation in `dispose` method to prevent memory leaks.
- Separated button building logic (`_buildImagePickerButton`, `_buildFlashToggleButton`, `_buildCameraSwitchButton`) to enhance code readability and maintainability.
2024-09-26 10:34:12 +08:00
flusheDData 1d6873f622 New translation terms (#9463)
* Update es.rs

New terms added

* Update es.rs

New terms added
2024-09-26 09:29:49 +08:00
21pages 7c55e3266b fix peers view ChangeNotifierProvider update (#9459)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-25 15:11:11 +08:00
Lumiphare ce5151032e Update README-ZH.md (#9457)
* Update CONTRIBUTING.md links to point to the Chinese version

* translated with AI assistance and manual refinement

* Adapted from the official Chinese translation of the Contributor Covenant

* Improve README-ZH.md

---------

Co-authored-by: sea <api@sea.com>
Co-authored-by: Lumiphare <lumiphare@foxmail.com>
2024-09-25 08:48:45 +08:00
bovirus ba88bc9e8b Update Italian language (#9452) 2024-09-24 20:33:28 +08:00
fufesou e0095aebda refact: web elevation (#9445)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-24 13:53:17 +08:00
rustdesk 664a3e186e clean SOFTWARE_UPDATE_URL 2024-09-24 12:00:37 +08:00
21pages e4f7e126e5 fix check update (#9444)
check_software_update runs in a new thread, won't return directly

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-24 11:37:30 +08:00
Kleofass 49989e34e4 Update lv.rs (#9437) 2024-09-24 09:51:58 +08:00
fufesou 75a14fea23 fix: keyboard, change mode, await, on input source changed (#9434)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-23 17:31:37 +08:00
fufesou f535406962 refact: web, keyboard, translate mode (#9432)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-23 16:58:21 +08:00
Vasyl Gello f3f3bb538f Fix F-Droid build and bump Android NDK (#9428)
* Fix F-Droid build

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

* Bump Android NDK to r27b

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-09-23 16:43:59 +08:00
21pages 8fefd34c15 update web (#9427)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-23 09:18:22 +08:00
XLion d98f947824 Update translation (#9426)
add translation for tw.rs and fix typo on cn.rs
2024-09-23 09:17:13 +08:00
solokot 5f52ce2c1b Update ru.rs (#9421) 2024-09-22 19:06:45 +08:00
Alex Rijckaert 1d799483d7 Update nl.rs (#9422) 2024-09-22 19:06:33 +08:00
rustdesk 3db55a718c build 48 2024-09-21 11:22:33 +08:00
Dmytro a516f01feb Update uk.rs (#9416) 2024-09-20 21:38:16 +08:00
rustdesk 2e314bf032 disable init clipboard sync by default 2024-09-20 17:38:29 +08:00
jkh0kr b93d4ce3fc Update ko.rs (#9411)
Add Korean language
2024-09-20 15:16:20 +08:00
fufesou 21bcfd173d fix: wayland, rdp input, mouse, scale (#9402)
* fix: wayland, rdp input, mouse, scale

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

* fix: rdp input, mouse, scale, check 0

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-20 11:15:19 +08:00
21pages 3d5262c36f opt gtk sudo ui, fix edit button show (#9399)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-20 11:06:56 +08:00
Mr-Update cfd801c5d6 Update de.rs (#9401) 2024-09-20 10:54:50 +08:00
bovirus 216a72592d Update Italian language (#9403) 2024-09-20 10:54:37 +08:00
fufesou ddd3401bd7 fix: keyboard, translate mode (#9406)
hotkey, linux -> win

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-20 10:52:19 +08:00
solokot 47139edd81 Update Russian translation (#9391)
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-09-19 18:48:54 +08:00
fufesou c6e3f60a6b refact: flutter, ChangeNotifier, reduce rebuild (#9392)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-19 18:48:01 +08:00
21pages 88a99211f3 replace pkexec with gtk sudo (#9383)
* Fix https://github.com/rustdesk/rustdesk/issues/9286, replace pkexec
  with gtk sudo. Tested on gnome (ubuntu 22.04, debian 13), xfce (manjaro, suse), kde (kubuntu 23), lxqt (lubuntu 22), Cinnamon (mint 21.3), Mate (mint 21.2)
* Fix incorrect config of the main window opened by the tray, replace
  xdg-open with run_me, replace with dbus + run_me
* Fix `check_if_stop_service`, it causes the problem fixed in
  https://github.com/rustdesk/rustdesk/pull/8414, now revert that fix and fix itself.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-19 18:47:37 +08:00
fufesou d08c335fdf fix: file transfer, show error, msgbox (#9389)
* fix: file transfer, show error, msgbox

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

* fix: translation

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-18 19:29:35 +08:00
fufesou e5ec6957fe fix: option OPTION_ONE_WAY_FILE_TRANSFER (#9387)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-18 18:22:12 +08:00
rustdesk e20f5dd001 fix ci 2024-09-18 13:00:15 +08:00
rustdesk e1a6ccc100 fix ci 2024-09-18 12:37:26 +08:00
rustdesk cc288272d3 OPTION_ONE_WAY_CLIPBOARD_REDIRECTION,
OPTION_ENABLE_CLIPBOARD_INIT_SYNC,
        OPTION_ALLOW_LOGON_SCREEN_PASSWORD,
        OPTION_ONE_WAY_FILE_TRANSFER,
2024-09-18 12:18:26 +08:00
Lumiphare 49ce4edb8a Chinese versions of CONTRIBUTING.md and CODE_OF_CONDUCT-ZH.md (#9386)
* Update CONTRIBUTING.md links to point to the Chinese version

* translated with AI assistance and manual refinement

* Adapted from the official Chinese translation of the Contributor Covenant

---------

Co-authored-by: sea <api@sea.com>
2024-09-18 10:39:26 +08:00
お餅のCreeeper 29c3b29bda Fix ja.rs typo (#9378) 2024-09-17 13:26:49 +08:00
お餅のCreeeper 8a8f708c3e update ja.rs (#9376) 2024-09-17 12:32:32 +08:00
fufesou c5038b1a78 Fix/virtual display do not plug out if not plugged in (#9372)
* fix: win VD, do not plug out if not plugged in

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

* Forcibly virtual display on clicking button "-"

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-16 15:57:40 +08:00
21pages f4c038ea93 update appindicator and recommends install it (#9364)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-15 14:33:59 +08:00
21pages d9ea717056 fix last commit (#9355)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-14 10:03:50 +08:00
21pages 40af9dc78b not run window focus service on wayland (#9354)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-14 09:59:14 +08:00
Alex Rijckaert 81fc22a156 Update nl.rs (#9344)
* Update nl.rs

* Update nl.rs

file updated after adjusting (as test) @FastAct to RijckAlex (same person, new account)
2024-09-13 21:04:04 +08:00
rustdesk 2e7bd26e4c fix leak fix 2024-09-13 15:42:51 +08:00
rustdesk 179b562472 another leak 2024-09-13 15:41:42 +08:00
21pages ab246fdcbf password lowercase check like uppercase (#9343)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-13 09:29:00 +08:00
rustdesk d65d3b7326 fix ci 2024-09-13 09:21:50 +08:00
rustdesk 9f9a22ec63 uppercase for all 2024-09-13 08:46:21 +08:00
m-hume a8f1a66043 Trim whitespace from Import server config (#9341) 2024-09-13 08:06:40 +08:00
21pages 0b3e7bf33e update hwcodec, fix linux ci (#9335)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-12 17:34:54 +08:00
fufesou c358399eca refact: reduce try_get_displays() on login (#9333)
* refact: reduce try_get_displays() on login

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

* Function rename

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-12 14:44:40 +08:00
rustdesk cacca7295c fix memory leak on mac because of wrong use of objc, by wrapping autoreleasepool 2024-09-12 14:26:29 +08:00
fufesou d2e98cc620 fix: reduce rebuild for online state (#9331)
* fix: reduce rebuild for online state

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

* Query online, update  on focus changed

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

* Use  to ensure  is right

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

* refact, window events on peer view

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-09-12 11:26:01 +08:00
fufesou 2e81bcb447 fix: Query online, remove loop retry (#9326)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-11 17:17:32 +08:00
fufesou cbca0eb340 fix: keyboard, move tab to new window (#9322)
Do not disable keyboard when moving tab to new window on dispose.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-11 10:01:03 +08:00
fufesou 9380f33d7c Refact/options (#9318)
* refact options

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

* Remove unused msg

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

* web, toggle virtual display

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-10 23:54:59 +08:00
Gheorghi 519539ed0a Update bg.rs with more translations (#9317) 2024-09-10 23:22:14 +08:00
rustdesk 1f2a75fbd8 revert back pub lock because it does not fix leak 2024-09-10 21:28:07 +08:00
fufesou 51055a7e5b fix: tokio, call future in context of runtime (#9310)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-10 17:39:22 +08:00
fufesou 13effe7f14 fix: web, switch display (#9307)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-10 11:29:20 +08:00
21pages 943f96ef8c downgrade url_launcher/uni_links for linux ci (#9306)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-10 10:57:01 +08:00
rustdesk 260a82ee5c upgrade pub for flutter memory leak 2024-09-09 17:25:35 +08:00
fufesou a2792d1527 comments (#9297)
* comments

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-08 23:07:42 +08:00
fufesou 2922ebe22a Fix/clipboard retry if is occupied (#9293)
* fix: clipboard, retry if is occupied

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

* fix: clipboard service, hold runtime to cm ipc

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

* update arboard

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

* refact: log

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

* fix: get formats, return only not

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-08 21:13:05 +08:00
rustdesk 1e6944b380 apply --cm-no-ui to non-windows 2024-09-08 12:54:27 +08:00
21pages 993862c103 quit cm process if ipc connection to ipc server closed (#9292)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-08 12:37:41 +08:00
Andrzej Rudnik c8cd564e69 Update pl.rs (#9285) 2024-09-07 17:58:07 +08:00
21pages a4cd64f0d5 fix qsv memory leak by updating ffmpeg (#9266)
* fix qsv memory leak by updating ffmpeg

* Memory leaks occur when destroying FFmpeg QSV VRAM encoders. This issue is resolved with FFmpeg version 7.
* FFmpeg requires ffnvcodec version 12.1.14.0 or higher, and an NVIDIA driver version greater than 530. For more details, https://github.com/FFmpeg/nv-codec-headers/tree/n12.1.14.0.
* The code of NVIDIA VRAM encoder is not changed, still use Video Codec SDK version 11, which is unaffected by FFmpeg. Drivers newer than 470 can support this, but we may consider an update later, as the support check by sdk code may not be accurate for FFmpeg RAM encoders.
* The issue is related to FFmpeg, not libmfx. FFmpeg version 7 recommends using libvpl, but vcpkg currently lacks ports for libvpl. We can add these in the future.
* D3D11 Texture Rendering: The "Shared GPU Memory" in the task manager continue increasing when using D3D11 texture render, which can exceed the GPU memory limit (e.g., reaching up to 100GB). I don't know what it is and will try to find it out.
* Roughly tests on Windows, Linux, macOS, and Android for quick fix. Further testing will be performed, and I will share the results in this pr.

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

* update flutter_gpu_texture_render, fix shared gpu memory leak while
rendering

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-07 10:20:52 +08:00
21pages f0ca4b9fee --no-server parameter to avoid server ipc occupied by ui (#9272)
Signed-off-by: 21pages <pages21@163.com>
2024-09-06 14:43:38 +08:00
Xp96 aa3402b44a Update ptbr.rs (#9271) 2024-09-06 01:15:07 +08:00
fufesou 26ebd0deb9 fix: clipboard, cmd ipc (#9270)
1. Send raw contents if `content_len` > 1024*3.
2. Send raw contents if it is not empty.
3. Try read clipboard again if no data from cm.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-05 23:39:07 +08:00
21pages 4150036589 remove first frame fallback if repeat (#9267)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-05 22:48:20 +08:00
fufesou 7a1157f1b0 refact: quality status event (#9268)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-05 22:37:14 +08:00
21pages 3bd34bf0b9 increase interval for restart linux ui, try fix loop start (#9264)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-05 18:34:48 +08:00
fufesou 5f29016861 fix: build web (#9259)
1. Web, build.
2. Web and mobile, `onSubmitted` for ID text field.
3. Web, remove unused key 'toggle_option'.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-04 22:27:52 +08:00
fufesou e40243b55d Fix/wf cliprdr c bugs (#9253)
* fix: ResetEvent() after WaitForSingleObject()

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

* fix: check and free mem

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-04 17:04:48 +08:00
fufesou dbbbd08934 fix: clipboard, support excel xml spreadsheet (#9252)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-04 16:44:36 +08:00
21pages 29e12b84a9 password max length prompt (#9248)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-04 11:31:13 +08:00
fufesou 04c0f66ca9 fix: set to OK if recv flag is TRUE (#9244)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-03 21:15:35 +08:00
fufesou ec28567362 fix: win, file clipboard (#9243)
1. Return the result of `wait_response_event()` in
   `cliprdr_send_format_list()`
2. Add recv flags to avoid waiting a long time.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-03 20:55:45 +08:00
fufesou d4377a13c5 refact: peer card, orientation (#9235)
* refact: peer card, orientation

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

* Do not change landscape/portrait on Desktop

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-03 19:06:11 +08:00
21pages 39e713838f Use fallback codec if first frame fails (#9242)
* Both encoding and decoding use fallback if first frame fails
* More aggresive max fail counter
* Update hwcodec, add judgement when length of the encoded data is zero, https://github.com/rustdesk/rustdesk-server-pro/discussions/382#discussioncomment-10525997
* Fix serde hwcodec config toml fails when the non-first vec![] is empty, https://github.com/toml-rs/toml-rs/issues/384, the config file is used for cache, when check process is not finished, the cache is used.
* Allow cm not start for pro user

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-09-03 18:48:17 +08:00
SimonHanel 75a4671bda Update da.rs (#9238)
* Update da.rs

Filled out every empty string and adjusted some for better translation.

Some translations might be janky due to my lack of context for what the string is used for, but it's good enough for now.

* Update da.rs

Minor tweaks
2024-09-03 10:09:25 +08:00
fufesou 827efabbc0 refact: remove fingerprint for web (#9226)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-02 02:00:59 +08:00
fufesou 532fe6aefb refact: web ui, login (#9225)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-01 23:14:57 +08:00
fufesou ae339f039d refact: web ui (#9217)
* refact: web ui

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

* refact: remove AppBar shadow

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-09-01 00:30:07 +08:00
fufesou bf390611ab fix: keyboard, sciter (#9216)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-31 19:02:50 +08:00
fufesou e3f6829d02 refact: android ios, lan discovery (#9207)
* refact: android ios, lan discovery

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

* fix: build and runtime error

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-30 00:37:38 +08:00
fufesou 832002a10f refact: web, remote toolbar, pin (#9206)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-29 23:38:08 +08:00
RustDesk d335cdbb0c Update README.md (#9196) 2024-08-28 12:25:00 +08:00
RustDesk 6a5d5875c8 Logo broken (#9195) 2024-08-28 12:23:40 +08:00
fufesou cf06d1028f fix: web, reset cursor on disconn, back to main page (#9192)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-27 23:58:04 +08:00
9amm fd178a7b6c Fix minor typo (#9191)
Co-authored-by: 9amm <>
2024-08-27 23:38:40 +08:00
rustdesk f3a2733d75 start mac service more later 2024-08-27 20:25:01 +08:00
fufesou 55de573a01 fix: keyboard input, mulit windows (#9189)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-27 19:30:51 +08:00
fufesou 40239a1c41 feat: macos, mouse button, back&forward (#9185)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-27 15:20:29 +08:00
fufesou c68ce7dd84 fix: web v2, keyboard mode (#9180)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-27 00:00:33 +08:00
rustdesk 690a2c8399 still find delegate failure when my mac restarted automatically sometimes 2024-08-26 17:07:02 +08:00
fufesou 4b4fd94f3e feat: web v2 keyboard (#9175)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-26 12:13:11 +08:00
21pages 5abe42f66c not run get window focus if no multiple displays (#9174)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-26 10:37:35 +08:00
21pages 48aec6484c refresh file transfer table on resume (#9167)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-25 21:29:41 +08:00
21pages a946d4d0c9 file transfer status text overflow at start (#9166)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-25 20:46:21 +08:00
ELForcer 24f4b94082 Update ru.rs (#9163) 2024-08-25 15:12:08 +08:00
fufesou aa1e122532 fix: revert key events to raw key events on Linux (#9161)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-25 00:03:31 +08:00
rustdesk d400999b9c bump to 1.3.1 2024-08-24 19:02:04 +08:00
fufesou 1d416f6626 refact: flutter keyboard, map mode (#9160)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-24 12:10:36 +08:00
Kleofass 9d9741f18e Update lv.rs (#9155) 2024-08-23 21:27:52 +08:00
21pages 50aa8e12ad desktop file transfer, all columns respond to tap, add right click item border (#9153)
When right click selected item, the border is not obvious but can feel
some change.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-23 10:00:36 +08:00
jxdv 5931af460e Update trs (#9144)
* update sk tr

* update cz tr
2024-08-22 09:36:03 +08:00
fufesou fc607d6789 fix: privacy mode 2, restore (#9141)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-22 09:35:50 +08:00
rustdesk 529e70910d build 47 2024-08-21 18:29:43 +08:00
fufesou f300d797e2 Fix/ios query onlines (#9134)
* fix: ios query onlines

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-21 18:21:25 +08:00
Daniel Ehrhardt e3cce2824d Added Public Server to Readme (#9132) 2024-08-21 09:28:02 +08:00
21pages f34b8411a7 Fix new cm tab not replace the old persisted tab (#9127)
* This happens when after changing DesktopTab to StatefulWidget, 1.2.7
  and 1.3.0 have this problem.
* When `addConnection` in server_model.dart is called, the old closed
  client is removed, the client parameter of buildConnectionCard is new,
but client id inside Consumer is old.
* The only state in cm page is timer, its value is kept in test.
* There may be a better way to solve the ui update.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-20 15:34:10 +08:00
21pages 8745fcbb6a opt desktop file manager status list (#9117)
* Show delete file/dir log
* Show full path rather than base file name
* Show files count
* Opt status card layout
* Change selected color to accent

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-20 10:53:55 +08:00
flusheDData 2a0fd55af7 Update es.rs (#9114)
New terms added
2024-08-19 14:41:28 +08:00
RustDesk da70cbcdda Update es.rs 2024-08-19 14:19:24 +08:00
flusheDData 921b64e1e0 Last tips (#9113)
* Update es.rs

New translations

* Update es.rs

Translation correction of 'Version'

* Update es.rs

Last tips

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-08-19 14:14:47 +08:00
XLion c0de0aa108 Update tw.rs (#9112) 2024-08-19 14:13:31 +08:00
fufesou 715d475f49 fix: privacy mode 2, more error info (#9111)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-19 11:35:35 +08:00
Mr-Update e3f09b3ec6 Update de.rs (#9107) 2024-08-19 03:12:56 +08:00
rustdesk 0a5fafb84f comment 2024-08-18 10:04:20 +08:00
rustdesk 4e084c5ee0 revert 2024-08-18 09:56:24 +08:00
rustdesk d1fe617670 fixing https://github.com/rustdesk/rustdesk/issues/9103, maybe 2024-08-18 09:30:53 +08:00
bovirus 7744bdbbe0 Update Italian language (#9093) 2024-08-16 18:39:36 +08:00
FastAct e1329c8157 Update nl.rs (#9092) 2024-08-16 15:19:18 +08:00
fufesou f31e60af5b fix: crash, drop tokio RunTime in async context (#9091)
* fix: crash, drop tokio RunTime in async context

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

* chore

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-16 14:55:42 +08:00
21pages ed18e3c786 file rename (#9089)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-16 12:55:58 +08:00
rustdesk 579e0fac36 fix https://github.com/rustdesk/rustdesk/issues/9088 2024-08-16 12:20:40 +08:00
fufesou 92752765ba fix: resize top edge (#9081)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-15 23:58:19 +08:00
kibeb 071f51cf6f Update cs.rs: more corrections, new phrases translated (#9075) 2024-08-15 17:34:39 +08:00
kibeb dde3cce120 Update cs.rs: typo corrected (#9074) 2024-08-15 14:55:44 +08:00
21pages bb1b9858d5 only main window show tabbar border, change cm tabbar color (#9073)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-15 13:25:59 +08:00
fufesou a31c27be73 fix: windows, remote window, resizing, #9061 (#9062)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-15 13:24:42 +08:00
21pages 85ae3916cb fix get_custom_server_from_string, relay= is not used (#9069)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-15 09:57:12 +08:00
jkh0kr cc9b7e64eb Update ko.rs (#9067)
Additional Korean translations
2024-08-15 09:25:03 +08:00
flusheDData 5e22a49e49 Translation correction (#9064)
* Update es.rs

New translations

* Update es.rs

Translation correction of 'Version'
2024-08-15 09:24:42 +08:00
21pages 07cf1b4db5 fix wallpaper get sid (#9058)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-14 18:09:47 +08:00
RustDesk f6ab5cdcb2 Update config.toml (#9057) 2024-08-14 17:32:05 +08:00
Mr-Update b477aded0b Update de.rs (#9054) 2024-08-14 10:39:05 +08:00
rustdesk 65318efd67 update README 2024-08-13 21:42:04 +08:00
bovirus dbd195a46e Update Italian language (#9047) 2024-08-13 14:22:08 +08:00
flusheDData 0651ad492f Update es.rs (#9045)
New translations
2024-08-13 14:11:36 +08:00
fufesou a3c5adb1f4 refact: remove dup translation (#9043)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-13 11:27:39 +08:00
fufesou a771abcdc2 fix: win, multi monitors, maximize, may fix 9033 (#9038)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-13 00:18:35 +08:00
21pages b8b3a089f3 android telebot setting (#9035)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-12 22:20:35 +08:00
fufesou 8f00067266 fix: build (#9036)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-12 22:15:59 +08:00
fufesou 83bf067d18 fix: not plug virtual dislay, non win, installed (#9034)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-12 21:53:41 +08:00
21pages 1729ee337f trust this device to skip 2fa (#9012)
* trust this device to skip 2fa

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

* Update connection.rs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-08-12 18:08:33 +08:00
Xp96 57834840b8 Update ptbr.rs (#9031) 2024-08-12 17:22:00 +08:00
fufesou 99d7b62d79 fix: privacy mode 2, resolution changed (#9027)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-11 23:28:02 +08:00
fufesou 6625aca994 fix: win, virtual display (#9023)
1. Default resolution 1920x1080.
2. Restore on conn & disconn.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-11 19:26:41 +08:00
FastAct ce56be6507 Update nl.rs (#9022) 2024-08-11 06:49:52 +08:00
mehdi-song fd69b14623 Update fa.rs (#9021) 2024-08-11 06:49:34 +08:00
bovirus 7521bbe15f Update Italian language (#9016) 2024-08-10 20:52:27 +08:00
fufesou 3c6ddd7403 fix: multi-displays, displays changed, switch idx (#9006)
Use init display index as the primary index.

But when displays changed, the primary display may also changes.

No need to change the old primary index.
But we need to make sure that the old primary index does not exceed the display number.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-09 21:17:06 +08:00
Mr-Update 6820e2f4c7 Update de.rs (#9004) 2024-08-09 08:43:18 +08:00
Ivan Beà 77f3ebaf1a Update ca.rs (#8997)
* Update ca.rs

update catalan translation

* Update ca.rs

minor changes
2024-08-08 23:18:01 +08:00
fufesou e7e244d4f2 fix: update pub desktop_multi_window (#9002)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-08 23:06:33 +08:00
fufesou f4c40d733e Fix/exe upgrade options (#9001)
* fix: exe upgrade, use previous options

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

* refact: msi, shortcuts options, swap pos

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-08 22:07:06 +08:00
fufesou 049c334db3 fix: privacy mode, windows vd, init resolution (#8992)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-08 15:31:11 +08:00
rustdesk 6197832317 update hwcodec 2024-08-08 11:58:12 +08:00
Vasyl Gello 2fd53f9825 Pass JVM to ffmpeg (#8985)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-08 11:53:49 +08:00
Yevhen Popok ae16b8975b Update Ukrainian translation (#8980)
Sync with #8977
2024-08-08 10:24:40 +08:00
solokot 171177c76f Update ru.rs (#8987) 2024-08-08 10:24:28 +08:00
bovirus ade1d8c0c7 Update Italian language (#8988) 2024-08-08 10:24:15 +08:00
rustdesk 9a194f0850 res/users.py 2024-08-07 23:26:17 +08:00
21pages 76d5a8b205 unlock with PIN (#8977)
* add custom password to unlock settings

* If not set, use admin password; if set, use custom settings password.
* At least 4 characters.
* Set with gui or command line.

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

* Update cn.rs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-08-07 16:21:38 +08:00
jkh0kr bc6ce6c7ee Update ko.rs (#8974)
Additional Korean translation
2024-08-07 10:12:43 +08:00
rustdesk 025cdfa25b fix ci 2024-08-07 01:19:29 +08:00
rustdesk 2f432e941d hide-tray option 2024-08-07 01:08:36 +08:00
21pages 96edca8f74 update sysinfo rlim_max, which causing debian 13 pkexec not work (#8968)
* update sysinfo rlim_max, which causing debian 13 pkexec not work

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

* Update Cargo.toml

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-08-06 18:19:35 +08:00
fufesou 51b250435d refact: audio input, combobox instead of radio (#8965)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-06 17:07:05 +08:00
21pages 5a2121501d fix build (#8964)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-06 12:36:39 +08:00
rustdesk 877b3e2ce5 fix ci 2024-08-06 12:19:06 +08:00
rustdesk 421ddc0016 fix https://github.com/rustdesk/rustdesk/issues/4863 2024-08-06 12:11:03 +08:00
fufesou 2662abc5a3 fix: reset voice call state, on conn (#8961)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-06 00:28:31 +08:00
fufesou b3e1c8a907 Refact/msi more install options (#8949)
* refact: msi, more install options

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

* refact: msi, reg values on upgrade/modify

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

* fix: msi, silent repair/upgrade, RemoveInstallFolder()

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

* Options support both 1/0 and Y/N

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

* refact: msi, preprocess, open file with explicit encoding

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

* fix: msi, read previous options

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

* fix: mis, install folder, read previous option

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

* Comment on Control -> Checkbox

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

* fix: UI, checkbox options, read previous values

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

* fix: shortcuts options, init state

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

* fix: shortcuts, init state

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

* Better shortcuts property conditions

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-05 17:49:48 +08:00
rustdesk 2266fde26f fix address_book_name cli 2024-08-04 19:31:13 +08:00
rustdesk e58e75eea9 add address_book_name, address_book_tag to cli, https://github.com/rustdesk/rustdesk/discussions/7866, need to use with server Pro 1.4.2 (not ready) 2024-08-04 18:45:49 +08:00
rustdesk eafebdba21 run universal for upload only 2024-08-04 16:32:05 +08:00
rustdesk 7bf5e69444 revert 2024-08-04 16:23:58 +08:00
rustdesk cb0dc46d08 turn it off since it does not work, please test it before submit PR 2024-08-04 16:22:42 +08:00
rustdesk 9b8209b61b log::trace audio buffer cleared 2024-08-04 16:17:59 +08:00
21pages b6ba9978e3 set max audio buffer to 150ms, clear audio buffer if full (#8947)
The device should have the capability to play a sufficient audio buffer during each period to meet the audio config, so the playback speed is not slow.

The audio delay is caused by network jitter. The controlled  side sends audio data every 10ms, but it often happens that multiple packets are sent together. During underrun periods, the controlling side plays extra silence data instead, resulting in the device playing more audio than the configured amount.
2024-08-04 16:11:00 +08:00
Vasyl Gello 0d1d7a9b87 Guard parameters for universal apk (#8943)
* Guard parameters for universal apk

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

* Free space before android builds

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-04 12:34:08 +08:00
fufesou 508dd5b383 fix: custom client, msi, conn type (#8944)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-03 22:29:30 +08:00
Vasyl Gello 31a1b7a80b Fix copy step for universal apk (#8942)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-03 19:00:27 +08:00
Vasyl Gello ba43424781 Build universal apk (#8941)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-03 18:39:12 +08:00
rustdesk f899b2a962 fix https://github.com/rustdesk/rustdesk/issues/8856 2024-08-03 18:05:32 +08:00
Yevhen Popok 2dd3d8c11e Use correct locale code for Ukrainian translation file name (#8932)
Rename Ukrainian translation file ua.rs to uk.rs to avoid confusion.
This complements commits [2333ee2](https://github.com/rustdesk/rustdesk/commit/2333ee2c07c6a67ed6c58ccfbc50a3bee203f90d) and [b6035fb](https://github.com/21pages/rustdesk/commit/b6035fbbdfffb9d6a083e8754e786a8abaa3feaf)
2024-08-02 22:58:45 +08:00
fufesou 6eea425280 fix: clipboard cm ipc data, raw bytes (#8930)
* fix: clipboard cm ipc data, raw bytes

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

* Remove useless check

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-02 21:36:49 +08:00
Vasyl Gello 5e7d4fd2d6 vcpkg deps (#8926)
* vcpkg: bump opus to 1.5.2

Should fix flakes caused by https://github.com/android/ndk/issues/2032

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

* vcpkg: actually use cached artifacts

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

* Print all vcpkg log files on errors

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-02 18:43:30 +08:00
rustdesk d9fba50606 fix https://github.com/rustdesk/rustdesk-server-pro/issues/334 again 2024-08-02 15:43:55 +08:00
21pages b6035fbbdf update supportedLocales (#8925)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-02 15:12:48 +08:00
Yevhen Popok e67b694f06 Update Ukrainian translation (#8921) 2024-08-02 09:31:37 +08:00
rustdesk 2333ee2c07 fix https://github.com/rustdesk/rustdesk/issues/8923 2024-08-02 09:29:51 +08:00
Vasyl Gello 61ccc2152e Bump Android NDK to r27 (#8918)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-01 21:33:59 +08:00
21pages f6aca4ca8e fix click cm audio permission button before auth (#8917)
1. When not authenticated, clicking the audio permission button on the cm will send audio data
2. Keep the cursor position code unchanged, because `show_remote_cursor` is false before auth, so subscription will not happen.
3. Keep the clipboard code unchanged, because the keyboard permission will also be determined in `try_sub_services`. If the clipboard permission is clicked before auth and the keyboard permission is clicked after auth, the clipboard service will not be subscribed.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-08-01 18:17:02 +08:00
Vasyl Gello 1707987a7b Fix typo in android-x86 lib copy step (#8914)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-08-01 15:09:33 +08:00
fufesou 85604dee79 refact: msi, custom client props (#8913)
* refact: msi, custom client props

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

* format

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-01 15:08:52 +08:00
Hamir Mahal a12969be30 fix: usage of actions/checkout@v3 (#8912)
* chore: changes from formatting on save

* fix: usage of `actions/checkout@v3`
2024-08-01 09:12:11 +08:00
rustdesk 9f91eada89 fix https://github.com/rustdesk/rustdesk-server-pro/issues/338 2024-08-01 09:09:45 +08:00
Vasyl Gello cb6a6aa42a flutter-build: Parameterize Android build matrix (#8907)
As @rustdesk noted debug builds are no-go in official RD repo
but this change makes it possible to filter jobs only relevant to
Android from flutter-build.yml to build only Android in a separate
mirror of Rustdesk.

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-31 20:38:00 +08:00
21pages 4fec8abad4 update repo to rustdesk-org (#8905)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-31 17:25:10 +08:00
solokot 35571dc8d7 Update ru.rs (#8901) 2024-07-31 14:16:55 +08:00
FastAct a103b83647 Update nl.rs (#8892) 2024-07-30 20:23:49 +08:00
21pages e1e4bf599b add libatomic1 to linux armv7 sciter depends (#8890)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-30 14:52:06 +08:00
rustdesk cba8aaa410 tooltip for https://github.com/rustdesk/rustdesk/issues/8600, and change
dialog error to richtext with link support
2024-07-30 14:42:21 +08:00
fufesou 8ced4ddaa2 simple refact (#8888)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-30 13:06:37 +08:00
fufesou 15404ecab4 fix: clipboard, windows, controlled side, formats (#8885)
* fix: clipboard, windows, controlled side, formats

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

* Clipboard, reuse ipc conn and send_raw()

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

* Clipboard, merge content buffer

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

* refact: clipboard service, ipc stream

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-30 11:35:39 +08:00
Generalworks Inc. 97772f9ac5 Fix typo on ja.rs (#8886) 2024-07-30 00:27:28 +08:00
rustdesk 764fbe2c9d addressing https://github.com/rustdesk/rustdesk/issues/8883 2024-07-30 00:27:01 +08:00
fufesou e03344d85b fix: clipboard, read as much as possible (#8881)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-29 21:19:29 +08:00
fufesou 0e98a51775 fix: clipboard, set formats and enable option (#8873)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-29 15:40:02 +08:00
XLion 0a1d3c4afb Update tw.rs and cn.rs (#8877)
* Update tw.rs

* Update cn.rs
2024-07-29 15:34:26 +08:00
rustdesk fd9b5f3c57 fix https://github.com/rustdesk/rustdesk-server-pro/issues/334 2024-07-28 22:55:14 +08:00
Mr-Update 73f6afd4c0 Update de.rs (#8870) 2024-07-28 21:09:36 +08:00
fufesou 50dd2b3aad chore (#8868)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-28 19:33:00 +08:00
fufesou c0e9445602 refact: embed crate mouce, uinput (#8836)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-28 17:27:09 +08:00
fufesou 541d9c6b86 feat: clipboard, multi formats (#8733)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-28 17:26:54 +08:00
fufesou 8c91e5c5ca refact: update crate tfc (#8867)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-28 17:26:18 +08:00
jxdv 19d1605d8c Update trs (#8866)
* update sk-tr

* update cs-tr
2024-07-28 17:13:15 +08:00
bovirus 0faf82f109 Update Italian language (#8863) 2024-07-28 17:13:01 +08:00
21pages ee5314de20 use selectableText for some errors (#8862)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-28 11:13:19 +08:00
21pages 7e8d3bd2ac remove calling refreshCurrentUser when connect status become ready (#8849)
When refreshCurrentUser throw error, show check network in ab and group
tab.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-28 10:15:09 +08:00
Vasyl Gello 9750e1409c Use VCPKG_FORCE_SYSTEM_BINARIES in actual RD sciter build step (#8859)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-28 08:44:16 +08:00
Vasyl Gello 67d4e061fb Try to detach /opt/artifacts from sciter (#8857)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-27 23:57:53 +08:00
Vasyl Gello f67f2be0cb Add built CMake to PATH in actual RD build step (#8855)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-27 21:28:34 +08:00
Vasyl Gello aa42bf548e Limit cmake buildstep to 1 threads to check OOM (#8852)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-27 16:29:53 +08:00
rustdesk d679e8fa7d WIN_RUST_VERSION -> SCITER_RUST_VERSION 2024-07-27 16:16:31 +08:00
Vasyl Gello 1357ef5d6f Try to fix sciter-armhf build (#8850)
* Bootstrap CMake 3.29.7 for ubuntu/bionic/armhf
  * vcpkg exevutable now needs GCC 8 but all product binaries
    are still OK with GCC 7
  * Remove res/vcpkg/linux.cmake

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-27 15:32:26 +08:00
21pages 30a5d1e0e1 avoid call refreshCurrentUser twice at startup (#8848)
refreshCurrentUser will be called at these 2 position:

1. runMainApp or runMobileApp in main.dart
2. when connect status is ready

Both of these two happens at startup, when connect status is ready and startup time < 5 seconds, not call refreshCurrentUser

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-27 09:50:06 +08:00
FastAct 9f0985c842 Update nl.rs (#8843) 2024-07-26 17:17:05 +08:00
21pages be06c0d738 tab border for desktop subwindow (#8842)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-26 11:20:16 +08:00
zuiyu f0f50f0f03 fix: remove getRadio todo and delete contentPadding param (#8839) 2024-07-26 10:31:01 +08:00
bovirus 1850d32f49 Update Italian language (#8838) 2024-07-26 10:00:18 +08:00
fufesou 3999d498be fix: keep window pos after new conn (#8834)
1. Keep window pos
2. Do some init in StatefulWidget constructor.
If try init in its state class, it may be too late. Because I see the
init function is called after building the widget tree.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-25 21:52:57 +08:00
rustdesk bbdce8d57b Send clipboard keystroke, https://github.com/rustdesk/rustdesk/discussions/5451 2024-07-25 15:21:28 +08:00
dignow baf70da2fe fix: trackpad, reverse horizontal scroll (#8827)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-25 14:44:58 +08:00
dignow b967d496cc refact: init values from initState to Constractor (#8817)
* refact: init values from initState to Constractor

Signed-off-by: dignow <linlong1265@gmail.com>

* fix: move RxBool init into Constructor

Signed-off-by: dignow <linlong1265@gmail.com>

* peer sort option

Signed-off-by: dignow <linlong1265@gmail.com>

* Remove empty initState()

Signed-off-by: dignow <linlong1265@gmail.com>

---------

Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-25 10:45:51 +08:00
dignow 2aef79688b fix: mobile, conn page, jump on exiting remote (#8820)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-25 00:43:14 +08:00
dignow 0451a1c45f fix: mobile, server page, jumps on loading (#8819)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-25 00:13:22 +08:00
dignow f7e9057a39 fix: video service, do not skip refresh message (#8815)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-24 21:59:25 +08:00
dignow d73e0e1e5a fix: video service (#8812)
1. Unset refresh flag if just refreshed.
2. Reduce the scope of the lock.

Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-24 18:41:52 +08:00
dignow 39dbd89287 fix: siwtching display, clear last old image (#8810)
* fix: siwtching display, clear last old image

1. Clear last old image.
2. Try refresh after switching display.
3. Add an interval check before refresh video service.

Signed-off-by: dignow <linlong1265@gmail.com>

* simple changes

Signed-off-by: dignow <linlong1265@gmail.com>

---------

Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-24 17:35:06 +08:00
21pages c04f460bbd fix more bool options (#8809)
* fix more bool options
* hide sort ab tags because it's already sorted

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-24 17:20:58 +08:00
dignow 79a1f888d6 fix: flutter remove setState in initState (#8807)
Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-24 14:00:49 +08:00
21pages 57d1b1ecc4 fix nextRgba not called when switching to texture render (#8792)
Because rgba buffer render doesn't support multi display, when switch to
multi display, it is possible that rgba.valid has been set to valid but
nextRgab is not called, when switching back to single display,
rgba.valid is still true.

Fix by rgba buffer, rgba texture and  gpu texture using different messages.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-24 12:06:16 +08:00
Vasyl Gello 8a4a2b5732 Remove existing /opt/artifacts/vcpkg in sciter builds (#8804)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-24 11:23:49 +08:00
Vasyl Gello 51a60a7eed Fix arm32 sciter build (#8803)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-24 10:16:25 +08:00
bovirus b26acde450 Update Italian language (#8800) 2024-07-24 09:41:43 +08:00
Mr-Update 2de81045ea Update de.rs (#8801) 2024-07-24 09:41:25 +08:00
Vasyl Gello a72a8906b0 vcpkg deps (#8764)
* Revert "Revert vcpkg ffmpeg (#8751)"

This reverts commit 5c16a8302e.

* vcpkg: Reland ffmpeg and try to fix sciter build

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

* vcpkg: Detect AVX2 by requiring __m256i

(ubuntu18.04 sciter)

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

* Install nasm from debian buster and python3.7

... from ubuntu universe

[Skip CI]

* vcpkg: Add libyuv port with fix for windows

From
https://github.com/dchapyshev/vcpkg4aspia/commit/abc59feabf5b3a70662631293eb67c941be4bb8a

Found by @deep-soft

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

* Bump vcpkg baseline to 2024.07.12

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

* Fix F-Droid version action

I thought the latest release will be updated by the time hook starts
but it is not the case. Get tag from GITHUB_REF instead if GITHUB_REF_TYPE
is "tag".

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-23 23:31:36 +08:00
dignow 614086a216 fix: mobile, query onlines, on active (#8796)
* fix: mobile, query onlines, on active

Signed-off-by: dignow <linlong1265@gmail.com>

* Update peer_tab_page.dart

---------

Signed-off-by: dignow <linlong1265@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-07-23 23:28:03 +08:00
rustdesk 2ffc2ad85b enhance https://github.com/rustdesk/rustdesk-server-pro/discussions/325 2024-07-23 22:26:23 +08:00
21pages eef091d4e8 fix hide ab tag when custom client startup (#8795)
not custom: 'Y', ''
custom: 'Y', 'N'
should not use isNotEmpty

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-23 21:35:05 +08:00
rustdesk 97f26f880b default-connect-password option 2024-07-23 10:46:50 +08:00
rustdesk 22c6f5e589 aggresive online query for self-hosting 2024-07-23 10:26:56 +08:00
お餅のCreeeper b828768fa9 Update ja.rs and Add "About Rustdesk" (#8784)
* Update ja.rs

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

* Update ja.rs

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

* Add 'About RustDesk'

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

---------

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>
2024-07-23 08:58:48 +08:00
dignow 31e7b6acf1 refact: msi, revision version (#8782)
* refact: msi, revision version

Signed-off-by: dignow <linlong1265@gmail.com>

* check revision version

Signed-off-by: dignow <linlong1265@gmail.com>

* refact: msi, default revision number

Signed-off-by: dignow <linlong1265@gmail.com>

* Simple refact

Signed-off-by: dignow <linlong1265@gmail.com>

---------

Signed-off-by: dignow <linlong1265@gmail.com>
2024-07-22 23:37:25 +08:00
21pages 14b505130b remove end slash when setting server config (#8779)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-22 17:11:32 +08:00
21pages 22f3425ace fix custom client show ip whiltelist warning (#8778)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-22 17:00:29 +08:00
rustdesk 4723d6a830 mouse wrong commit ref 2024-07-22 10:29:00 +08:00
DiegoGZ c4f3c0f133 Update ca.rs - Added new translation into Catalan and a few corrections (#8777)
Added new translation into Catalan and a few corrections
2024-07-22 09:58:04 +08:00
Bot_wxt1221 de375c91bb fix: nixos's headless mode. (#8774) 2024-07-21 17:05:32 +08:00
dignow d3454f07d3 fix: move some crates to rustdesk-org (#8772)
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
2024-07-20 23:49:40 +08:00
fufesou cf8ef2533a fix: keyboard, linux, repeated keys, #6793 (#8757)
* fix: keyboard, linux, repeat keys, #6793

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

* fix: keyboard, linux->linux, may also repeat keys with bad network

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-20 00:42:49 +08:00
お餅のCreeeper 6ad662260e Update ja.rs (#8758)
* Update ja.rs

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

* Update README-JP.md

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

---------

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>
2024-07-19 23:56:39 +08:00
21pages 2b54a553c7 buildin options and add to mobile (#8759)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-19 23:55:52 +08:00
rustdesk 85ded0a3e5 no idea why publish sciter windows tar.gz, remove it 2024-07-19 00:32:19 +08:00
21pages 5c16a8302e Revert vcpkg ffmpeg (#8751)
* Revert "fix linux ffmpeg link, still link ffmpeg in hwcodec (#8747)"

This reverts commit 48464835f5.

* Revert "fix linux armv7 ffmpeg arch, linux x64 sciter add hwcodec feature (#8744)"

This reverts commit b68d7a3054.

* Revert "arm linux remove cuda-llvm option (#8735)"

This reverts commit bc672b3367.

* Reapply "try fix arm64 linux ci and publish error log (#8730)" (#8732)

This reverts commit e283d33f28.

* Revert "try fix arm64 linux ci and publish error log (#8730)"

This reverts commit a4565bf0da.

* Revert "F-Droid: enable hwcodec for future builds (#8726)"

This reverts commit 188f85b042.

* Revert "remove unused vcpkg ffmpeg code (#8725)"

This reverts commit 72c96f22b6.

* Revert "install ffmpeg lib with vcpkg (#8724)"

This reverts commit 0143eaf601.

* update hwcodec to reverted

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-18 22:28:35 +08:00
21pages 04c175c62e fix custom client settings tab visibility (#8752)
* fix custom client settings tab visibility

only control with tabKeys

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

* fix preset-user-name typo

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-18 22:27:31 +08:00
fufesou 2be05608d8 fix: windows, headless, not work when exiting RDP (#8753)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-18 22:23:45 +08:00
21pages c3c99ba107 fix missing retry (#8750)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-18 17:16:25 +08:00
fufesou a81d6468cc fix: desktop settings, disconnect, timeout (#8749)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-18 15:57:36 +08:00
21pages 48464835f5 fix linux ffmpeg link, still link ffmpeg in hwcodec (#8747)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-18 13:18:21 +08:00
rustdesk edc5d86ee7 fix hide-help-cards and fix https://github.com/rustdesk/rustdesk/issues/8687 2024-07-18 11:26:11 +08:00
fufesou e9c8ba5393 fix: macos, remove unused workaround (#8746)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-18 10:38:15 +08:00
rustdesk a72bc0fb28 hide-help-cards 2024-07-18 09:47:04 +08:00
rustdesk 5a8c8cbf7c hide-help-cards 2024-07-18 09:47:04 +08:00
21pages b68d7a3054 fix linux armv7 ffmpeg arch, linux x64 sciter add hwcodec feature (#8744)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-17 22:37:05 +08:00
rustdesk 9e931a6f04 change remove-preset-password-warning default value to Y 2024-07-17 21:17:54 +08:00
rustdesk f0587796e2 OPTION_PRESET_STRATEGY_NAME, OPTION_PRESET_USERNAME 2024-07-17 20:11:30 +08:00
fufesou 875ac28ab5 fix: macos, remove useless workaround (#8738)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-17 19:13:03 +08:00
お餅のCreeeper 6821bef5e5 Update README-JP.md and ja.rs (#8737)
* Update README-JP.md

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

* Update ja.rs

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

* Fix README-JP.md

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>

---------

Signed-off-by: creeper-0910 <56744841+creeper-0910@users.noreply.github.com>
2024-07-17 19:11:04 +08:00
rustdesk 930561f431 remove unused protobuf 2024-07-17 18:17:30 +08:00
21pages bc672b3367 arm linux remove cuda-llvm option (#8735)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-17 17:19:21 +08:00
21pages e283d33f28 Revert "try fix arm64 linux ci and publish error log (#8730)" (#8732)
setup a selfhost and will fix it later

This reverts commit a4565bf0da.
2024-07-17 13:35:32 +08:00
fufesou 901505e8be fix: macos, load multi dylib instances (#8731)
Multiple dylib instances will cause some global instances to be invalid.

eg. lazy_static objects in rust side, will be created more than once.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-17 11:49:11 +08:00
21pages a4565bf0da try fix arm64 linux ci and publish error log (#8730)
FFmepg can be built on arm64 ubuntu vm, possible reason is that the
condition of nvcodec can't be satisfied. If this still can't work, nvcodec can
be removed for arm linux.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-17 11:40:50 +08:00
fufesou 092e4089c7 fix: try workaround, macos, subwindow, frozen (#8729)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-17 09:55:46 +08:00
Vasyl Gello 188f85b042 F-Droid: enable hwcodec for future builds (#8726)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-16 16:35:10 +08:00
21pages 72c96f22b6 remove unused vcpkg ffmpeg code (#8725)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-16 15:46:20 +08:00
21pages 0143eaf601 install ffmpeg lib with vcpkg (#8724)
* use vcpkg to install ffmpeg and build sdk from source, so no prebuild lib in hwcodec.
* link ffmpeg in rustdesk directly, ffmpeg can be used as basic library.
* for windows developers, `VCPKG_DEFAULT_HOST_TRIPLET` env need to be set to `x64-windows-static` during installation.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-16 15:10:24 +08:00
21pages 09466680d3 mobile virtual display, resolution menu, proxy setting (#8717)
1. Merge code of mobile and desktop virtual display menu.
2. Mobile add seperate resolution menu, only support changing
   resolutions.
3. Android add proxy setting

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-15 18:53:14 +08:00
fufesou eec879a801 refact: macos, hide&show on leaving view (#8712)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-15 10:50:54 +08:00
21pages 3f11d9cdb6 remove Instant sub (#8714)
which cause crash when connect to windows just startup

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-15 10:49:09 +08:00
21pages 8512c2b2b0 fix lan peers batch deletion (#8715)
`peers.map((p) async {})` is not sync and peers are loaded before deletion.

```
let mut peers = config::LanPeers::load().peers;
peers.retain(|x| x.id != id);
```

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-15 10:47:19 +08:00
fufesou e2a7e38a39 fix: build ios (#8709)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-14 15:05:56 +08:00
21pages 3a0ece1447 mobile view mode menu (#8707)
* mobile add view mode menu, disable same menus as desktop
* show forbidden cursor when not view mode && no keyboard permission &&
  not show remote cursor
* hide keyboard/mouse bottom action when keyboard disabled
* fix not listen keyboard permission change
* mobile missing changing resolution and http proxy setting, will add if
  needed.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-14 15:04:35 +08:00
rustdesk d0a54a6cc6 hc 2024-07-14 05:47:42 +08:00
RustDesk bed214bd37 Update playground.yml 2024-07-14 04:56:03 +08:00
RustDesk 5f31211db3 Update playground.yml 2024-07-14 04:35:23 +08:00
RustDesk 29b8875c1c Update playground.yml 2024-07-14 04:28:22 +08:00
RustDesk 3367c541b2 Update playground.yml 2024-07-14 04:08:11 +08:00
fufesou 30afe4f779 refact: seperate audio device for voice call (#8703)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-14 04:07:02 +08:00
fufesou d18e95703e fix: mouse forward back (#8705)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-14 03:49:16 +08:00
RustDesk 9adc083def Update playground.yml 2024-07-14 03:47:15 +08:00
RustDesk 0dc664474a Update playground.yml 2024-07-14 03:46:48 +08:00
Vasyl Gello 5e8fe239fa build_fdroid.sh: Fix review points by Licaon_Kter (#8701)
* NDK should be downloaded via verified Android SDK's sdkmanager
  * vcpkg should be built from sources not prebuilt

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-13 20:46:05 +08:00
RustDesk 7a3100a87c Update playground.yml 2024-07-13 16:08:07 +08:00
RustDesk 8a1acedae5 Update playground.yml 2024-07-13 15:51:51 +08:00
RustDesk f5bcc17636 Update playground.yml 2024-07-13 15:45:59 +08:00
RustDesk 883c630206 Update playground.yml 2024-07-13 15:35:24 +08:00
RustDesk a95a6ab733 Update playground.yml 2024-07-13 15:24:01 +08:00
RustDesk 46605fab1b Update playground.yml 2024-07-13 14:33:17 +08:00
RustDesk 9d26fec631 Update playground.yml 2024-07-13 11:33:15 +08:00
RustDesk 294a6ce9bc Update playground.yml 2024-07-13 11:15:00 +08:00
RustDesk 183ea47ba4 Update playground.yml 2024-07-13 01:29:17 +08:00
RustDesk 06e04143a8 Update playground.yml 2024-07-13 01:26:09 +08:00
RustDesk a532b36e28 Update playground.yml 2024-07-13 01:03:43 +08:00
fufesou c873b69662 refact: unwrap offstage for cursor paint (#8693)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-13 00:44:08 +08:00
RustDesk b30f84623b Update playground.yml 2024-07-12 23:44:25 +08:00
fufesou 888e993534 fix: unable to close on fullscreen (#8690)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-12 22:04:11 +08:00
rustdesk 1d59a7fe5f adjust uni_link_desktop version to make work with old flutter 2024-07-12 18:07:27 +08:00
RustDesk 2c027cdcf5 Update playground.yml 2024-07-12 17:55:25 +08:00
RustDesk fe513dd967 Update playground.yml 2024-07-12 17:26:00 +08:00
RustDesk d652b99d5b Update playground.yml 2024-07-12 16:33:55 +08:00
RustDesk c2716c2509 Update playground.yml 2024-07-12 15:44:51 +08:00
21pages 821f7245b0 videotoolbox ram codec (#8683)
* h265 encoding: the second frame always fails, use repeat encoding to achieve real-time encoding
* h264 encoding: Not supported because encoding fails too frequently, about 50%, with one failure followed by another success.
* h264/h265 decoding: No issues found.
* Does not support dynamically changing the bitrate and changing the quality by resetting the encoder.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-12 11:08:51 +08:00
RustDesk 0ea88ce6ff Update playground.yml 2024-07-12 11:01:12 +08:00
RustDesk 21f41e98a0 Update playground.yml 2024-07-12 10:44:04 +08:00
RustDesk 282ea02ebf Update playground.yml 2024-07-12 10:41:13 +08:00
RustDesk 170200fa49 Update playground.yml 2024-07-12 10:14:25 +08:00
rustdesk d8cee6507d test old flutter for mac intel freeze issue 2024-07-12 10:07:55 +08:00
fufesou 2391b18046 fix: ubuntu 18.04, service, Exec format error (#8680)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-11 23:13:41 +08:00
fufesou b5a7165015 feat: android, controlled side, clipboard text (#8677)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-11 12:02:19 +08:00
RustDesk ef4d84657b Revert "feat: clipboard, multi format (#8672)" (#8673)
This reverts commit 011647511c.
2024-07-11 00:36:29 +08:00
fufesou 011647511c feat: clipboard, multi format (#8672)
* feat: clipboard, multi format

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

* inline

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-11 00:05:25 +08:00
21pages e2d217a138 fix wayland Display::all() comment (#8664)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-09 22:10:39 +08:00
21pages f07936a911 wayland not call check_get_displays_changed_msg (#8637)
* wayland not call check_get_displays_changed_msg

For wayland, call Display::all() in video service will cause block, I
don't know the reason.

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

* Update display_service.rs

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-07-09 18:01:30 +08:00
21pages 0bb4d43e9e add custom client option to allow remote cm modification (#8660)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-09 15:19:16 +08:00
Vasyl Gello 6f74080a2d build_fdroid: Move sudo-deps back to f-droid spec (#8659)
... as requested by linsui here:
https://gitlab.com/fdroid/fdroiddata/-/merge_requests/15343#note_1988918695

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-09 14:36:05 +08:00
rustdesk 8a370e640a add hide-username-on-card, https://github.com/rustdesk/rustdesk-server-pro/issues/284#issuecomment-2216521407 2024-07-09 13:45:54 +08:00
Vasyl Gello d007408061 Factor out F-Droid buildscript (#8625)
Now RustDesk maintainers can make proactive changes in F-Droid
packaging script so F-Droid side is (fully) automated.

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-09 11:45:08 +08:00
fufesou 02572e9032 fix: obx, no rx value (#8652)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-08 21:51:16 +08:00
21pages af66d2a73b main window add block mask, cm add keyboard block (#8640)
* block window body only so user can still click minisize button.
* cm doesn't show mask
* Remove focusable Offstage in tabbar_widget.dart

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-08 20:08:05 +08:00
bovirus eb5ab4d7d9 Update Italian language (#8645) 2024-07-08 19:59:29 +08:00
rustdesk c02b4f994a fix two tray icon on macos 2024-07-08 14:46:28 +08:00
XLion d093fdc256 Translation (#8634)
* Update tw.rs

* Update en.rs: Using `.` instead of `,`

* Update tw.rs: Fix formatting
2024-07-08 12:10:02 +08:00
FastAct 2af799f46e Update nl.rs (#8632) 2024-07-08 12:09:50 +08:00
rustdesk 3c7e24c605 add MOUSE_MOVE_TIME to key event, may work for
https://github.com/rustdesk/rustdesk/issues/8633 when window resume via
keyboard rather than mouse
2024-07-08 11:38:42 +08:00
rustdesk 7d961d895b add assign function in device.py for user/strategy/ab 2024-07-07 16:35:57 +08:00
rustdesk 53dbc2fa6f add OPTION_DISABLE_UDP 2024-07-07 09:25:27 +08:00
rustdesk 024220e58a add more fine-grained hide options for https://github.com/rustdesk/rustdesk-server-pro/issues/276 2024-07-06 23:41:53 +08:00
rustdesk 8621b93436 add display-name option for https://github.com/rustdesk/rustdesk-server-pro/issues/277 2024-07-06 23:10:50 +08:00
rustdesk ac88121c4a fix ci 2024-07-06 11:46:33 +08:00
Vasilis553 90df80ed78 Update el.rs (#8628)
* Update el.rs

* Update el.rs
2024-07-06 11:35:29 +08:00
rustdesk d4f3a87276 change to remove-preset-password-warning 2024-07-05 20:09:26 +08:00
rustdesk 48efdcf1f0 add remove-preset-password-warning-of 2024-07-05 19:26:20 +08:00
fufesou 0511cdbb21 feat: clipboard svg (#8615)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-05 09:30:59 +08:00
Thadah D. Denyse 8747b9847f Add basque language (#8597)
* Add basque language

* Update lang.rs
2024-07-05 09:30:07 +08:00
RustDesk 92d0fe1c3f Revert "feat: clipboard svg (#8608)" (#8612)
This reverts commit a9015bcf70.
2024-07-04 21:31:19 +08:00
fufesou a9015bcf70 feat: clipboard svg (#8608)
* feat: clipboard svg

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

* fix: is_last_plain, reset on clipboard event

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-04 21:23:08 +08:00
flusheDData f8f2686267 Update es.rs (#8609)
* Update es.rs

New terms added

* Update es.rs

Mistype correction
2024-07-04 20:21:28 +08:00
solokot c2bd1b8965 Update ru.rs (#8610) 2024-07-04 20:21:18 +08:00
Nevaran 4eeee5b7ee Update bg.rs (#8611)
Added more translated parts, some might need more context to be more accurate
2024-07-04 20:21:06 +08:00
fufesou dfc224ec01 fix: #8599 (#8603)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-04 20:20:25 +08:00
fufesou 86ff768241 clear unwrap (#8605)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-04 20:18:53 +08:00
jxdv 94addb162b update sk && cz tr (#8595)
* update sk translations

* update cz translations
2024-07-04 11:43:09 +08:00
rustdesk bea65f8739 fix ci 2024-07-03 21:47:13 +08:00
rustdesk 92f570831d return x11 for tty session 2024-07-03 21:37:25 +08:00
Kleofass 9349210a87 Update lv.rs (#8591) 2024-07-03 20:40:57 +08:00
Vasyl Gello 95f4274eca vcpkg: sort triplets and restore local opus port for 1.5.1 (#8585)
* ci/flutter-build: Sort out triplets in job matrices

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

* Restore local port for opus 1.5.1

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-03 17:57:58 +08:00
Vasyl Gello a6febb2816 [WIP] Fix F-Droid version file workflow (#8583) 2024-07-03 17:12:21 +08:00
Vasyl Gello e294dafe7c Bump vcpkg baseline to 2024.06.15 (#8582)
* res/vcpkg: drop old opus port overlay

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

* res/vcpkg: bump aom to 3.9.1

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

* res/vcpkg: bump libvpx to 1.14.1

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

* Bump vcpkg baseline to 2024.06.15

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-07-03 15:53:17 +08:00
21pages d00582e929 linux x11 rgb565 capture (#8580)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-03 14:20:41 +08:00
Vasyl Gello 6d2e985593 res/vcpkg: Bump aom to 3.9.1, opus to 1.5.1 and libvpx to 1.14.1 (#8555)
* res/vcpkg: drop old opus port overlay

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

* res/vcpkg: bump aom to 3.9.1

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

* res/vcpkg: bump libvpx to 1.14.1

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

---------

Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
2024-07-03 14:12:19 +08:00
Mr-Update 182e8c4ac0 Update de.rs (#8577) 2024-07-03 11:55:59 +08:00
bovirus 40019b80f6 Update Italian language (#8576) 2024-07-03 11:55:47 +08:00
fufesou 2f40b9dc04 fix: build ios (#8570)
ld: Undefined symbols:
     _kSecMatchSubjectWholeString, referenced from:
         security_framework::item::ItemSearchOptions::search::he568de2b0004b0c0 in liblibrustdesk.a[872](security_framework-88ef6afe340eb2ab.security_framework.e4562ffe63567184-cgu.0.rcgu.o)

TODO:

There's also a warning after commit "2116fec20b5a2165df97e329c156baf308668efc".
No idea how to fix it. But it does not stop building.

ld: warning: Could not find or use auto-linked framework 'CoreAudioTypes': framework 'CoreAudioTypes' not found

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-02 15:37:45 +08:00
21pages 8602b036bd remove special treatment when only use permanent passwrod but no password set (#8566)
1. Remove special treatment when only use permanent passwrod  but no password set, it has  no need and `Connection not allowd` prompt make user confusing.
2. When only use permanent password is chosen and the permanent password
   is empty, pop up the set-password dialog, if still not set in the
   dialog, back to the old choice
3. Add cancel confirm for 2fa and telegram bot

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-02 14:32:22 +08:00
fufesou 51db8e706d fix: win, clipboard image (#8561)
The window must belong to the current thread for clipboard-master.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage#:~:text=The%20window%20must%20belong%20to%20the%20current%20thread.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-02 00:46:55 +08:00
21pages a0dc38f749 try fix linux arm64 build (#8560)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-02 00:19:18 +08:00
Mr-Update 625b610cfd Update de.rs (#8559) 2024-07-02 00:18:54 +08:00
fufesou 62a8349739 fix: clipboard data, decompress, buf too small (#8556)
* fix: clipboard data, decompress, buf too small

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

* fix: compress image

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

* decompress image, use default level

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

* chore

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

* decompress, zstd::decode_all

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-02 00:18:38 +08:00
rustdesk 0ab500c27c bring fdroid.yml back 2024-07-01 16:50:27 +08:00
21pages 285e974d1a ci for linux x86_64 sciter deb and flatpak (#8545)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-07-01 11:01:35 +08:00
rustdesk e71d86c124 move clipboard in common.rs to clipboard.rs 2024-07-01 02:14:58 +08:00
rustdesk 14343e89d4 fix ci 2024-07-01 01:52:39 +08:00
rustdesk 3f2dfa521c fix ci 2024-07-01 01:43:16 +08:00
rustdesk cd73368cb9 make clipboard dropped after no sub 2024-07-01 00:24:23 +08:00
rustdesk 84b5cd70ed initialize change_count to 1 so that first data always got 2024-06-30 23:27:33 +08:00
rustdesk 01672bc697 clipboard image, not tested, todo: set_html 2024-06-30 21:57:32 +08:00
21pages 763174657b add type to all Getx put/get/delete/isRegistered (#8550)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-30 21:24:18 +08:00
rustdesk 15fa80fb26 fix ci 2024-06-30 19:17:24 +08:00
FastAct d537e2563d Update nl.rs (#8546) 2024-06-30 19:04:22 +08:00
rustdesk 1719e478e3 remove debug code 2024-06-30 17:07:23 +08:00
rustdesk 1f129e6ef3 change update_clipboard to threaded, since creating a context may take long 2024-06-30 17:05:09 +08:00
21pages 25d0ced8ba fix ci (#8543)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-30 13:54:40 +08:00
rustdesk 2116fec20b wl-clipboard-rs 0.9 and refresh cargo.lock 2024-06-30 12:07:32 +08:00
rustdesk 1252f45506 fix 100% cpu usage of clipboard disabled on remote menu 2024-06-30 10:57:15 +08:00
fufesou 1f4c62e480 fix: linux, arboard::new(), retry (#8538)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-30 01:13:45 +08:00
fufesou bd334769fa fix: win, virtual display, privacy mode (#8537)
Restore display settings.

Only restore by the display registry settings.

Do not change display settings twice when turning off privacy mode.

Only restore the registry values.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-30 00:44:10 +08:00
jxdv 750368af7b Update cs tr (#8536)
* update common tr

* update tips
2024-06-29 22:26:06 +08:00
jxdv 2fb35c3596 Update sk tr (#8535)
* translate normal

* translate bot tips
2024-06-29 22:16:40 +08:00
21pages 5114a9d369 shared memory size use the max resolution of all displays (#8534)
The case of setting a monitor inserted later as the primary monitor is not taken into account

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-29 21:50:21 +08:00
solokot 4b6ba7938f Update ru.rs (#8533)
* Update ru.rs

* Update ru.rs
2024-06-29 21:41:09 +08:00
Vedant 1e400d2a64 Update winget-releaser to latest (#8532) 2024-06-29 21:17:10 +08:00
fufesou 967e63266f fix: win, multi-display settings changed (#8531)
1. Windows, multi-display, as the controlled side.
2. Connect
3. Turn on privacy mode, with mode 2.
4. Turn off privacy mode.

Then the display settings are continues changed when connect &
disconnect.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-29 20:16:22 +08:00
21pages f9b0a88213 fix switch to primary display but can't see UAC (#8527)
How to reproduce: elevate at primary display->switch to another
display->
trigger UAC->switch to primary display->can't see UAC

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-29 17:14:09 +08:00
21pages d67afa49b4 portable service only run on main display (#8525)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-29 14:17:24 +08:00
fufesou 1fd170b089 fix: mobile more actions, check version 1.2.7 (#8526)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-29 11:50:40 +08:00
rustdesk a632718e80 typo 2024-06-29 10:46:21 +08:00
rustdesk 9f72d05749 do not run self-host in ci commit flow 2024-06-28 23:32:23 +08:00
fufesou c062813c6d Upgrade arboard (#8522)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-06-28 23:15:33 +08:00
21pages 3ae1638125 fix extracted forground window not foreground (#8521)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-28 22:36:29 +08:00
bovirus 96aff38862 Update Italian language (#8520) 2024-06-28 16:28:43 +08:00
rustdesk ed3fb1efa4 bump to 1.2.7 2024-06-28 09:59:10 +08:00
fufesou d689bbf38e refact: mobile more actions, divider (#8512)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-28 00:57:16 +08:00
fufesou c1bbdaf9ae refact: mobile min cursor size, 12 (#8510)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-27 23:19:37 +08:00
21pages ab9e1013b2 remove dpiAware from manifest in ci for self-extracted executable (#8508)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-27 22:23:25 +08:00
Samuel FORESTIER e1140b1bea Tidy up configuration files UNIX permissions (#7983)
* Add missing libpam package causing build to fail using Docker image

```
wrapper.h:1:10: fatal error: 'security/pam_appl.h' file not found
```

Signed-off-by: Samuel FORESTIER <samuel+dev@forestier.app>

* Tidy up configuration files UNIX permissions

Signed-off-by: Samuel FORESTIER <samuel+dev@forestier.app>

---------

Signed-off-by: Samuel FORESTIER <samuel+dev@forestier.app>
2024-06-27 20:14:32 +08:00
21pages cfd27c8d87 wakelock not block system sleep (#8504)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-27 20:09:51 +08:00
rustdesk a18947eed2 fix typo 2024-06-27 18:24:04 +08:00
bovirus f8592e0d5b Update Italian language (#8502)
* Update Italian language

* Fix typo
2024-06-27 18:10:41 +08:00
rustdesk 5bfdf05ff2 /hello rather than hello to activate bot 2024-06-27 18:01:14 +08:00
rustdesk 9e851542ec telegram bot works now 2024-06-27 17:36:50 +08:00
rustdesk e79946b4e4 telegram bot ui settings and code sending 2024-06-27 16:18:41 +08:00
solokot aed212d8f8 Update ru.rs (#8497) 2024-06-27 15:04:53 +08:00
fufesou c5d3c7f390 Feat/android more actions (#8496)
* feat: android volume and power actions

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

* Add translations and refact action menus

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

* Remove divider

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

* fix: recover deleted translations

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-27 13:28:05 +08:00
fufesou b047730830 Refact/android input changed notify clients (#8494)
* refact: android, input control changed, notify clients

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

* fix: android init input perm

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-27 13:05:45 +08:00
21pages 9c7d4ef1f7 not use nwg default features (#8492)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-27 12:11:08 +08:00
21pages 12d3c59172 windows portable loading ui (#8490)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-27 11:04:55 +08:00
Stas Solovey ef06b7d5d0 add Belarusian locale (#8480)
* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Create be.rs (Belarussian locale)

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update be.rs

* Update lang.rs

* Update lang.rs

* Update be.rs

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-06-26 20:32:05 +08:00
fufesou f17e17a6b9 fix: mobile cursor, check null (#8481)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-26 19:49:52 +08:00
rustdesk faf363cfd2 add TelegramBot 2024-06-26 18:49:41 +08:00
fufesou dbbd9179b7 fix: android cursor scale (#8478)
* fix: android cursor scale

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

* Min scale restriction for mobile cursor

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-26 18:42:08 +08:00
rustdesk 49f848a453 refactor 2024-06-26 16:35:29 +08:00
rustdesk ef56aea74f fix https://github.com/rustdesk/rustdesk/issues/8479 2024-06-26 16:28:31 +08:00
rustdesk cb5fa85ac2 build 45 2024-06-26 09:59:35 +08:00
fufesou 11bdd3cfcd fix: ios keyboard, composing input (#8471)
* fix: ios keyboard, composing input

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

* Incorrect changes

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-26 00:24:57 +08:00
fufesou f0dcc91907 fix: wrong use of Instant sub, just after booting (#8470)
* fix: wrong use of Instant sub, just after booting

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

* fix: ThrottledInterval, first next tick

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-25 20:33:38 +08:00
21pages c1c2d26ec7 fix, check video_queue len rather than len when refresh (#8469)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-25 18:30:32 +08:00
21pages 93133b9a6c if fps is adjusted below 30, not set fps to 30 when changing image quality (#8468)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-25 17:25:50 +08:00
fufesou 245f08055f fix: mobile, chat menu, hide after tapping (#8465)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-25 00:31:41 +08:00
fufesou 00ddd63372 fix: ios, chat window position, wrong member value (#8464)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-24 22:56:13 +08:00
21pages 1765c7bbf4 fix multi display fps control (#8455)
* Calculate fps without distinguish displays, use one fps control
  because the controlled side control fps of all displays with one FPS
  variable.
* Because all displays decode frame in one thread, when there are N
  displays, the video frames received in one second is `fps * N`, so the
  calculated decode fps should be divided by N. Because the actual
  display count is not obvious in rust, when no data frame is received for 5 seconds, the display is considered inactive, and only the active display is used as the dividend.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-24 19:41:15 +08:00
Yevhen Popok 65edd55516 Update Ukrainian translation (#8453) 2024-06-24 17:32:00 +08:00
rustdesk 4947cf8718 fix https://github.com/rustdesk/rustdesk/issues/8452 2024-06-23 22:50:54 +08:00
jxdv 65dd2b8993 update sk.rs (#8449) 2024-06-23 18:05:28 +08:00
jxdv ef82cfa034 update cs.rs (#8450) 2024-06-23 18:05:06 +08:00
rustdesk 1a69d525af fix tile type droplist and change to build 44 2024-06-23 11:39:44 +08:00
fufesou 307827be3c fix: mobile actions hide and mobile theme (#8447)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-23 11:26:15 +08:00
fufesou 40cb59336f fix: mobile actions, position (#8446)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-23 11:06:47 +08:00
XLion a9e0ea8520 Update tw.rs (#8444) 2024-06-23 09:09:08 +08:00
rustdesk baeee642dd build 43 2024-06-22 20:51:41 +08:00
Mr-Update 416efe9fd3 Update de.rs (#8443) 2024-06-22 20:51:33 +08:00
solokot 8b5ac390d1 Update ru.rs (#8442) 2024-06-22 20:05:56 +08:00
rustdesk 212e8e7559 fix one missing file 2024-06-22 12:45:32 +08:00
rustdesk 41a20b50ea split web js to v1 and v2 2024-06-22 12:29:20 +08:00
21pages 3742b51d58 quality monitor, delay displays as 0 when fps is 0 (#8441)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-22 09:39:02 +08:00
bovirus 1a21dff5d4 Update Italian language (#8439) 2024-06-22 08:11:09 +08:00
fufesou bbf7d9e08a fix: android, no voice call under android 11 (#8440)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-22 08:10:54 +08:00
21pages ffed29e632 fix typo (#8436)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-21 23:58:00 +08:00
21pages 0f6538c1a7 add enable directx option, android software encoding half resolution option (#8435)
* add option enable directx capture screen, default true

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

* option android software encoding half scale, check isStart flag

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-21 18:54:32 +08:00
rustdesk ff2e055a5a use flutter 3.13 for android because its video super slow on my phone 2024-06-21 18:14:58 +08:00
rustdesk cdf97f8717 try out 3.13.9 with master 2024-06-21 17:42:58 +08:00
rustdesk b2af79a3c5 try out 3.16.0 2024-06-21 17:05:51 +08:00
fufesou 74cc5abd09 fix: android prompt "Failed to stop voice call" on conn ended (#8434)
* fix: android prompt "Failed to stop voice call" on conn ended

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

* Remove invalid comment

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

* Better control of voice call status

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

* Better voice call status control

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

* better end conn for voice call

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-21 16:43:54 +08:00
rustdesk 32c4712d5e fix ci 2024-06-21 12:42:27 +08:00
rustdesk 3244395bfb try 3.22.2 playground 2024-06-21 12:34:00 +08:00
rustdesk 1cb0e1ce7b try out 3.22.2 2024-06-21 12:33:24 +08:00
fufesou 42394fcbdd fix: android, two finger pan, scale (#8429)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-21 09:07:32 +08:00
rustdesk 0b32e741f7 test 3.13.9 on master for android 2024-06-21 01:29:00 +08:00
rustdesk 80c5d59916 fix ci 2024-06-21 01:13:05 +08:00
rustdesk e95823f543 for try out flutter 3.22.2 2024-06-21 01:00:46 +08:00
rustdesk 06fe972683 try out 3.22.2 for android 2024-06-21 00:45:45 +08:00
rustdesk 3057396c02 try latest 2024-06-21 00:19:05 +08:00
rustdesk 7db9543fee change back to old settings, only use flutter 3.13.9 2024-06-21 00:15:21 +08:00
rustdesk 58d86acf0d change back to 3.16.9 2024-06-20 23:51:58 +08:00
rustdesk 859020583d fix ci 2024-06-20 23:21:08 +08:00
rustdesk 0cab620ba5 try old flutter and vcpkg 2024-06-20 23:09:38 +08:00
rustdesk 4338fcc51a check if vpckg overrided 2024-06-20 23:06:42 +08:00
rustdesk 5f6f1e8d36 use prebuilt vcpkg to overwrite 2024-06-20 22:45:07 +08:00
fufesou a91f244f35 fix: android, touch mode, one finger pan, start pos (#8427)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-20 22:21:14 +08:00
fufesou 82bf04da81 fix: android, touch mode, correct cursor input, on soft keyboard shows (#8426)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-20 22:18:57 +08:00
rustdesk a679e4a5e3 vcpkg_root 2024-06-20 22:16:04 +08:00
rustdesk 3c79404534 use my vcpkg 2024-06-20 21:53:43 +08:00
rustdesk ba707d1149 try out different ndk 2024-06-20 21:28:27 +08:00
rustdesk 93d88f30b4 test more commits 2024-06-20 21:10:43 +08:00
rustdesk f5bc136b07 fix ci 2024-06-20 20:32:00 +08:00
rustdesk ae69cbb207 fix ci 2024-06-20 20:30:55 +08:00
fufesou 39e3da1eb0 android, secure keyboard on remote input (#8425)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-20 20:20:32 +08:00
rustdesk c1322b47c3 more commits 2024-06-20 20:13:57 +08:00
rustdesk 67f83bd5dd more commits 2024-06-20 19:48:40 +08:00
rustdesk e424d01f3d publish missed 2024-06-20 19:41:43 +08:00
rustdesk a424830893 fix ci 2024-06-20 19:23:17 +08:00
rustdesk 3c5810cc01 prepare android old version test 2024-06-20 19:16:51 +08:00
21pages 30bd4e1cef update hwcodec, use ms as pts like vpx (#8422)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-20 13:57:56 +08:00
RustDesk 7956953669 Revert "fix: android, touch mode, move cursor (#8419)" (#8421)
This reverts commit dcba4615a2.
2024-06-20 12:22:36 +08:00
fufesou dcba4615a2 fix: android, touch mode, move cursor (#8419)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-20 08:29:07 +08:00
rustdesk 0bf9de8256 also check --server in loop 2024-06-19 21:29:54 +08:00
rustdesk 77f1c7e74c add crate::platform::quit_gui(); for double sure 2024-06-19 21:21:51 +08:00
rustdesk 27478946ea open new window not always work, so give it a little time before exit 2024-06-19 21:17:26 +08:00
rustdesk 1f25a8af86 fix macos stop service on gui not restart 2024-06-19 20:23:05 +08:00
rustdesk d75caad71f move --server check into daemon.plist 2024-06-19 19:54:30 +08:00
rustdesk adf0226641 fix ci and make macos service time check more aggressive 2024-06-19 19:10:44 +08:00
rustdesk 137f58a84a refactor macos service for delegate again, remove runme in install service of linux 2024-06-19 18:49:49 +08:00
21pages 7c45a68870 linux install service, stop service before start (#8414)
If the stop-service option before installation is "", after
installation --sever is also started up. When clicking to
start service, restart --server to make it read the config file,
otherwise the service can't be started util --server is restarted.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-19 16:29:40 +08:00
rustdesk 99edab4b61 hide docker from tao, this may fix https://github.com/rustdesk/rustdesk/issues/8399 2024-06-19 16:25:48 +08:00
fufesou e50b72622c fix: android, touch mode, soft keyboard, no pointer events (#8409)
* fix: android, touch mode, soft keyboard, no pointer events

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

* Reset lastIsBlocked on touch mode toggled

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

* refact: reset lastIsBlocked when updating keyHelpToolsRect

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-19 15:58:23 +08:00
rustdesk 60dc40f47f try hide docker in tao delegate because hide in rustdesk side a bit late so that still seeing it sometimes
refactor service to make it restart after login to avoid delegate caught for seconds after login
also make main windows Close event restart itself for above case
2024-06-19 15:42:53 +08:00
21pages 841c331981 fix sleep duration when receive ipc close (#8410)
unit is second

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-19 14:02:13 +08:00
21pages 4eafa5a585 fix ci (#8407)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-19 10:12:10 +08:00
rustdesk 5a740e891e make main window can be reopen if killed by --server for creating ipc 2024-06-19 09:27:29 +08:00
rustdesk 1fcc7001bd use exit(-1) in Data::Close to make sure --server can restart 2024-06-18 22:42:42 +08:00
rustdesk e57854422a fix kill main window in --server 2024-06-18 22:04:34 +08:00
21pages 8c39979848 fix get mac display scale, find screen from display id (#8401) 2024-06-18 19:37:15 +08:00
Stas Solovey 2c38648e39 update ru.rs (#8400)
* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

* Update ru.rs

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-06-18 19:36:32 +08:00
rustdesk 97aa739d69 revert https://github.com/rustdesk/rustdesk/pull/8368 2024-06-18 18:21:29 +08:00
Stas Solovey b0042f29fb Update ru.rs (#8398)
* Update ru.rs

* Update ru.rs

* Update ru.rs
2024-06-18 17:08:44 +08:00
rustdesk e3ca82945f fix https://github.com/rustdesk/rustdesk/issues/2680 2024-06-18 16:30:56 +08:00
rustdesk bf6a3a7067 fix stupid flutter 2024-06-18 15:06:43 +08:00
rustdesk d25670c79a fix https://github.com/rustdesk/rustdesk/issues/2680 2024-06-18 14:39:56 +08:00
XLion 32b26e4ad3 Update translation (#8394)
* Update tw.rs

* Update cn.rs

Add spacing
2024-06-18 09:54:03 +08:00
rustdesk 818439db48 fix ci 2024-06-18 08:43:19 +08:00
21pages e23a9da1a8 sync get option in android setting (#8393)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-18 08:29:10 +08:00
21pages 37ebac2a9e update hwcodec, remove AVCodecParserContext (#8389)
It was used to decode different resolution with same decoder, but may
cause crash.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-17 23:53:43 +08:00
Kleofass 46bf552afc Update lv.rs (#8383) 2024-06-17 17:31:38 +08:00
rustdesk 70151e3dd8 add Push Notifications capability though we do not use it explictly, but our dep used it, have to enable it, otherwise review will refuse us 2024-06-17 12:50:29 +08:00
rustdesk e933f0baf2 build 41 2024-06-17 11:16:33 +08:00
rustdesk f2a612c3d9 add voice_call start_pa 2024-06-17 10:57:25 +08:00
flusheDData 4a648f0068 New terms added (#8377)
* Update es.rs

New term and tip added

* Update es.rs

change representación por renderizado (render)

* Update es.rs

New terms added
2024-06-17 10:36:10 +08:00
21pages 5b52742cf7 fix mobile ab/group not update when login with 2fa/email (#8378)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-17 10:35:57 +08:00
rustdesk 237d234277 add rustc 1.78 ABI change comment 2024-06-17 09:44:36 +08:00
rustdesk ed0cba281f start dbus only for main 2024-06-16 23:59:09 +08:00
rustdesk 2e0eaed322 call _ffiBind.mainStartPa only for --cm 2024-06-16 23:51:00 +08:00
21pages e2a6d66805 make mobile ab dropdown button text vertical center (#8376)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-16 23:41:40 +08:00
21pages 8d6de9ca59 opt android ab ui (#8374)
* multiline error banner
* mobile remove ab permission icons due to hard to press
* center ab dropdown button text

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-16 23:13:46 +08:00
fufesou db108d964b fix: build https://github.com/1wilkens/pam-sys/commit/09f452b055928f70cfbcdb301a049265bbadf511 (#8373)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-06-16 22:37:14 +08:00
solokot f016d453fa Update ru.rs (#8370) 2024-06-16 15:14:45 +08:00
21pages 60ea8d2c2b mac scale factor of each screen (#8368)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-16 12:01:41 +08:00
jxdv 12ff1319f1 update sk.rs (#8364) 2024-06-15 16:03:56 +08:00
jxdv f224d8872e update cs.rs (#8365) 2024-06-15 16:03:42 +08:00
rustdesk 5cf2d5f062 change back to 1.75 since sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html 2024-06-15 14:03:33 +08:00
fufesou 92dd0ee1dd fix: non texture, multi window, switch display (#8353)
* fix: non texture, multi window, switch display

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

* fix build

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-14 17:55:03 +08:00
bovirus 70c20fc76f Update Italian language (#8352) 2024-06-14 17:39:24 +08:00
fufesou 07e0b5ac10 fix: desktop, remote toolbar, remember collapse (#8349)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-14 00:28:59 +08:00
fufesou 12f7fc3d33 fix: push rgba only on desktop (#8348)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-13 23:22:03 +08:00
fufesou 60f47cb549 fix: desktop, remote toolbar autohide (#8347)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-13 21:04:00 +08:00
Mr-Update d33fa3f073 Update de.rs (#8346) 2024-06-13 21:01:24 +08:00
21pages 2e4fafcf46 add missing call of androidUpdatekeepScreenOn (#8345)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-13 19:24:38 +08:00
21pages ab451b9056 android keep screen on option (#8344)
* android keep screen on option

Keep screen on option relays on floating window.

Three options: Never, During controlled(default), During service is on

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

* When rustdesk is in forground, be consistent with the settings

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

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-13 18:30:29 +08:00
fufesou bc875a35b0 Refact/multi window soft rendering (#8343)
* refact: multi_window_soft_rendering

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

* fix: window pos, potential wait for image

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

* comments

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

* remove debug print

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

* explicitly set rgba_data.size_got to false after init

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

* refact: multi window, merge images, render with texture

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

* revert, flutter.rs, rgba valid

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

* Add displays index before sending capture msg

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

* refact: multi window, soft rendering

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

* fix: build

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-13 18:03:41 +08:00
rustdesk 8e12a34634 upgrade our ipc-parity and tokio-socks crate to tokio 1.38 2024-06-13 13:17:34 +08:00
rustdesk 77204127f2 use latest rust for non-windows, and upgrade tokio to 3.18 which fix a mpsc channel bug 2024-06-13 13:05:35 +08:00
fufesou 65c2ccdc93 fix: try fix, macos, flutter, focus changed, no reponse (#8338)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-13 10:42:50 +08:00
fufesou 964d4f1f87 try fix cursor id, int.parse, exceeds limit (#8333)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-13 00:34:23 +08:00
21pages f559e9c74a disable hardware encoding if encoding fails too many times (#8327)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-12 23:37:51 +08:00
21pages 610009528b hwcodec, only process that start ipc server start check process (#8325)
check process send config to ipc server, other process get config from ipc server. Process will save config to toml, and the toml will be used if the config is none.

when start check process: ipc server process start or option changed
from disable to enable

when get config: main window start or option changed from disable to
enable, start_video_audio_threads.

Only windows implements signature, which is used to mark whether the gpu software and hardware information changes. After reboot, the signature doesn't change. https://asawicki.info/news_1773_how_to_programmatically_check_graphics_driver_version, use dxgi way to get software version, it's not consistent with the visible driver version, after updating intel driver with small version change, the signature doesn't change. Linux doesn't use toml file.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-12 20:40:35 +08:00
rustdesk 0f10a88b23 remove elevation/installation requirement for --get-id 2024-06-12 20:35:04 +08:00
Kleofass 60049c8cc5 Update lv.rs (#8323) 2024-06-12 17:20:29 +08:00
rustdesk 50aa5880de always call platformFFI.nextRgba no matter what to avoid dead lock because of unknown reason 2024-06-12 02:34:15 +08:00
rustdesk 47143318ba ensure nextRgba called no matter if image created 2024-06-12 01:40:54 +08:00
fufesou c27791a9ac comments (#8316)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-12 00:53:54 +08:00
fufesou b19d732a3a fix: audio rechannel len (#8315)
* fix: audio rechannel len

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-12 00:32:10 +08:00
rustdesk cd3db3a686 try to fix https://github.com/rustdesk/rustdesk-server-pro/issues/266 2024-06-11 19:51:33 +08:00
fufesou 35fb9f8897 fix: peer option, individual_windows, use 'N' instead of '' (#8307)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-10 23:01:55 +08:00
21pages ec042434be use sihost.exe as fallback for run_as_user if no explorer.exe (#8305)
* There is no relevant information, but I found that each session has a unique sihost.exe, and the user name of the process is consistent with the user name of the session, and after using the task manager to kill this process, it will automatically restart. Checking sessionUserName=siHost UserName may be unnecessary, but since there is no evidence, check it anyway.
* GetFallbackUserPid is called only when explorer.exe does not exist.
* ProcessHacker shows that the tokens of explorer.exe and sihost.exe are the same, I know little about it.

Signed-off-by: 21pages <pages21@163.com>
2024-06-10 20:29:53 +08:00
rustdesk f8041a3de5 fix merge problem of last commit 2024-06-10 19:53:02 +08:00
rustdesk dd90096e13 remove useless stop-rendezvous-service 2024-06-10 16:12:08 +08:00
fufesou 9ab5512bfa fix: custom client, option to bool (#8303)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-10 11:01:39 +08:00
fufesou 32ab56f864 fix: custom client, options, option2bool() (#8302)
* fix: custom client, options, option2bool()

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

* format

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-10 00:11:59 +08:00
rustdesk 78d7bfac01 fix https://github.com/rustdesk/rustdesk/discussions/8031 2024-06-09 19:48:42 +08:00
21pages 57570c3ba6 is_ipc_file_exist quote Config::ipc_path (#8295)
Signed-off-by: 21pages <pages21@163.com>
2024-06-08 21:56:47 +08:00
21pages ffac670f95 fix nt_terminate_process missing CloseHandle (#8294)
Signed-off-by: 21pages <pages21@163.com>
2024-06-08 21:15:01 +08:00
Yevhen Popok be16f1be44 Update Ukrainian translation (#8293) 2024-06-08 20:51:06 +08:00
21pages fd0f85d565 no explorer.exe, judge by pid retrived from cpp (#8291)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-08 16:50:35 +08:00
21pages 8de5f3f0d3 not close connection if failed to start cm due to no explorer.exe (#8290)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-08 16:11:51 +08:00
21pages 0bb537b872 fix kill occupied ipc process, find with enumerate, kill with NtTerminateProcess (#8289)
* I reproduced the issue, that process did't have title, couldn't be connected
  to and taskkill not work
* Test whether ipc is opccupied with enumerating named pipe
* With NtTerminateProcess, it was killed successfully.
* There is a way to find the exact process which occupy the ipc, I have
  not check it, it's from https://github.com/winsiderss/systeminformer

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-08 14:09:16 +08:00
Mr-Update 987da00be0 Update de.rs (#8286) 2024-06-08 09:43:50 +08:00
jxdv e9e2214d29 update cs.rs (#8285) 2024-06-08 09:43:32 +08:00
jxdv ac9f3317f1 update sk.rs (#8284) 2024-06-08 09:43:17 +08:00
bovirus 7da85d277e Update Italian language (#8282) 2024-06-07 20:48:19 +08:00
solokot 274244b055 Update ru.rs (#8281) 2024-06-07 15:49:52 +08:00
fufesou 8fa611daed refact: Wayland, not support multiple displays (#8280)
* refact: Wayland, not support multiple displays

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: Wayland disable multiple for RemoteDesktop

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

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-07 12:59:42 +08:00
21pages 64d0fb17f7 add floating window setting (#8279)
* Set `disable-floating-window` in client ui, it shows enabled when
  option is enabled and has floating window permission.
* Remove ignore battery setting because not work on every device.
* When the phone orientation changes, make the Y coordinate change
  proportionally, when changing back, the floating window position is still the original one.
* Add custom client option `floating-window-untouchable` to make the
  click event pass through the floating window automically. Set it untouchable automically when transparency is 0.
* On my phone, floating window size 16 no works and 32 works, so keep
  the size range [32, 320]

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-07 11:04:18 +08:00
fufesou 6d1d844b14 refact: Wayland, do not show multi displays (#8277)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-07 09:54:50 +08:00
fufesou 686dd11d8e fix: peer menu, hidden by wrong check (#8275)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-07 00:20:55 +08:00
21pages 9d42ee9df8 vram avoid always fallback to gdi (#8272)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-06 22:52:31 +08:00
21pages 9562768a04 android floating window (#8268)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-05 23:11:44 +08:00
Kleofass 54b8daede4 Update lv.rs (#8262) 2024-06-05 18:44:47 +08:00
21pages bd51afe86c fix rustPointerInput jni parameter declaration, call new_string in with_local_frame (#8266)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-05 18:09:01 +08:00
fufesou a84b9bd2c8 fix: setMovable only on macos (#8261)
* fix: setMovable only on macos

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

* Refact and comments

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

* comments

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

* Refact comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-05 14:52:56 +08:00
rustdesk ce1dac3b86 attempt to fix local reference table overflow, https://github.com/rustdesk/rustdesk/issues/4118 2024-06-05 00:38:54 +08:00
rustdesk 2dcd9f02cd bump to 1.2.6 2024-06-03 21:01:41 +08:00
rustdesk 416d57bec6 https://github.com/rustdesk/rustdesk/pull/7971 2024-06-03 13:40:14 +08:00
21pages 7d56717cf5 msgbox responds to numpadEnter as submit (#8248)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-06-03 11:19:35 +08:00
fufesou 32ef5f47f8 fix: macos tab drag (#8242)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-02 10:56:29 +08:00
fufesou 32346c23e0 fix: macos, terminate, close main then remote windows (#8240)
* fix: macos, terminate, close main then remote windows

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

* comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-02 10:35:54 +08:00
fufesou b6ebf61d6c fix: macos, fullscreen, remove mimimize button (#8238)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-01 21:28:35 +08:00
fufesou d79efcedef fix: close all windows, obfuscated window id (#8237)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-01 21:28:03 +08:00
21pages 18464ec570 windows replace uni with cmd to start up main window after installation (#8235)
This is to avoid uni link not working

Other change: The install window now has the title "appname - Install" to distinguish it from the main application window.

Signed-off-by: 21pages <pages21@163.com>
2024-06-01 20:28:21 +08:00
fufesou ed5487a1fc refact: mobile actions (#8236)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-01 20:23:58 +08:00
fufesou df36580451 fix: tab, disable dragging (#8228)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-06-01 00:10:18 +08:00
rustdesk bd7790c1eb https://github.com/rustdesk/rustdesk-server-pro/issues/261 2024-05-31 20:58:55 +08:00
FastAct 67d66c6750 Update nl.rs (#8224) 2024-05-31 19:57:26 +08:00
21pages 1c00d7aa1a android broadcast display changed immediately when change scale (#8219)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-31 16:44:42 +08:00
21pages 68cabe596d update hwcodec, fix wrong gop, which causes FFmpeg nvenc vram encode delay (#8220)
Signed-off-by: 21pages <pages21@163.com>
2024-05-31 16:44:18 +08:00
21pages 8919ea65e3 fix, mac hwcodec decoding align use dst_align (#8215)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-30 23:40:25 +08:00
bovirus d4dda94e2a Update Italian language (#8214) 2024-05-30 16:57:14 +08:00
XLion ee58b37d1e Update translations (#8210)
* Update translations

Add additional hint for `texture_render_tip` to let user know that it may be an option to fix issue.

* fix formatting for tw.rs
2024-05-30 15:09:12 +08:00
rustdesk 06cb49ec71 preset-address-book-name preset-address-book-tag https://github.com/rustdesk/rustdesk-server-pro/issues/257 2024-05-30 14:27:47 +08:00
jxdv caca7e5860 update cs.rs (#8208) 2024-05-30 08:33:32 +08:00
jxdv 3ddb4c9799 update sk.rs (#8207) 2024-05-30 08:30:54 +08:00
21pages 74c24caae9 not show paste menu when mobile control mobile (#8204)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-29 20:38:56 +08:00
æLtorio 48e8a25f6e Add missing french language tips (#8201)
* french language tips

* makes spaces conformed to original file

---------

Co-authored-by: Ronan <ronan.le_meillat@highcanfly.club>
2024-05-29 19:10:47 +08:00
fufesou fd045043a1 fix: flutter image pub, version 3 to 4, get bytes (#8202)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-29 19:10:15 +08:00
Mr-Update 0e97696b47 Translation missing (#8199) 2024-05-29 17:11:13 +08:00
æLtorio 804764d529 french translations (#8198)
* french translations

* typo

---------

Co-authored-by: Ronan <ronan.le_meillat@highcanfly.club>
2024-05-29 16:52:40 +08:00
Yevhen Popok 98a38754d4 Update Ukrainian translation (#8195) 2024-05-29 15:07:37 +08:00
21pages 837382349e fix the bottom of FFmpeg vram qsv encoding being cropped (#8191)
Signed-off-by: 21pages <pages21@163.com>
2024-05-29 13:34:22 +08:00
fufesou d2f119b85e fix: no forcely adaptive view style, if peer is android (#8189)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-29 12:24:33 +08:00
fufesou 89d855d085 fix: deactive widget ancester (#8187)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-29 08:16:36 +08:00
bovirus 1bfafaf07c Update Italian language (#8185) 2024-05-29 08:15:17 +08:00
solokot cf4f073153 Update ru.rs (#8183) 2024-05-29 08:14:57 +08:00
flusheDData aca9ba1a49 term change (#8181)
* Update es.rs

New term and tip added

* Update es.rs

change representación por renderizado (render)
2024-05-29 08:14:31 +08:00
flusheDData 399e20a14a Update es.rs (#8179)
New term and tip added
2024-05-28 22:33:52 +08:00
fufesou 7ca5a0b977 fix: switch texture, do not input os password (#8178)
* refact: switch texture, do not input os password

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

* Switch texture, refresh video

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-28 22:33:28 +08:00
Mr-Update 74d4505b3d Update de.rs (#8177) 2024-05-28 21:56:36 +08:00
rustdesk df74a38b90 disable-group-panel for https://github.com/rustdesk/rustdesk-server-pro/issues/250 2024-05-28 20:10:12 +08:00
rustdesk e8d02905fe pre-elevate-service for https://github.com/rustdesk/rustdesk-server-pro/issues/252 2024-05-28 19:58:47 +08:00
rustdesk 123a45149d bump to 1.2.5 2024-05-28 17:14:55 +08:00
fufesou 72ec86b58d refact: texture render as an option (#8168)
* refact: texture render as an option

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

* refact: texture render, translation

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

* refact: texture render as option

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

* Update ui_interface.rs

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-05-28 16:42:30 +08:00
21pages 010b17509a fix android get raw data (#8171)
If `std::ptr::copy_nonoverlapping` is not in `take`, it's not protected by the lock.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-28 16:35:59 +08:00
rustdesk 278d593580 adjust log file days from 7 days to 31 days 2024-05-28 15:52:50 +08:00
FastAct f997a1ff52 Update nl.rs (#8170) 2024-05-28 15:40:04 +08:00
21pages 036d10cfbe android: for software encoding and screen size > 1200, display size reduced to half (#8167)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-28 12:43:13 +08:00
fufesou b0c21e927b https://github.com/rustdesk-org/rustdesk_desktop_multi_window/pull/11 (#8166)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-28 10:30:56 +08:00
21pages fcece3732c android request external stroage when start service for log (#8161)
This will not influence file transfer permission. Requesting external storage permissions does not turn on file transfer permissions, and turning off file transfer permissions does not turn off external storage permissions.

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-27 21:01:09 +08:00
21pages c7308dbbc9 fix mediacodec bad encoding quality (#8159)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-27 19:34:40 +08:00
fufesou 9ce62dc584 fix: window backgroud color (#8155)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-27 09:29:31 +08:00
21pages 0442f7012b fix mac render memory, dispose old decoded image (#8140)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-27 09:27:30 +08:00
mehdi-song e7f0f0ff8d Update fa.rs (#8141) 2024-05-25 09:58:53 +08:00
21pages 4fd4b24fa2 call capture_displays when no enable flutter_texture_render (#8146)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-25 09:45:12 +08:00
21pages 17d18f1dd8 impore fps control, quicker send refresh and clear video queue (#8137)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-24 17:21:16 +08:00
21pages 902e166f0c fix mediacodec check_bitrate_range not work (#8133)
should check with mc_name, and that cause the highest custom bitrate
being invalid

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-24 14:07:48 +08:00
fufesou 1efce51222 fix: restore window, on connection (#8129)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-23 22:11:40 +08:00
21pages 7da09f6296 sort address book name dropdown (#8127)
Signed-off-by: 21pages <pages21@163.com>
2024-05-23 17:44:48 +08:00
fufesou b8d9c4c378 fix: mutlwindow, fullscreen (#8123)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-05-23 09:51:19 +08:00
21pages 49b0630752 fix android mediacodec encoding align (#8121)
* update ffmpeg, mediacodec encode align 64
* more d3d11 decode availablity check
* remove unused mediacodec info

Signed-off-by: 21pages <sunboeasy@gmail.com>
2024-05-22 20:18:09 +08:00
Yevhen Popok 91f07b4b03 Update Ukrainian translation (#8118) 2024-05-22 17:17:59 +08:00
21pages b2f4ba0882 fix cursor menu divider and Auto (null) (#8116)
Signed-off-by: 21pages <pages21@163.com>
2024-05-22 15:07:47 +08:00
fufesou 1acd7bd19c fix: macos fullscreen state (#8110)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-21 23:38:27 +08:00
fufesou 534fc9c40c fix: linux, login wayland, server (#8111)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-21 23:35:47 +08:00
fufesou e192f10c56 fix: proxy, username, enable (#8109)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-21 21:27:32 +08:00
fufesou 676ee99709 fix: revert window border width on Win and Mac (#8100)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-20 16:24:28 +08:00
fufesou 6e5622a97a fix: user option, enable file copy paste (#8096)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-19 20:41:19 +08:00
fufesou d8c9250aab refact: custom client proxy (#8093)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-19 16:40:49 +08:00
fufesou 986b9fb0e0 refact: custom client, remove enable-check-update (#8092)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-19 16:32:15 +08:00
fufesou 3c502c6fc2 refact: replace some option keys (#8090)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-19 14:07:42 +08:00
fufesou 72d59af7b0 fix: popup menu, nav, pop (#8089)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-18 23:55:05 +08:00
21pages d3eaa6600d fix, windows try kill flutter main window process only when --server's ipc is (#8086)
occupied

Signed-off-by: 21pages <pages21@163.com>
2024-05-18 23:14:42 +08:00
fufesou 96f41fcc02 refact: custom client, more advanced settings (#8085)
* refact: custom client, more advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* feat: custom client, more advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-18 23:13:54 +08:00
21pages c2b7810c33 windows kill flutter main window when --server close (#8077)
Signed-off-by: 21pages <pages21@163.com>
2024-05-18 08:24:28 +08:00
fufesou 44d4e13fa7 fix: windows, restore windows pos (#8075)
* fix: windows, restore windows pos

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: restore window pos, a better way to get cur scale factor

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* chore

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-18 08:23:09 +08:00
ddsardella 1dd7cd9384 Update Dockerfile for missing pam dev lib (#8078)
After failing to build with this due to the missing pam development library package I have added it to the DockerFile for the build image
2024-05-18 08:21:40 +08:00
fufesou 8357d4675a Fix/custom client advanced settings (#8066)
* fix: custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: custom client, default options

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: cargo test

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: remove prefix $ and unify option keys

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* refact: custom client, advanced options

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* debug custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* custom client, advanced settings. Add filter-transfer to display settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* custom client, advanced settings

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: custom client, advanced settings, codec

Signed-off-by: fufesou <shuanglongchen@yeah.net>

* fix: custom client, advanced settings, whitelist

Signed-off-by: fufesou <shuanglongchen@yeah.net>

---------

Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-17 14:19:11 +08:00
21pages 3a4390e0c7 fix wrong feature to call update_supported_encoding (#8067)
Signed-off-by: 21pages <pages21@163.com>
2024-05-16 16:05:18 +08:00
XLion 68fa688c96 Update tw.rs (#8065) 2024-05-16 15:48:39 +08:00
Mr-Update 42428261d7 Update de.rs (#8063) 2024-05-15 17:20:03 +08:00
flusheDData e01b1ed04d Android new call tip (#8057)
* Update es.rs

New terms added

* Update es.rs

New tip added

---------

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-05-15 09:42:07 +08:00
Mikhail Samodurov 4e5dcd827b CI: Remove unused apt package (#8048)
* Update bridge.yml

Removed apt instructions for metapackages clang and llvm in favor of specific version package

* Replaced apt instructions to use specific llvm and libclang version
2024-05-15 07:38:31 +08:00
flusheDData e8003510ef Update es.rs (#8047)
New terms added

Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-05-15 07:15:26 +08:00
bovirus da23e26a70 Update Italian language (#8052) 2024-05-15 07:12:33 +08:00
jxdv c5b781fb02 update sk.rs (#8053) 2024-05-15 07:12:14 +08:00
jxdv 3bb1c22f49 update cs.rs (#8054) 2024-05-15 07:11:46 +08:00
fufesou 53647fd58e fix: mobile chat icon (#8041)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-14 15:35:34 +08:00
fufesou 0500bf070e refact: android audio input, voice call (#8037)
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2024-05-14 09:20:27 +08:00
21pages d70b0cdd4f Not require both max-width and max-height of mediacodec larger than (#8036)
screen width and screen height

* Only use hardware codec, when api < 29, judge with codec name prefix.

Signed-off-by: 21pages <pages21@163.com>
2024-05-13 20:24:50 +08:00
21pages 7e09809ad8 auto codec, h265 > h264 > vp9/vp8 (#8032)
Signed-off-by: 21pages <pages21@163.com>
2024-05-13 16:27:48 +08:00
21pages a7499c2de8 add ffmpeg mediacodec h264/h265 encode (#8028)
* Check available when app start from kotlin via get codec info
* For latency free, repeat encode 10 frame at most when capture return WouldBlock
* For changing quality, kotlin support but jni doesn't support, rerun video service when quality is manualy
  changed
* 3 or 6 times bitrate for mediacodec because its quality is poor

Signed-off-by: 21pages <pages21@163.com>
2024-05-13 12:39:04 +08:00
rustdesk 4c99b8c70e upgrade tokio to 3.17 for a windows named pipe race condition,
https://github.com/tokio-rs/mio/pull/1760, https://github.com/tokio-rs/tokio/issues/6369
2024-05-12 14:08:21 +08:00
XLion 8b6913d31f Update tw.rs (#8022) 2024-05-12 10:49:49 +08:00
rustdesk 97f0642a8b remove fdroid, no idea why do we need this. 2024-05-11 16:27:44 +08:00
rustdesk a04dd6ad31 split fdroid ci out 2024-05-11 16:24:04 +08:00
474 changed files with 39273 additions and 27463 deletions
+9 -1
View File
@@ -1,8 +1,16 @@
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"] rustflags = ["-Ctarget-feature=+crt-static"]
[target.i686-pc-windows-msvc] [target.i686-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"] rustflags = ["-C", "target-feature=+crt-static", "-C", "link-args=/NODEFAULTLIB:MSVCRT"]
[target.'cfg(target_os="macos")'] [target.'cfg(target_os="macos")']
rustflags = [ rustflags = [
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null", "-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
] ]
#[target.'cfg(target_os="linux")']
# glibc-static required, this may fix https://github.com/rustdesk/rustdesk/issues/9103, but I do not want this big change
# this is unlikely to help also, because the other so files still use libc dynamically
#rustflags = [
# "-C", "link-args=-Wl,-Bstatic -lc -Wl,-Bdynamic"
#]
[net]
git-fetch-with-cli = true
+4 -4
View File
@@ -26,8 +26,8 @@ body:
- type: input - type: input
id: os id: os
attributes: attributes:
label: Operating system(s) on local side and 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 side -> remote side. description: What operating system(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
placeholder: | placeholder: |
Windows 10 -> osx Windows 10 -> osx
validations: validations:
@@ -35,8 +35,8 @@ body:
- type: input - type: input
id: version id: version
attributes: attributes:
label: RustDesk Version(s) on local side and 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 side -> remote side. description: What RustDesk version(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
placeholder: | placeholder: |
1.1.9 -> 1.1.8 1.1.9 -> 1.1.8
validations: validations:
+11
View File
@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "gitsubmodule"
directory: "/"
target-branch: "master"
schedule:
interval: "daily"
commit-message:
prefix: "Git submodule"
labels:
- "dependencies"
@@ -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) {
+11 -6
View File
@@ -6,7 +6,8 @@ on:
workflow_call: workflow_call:
env: env:
FLUTTER_VERSION: "3.16.9" CARGO_EXPAND_VERSION: "1.0.95"
FLUTTER_VERSION: "3.22.3"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1" FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503 RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
@@ -25,6 +26,8 @@ jobs:
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: recursive
- name: Install prerequisites - name: Install prerequisites
run: | run: |
@@ -38,10 +41,8 @@ jobs:
git \ git \
g++ \ g++ \
libclang-10-dev \ libclang-10-dev \
libclang-dev \
libgtk-3-dev \ libgtk-3-dev \
llvm-10-dev \ llvm-10-dev \
llvm-dev \
nasm \ nasm \
ninja-build \ ninja-build \
pkg-config \ pkg-config \
@@ -75,12 +76,14 @@ jobs:
- name: Install flutter rust bridge deps - name: Install flutter rust bridge deps
shell: bash shell: bash
run: | run: |
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" cargo install cargo-expand --version ${{ env.CARGO_EXPAND_VERSION }} --locked
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 - name: Run flutter rust bridge
run: | 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 - name: Upload Artifact
uses: actions/upload-artifact@master uses: actions/upload-artifact@master
@@ -91,3 +94,5 @@ jobs:
./src/bridge_generated.io.rs ./src/bridge_generated.io.rs
./flutter/lib/generated_bridge.dart ./flutter/lib/generated_bridge.dart
./flutter/lib/generated_bridge.freezed.dart ./flutter/lib/generated_bridge.freezed.dart
./flutter/macos/Runner/bridge_generated.h
./flutter/ios/Runner/bridge_generated.h
-230
View File
@@ -1,230 +0,0 @@
name: Flutter Nightly MacOS Arm64 Build
on:
#schedule:
# schedule build every night
# - cron: "0/6 * * * *"
workflow_dispatch:
env:
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
CARGO_NDK_VERSION: "3.1.2"
LLVM_VERSION: "15.0.6"
FLUTTER_VERSION: "3.19.6"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
# for arm64 linux because official Dart SDK does not work
FLUTTER_ELINUX_VERSION: "3.16.9"
TAG_NAME: "nightly"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.03.25
VCPKG_COMMIT_ID: "a34c873a9717a888f58dc05268dea15592c2f0ff"
VERSION: "1.2.4"
NDK_VERSION: "r26d"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
# To make a custom build with your own servers set the below secret values
RS_PUB_KEY: "${{ secrets.RS_PUB_KEY }}"
RENDEZVOUS_SERVER: "${{ secrets.RENDEZVOUS_SERVER }}"
API_SERVER: "${{ secrets.API_SERVER }}"
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
jobs:
build-appimage:
name: Build image ${{ matrix.job.target }}
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
job:
- {
target: x86_64-unknown-linux-gnu,
arch: x86_64,
}
- {
target: aarch64-unknown-linux-gnu,
arch: aarch64,
}
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Rename Binary
run: |
sudo apt-get update -y
sudo apt-get install -y wget libarchive-tools
wget https://github.com/rustdesk/rustdesk/releases/download/nightly/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb
mv rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb rustdesk-${{ env.VERSION }}.deb
- name: Patch archlinux PKGBUILD
if: matrix.job.arch == 'x86_64'
run: |
sed -i "s/x86_64/${{ matrix.job.arch }}/g" res/PKGBUILD
if [[ "${{ matrix.job.arch }}" == "aarch64" ]]; then
sed -i "s/linux\/x64/linux\/arm64/g" ./res/PKGBUILD
fi
bsdtar -zxvf rustdesk-${{ env.VERSION }}.deb
tar -xvf ./data.tar.xz
case ${{ matrix.job.arch }} in
aarch64)
mkdir -p flutter/build/linux/arm64/release/bundle
cp -rf usr/lib/rustdesk/* flutter/build/linux/arm64/release/bundle/
;;
x86_64)
mkdir -p flutter/build/linux/x64/release/bundle
cp -rf usr/lib/rustdesk/* flutter/build/linux/x64/release/bundle/
;;
esac
- name: Build archlinux package
if: matrix.job.arch == 'x86_64'
uses: rustdesk-org/arch-makepkg-action@master
with:
packages: >
llvm
clang
libva
libvdpau
rust
gstreamer
unzip
git
cmake
gcc
curl
wget
nasm
zip
make
pkg-config
clang
gtk3
xdotool
libxcb
libxfixes
alsa-lib
pipewire
python
ttf-arphic-uming
libappindicator-gtk3
pam
gst-plugins-base
gst-plugin-pipewire
scripts: |
cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f
- name: Publish archlinux package
if: matrix.job.arch == 'x86_64'
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
res/rustdesk-${{ env.VERSION }}*.zst
- name: Build appimage package
shell: bash
run: |
# set-up appimage-builder
pushd /tmp
wget -O appimage-builder-x86_64.AppImage https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
chmod +x appimage-builder-x86_64.AppImage
sudo mv appimage-builder-x86_64.AppImage /usr/local/bin/appimage-builder
popd
# run appimage-builder
pushd appimage
sudo appimage-builder --skip-tests --recipe ./AppImageBuilder-${{ matrix.job.arch }}.yml
- name: Publish appimage package
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
./appimage/rustdesk-${{ env.VERSION }}-*.AppImage
build-flatpak:
name: Build Flatpak ${{ matrix.job.target }}
runs-on: ${{ matrix.job.on }}
strategy:
fail-fast: false
matrix:
job:
- {
target: x86_64-unknown-linux-gnu,
distro: ubuntu18.04,
on: ubuntu-20.04,
arch: x86_64,
}
- {
target: aarch64-unknown-linux-gnu,
# try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub"
distro: ubuntu22.04,
on: [self-hosted, Linux, ARM64],
arch: aarch64,
}
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Rename Binary
run: |
sudo apt-get update -y
sudo apt-get install -y wget
wget https://github.com/rustdesk/rustdesk/releases/download/nightly/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb
mv rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.deb rustdesk-${{ env.VERSION }}.deb
- uses: rustdesk-org/run-on-arch-action@amd64-support
name: Build rustdesk flatpak package for ${{ matrix.job.arch }}
id: rpm
with:
arch: ${{ matrix.job.arch }}
distro: ${{ matrix.job.distro }}
githubToken: ${{ github.token }}
setup: |
ls -l "${PWD}"
dockerRunArgs: |
--volume "${PWD}:/workspace"
shell: /bin/bash
install: |
apt-get update -y
apt-get install -y \
curl \
git \
rpm \
wget
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
# 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 build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.flatpak com.rustdesk.RustDesk
- name: Publish flatpak package
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
flatpak/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.flatpak
+8 -2
View File
@@ -4,9 +4,9 @@ env:
# MIN_SUPPORTED_RUST_VERSION: "1.46.0" # MIN_SUPPORTED_RUST_VERSION: "1.46.0"
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates" # CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2023.10.19 # vcpkg version: 2024.11.16
# for multiarch gcc compatibility # for multiarch gcc compatibility
VCPKG_COMMIT_ID: "8eb57355a4ffb410a2e94c07b4dca2dffbee8e50" VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
on: on:
workflow_dispatch: workflow_dispatch:
@@ -45,6 +45,8 @@ jobs:
# steps: # steps:
# - name: Checkout source code # - name: Checkout source code
# uses: actions/checkout@v3 # uses: actions/checkout@v3
# with:
# submodules: recursive
# - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }}) # - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
# uses: actions-rs/toolchain@v1 # uses: actions-rs/toolchain@v1
@@ -92,6 +94,8 @@ jobs:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: recursive
- name: Install prerequisites - name: Install prerequisites
shell: bash shell: bash
@@ -112,6 +116,8 @@ jobs:
libgstreamer-plugins-base1.0-dev \ libgstreamer-plugins-base1.0-dev \
libgtk-3-dev \ libgtk-3-dev \
libpulse-dev \ libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \ libxcb-randr0-dev \
libxcb-shape0-dev \ libxcb-shape0-dev \
libxcb-xfixes0-dev \ libxcb-xfixes0-dev \
+39
View File
@@ -0,0 +1,39 @@
name: Fdroid version file generation
on:
workflow_dispatch:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+-[0-9]+'
jobs:
# https://gitlab.com/fdroid/fdroiddata/-/blob/master/metadata/com.carriez.flutter_hbb.yml
# Finds latest release and transforms F-Droid version code from version as follows:
# X.Y.Z-A => X * 1e6 + Y * 1e4 + Z * 1e2 + A
update-fdroid-version-file:
name: Publish RustDesk version file for F-Droid updater
runs-on: ubuntu-latest
steps:
- name: Generate RustDesk version file
run: |
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
UPSTREAM_VERNAME="${GITHUB_REF##refs/tags/}"
UPSTREAM_VERNAME="${UPSTREAM_VERNAME##v}"
else
UPSTREAM_VERNAME="$(curl https://api.github.com/repos/rustdesk/rustdesk/releases/latest | jq -r .tag_name | sed 's/^v//')"
fi
UPSTREAM_VERCODE="$(echo "$UPSTREAM_VERNAME" | tr '.' ' ' | tr '-' ' ' | while read -r MAJOR MINOR PATCH REV; do [ -z "$MAJOR" ] && MAJOR=0; [ -z "$MINOR" ] && MINOR=0; [ -z "$PATCH" ] && PATCH=0; [ -z "$REV" ] && REV=0; echo "$(( 1000000 * $MAJOR + 10000 * $MINOR + 100 * $PATCH + $REV ))"; done)"
echo "versionName=$UPSTREAM_VERNAME" > rustdesk-version.txt
echo "versionCode=$UPSTREAM_VERCODE" >> rustdesk-version.txt
shell: bash
- name: Publish RustDesk version file
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: "fdroid-version"
files: |
./rustdesk-version.txt
File diff suppressed because it is too large Load Diff
-20
View File
@@ -16,23 +16,3 @@ jobs:
with: with:
upload-artifact: true upload-artifact: true
upload-tag: ${{ github.ref_name }} upload-tag: ${{ github.ref_name }}
update-fdroid-version-file:
name: Publish RustDesk version file for F-Droid updater
runs-on: ubuntu-latest
steps:
- name: Generate RustDesk version file
run: |
UPSTREAM_VERNAME="$GITHUB_REF_NAME"
UPSTREAM_VERCODE="$(echo "$UPSTREAM_VERNAME" | tr -d '.')"
echo "versionName=$UPSTREAM_VERNAME" > rustdesk-version.txt
echo "versionCode=$UPSTREAM_VERCODE" >> rustdesk-version.txt
shell: bash
- name: Publish RustDesk version file
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: "fdroid-version"
files: |
./rustdesk-version.txt
-84
View File
@@ -1,84 +0,0 @@
name: Flutter Windows History Build
on: [workflow_dispatch]
env:
LLVM_VERSION: "10.0"
FLUTTER_VERSION: "3.16.9"
TAG_NAME: "tmp"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
VERSION: "1.2.4"
jobs:
build-for-history-windows:
name: ${{ matrix.job.date }}
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { target: x86_64-pc-windows-msvc, os: windows-2022, arch: x86_64, date: 2023-08-04, ref: 72c198a1e94cc1e0242fce88f92b3f3caedcd0c3 }
steps:
- name: Checkout source code
uses: actions/checkout@v4
with:
ref: ${{ matrix.job.ref }}
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
with:
version: ${{ env.LLVM_VERSION }}
- name: Install flutter
uses: subosito/flutter-action@v2.12.0 #https://github.com/subosito/flutter-action/issues/277
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.job.target }}
override: true
components: rustfmt
profile: minimal # minimal component installation (ie, no documentation)
- name: Install flutter rust bridge deps
run: |
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:
vcpkgDirectory: C:\vcpkg
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
- name: Install vcpkg dependencies
run: |
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
shell: bash
- name: Build rustdesk
run: python3 .\build.py --portable --hwcodec --flutter
- name: Build self-extracted executable
shell: bash
run: |
pushd ./libs/portable
python3 ./generate.py -f ../../flutter/build/windows/runner/Release/ -o . -e ../../flutter/build/windows/runner/Release/rustdesk.exe
popd
mkdir -p ./SignOutput
mv ./target/release/rustdesk-portable-packer.exe ./SignOutput/rustdesk-${{ matrix.job.date }}-${{ matrix.job.target }}.exe
- name: Publish Release
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
./SignOutput/rustdesk-*.exe
+418
View File
@@ -0,0 +1,418 @@
name: playground
on:
#schedule:
# schedule build every night
# - cron: "0/6 * * * *"
workflow_dispatch:
env:
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
CARGO_NDK_VERSION: "3.1.2"
LLVM_VERSION: "15.0.6"
FLUTTER_VERSION: "3.22.2"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
# for arm64 linux because official Dart SDK does not work
FLUTTER_ELINUX_VERSION: "3.16.9"
TAG_NAME: "nightly"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
# vcpkg version: 2024.11.16
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
VERSION: "1.3.8"
NDK_VERSION: "r26d"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
# To make a custom build with your own servers set the below secret values
RS_PUB_KEY: "${{ secrets.RS_PUB_KEY }}"
RENDEZVOUS_SERVER: "${{ secrets.RENDEZVOUS_SERVER }}"
API_SERVER: "${{ secrets.API_SERVER }}"
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
jobs:
build-for-macOS:
name: ${{ matrix.job.target }}
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- {
target: x86_64-apple-darwin,
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,
flutter: "3.13.9",
ref: "f6509e3fd6917aa976bad2fc684182601ebf2434",
bridge: "1.80.1",
date: "20231219"
}
- {
target: x86_64-apple-darwin,
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,
flutter: "3.10.6",
ref: "f6509e3fd6917aa976bad2fc684182601ebf2434",
bridge: "1.80.1",
date: "20231219"
}
- {
target: x86_64-apple-darwin,
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,
flutter: "3.10.6",
ref: "85ddfc0739f052cab0029c46b899b959ee94eeb8",
bridge: "1.80.1",
date: "20231119"
}
- {
target: x86_64-apple-darwin,
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,
flutter: "3.13.9",
ref: "85ddfc0739f052cab0029c46b899b959ee94eeb8",
bridge: "1.80.1",
date: "20231119"
}
steps:
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v6
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Checkout source code
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
with:
p12-file-base64: ${{ secrets.MACOS_P12_BASE64 }}
p12-password: ${{ secrets.MACOS_P12_PASSWORD }}
keychain: rustdesk
- name: Check sign and import sign key
if: env.MACOS_P12_BASE64 != null
run: |
security default-keychain -s rustdesk.keychain
security find-identity -v
- name: Import notarize key
if: env.MACOS_P12_BASE64 != null
uses: timheuer/base64-to-file@v1.2
with:
# https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_rcodesign.html#notarizing-and-stapling
fileName: rustdesk.json
fileDir: ${{ github.workspace }}
encodedString: ${{ secrets.MACOS_NOTARIZE_JSON }}
- name: Install rcodesign tool
if: env.MACOS_P12_BASE64 != null
shell: bash
run: |
pushd /tmp
wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-macos-universal.tar.gz
tar -zxvf apple-codesign-0.22.0-macos-universal.tar.gz
mv apple-codesign-0.22.0-macos-universal/rcodesign /usr/local/bin
popd
- name: Install build runtime
run: |
brew install llvm create-dmg nasm cmake gcc wget ninja pkg-config
- name: Install flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ matrix.job.flutter }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ env.RUST_VERSION }}
targets: ${{ matrix.job.target }}
components: "rustfmt"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: ${{ matrix.job.os }}
- name: Install flutter rust bridge deps
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" --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
~/.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: Setup vcpkg with Github Actions binary cache
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
- name: Install vcpkg dependencies
run: |
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
- name: Restore from cache and install vcpkg
uses: lukka/run-vcpkg@v7
if: false
with:
setupOnly: true
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
- name: Install vcpkg dependencies
if: false
run: |
$VCPKG_ROOT/vcpkg install libvpx libyuv opus aom
- name: Show version information (Rust, cargo, Clang)
shell: bash
run: |
clang --version || true
rustup -V
rustup toolchain list
rustup default
cargo -V
rustc -V
- name: Build rustdesk
run: |
./build.py --flutter ${{ matrix.job.extra-build-args }}
- name: create unsigned dmg
run: |
CREATE_DMG="$(command -v create-dmg)"
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG"
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
- name: Codesign app and create signed dmg
if: env.MACOS_P12_BASE64 != null
run: |
# Patch create-dmg to give more attempts to unmount image
CREATE_DMG="$(command -v create-dmg)"
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG"
# Unlock keychain
security default-keychain -s rustdesk.keychain
security unlock-keychain -p ${{ secrets.MACOS_P12_PASSWORD }} rustdesk.keychain
# start sign the rustdesk.app and dmg
rm -rf *.dmg || true
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict rustdesk-${{ env.VERSION }}.dmg -vvv
# notarize the rustdesk-${{ env.VERSION }}.dmg
rcodesign notary-submit --api-key-path ${{ github.workspace }}/rustdesk.json --staple rustdesk-${{ env.VERSION }}.dmg
- name: Rename rustdesk
run: |
for name in rustdesk*??.dmg; do
mv "$name" "${name%%.dmg}-${{ matrix.job.arch }}-flutter${{ matrix.job.flutter }}-flutter${{ matrix.job.date }}.dmg"
done
- name: Publish DMG package
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
rustdesk*-${{ matrix.job.arch }}*.dmg
build-rustdesk-android:
if: false
name: build rustdesk android apk ${{ matrix.job.target }}
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- {
arch: aarch64,
target: aarch64-linux-android,
os: ubuntu-20.04,
openssl-arch: android-arm64,
ref: master, # latest
}
steps:
- name: Checkout source code
uses: actions/checkout@v3
with:
ref: ${{ matrix.job.ref }}
submodules: recursive
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
clang \
cmake \
curl \
gcc-multilib \
git \
g++ \
g++-multilib \
libayatana-appindicator3-dev\
libasound2-dev \
libc6-dev \
libclang-10-dev \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
libgtk-3-dev \
libpam0g-dev \
libpulse-dev \
libva-dev \
libvdpau-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
libxdo-dev \
libxfixes-dev \
llvm-10-dev \
nasm \
yasm \
ninja-build \
openjdk-11-jdk-headless \
pkg-config \
tree \
wget
- name: Install flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ env.RUST_VERSION }}
components: "rustfmt"
- 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" --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
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: ${{ env.NDK_VERSION }}
add-to-path: true
- name: Setup vcpkg with Github Actions binary cache
uses: lukka/run-vcpkg@v11
with:
vcpkgDirectory: /opt/artifacts/vcpkg
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
- name: Install vcpkg dependencies
run: |
case ${{ matrix.job.target }} in
aarch64-linux-android)
./flutter/build_android_deps.sh arm64-v8a
;;
armv7-linux-androideabi)
./flutter/build_android_deps.sh armeabi-v7a
;;
esac
shell: bash
- name: Clone deps
shell: bash
run: |
pushd /opt
git clone https://github.com/rustdesk-org/rustdesk_thirdparty_lib.git --depth=1
ls -ls /opt/artifacts/vcpkg/installed/arm64-android/lib/
# cp -rf /opt/rustdesk_thirdparty_lib/vcpkg/* /opt/artifacts/vcpkg/
ls -ls /opt/artifacts/vcpkg/installed/arm64-android/lib/
- name: Build rustdesk lib
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
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 }} --locked
case ${{ matrix.job.target }} in
aarch64-linux-android)
./flutter/ndk_arm64.sh
mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
;;
armv7-linux-androideabi)
./flutter/ndk_arm.sh
mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so
;;
esac
- name: Build rustdesk
shell: bash
env:
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
run: |
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
# temporary use debug sign config
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
case ${{ matrix.job.target }} in
aarch64-linux-android)
mkdir -p ./flutter/android/app/src/main/jniLibs/arm64-v8a
cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
# build flutter
pushd flutter
flutter build apk --release --target-platform android-arm64 --split-per-abi
mv build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk
;;
armv7-linux-androideabi)
mkdir -p ./flutter/android/app/src/main/jniLibs/armeabi-v7a
cp ${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/arm-linux-androideabi/libc++_shared.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/
cp ./target/${{ matrix.job.target }}/release/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/armeabi-v7a/librustdesk.so
# build flutter
pushd flutter
flutter build apk --release --target-platform android-arm --split-per-abi
mv build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk
;;
esac
popd
mkdir -p signed-apk; pushd signed-apk
mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk ./rustdesk-test-${{ matrix.job.ref }}-${{ matrix.job.ndk }}.apk
- uses: r0adkll/sign-android-release@v1
name: Sign app APK
if: env.ANDROID_SIGNING_KEY != null
id: sign-rustdesk
with:
releaseDirectory: ./signed-apk
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
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"
- name: Publish signed apk package
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
${{steps.sign-rustdesk.outputs.signedReleaseFile}}
+2 -2
View File
@@ -4,9 +4,9 @@ on:
types: [released] types: [released]
jobs: jobs:
publish: publish:
runs-on: windows-latest # action can only be run on windows runs-on: ubuntu-latest
steps: steps:
- uses: vedantmgoyal2009/winget-releaser@v1 - uses: vedantmgoyal9/winget-releaser@main
with: with:
identifier: RustDesk.RustDesk identifier: RustDesk.RustDesk
version: ${{ github.event.release.tag_name }} version: ${{ github.event.release.tag_name }}
+3
View File
@@ -52,3 +52,6 @@ lib/generated_bridge.dart
examples/**/target/ examples/**/target/
# === # ===
vcpkg_installed vcpkg_installed
flutter/lib/generated_plugin_registrant.dart
libsciter.dylib
flutter/web/
+3
View File
@@ -0,0 +1,3 @@
[submodule "libs/hbb_common"]
path = libs/hbb_common
url = https://github.com/rustdesk/hbb_common
Generated
+1544 -1413
View File
File diff suppressed because it is too large Load Diff
+25 -15
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "rustdesk" name = "rustdesk"
version = "1.2.4" version = "1.3.8"
authors = ["rustdesk <info@rustdesk.com>"] authors = ["rustdesk <info@rustdesk.com>"]
edition = "2021" edition = "2021"
build= "build.rs" build= "build.rs"
@@ -16,10 +16,13 @@ crate-type = ["cdylib", "staticlib", "rlib"]
name = "naming" name = "naming"
path = "src/naming.rs" path = "src/naming.rs"
[[bin]]
name = "service"
path = "src/service.rs"
[features] [features]
inline = [] inline = []
cli = [] cli = []
flutter_texture_render = []
use_samplerate = ["samplerate"] use_samplerate = ["samplerate"]
use_rubato = ["rubato"] use_rubato = ["rubato"]
use_dasp = ["dasp"] use_dasp = ["dasp"]
@@ -37,6 +40,7 @@ unix-file-copy-paste = [
"dep:once_cell", "dep:once_cell",
"clipboard/unix-file-copy-paste", "clipboard/unix-file-copy-paste",
] ]
screencapturekit = ["cpal/screencapturekit"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -67,7 +71,7 @@ default-net = "0.14"
wol-rs = "1.0" wol-rs = "1.0"
flutter_rust_bridge = { version = "=1.80", features = ["uuid"], optional = true} flutter_rust_bridge = { version = "=1.80", features = ["uuid"], optional = true}
errno = "0.3" errno = "0.3"
rdev = { git = "https://github.com/fufesou/rdev" } rdev = { git = "https://github.com/rustdesk-org/rdev" }
url = { version = "2.3", features = ["serde"] } url = { version = "2.3", features = ["serde"] }
crossbeam-queue = "0.3" crossbeam-queue = "0.3"
hex = "0.4" hex = "0.4"
@@ -79,18 +83,22 @@ zip = "0.6"
shutdown_hooks = "0.1" shutdown_hooks = "0.1"
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] } totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
cpal = "0.15" # 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" ringbuf = "0.3"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
mac_address = "1.1" 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" sys-locale = "0.3"
enigo = { path = "libs/enigo", features = [ "with_serde" ] } enigo = { path = "libs/enigo", features = [ "with_serde" ] }
clipboard = { path = "libs/clipboard" } clipboard = { path = "libs/clipboard" }
ctrlc = "3.2" ctrlc = "3.2"
arboard = { git = "https://github.com/fufesou/arboard", branch = "feat/x11_set_conn_timeout", features = ["wayland-data-control"] } # arboard = { version = "3.4.0", features = ["wayland-data-control"] }
arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] }
clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" }
system_shutdown = "4.0" system_shutdown = "4.0"
qrcode-generator = "4.1" qrcode-generator = "4.1"
@@ -112,7 +120,7 @@ winapi = { version = "0.3", features = [
winreg = "0.11" winreg = "0.11"
windows-service = "0.6" windows-service = "0.6"
virtual_display = { path = "libs/virtual_display" } virtual_display = { path = "libs/virtual_display" }
impersonate_system = { git = "https://github.com/21pages/impersonate-system" } impersonate_system = { git = "https://github.com/rustdesk-org/impersonate-system" }
shared_memory = "0.12" shared_memory = "0.12"
tauri-winrt-notification = "0.1.2" tauri-winrt-notification = "0.1.2"
runas = "1.2" runas = "1.2"
@@ -136,7 +144,7 @@ image = "0.24"
keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" } keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" }
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies] [target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
wallpaper = { git = "https://github.com/21pages/wallpaper.rs" } wallpaper = { git = "https://github.com/rustdesk-org/wallpaper.rs" }
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support # https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
@@ -148,30 +156,32 @@ reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocki
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
psimple = { package = "libpulse-simple-binding", version = "2.27" } psimple = { package = "libpulse-simple-binding", version = "2.27" }
pulse = { package = "libpulse-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" async-process = "1.7"
mouce = { git="https://github.com/fufesou/mouce.git" } evdev = { git="https://github.com/rustdesk-org/evdev" }
evdev = { git="https://github.com/fufesou/evdev" }
dbus = "0.9" dbus = "0.9"
dbus-crossroads = "0.5" dbus-crossroads = "0.5"
pam = { git="https://github.com/fufesou/pam" } pam = { git="https://github.com/rustdesk-org/pam" }
users = { version = "0.11" } users = { version = "0.11" }
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
x11rb = {version = "0.12", features = ["all-extensions"], optional = true} x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
percent-encoding = {version = "2.3", optional = true} percent-encoding = {version = "2.3", optional = true}
once_cell = {version = "1.18", optional = true} once_cell = {version = "1.18", optional = true}
nix = { version = "0.29", features = ["term", "process"]}
gtk = "0.18"
termios = "0.3"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.13" android_logger = "0.13"
jni = "0.21" jni = "0.21"
android-wakelock = { git = "https://github.com/21pages/android-wakelock" } android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" }
[workspace] [workspace]
members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable"] members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable"]
exclude = ["vdi/host", "examples/custom_plugin"] exclude = ["vdi/host", "examples/custom_plugin"]
[package.metadata.winres] [package.metadata.winres]
LegalCopyright = "Copyright © 2024 Purslane Ltd. All rights reserved." LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved."
ProductName = "RustDesk" ProductName = "RustDesk"
FileDescription = "RustDesk Remote Desktop" FileDescription = "RustDesk Remote Desktop"
OriginalFilename = "rustdesk.exe" OriginalFilename = "rustdesk.exe"
+11 -1
View File
@@ -2,6 +2,7 @@ FROM debian:bullseye-slim
WORKDIR / WORKDIR /
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
RUN apt update -y && \ RUN apt update -y && \
apt install --yes --no-install-recommends \ apt install --yes --no-install-recommends \
g++ \ g++ \
@@ -18,9 +19,11 @@ RUN apt update -y && \
libxcb-shape0-dev \ libxcb-shape0-dev \
libxcb-xfixes0-dev \ libxcb-xfixes0-dev \
libasound2-dev \ libasound2-dev \
libpam0g-dev \
libpulse-dev \ libpulse-dev \
make \ make \
cmake \ wget \
libssl-dev \
unzip \ unzip \
zip \ zip \
sudo \ sudo \
@@ -30,6 +33,13 @@ RUN apt update -y && \
ninja-build && \ ninja-build && \
rm -rf /var/lib/apt/lists/* 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 && \ RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \ /vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom /vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
+14 -6
View File
@@ -1,11 +1,10 @@
<p align="center"> <p align="center">
<img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br> <img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
<a href="#free-public-servers">Servers</a> •
<a href="#raw-steps-to-build">Build</a> • <a href="#raw-steps-to-build">Build</a> •
<a href="#how-to-build-with-docker">Docker</a> • <a href="#how-to-build-with-docker">Docker</a> •
<a href="#file-structure">Structure</a> • <a href="#file-structure">Structure</a> •
<a href="#snapshot">Snapshot</a><br> <a href="#snapshot">Snapshot</a><br>
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br> [<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>]<br>
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b> <b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
</p> </p>
@@ -25,9 +24,12 @@ RustDesk welcomes contribution from everyone. See [CONTRIBUTING.md](docs/CONTRIB
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) [**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" alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb) 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 ## Dependencies
@@ -59,19 +61,19 @@ Please download Sciter dynamic library yourself.
```sh ```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
``` ```
### openSUSE Tumbleweed ### openSUSE Tumbleweed
```sh ```sh
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
``` ```
### Fedora 28 (CentOS 8) ### Fedora 28 (CentOS 8)
```sh ```sh
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
``` ```
### Arch (Manjaro) ### Arch (Manjaro)
@@ -125,6 +127,7 @@ Begin by cloning the repository and building the Docker container:
```sh ```sh
git clone https://github.com/rustdesk/rustdesk git clone https://github.com/rustdesk/rustdesk
cd rustdesk cd rustdesk
git submodule update --init --recursive
docker build -t "rustdesk-builder" . docker build -t "rustdesk-builder" .
``` ```
@@ -162,6 +165,10 @@ Please ensure that you are running these commands from the root of the RustDesk
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
> [!Caution]
> **Misuse Disclaimer:** <br>
> The developers of RustDesk do not condone or support any unethical or illegal use of this software. Misuse, such as unauthorized access, control or invasion of privacy, is strictly against our guidelines. The authors are not responsible for any misuse of the application.
## Screenshots ## Screenshots
![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651) ![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651)
@@ -171,3 +178,4 @@ Please ensure that you are running these commands from the root of the RustDesk
![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad) ![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad)
![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5) ![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
+4 -4
View File
@@ -18,8 +18,8 @@ AppDir:
id: rustdesk id: rustdesk
name: rustdesk name: rustdesk
icon: rustdesk icon: rustdesk
version: 1.2.4 version: 1.3.8
exec: usr/lib/rustdesk/rustdesk exec: usr/share/rustdesk/rustdesk
exec_args: $@ exec_args: $@
apt: apt:
arch: arch:
@@ -47,9 +47,9 @@ AppDir:
- libasound2 - libasound2
- libsystemd0 - libsystemd0
- curl - curl
- libva2
- libva-drm2 - libva-drm2
- libva-x11-2 - libva-x11-2
- libvdpau1
- libgstreamer-plugins-base1.0-0 - libgstreamer-plugins-base1.0-0
- gstreamer1.0-pipewire - gstreamer1.0-pipewire
- libwayland-client0 - libwayland-client0
@@ -77,7 +77,7 @@ AppDir:
env: env:
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules
GDK_BACKEND: x11 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_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 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: test:
+7 -4
View File
@@ -18,8 +18,8 @@ AppDir:
id: rustdesk id: rustdesk
name: rustdesk name: rustdesk
icon: rustdesk icon: rustdesk
version: 1.2.4 version: 1.3.8
exec: usr/lib/rustdesk/rustdesk exec: usr/share/rustdesk/rustdesk
exec_args: $@ exec_args: $@
apt: apt:
arch: arch:
@@ -37,6 +37,9 @@ AppDir:
- sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted
universe multiverse universe multiverse
include: include:
# https://github.com/rustdesk/rustdesk/issues/9103
# Because of APPDIR_LIBRARY_PATH, this libc6 is not used, use LD_PRELOAD: $APPDIR/usr/lib/x86_64-linux-gnu/libc.so.6 may help, If you have time, please have a try.
# We modify APPDIR_LIBRARY_PATH to use system lib first because gst crashed if not doing so, but you can try to change it.
- libc6:amd64 - libc6:amd64
- libgtk-3-0 - libgtk-3-0
- libxcb-randr0 - libxcb-randr0
@@ -47,9 +50,9 @@ AppDir:
- libasound2 - libasound2
- libsystemd0 - libsystemd0
- curl - curl
- libva2
- libva-drm2 - libva-drm2
- libva-x11-2 - libva-x11-2
- libvdpau1
- libgstreamer-plugins-base1.0-0 - libgstreamer-plugins-base1.0-0
- gstreamer1.0-pipewire - gstreamer1.0-pipewire
- libwayland-client0 - libwayland-client0
@@ -77,7 +80,7 @@ AppDir:
env: env:
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/x86_64-linux-gnu/gio/modules:$APPDIR/usr/lib/x86_64-linux-gnu/gio/modules 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 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_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 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: test:
+45 -40
View File
@@ -9,6 +9,7 @@ import shutil
import hashlib import hashlib
import argparse import argparse
import sys import sys
from pathlib import Path
windows = platform.platform().startswith('Windows') windows = platform.platform().startswith('Windows')
osx = platform.platform().startswith( osx = platform.platform().startswith(
@@ -25,12 +26,17 @@ flutter_build_dir_2 = f'flutter/{flutter_build_dir}'
skip_cargo = False skip_cargo = False
def get_arch() -> str: def get_deb_arch() -> str:
custom_arch = os.environ.get("ARCH") custom_arch = os.environ.get("DEB_ARCH")
if custom_arch is None: if custom_arch is None:
return "amd64" return "amd64"
return custom_arch return custom_arch
def get_deb_extra_depends() -> str:
custom_arch = os.environ.get("DEB_ARCH")
if custom_arch == "armhf": # for arm32v7 libsciter-gtk.so
return ", libatomic1"
return ""
def system2(cmd): def system2(cmd):
exit_code = os.system(cmd) exit_code = os.system(cmd)
@@ -48,15 +54,7 @@ def get_version():
def parse_rc_features(feature): def parse_rc_features(feature):
available_features = { available_features = {}
'PrivacyMode': {
'platform': ['windows'],
'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3'
'/TempTopMostWindow_x64.zip',
'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3/checksum_md5',
'include': ['WindowInjection.dll'],
}
}
apply_features = {} apply_features = {}
if not feature: if not feature:
feature = [] feature = []
@@ -81,7 +79,6 @@ def parse_rc_features(feature):
elif isinstance(feature, list): elif isinstance(feature, list):
if windows: if windows:
# download third party is deprecated, we use github ci instead. # download third party is deprecated, we use github ci instead.
# force add PrivacyMode
# feature.append('PrivacyMode') # feature.append('PrivacyMode')
pass pass
for feat in feature: for feat in feature:
@@ -108,16 +105,14 @@ def make_parser():
nargs='+', nargs='+',
default='', default='',
help='Integrate features, windows only.' help='Integrate features, windows only.'
'Available: PrivacyMode. Special value is "ALL" and empty "". Default is empty.') 'Available: [Not used for now]. Special value is "ALL" and empty "". Default is empty.')
parser.add_argument('--flutter', action='store_true', parser.add_argument('--flutter', action='store_true',
help='Build flutter package', default=False) help='Build flutter package', default=False)
parser.add_argument('--disable-flutter-texture-render', action='store_true',
help='Build flutter package', default=False)
parser.add_argument( parser.add_argument(
'--hwcodec', '--hwcodec',
action='store_true', action='store_true',
help='Enable feature hwcodec' + ( 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( parser.add_argument(
'--vram', '--vram',
@@ -149,6 +144,12 @@ def make_parser():
"--package", "--package",
type=str type=str
) )
if osx:
parser.add_argument(
'--screencapturekit',
action='store_true',
help='Enable feature screencapturekit'
)
return parser return parser
@@ -278,10 +279,11 @@ def get_features(args):
features.append('vram') features.append('vram')
if args.flutter: if args.flutter:
features.append('flutter') features.append('flutter')
if not args.disable_flutter_texture_render:
features.append('flutter_texture_render')
if args.unix_file_copy_paste: if args.unix_file_copy_paste:
features.append('unix-file-copy-paste') features.append('unix-file-copy-paste')
if osx:
if args.screencapturekit:
features.append('screencapturekit')
print("features:", features) print("features:", features)
return features return features
@@ -291,14 +293,17 @@ def generate_control_file(version):
system2('/bin/rm -rf %s' % control_file_path) system2('/bin/rm -rf %s' % control_file_path)
content = """Package: rustdesk content = """Package: rustdesk
Section: net
Priority: optional
Version: %s Version: %s
Architecture: %s Architecture: %s
Maintainer: rustdesk <info@rustdesk.com> Maintainer: rustdesk <info@rustdesk.com>
Homepage: https://rustdesk.com Homepage: https://rustdesk.com
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g, libappindicator3-1, gstreamer1.0-pipewire 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. Description: A remote control software.
""" % (version, get_arch()) """ % (version, get_deb_arch(), get_deb_extra_depends())
file = open(control_file_path, "w") file = open(control_file_path, "w")
file.write(content) file.write(content)
file.close() file.close()
@@ -317,7 +322,7 @@ def build_flutter_deb(version, features):
os.chdir('flutter') os.chdir('flutter')
system2('flutter build linux --release') system2('flutter build linux --release')
system2('mkdir -p tmpdeb/usr/bin/') system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/lib/rustdesk') system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mkdir -p tmpdeb/etc/rustdesk/') system2('mkdir -p tmpdeb/etc/rustdesk/')
system2('mkdir -p tmpdeb/etc/pam.d/') system2('mkdir -p tmpdeb/etc/pam.d/')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/') system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
@@ -327,7 +332,7 @@ def build_flutter_deb(version, features):
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions') system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true') system2('rm tmpdeb/usr/bin/rustdesk || true')
system2( system2(
f'cp -r {flutter_build_dir}/* tmpdeb/usr/lib/rustdesk/') f'cp -r {flutter_build_dir}/* tmpdeb/usr/share/rustdesk/')
system2( system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') 'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2( system2(
@@ -338,8 +343,6 @@ def build_flutter_deb(version, features):
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2( system2(
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2(
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
system2( system2(
'cp ../res/startwm.sh tmpdeb/etc/rustdesk/') 'cp ../res/startwm.sh tmpdeb/etc/rustdesk/')
system2( system2(
@@ -352,7 +355,7 @@ def build_flutter_deb(version, features):
system2('mkdir -p tmpdeb/DEBIAN') system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version) generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') 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('dpkg-deb -b tmpdeb rustdesk.deb;')
system2('/bin/rm -rf tmpdeb/') system2('/bin/rm -rf tmpdeb/')
@@ -364,7 +367,7 @@ def build_flutter_deb(version, features):
def build_deb_from_folder(version, binary_folder): def build_deb_from_folder(version, binary_folder):
os.chdir('flutter') os.chdir('flutter')
system2('mkdir -p tmpdeb/usr/bin/') 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/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/') system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
@@ -372,7 +375,7 @@ def build_deb_from_folder(version, binary_folder):
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions') system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true') system2('rm tmpdeb/usr/bin/rustdesk || true')
system2( system2(
f'cp -r ../{binary_folder}/* tmpdeb/usr/lib/rustdesk/') f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/')
system2( system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/') 'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2( system2(
@@ -383,15 +386,13 @@ def build_deb_from_folder(version, binary_folder):
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2( system2(
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2(
'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/')
system2( system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") "echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
system2('mkdir -p tmpdeb/DEBIAN') system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version) generate_control_file(version)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/') 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('dpkg-deb -b tmpdeb rustdesk.deb;')
system2('/bin/rm -rf tmpdeb/') system2('/bin/rm -rf tmpdeb/')
@@ -404,12 +405,13 @@ def build_flutter_dmg(version, features):
if not skip_cargo: if not skip_cargo:
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project # set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
system2( 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 # copy dylib
system2( system2(
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib") "cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
os.chdir('flutter') os.chdir('flutter')
system2('flutter build macos --release') system2('flutter build macos --release')
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
''' '''
system2( 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") "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")
@@ -621,21 +623,24 @@ def main():
os.system('mkdir -p tmpdeb/etc/pam.d/') os.system('mkdir -p tmpdeb/etc/pam.d/')
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk') os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2('strip tmpdeb/usr/bin/rustdesk') system2('strip tmpdeb/usr/bin/rustdesk')
system2('mkdir -p tmpdeb/usr/lib/rustdesk') system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/') system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/')
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/') system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service') md5_file_folder("tmpdeb/")
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('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/') system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version) os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
def md5_file(fn): def md5_file(fn):
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest() 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__": if __name__ == "__main__":
+6 -3
View File
@@ -1,7 +1,7 @@
#[cfg(windows)] #[cfg(windows)]
fn build_windows() { fn build_windows() {
let file = "src/platform/windows.cc"; 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"); cc::Build::new().file(file).file(file2).compile("windows");
println!("cargo:rustc-link-lib=WtsApi32"); println!("cargo:rustc-link-lib=WtsApi32");
println!("cargo:rerun-if-changed={}", file); println!("cargo:rerun-if-changed={}", file);
@@ -61,7 +61,11 @@ fn install_android_deps() {
let target = format!("{}-android", target_arch); let target = format!("{}-android", target_arch);
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap(); let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
let mut path: std::path::PathBuf = vcpkg_root.into(); 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); path.push(target);
println!( println!(
"{}", "{}",
@@ -72,7 +76,6 @@ fn install_android_deps() {
); );
println!("cargo:rustc-link-lib=ndk_compat"); println!("cargo:rustc-link-lib=ndk_compat");
println!("cargo:rustc-link-lib=oboe"); println!("cargo:rustc-link-lib=oboe");
println!("cargo:rustc-link-lib=oboe_wrapper");
println!("cargo:rustc-link-lib=c++"); println!("cargo:rustc-link-lib=c++");
println!("cargo:rustc-link-lib=OpenSLES"); println!("cargo:rustc-link-lib=OpenSLES");
} }
+125
View File
@@ -0,0 +1,125 @@
# Atferdskodeks for bidragsyterpaktern
## Hva Vi Står For
Vi som medlemer, bidragere, og ledere står for å skape ett hat-fritt felleskap,
uansett alder, kroppstørrelse, synlig eller usynlige funksjonsnedsettninger,
etnesitet, kjønns karaktertrekk, kjønnsidentitet, kunnskapsnivå, utdanning,
sosial-økonomisk status, nasjonalitet, utsende, rase, religion, eller seksual
identitet og orientasjon.
Vi står for åpen, velkommende, mangfold, inklusiv og sunn oppførsel i vårt felleskap.
## Våre Standarer
Eksempler på oppførsel som hjelper ett positivt felleskap inkluderer:
* Vise empati og vennlighet mot andre mennesker
* Være respektfull ovenfor ulike meninger, synspunkter og erfaringer
* Gi og ta konstruktiv kritikk i beste mening
* Akseptere ansvar og unskylde seg for de som er utsatt av våre feil,
og lære av disse
* Fokusere på det som er best ikke bare for individer, men for felleskapet
Eksempler på uakseptabel oppførsel inkluderer:
* Bruk av seksualisert språk eller bilder, og seksual oppmerksomhet.
* Troll-ene, fornermende og nedsettende kommentarer, og personlig eller politiske angrep
* Offentlig eller privat trakassering
* Publisering av andres private informasjon, sånn som bosteds- og epost-addresser,
uten deres godskjenning.
* Andre rettningslinjer som kan bli sett på som upassende i en profesjonell setting.
## Håndhevingsansvar
Felleskapets ledere har ansvar for å klarifisere og håndheve våre standarer av
akseptert oppførsel og vill ta rimelige og rettferdige handliger som respons på
oppførsel de anser som upassende, truende, fornermende eller skadelig.
Felleskapets ledere har retten og ansvaret til å fjerne, redigere, eller avslå
kommentarer, commits, kode, wiki endringer, issues, og andre birag som ikke
samsvarer med disse etiske rettningslinjene, og vill kommunisere grunner for
moderatorenes valg når passende.
## Omfang
Disse etiske rettningslinjene gjelder innenfor alle platformene til felleskapet, og
de gjelder også når ett individ representerer felleskapet på offentlige medier.
Eksempler på representasjon av vårt felleskap inkluderer bruke av offisielle e-mail
addresser, publisering gjennom en offisiell sosial media bruker, eller oppførsel som en
utpekt representant på digitale og fysiske arrangsjemanger.
## Håndheving
Hendelser av misbruk, trakasserende eller på noen måte uakseptert oppførsel kann
bli raportert til felleskapets ledere med ansvar for håndheving på
[info@rustdesk.com](mailto:info@rustdesk.com).
All tilbakemelding vill bli sett gjennom og investigert rettferdig så fort som mulig.
Alle felleskapets ledere er obligert til å respektere privatlivet og sikkerhetet ovenfor
den som raporterer en hendelse.
## Håndhevings Guide
Felleskapets ledere vill følge disse Rettningslinjene for sammfunspåvirkning med
tanke på konsekvenser for en handling de anser i brudd med disse etiske rettningslinjene:
### 1. Korreksjon
**Sammfunspåvirkning**: Bruk av upassende språk eller annen oppførsel ansett som
uprofesjonelt eller uvelkommen i dette felleskapet.
**Konsekvens**: En privat, skrevet advarsel fra en leder av felleskapet, som
klarifiserer grunnlaget til hvorfor denne oppførselen var upassende. En offentlig
unskyldning kan bli forespurt.
### 2. Advarsel
**Sammfunspåvirkning**: Ett brudd på en singulær hendelse eller en serie handlinger.
**Konsekvens**: En advarsel med konsekvenser for kontinuerende oppførsel. Ingen
interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med
de som håndhever disse etiske rettningslinjene, er tillat for en spesifisert tidsperiode.
Dette inkluderer å unngå interaksjoner i felleskapets platformer, samt eksterne
kanaler, som f.eks sosial media. Brudd av disse vilkårene kan føre til midlertidig
eller permanent bannlysning.
### 3. Midlertidig Bannlysning
**Sammfunspåvirkning**: Ett særiøst brudd på felleskapets standarer, inkludert
vedvarende upassende oppførsel.
**Konsekvens**: En midlertidig bannlysning fra noen som helst interaksjon eller
offentlig kommunikasjon med felleskapet for en spesifisert tidsperiode. Ingen
interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med
de som håndhever disse etiske rettningslinjene, er tillat for denne perioden.
Brudd på disse vilkårene kan føre til permanent bannlysning.
### 4. Permanent Bannlysning
**Sammfunspåvirkning**: Demonstasjon av mønster i brudd på felleskapets standarer,
inklusivt vedvarende upassende oppførsel, trakassering av ett individ, eller
aggresjon mot eller nedsettelse av grupper individer.
**Konsekvens**: En permanent bannlysning fra alle offentlige interaksjoner i
felleskapet
## Attribusjon
Disse etiske rettningslinjene er adaptert fra [Contributor Covenant][homepage],
versjon 2.0, tilgjengelig ved
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Sammfunspåvirknings guid inspirert av
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For svar til vanlige spørsmål angående disse etiske rettningslinjene, se FAQ på
[https://www.contributor-covenant.org/faq][FAQ]. Oversettelse tilgjengelig
ved [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+87
View File
@@ -0,0 +1,87 @@
# 贡献者公约行为准则
## 我们的承诺
身为社区成员、贡献者和领袖,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。
我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。
## 我们的标准
有助于为我们的社区创造积极环境的行为例子包括但不限于:
* 表现出对他人的同情和善意
* 尊重不同的主张、观点和感受
* 提出和大方接受建设性意见
* 承担责任并向受我们错误影响的人道歉
* 注重社区共同诉求,而非个人得失
不当行为例子包括:
* 使用情色化的语言或图像,及性引诱或挑逗
* 嘲弄、侮辱或诋毁性评论,以及人身或政治攻击
* 公开或私下的骚扰行为
* 未经他人明确许可,公布他人的私人信息,如物理或电子邮件地址
* 其他有理由认定为违反职业操守的不当行为
## 责任和权力
社区领袖有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。
社区领导有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论(comment)、提交(commits)、代码、维基(wiki)编辑、议题(issues)或其他贡献,并在适当时机知采取措施的理由。
## 适用范围
本行为准则适用于所有社区场合,也适用于在公共场所代表社区时的个人。
代表社区的情形包括使用官方电子邮件地址、通过官方社交媒体帐户发帖或在线上或线下活动中担任指定代表。
## 监督
辱骂、骚扰或其他不可接受的行为可通过[info@rustdesk.com](mailto:info@rustdesk.com)向负责监督的社区领袖报告。 所有投诉都将得到及时和公平的审查和调查。
所有社区领袖都有义务尊重任何事件报告者的隐私和安全。
## 处理方针
社区领袖将遵循下列社区处理方针来明确他们所认定违反本行为准则的行为的处理方式:
### 1. 纠正
**社区影响**: 使用不恰当的语言或其他在社区中被认定为不符合职业道德或不受欢迎的行为。
**处理意见**: 由社区领袖发出非公开的书面警告,明确说明违规行为的性质,并解释举止如何不妥。或将要求公开道歉。
### 2. 警告
**社区影响**: 单个或一系列违规行为。
**处理意见**: 警告并对连续性行为进行处理。在指定时间内,不得与相关人员互动,包括主动与行为准则执行者互动。这包括避免在社区场所和外部渠道中的互动。违反这些条款可能会导致临时或永久封禁。
### 3. 临时封禁
**社区影响**: 严重违反社区准则,包括持续的不当行为。
**处理意见**: 在指定时间内,暂时禁止与社区进行任何形式的互动或公开交流。在此期间,不得与相关人员进行公开或私下互动,包括主动与行为准则执行者互动。违反这些条款可能会导致永久封禁。
### 4. 永久封禁
**社区影响**: 行为模式表现出违反社区准则,包括持续的不当行为、骚扰个人或攻击或贬低某个类别的个体。
**处理意见**: 永久禁止在社区内进行任何形式的公开互动。
## 参见
本行为准则改编自[参与者公约][homepage]2.0 版, 参见
[https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html][v2.0].
指导方针借鉴自[Mozilla纪检分级][Mozilla CoC].
有关本行为准则的常见问题的答案,参见 [https://www.contributor-covenant.org/faq][FAQ]。 其他语言翻译参见[https://www.contributor-covenant.org/translations][translations]。
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/zh-cn/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+46
View File
@@ -0,0 +1,46 @@
# Bidrag til RustDesk
RustDesk er åpene for bidrag fra alle. Her er reglene for de som har lyst til å
hjelpe oss:
## Bidrag
Bidrag til RustDesk eller deres avhengigheter burde være i form av GitHub pull requests.
Hver pull request vill bli sett igjennom av en kjerne bidrager (noen med autoritet til
å godkjenne endringene) og enten bli sendt til main treet eller respondert med
tilbakemelding på endringer som er nødvendig. Alle bidrag burde følge dette formate
også de fra kjerne bidragere.
Om du ønsker å jobbe på en issue må du huske å gjøre krav på den først. Dette
kann gjøres ved å kommentere på den GitHub issue-en du ønsker å jobbe på.
Dette er for å hindre duplikat innsats på samme problem.
## Pull Request Sjekkliste
- Lag en gren fra master grenen og, hvis det er nødvendig, rebase den til den nåværende
master grenen før du sender inn din pull request. Hvis ikke dette gjøres på rent
vis vill du bli spurt om å rebase dine endringer.
- Commits burde være så små som mulig, samtidig som de må være korrekt uavhenging av hverandre
(hver commit burde kompilere og bestå tester).
- Commits burde være akkopaniert med en Developer Certificate of Origin
(http://developercertificate.org), som indikerer att du (og din arbeidsgiver
i det tilfellet) godkjenner å bli knyttet til vilkårene av [prosjekt lisensen](../LICENCE).
Ved bruk av git er dette `-s` opsjonen til `git commit`.
- Hvis dine endringer ikke blir sett eller hvis du trenger en spesefik person til
å se på dem kan du @-svare en med autoritet til å godkjenne dine endringer.
Dette kann gjøres i en pull request, en kommentar eller via epost på [email](mailto:info@rustdesk.com).
- Legg til tester relevant til en fikset bug eller en ny tilgjengelighet.
For spesefike git instruksjoner, se [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
## Oppførsel
https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
## Kommunikasjon
RustDesk bidragere burker [Discord](https://discord.gg/nDceKgxnkV).
+32
View File
@@ -0,0 +1,32 @@
# 为RustDesk做贡献
Rust欢迎每一位贡献者,如果您有意向为我们做出贡献,请遵循以下指南:
## 贡献方式
对 RustDesk 或其依赖项的贡献需要通过 GitHub 的 Pull Request (PR) 的形式提交。每个 PR 都会由核心贡献者(即有权限合并代码的人)进行审核,审核通过后代码会合并到主分支,或者您会收到需要修改的反馈。所有贡献者,包括核心贡献者,提交的代码都应遵循此流程。
如果您希望处理某个问题,请先在对应的 GitHub issue 下发表评论,声明您将处理该问题,以避免该问题被多位贡献者重复处理。
## PR 注意事项
- 从 master 分支创建一个新的分支,并在提交PR之前,如果需要,将您的分支 变基(rebase) 到最新的 master 分支。如果您的分支无法顺利合并到 master 分支,您可能会被要求更新您的代码。
- 每次提交的改动应该尽可能少,并且要保证每次提交的代码都是正确的(即每个 commit 都应能成功编译并通过测试)。
- 每个提交都应附有开发者证书签名(http://developercertificate.org), 表明您(以及您的雇主,若适用)同意遵守项目[许可证条款](../LICENCE)。在使用 git 提交代码时,可以通过在 `git commit` 时使用 `-s` 选项加入签名
- 如果您的 PR 未被及时审核,或需要指定的人员进行审核,您可以通过在 PR 或评论中 @ 提到相关审核者,以及发送[电子邮件](mailto:info@rustdesk.com)的方式请求审核。
- 请为修复的 bug 或新增的功能添加相应的测试用例。
有关具体的 git 使用说明,请参考[GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
## 行为准则
请遵守项目的[贡献者公约行为准则](./CODE_OF_CONDUCT-ZH.md)。
## 沟通渠道
RustDesk 的贡献者主要通过 [Discord](https://discord.gg/nDceKgxnkV) 进行交流。
+14
View File
@@ -0,0 +1,14 @@
Etter start av devcontainer i docker konteineren, blir en linux binærfil i debug modus laget.
Nå tilbyr devcontainer linux og android builds i både debug og release modus.
Under er tabellen over kommandoer som kan kjøres fra rot-direktive for kreasjon av spesefike builds.
Kommando|Build Type|Modus
-|-|-|
`.devcontainer/build.sh --debug linux`|Linux|debug
`.devcontainer/build.sh --release linux`|Linux|release
`.devcontainer/build.sh --debug android`|android-arm64|debug
`.devcontainer/build.sh --release android`|android-arm64|release
+4
View File
@@ -147,6 +147,10 @@ target/release/rustdesk
Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System. Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System.
> [!Vorsicht]
> **Haftungsausschluss bei Missbrauch::** <br>
> Die Entwickler von RustDesk billigen oder unterstützen keine unethische oder illegale Nutzung dieser Software. Missbrauch, wie unbefugter Zugriff, unbefugte Kontrolle oder Verletzung der Privatsphäre, verstößt strikt gegen unsere Richtlinien. Die Autoren sind nicht verantwortlich für jeglichen Missbrauch der Anwendung.
## Dateistruktur ## Dateistruktur
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen - **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen
+4
View File
@@ -147,6 +147,10 @@ Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para el cliente web Flutter - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para el cliente web Flutter
> [!Precaución]
> **Descargo de responsabilidad por uso indebido:** <br>
> Los desarrolladores de RustDesk no aprueban ni apoyan ningún uso no ético o ilegal de este software. El uso indebido, como el acceso no autorizado, el control o la invasión de la privacidad, está estrictamente en contra de nuestras directrices. Los autores no son responsables de ningún uso indebido de la aplicación.
## Capturas de pantalla ## Capturas de pantalla
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+4
View File
@@ -137,6 +137,10 @@ Veuillez vous assurer que vous exécutez ces commandes à partir de la racine du
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée. - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée.
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme
> [!Attention]
> **Avertissement contre l'utilisation abusive:** <br>
> Les développeurs de RustDesk ne cautionnent ni ne soutiennent aucune utilisation non éthique ou illégale de ce logiciel. Toute utilisation abusive, telle que l'accès non autorisé, le contrôle ou l'invasion de la vie privée, est strictement contraire à nos directives. Les auteurs ne sont pas responsables de toute utilisation abusive de l'application.
## Images ## Images
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+4
View File
@@ -164,6 +164,10 @@ Assicurati di eseguire questi comandi dalla radice del repository RustDesk, altr
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: codice Flutter per desktop e mobile - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: codice Flutter per desktop e mobile
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript per client web Flutter - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript per client web Flutter
> [!Attenzione]
> **Dichiarazione di non responsabilità per uso improprio:** <br>
> Gli sviluppatori di RustDesk non approvano né supportano alcun uso non etico o illegale di questo software. L'uso improprio, come l'accesso non autorizzato, il controllo o l'invasione della privacy, è strettamente contro le nostre linee guida. Gli autori non sono responsabili per qualsiasi uso improprio dell'applicazione.
## Schermate ## Schermate
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+64 -41
View File
@@ -1,60 +1,73 @@
<p align="center"> <p align="center">
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br> <img src="../res/logo-header.svg" alt="RustDesk - あなたのためのリモートデスクトップ"><br>
<a href="#free-public-servers">Servers</a> • <a href="#free-public-servers">Servers</a> •
<a href="#raw-steps-to-build">Build</a> • <a href="#raw-steps-to-build">Build</a> •
<a href="#how-to-build-with-docker">Docker</a> • <a href="#how-to-build-with-docker">Docker</a> •
<a href="#file-structure">Structure</a> • <a href="#file-structure">Structure</a> •
<a href="#snapshot">Snapshot</a><br> <a href="#snapshot">Snapshot</a><br>
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br> [<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br>
<b>このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。</b> <b>READMEや<a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a>、 <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a>の翻訳者を歓迎します!</b>
</p> </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) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分で設定する](https://rustdesk.com/server) ことも、 [自分でランデブー/リレーサーバを書くこともできます](https://github.com/rustdesk/rustdesk-server-demo)。 Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分でサーバーをセットアップする](https://rustdesk.com/server) ことも、 [自分でランデブー/リレーサーバを作成する](https://github.com/rustdesk/rustdesk-server-demo)こともできます
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
RustDeskは誰からの貢献歓迎します。 貢献するには [`docs/CONTRIBUTING.md`](CONTRIBUTING.md) を参照してください。 RustDeskは皆さんの貢献歓迎します。
貢献の方法については[CONTRIBUTING.md](docs/CONTRIBUTING.md)をご確認ください。
[**RustDeskはどの様に動くのか?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) [**よくある質問**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
[**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) [**パッケージのダウンロード**](https://github.com/rustdesk/rustdesk/releases)
[**ナイトリービルド**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="F-Droidで入手する"
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
## 依存関係 ## 依存関係
デスクトップ版ではGUIに [sciter](https://sciter.com/) が使われています。 sciter dynamic library をダウンロードしてください。 デスクトップ版ではGUIにFlutterまたはSciter(非推奨)を使用しますが、チュートリアルでは分かりやすく、簡単なSciterのみを対象に解説しています。Flutterでのビルド方法については[CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)をご覧ください。
Sciter dynamic libraryを事前にダウンロードしてください。
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | [Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | [Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
[MacOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) [macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
モバイル版はFlutterを利用します。デスクトップ版もSciterからFlutterへマイグレーション予定です。
## ビルド手順 ## ビルド手順
- Rust開発環境とC ++ビルド環境を準備します - Rust開発環境とC++ビルド環境を準備します
- [vcpkg](https://github.com/microsoft/vcpkg), をインストールし、 `VCPKG_ROOT` 環境変数を正しく設定します。 - [vcpkg](https://github.com/microsoft/vcpkg)をインストールし、環境変数に`VCPKG_ROOT`設定します。
その後、以下のコマンドを実行します。
- 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`
- 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
- `cargo run`を実行します。
## [ビルド](https://rustdesk.com/docs/en/dev/build/) ## [ビルド](https://rustdesk.com/docs/en/dev/build/)
## Linuxでのビルド手順 ## Linuxでのビルド方法
### Ubuntu 18 (Debian 10) ### Ubuntu 18 (Debian 10)
```sh ```sh
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
```
### openSUSE Tumbleweed
```sh
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel
``` ```
### Fedora 28 (CentOS 8) ### Fedora 28 (CentOS 8)
@@ -69,7 +82,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 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 ```sh
git clone https://github.com/microsoft/vcpkg git clone https://github.com/microsoft/vcpkg
@@ -81,7 +94,7 @@ export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom vcpkg/vcpkg install libvpx libyuv opus aom
``` ```
### Fix libvpx (For Fedora) ### libvpxの修正 (Fedoraのみ)
```sh ```sh
cd vcpkg/buildtrees/libvpx/src cd vcpkg/buildtrees/libvpx/src
@@ -107,9 +120,9 @@ mv libsciter-gtk.so target/debug
VCPKG_ROOT=$HOME/vcpkg cargo run VCPKG_ROOT=$HOME/vcpkg cargo run
``` ```
## Dockerでビルドする方法 ## Dockerでビルド方法
リポジトリクローンを作成し、Dockerコンテナを構築することから始めます リポジトリクローンし、Dockerコンテナを構築ます:
```sh ```sh
git clone https://github.com/rustdesk/rustdesk git clone https://github.com/rustdesk/rustdesk
@@ -117,44 +130,54 @@ cd rustdesk
docker build -t "rustdesk-builder" . docker build -t "rustdesk-builder" .
``` ```
その後、アプリケーションをビルドする必要があるたびに、以下のコマンドを実行します 以下のコマンドを実行します:
```sh ```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 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
``` ```
このコマンドはRustDeskをビルドする度に実行する必要があります。
なお、最初のビルドでは、依存関係がキャッシュされるまで時間がかかることがありますが、その後のビルドではより速くなります。さらに、ビルドコマンドに別の引数を指定する必要がある場合は、コマンドの最後にある `<OPTIONAL-ARGS>` の位置で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に 初回ビルドは時間がかかるかもしれませんが、2回目以降は依存関係がキャッシュされるため、ビルドにかかる時間が短くなります。
`--release` を実行します。できあがった実行ファイルはシステムのターゲットフォルダに格納され、のコマンドで実行できます。 ビルドコマンドに追加の引数を指定する必要がある場合は、コマンドの最後(`<OPTIONAL-ARGS>`の位置)で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に `--release`追記し実行します。ビルドされた実行ファイルはあなたのシステムのターゲットフォルダに保存され、下記のコマンドで実行することができます。
デバッグビルドを起動する場合:
```sh ```sh
target/debug/rustdesk target/debug/rustdesk
``` ```
あるいは、リリース用の実行ファイルを実行している場合: リリースビルドを起動する場合:
```sh ```sh
target/release/rustdesk target/release/rustdesk
``` ```
これらのコマンドをRustDeskリポジトリのルートから実行していることを確認してください。そうしないと、アプリケーションが必要なリソースを見つけられない可能性があります。また、 `install``run` などの他の cargo サブコマンドは、ホストではなくコンテナ内プログラムをインストールまたは実行するため、現在の方法ではサポートされていないことに注意してください コマンドをRustDeskリポジトリのルートから実行していることを確認してください。また、`install``run` などの他のcargoサブコマンドは、ホストではなくコンテナ内プログラムをインストール実行するため、現在の方法ではサポートされていません
## ファイル構造 ## ファイル構造
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: ビデオコーデック、コンフィグ、tcp/udpラッパー、protobuf、ファイル転送用のfs関数その他のユーティリティ関数 - **[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)**: スクリーンキャプチャ - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: スクリーンキャプチャ
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: プラットフォーム固有のキーボード/マウスコントロール - **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: プラットフォーム固有のキーボード/マウス操作
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI - **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows、Linux、macOS向けのファイルのコピーと貼り付けの実装
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: オーディオ/クリップボード/入力/ビデオサービス、ネットワーク接続 - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 廃止された Sciter UI (非推奨)
- **[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/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) または中継接続を待つ - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)と通信し、リモートの直接接続(TCPホールパンチング)や中継接続を担う
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: プラットフォーム固有のコード - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: プラットフォーム固有のコード
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: デスクトップとモバイル向けのFlutterコード
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutterウェブクライアント向けのJavaScript
## スナップショット > [!注意]
> **:不正使用に関する免責事項** <br>
> RustDeskの開発者は、このソフトウェアの非倫理的または違法な使用を容認または支持しません。不正アクセス、不正な制御、またはプライバシーの侵害などの不正使用は、当社のガイドラインに厳密に違反します。開発者は、アプリケーションの不正使用に対して一切の責任を負いません。
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ## スクリーンショット
![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) ![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651)
![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) ![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea)
![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) ![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad)
![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
+20 -16
View File
@@ -9,12 +9,12 @@
<b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b> <b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
</p> </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) [![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) ![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 - 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 - 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에서 빌드 순서 ## 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 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 ```sh
git clone https://github.com/microsoft/vcpkg git clone https://github.com/microsoft/vcpkg
@@ -79,7 +79,7 @@ export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom vcpkg/vcpkg install libvpx libyuv opus aom
``` ```
### Fix libvpx (For Fedora) ### libvpx 수정 (For Fedora)
```sh ```sh
cd vcpkg/buildtrees/libvpx/src cd vcpkg/buildtrees/libvpx/src
@@ -92,7 +92,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
cd cd
``` ```
### Build ### 빌드
```sh ```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | 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 컨테이너 구성하는 것으로 시작합니다. 포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
```sh ```sh
git clone https://github.com/rustdesk/rustdesk git clone https://github.com/rustdesk/rustdesk
@@ -115,13 +115,13 @@ cd rustdesk
docker build -t "rustdesk-builder" . docker build -t "rustdesk-builder" .
``` ```
이후, 애플리케이션을 빌드할 필요가 있을 때마다, 이하의 커맨드를 실행합니다. 이후, 애플리케이션을 빌드할 필요가 있을 때마다, 아래의의 명령을 실행합니다.
```sh ```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 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 ```sh
target/debug/rustdesk target/debug/rustdesk
@@ -133,9 +133,9 @@ target/debug/rustdesk
target/release/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/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)**: 화면 캡처 - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡처
@@ -143,12 +143,16 @@ target/release/rustdesk
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 오디오, 클립보드, 입력, 비디오 서비스 그리고 네트워크 연결 - **[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/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)**: 플랫폼 고유의 코드 - **[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](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 모바일용 Flutter 코드
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter 웹 클라이언트용 자바스크립트
## Snapshot > [!주의]
> **오용에 대한 면책 조항:** <br>
> RustDesk의 개발자들은 이 소프트웨어의 비윤리적이거나 불법적인 사용을 용인하거나 지원하지 않습니다. 무단 접근, 제어 또는 개인정보 침해와 같은 오용은 우리의 지침을 엄격히 위반하는 것입니다. 개발자들은 애플리케이션의 오용에 대해 책임을 지지 않습니다.
## 스냅샷
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+177
View File
@@ -0,0 +1,177 @@
<p align="center">
<img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
<a href="#public-servers">Servere</a> •
<a href="#raw-steps-to-build">Build</a> •
<a href="#how-to-build-with-docker">Docker</a> •
<a href="#file-structure">Struktur</a> •
<a href="#snapshot">Snapshot</a><br>
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a><br>
<b>Vi trenger din hjelp til å oversette denne README-en, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> og <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> tid ditt morsmål</b>
</p>
Snakk med oss: [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)
Enda en annen fjernstyrt desktop programvare, skrevet i Rust. Virker rett ut av pakken, ingen konfigurasjon nødvendig. Du har full kontroll over din data, uten beskymring for sikkerhet. Du kan bruke vår rendezvous_mediator/relay server, [sett opp din egen](https://rustdesk.com/server), eller [skriv din egen rendezvous_mediator/relay server](https://github.com/rustdesk/rustdesk-server-demo).
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
RustDesk er velkommen for bidrag fra alle. Se [CONTRIBUTING.md](docs/CONTRIBUTING-NO.md) for hjelp med oppstart.
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
[**BINARY NEDLASTING**](https://github.com/rustdesk/rustdesk/releases)
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Få det på 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="Få det på Flathub"
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
## Avhengigheter
Desktop versjoner bruker Flutter eller Sciter (avviklet) for GUI, denne veiledningen er bare for Sciter, grunnet att det er letter og en mer venlig start. Skjekk ut vår [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for bygging av Flutter versjonen.
Venligst last ned Sciters dynamiske bibliotek selv.
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
## Rå steg for bygging
- Klargjør ditt Rust development env og C++ build env
- Installer [vcpkg](https://github.com/microsoft/vcpkg), og koriger `VCPKG_ROOT` env vaiabelen
- 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
- Kjør `cargo run`
## [Bygg](https://rustdesk.com/docs/en/dev/build/)
## Hvordan Bygge til Linux
### Ubuntu 18 (Debian 10)
```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
```
### openSUSE Tumbleweed
```sh
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
```
### Fedora 28 (CentOS 8)
```sh
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
```
### Arch (Manjaro)
```sh
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
```
### Installer vcpkg
```sh
git clone https://github.com/microsoft/vcpkg
cd vcpkg
git checkout 2023.04.15
cd ..
vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom
```
### Fiks libvpx (For Fedora)
```sh
cd vcpkg/buildtrees/libvpx/src
cd *
./configure
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
make
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
cd
```
### Bygg
```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
git clone https://github.com/rustdesk/rustdesk
cd rustdesk
mkdir -p target/debug
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
mv libsciter-gtk.so target/debug
VCPKG_ROOT=$HOME/vcpkg cargo run
```
## Hvordan bygge med Docker
Start med å klone repositoret og bygg Docker konteineren:
```sh
git clone https://github.com/rustdesk/rustdesk
cd rustdesk
docker build -t "rustdesk-builder" .
```
Deretter, hver gang du trenger å bygge applikasjonen, kjør følgene kommando:
```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
```
Det kan ta lengere tid før avhengighetene blir bufret første gang du bygger, senere bygg er raskere. Hvis du trenger å spesifisere forkjellige argumenter til bygge kommandoen, kan du gjøre det på slutten av kommandoen ved `<OPTIONAL-ARGS>` feltet. For eksempel, hvis du ville bygge en optimalisert release versjon, ville du kjørt kommandoen over fulgt `--release`. Den kjørbare filen vill være tilgjengelig i mål direktive på ditt system, og kan bli kjørt med:
```sh
target/debug/rustdesk
```
Eller, hvis du kjører ett release program:
```sh
target/release/rustdesk
```
Venligst pass på att du kjører disse kommandoene fra roten av RustDesk repositoret, eller kan det hende att applikasjon ikke finner de riktige ressursene. Pass også på att andre cargo subkommandoer som for eksempel `install` eller `run` ikke støttes med denne metoden da de vill installere eller kjøre programmet i konteineren istedet for verten.
## Fil Struktur
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video kodek, configurasjon, tcp/udp innpakning, protobuf, fs funksjon for fil overføring, og noen andre verktøy funksjoner
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: skjermfangst
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform spesefik keyboard/mus kontroll
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: fil kopi og innliming implementasjon for Windows, Linux, macOS.
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: foreldret Sciter UI (avviklet)
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: lyd/utklippstavle/input/video tjenester, og internett tilkobling
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start en peer tilkobling
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Kommunikasjon med [rustdesk-server](https://github.com/rustdesk/rustdesk-server), vent på direkte fjernstyring (TCP hulling) eller vidresendt tilkobling
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform spesefik kode
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter kode for desktop og mobil
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter nettsted klient
## Skjermbilder
![Tilkoblings Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651)
![Koble til Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea)
![Fil Overføring](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad)
![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
+1
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/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png)
![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) ![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png)
+4
View File
@@ -137,6 +137,10 @@ Por favor verifique que está executando estes comandos da raiz do repositório
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicação com [rustdesk-server](https://github.com/rustdesk/rustdesk-server), aguardar pela conexão remota direta (TCP hole punching) ou conexão indireta (relayed) - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicação com [rustdesk-server](https://github.com/rustdesk/rustdesk-server), aguardar pela conexão remota direta (TCP hole punching) ou conexão indireta (relayed)
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico a cada plataforma - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico a cada plataforma
> [!Cuidadob]
> **Aviso de uso indevido:** <br>
> Os desenvolvedores do RustDesk não aprovam nem apoiam qualquer uso antiético ou ilegal deste software. O uso indevido, como acesso não autorizado, controle ou invasão de privacidade, é estritamente contra nossas diretrizes. Os autores não são responsáveis por qualquer uso indevido da aplicação.
## Screenshots ## Screenshots
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+4
View File
@@ -148,6 +148,10 @@ target/release/rustdesk
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: свяжитесь с [rustdesk-server](https://github.com/rustdesk/rustdesk-server), дождитесь удаленного прямого (обход TCP NAT) или ретранслируемого соединения - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: свяжитесь с [rustdesk-server](https://github.com/rustdesk/rustdesk-server), дождитесь удаленного прямого (обход TCP NAT) или ретранслируемого соединения
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код
> [!Осторожно]
> **Отказ от ответственности за неправомерное использование:** <br>
> Разработчики RustDesk не одобряют и не поддерживают какое-либо неэтичное или незаконное использование данного программного обеспечения. Неправомерное использование, такое как несанкционированный доступ, контроль или вторжение в частную жизнь, строго противоречит нашим правилам. Авторы не несут ответственности за любое неправомерное использование приложения.
## Скриншоты ## Скриншоты
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+4
View File
@@ -166,6 +166,10 @@ Lütfen bu komutları RustDesk deposunun kökünden çalıştırdığınızdan e
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript
> [!Dikkat]
> **Yanlış Kullanım Uyarısı:** <br>
> RustDesk geliştiricileri, bu yazılımın etik olmayan veya yasa dışı kullanımını onaylamaz veya desteklemez. Yetkisiz erişim, kontrol veya gizlilik ihlali gibi kötüye kullanımlar kesinlikle yönergelerimize aykırıdır. Yazarlar, uygulamanın herhangi bir yanlış kullanımından sorumlu değildir.
## Ekran Görüntüleri ## Ekran Görüntüleri
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+16 -17
View File
@@ -1,20 +1,18 @@
<p align="center"> <p align="center">
<img src="../res/logo-header.svg" alt="RustDesk - Ваша віддалена стільниця"><br> <img src="../res/logo-header.svg" alt="RustDesk - Ваша віддалена стільниця"><br>
<a href="#безкоштовні-загальнодоступні-сервери">Сервери</a> • <a href="#публічні-сервери">Сервери</a> •
<a href="#кроки-для-збірки">Збирання</a> • <a href="#кроки-для-збірки">Збирання</a> •
<a href="#як-зібрати-за-допомогою-docker">Docker</a> • <a href="#як-зібрати-за-допомогою-docker">Docker</a> •
<a href="#структура-файлів">Структура</a> • <a href="#структура-файлів">Структура</a> •
<a href="#знімки">Знімки</a><br> <a href="#знімки-екрана">Знімки екрана</a><br>
[<a href="../README.md">English</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br> [<a href="../README.md">English</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-DA.md">Dansk</a>] | [<a href="README-GR.md">Ελληνικά</a>] | [<a href="README-TR.md">Türkçe</a>]<br>
<b>Нам потрібна ваша допомога для перекладу цього README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">інтерфейсу</a> та <a href="https://github.com/rustdesk/doc.rustdesk.com">документації</a> RustDesk на вашу рідну мову</B> <b>Нам потрібна ваша допомога для перекладу цього README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">інтерфейсу</a> та <a href="https://github.com/rustdesk/doc.rustdesk.com">документації</a> RustDesk вашою рідною мовою</B>
</p> </p>
Спілкування з нами: [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) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open)
Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo). Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo).
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
@@ -61,19 +59,19 @@ RustDesk вітає внесок кожного. Ознайомтеся з [CONT
```sh ```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
``` ```
### openSUSE Tumbleweed ### openSUSE Tumbleweed
```sh ```sh
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
``` ```
### Fedora 28 (CentOS 8) ### Fedora 28 (CentOS 8)
```sh ```sh
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
``` ```
### Arch (Manjaro) ### Arch (Manjaro)
@@ -158,18 +156,19 @@ target/release/rustdesk
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: реалізація копіювання та вставлення файлів для Windows, Linux, macOS. - **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: реалізація копіювання та вставлення файлів для Windows, Linux, macOS.
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графічний інтерфейс користувача - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графічний інтерфейс користувача
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: сервіси аудіо/буфера обміну/вводу/відео та мережевих підключень - **[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/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 NAT) або ретрансльованого з'єднання - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: комунікація з [rustdesk-server](https://github.com/rustdesk/rustdesk-server), очікування віддаленого прямого (обхід TCP NAT) або ретрансльованого зʼєднання
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфічний для платформи код - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфічний для платформи код
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для мобільних пристроїв - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для мобільних пристроїв
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript для Flutter веб клієнту - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript для веб клієнта на Flutter
## Знімки ## Знімки екрана
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![Менеджер зʼєднань](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651)
![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) ![Підключення до ПК з Windows](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea)
![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) ![Передача файлів](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad)
![Тунелювання TCP](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)
![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png)
+14 -7
View File
@@ -8,7 +8,7 @@
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br> [<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
</p> </p>
Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) 与我们交流: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09)
@@ -18,7 +18,7 @@ Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https:
![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING.md](CONTRIBUTING.md). RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING-ZH.md](CONTRIBUTING-ZH.md).
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) [**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
@@ -32,7 +32,9 @@ RustDesk 期待各位的贡献. 如何参与开发? 详情请看 [CONTRIBUTING.m
## 依赖 ## 依赖
桌面版本界面使用[sciter](https://sciter.com/), 请自行下载 桌面版本使用 Flutter 或 Sciter(已弃用)作为 GUI,本教程仅适用于 Sciter,因为它更简单且更易于上手。查看我们的[CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml)以构建 Flutter 版本
请自行下载Sciter动态库。
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | [Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | [Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
@@ -133,8 +135,8 @@ docker build -t "rustdesk-builder" . # 构建容器
``` ```
在Dockerfile的RUN apt update之前插入两行: 在Dockerfile的RUN apt update之前插入两行:
RUN sed -i "s/deb.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 && \
RUN sed -i "s/security.debian.org/mirrors.163.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`后插入下面代码: 2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码:
@@ -207,14 +209,19 @@ target/release/rustdesk
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数 - **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 视频编解码, 配置, tcp/udp 封装, protobuf, 文件传输相关文件系统操作函数, 以及一些其他实用函数
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 屏幕截取 - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 屏幕截取
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入 - **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI - **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: Windows、Linux、macOS 的文件复制和粘贴实现
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: 过时的 Sciter UI(已弃用)
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 被控端服务音频、剪切板、输入、视频服务、网络连接的实现 - **[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/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)保持UDP通讯, 等待远程连接(通过打洞直连或者中继) - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: 与[rustdesk-server](https://github.com/rustdesk/rustdesk-server)保持UDP通讯, 等待远程连接(通过打洞直连或者中继)
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码 - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 移动版本的Flutter代码 - **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 适用于桌面和移动设备的 Flutter 代码
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter Web版本中的Javascript代码 - **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter Web版本中的Javascript代码
> [!警告]
> **免责声明:** <br>
> RustDesk 的开发人员不纵容或支持任何不道德或非法的软件使用行为。滥用行为,例如未经授权的访问、控制或侵犯隐私,严格违反我们的准则。作者对应用程序的任何滥用行为概不负责。
## 截图 ## 截图
![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png)
+9
View File
@@ -0,0 +1,9 @@
# Sikkerhets Rettningslinjer
## Reportering av en Sårbarhet
Vi verdsetter pris på sikkerhet for prosjektet høyt. Og oppmunterer alle brukere til å rapportere sårbarheter de oppdager til oss.
Om du finner en sikkerhets sårbarhet i RustDesk prosjektet, venligst raportere det ansvarsfult ved å sende oss en email til info@rustdesk.com.
På dette tidspunktet har vi ingen bug dusør program. Vi er ett lite team som prøver å løse ett stort problem. Vi trenger att du raporterer alle sårbarhetene
annsvarfult så vi kan fortsettte å bygge ett en sikker applikasjon for hele felleskapet.
-28
View File
@@ -1,28 +0,0 @@
[package]
name = "custom_plugin"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "custom_plugin"
path = "src/lib.rs"
crate-type = ["cdylib"]
[features]
default = ["flutter"]
flutter = []
[dependencies]
lazy_static = "1.4.0"
rustdesk = { path = "../../", version = "1.2.0", features = ["flutter"]}
[profile.release]
lto = true
codegen-units = 1
panic = 'abort'
strip = true
#opt-level = 'z' # only have smaller size after strip
rpath = true
-30
View File
@@ -1,30 +0,0 @@
use librustdesk::api::RustDeskApiTable;
/// This file demonstrates how to write a custom plugin for RustDesk.
use std::ffi::{c_char, c_int, CString};
lazy_static::lazy_static! {
pub static ref PLUGIN_NAME: CString = CString::new("A Template Rust Plugin").unwrap();
pub static ref PLUGIN_ID: CString = CString::new("TemplatePlugin").unwrap();
// Do your own logic based on the API provided by RustDesk.
pub static ref API: RustDeskApiTable = RustDeskApiTable::default();
}
#[no_mangle]
fn plugin_name() -> *const c_char {
return PLUGIN_NAME.as_ptr();
}
#[no_mangle]
fn plugin_id() -> *const c_char {
return PLUGIN_ID.as_ptr();
}
#[no_mangle]
fn plugin_init() -> c_int {
return 0 as _;
}
#[no_mangle]
fn plugin_dispose() -> c_int {
return 0 as _;
}
@@ -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>
+23 -20
View File
@@ -1,19 +1,30 @@
{ {
"id": "com.rustdesk.RustDesk", "id": "com.rustdesk.RustDesk",
"runtime": "org.freedesktop.Platform", "runtime": "org.freedesktop.Platform",
"runtime-version": "23.08", "runtime-version": "24.08",
"sdk": "org.freedesktop.Sdk", "sdk": "org.freedesktop.Sdk",
"command": "rustdesk", "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": [ "modules": [
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json", "shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
"xdotool.json", {
"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", "name": "pam",
"buildsystem": "simple", "buildsystem": "autotools",
"build-commands": [ "config-opts": ["--disable-selinux"],
"./configure --disable-selinux --prefix=/app && make -j4 install"
],
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
@@ -26,32 +37,24 @@
"name": "rustdesk", "name": "rustdesk",
"buildsystem": "simple", "buildsystem": "simple",
"build-commands": [ "build-commands": [
"bsdtar -zxvf rustdesk.deb", "bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -",
"tar -xvf ./data.tar.xz", "cp -r usr/* /app/",
"cp -r ./usr/* /app/", "mkdir -p /app/bin && ln -s /app/share/rustdesk/rustdesk /app/bin/rustdesk"
"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"
], ],
"cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"],
"sources": [ "sources": [
{ {
"type": "file", "type": "file",
"path": "./rustdesk.deb" "path": "rustdesk.deb"
}, },
{ {
"type": "file", "type": "file",
"path": "../res/scalable.svg" "path": "com.rustdesk.RustDesk.metainfo.xml"
} }
] ]
} }
], ],
"finish-args": [ "finish-args": [
"--share=ipc", "--share=ipc",
"--socket=x11",
"--socket=fallback-x11", "--socket=fallback-x11",
"--socket=wayland", "--socket=wayland",
"--share=network", "--share=network",
-15
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"
}
]
}
+6 -12
View File
@@ -1,6 +1,9 @@
import com.google.protobuf.gradle.* import com.google.protobuf.gradle.*
plugins { plugins {
id "com.google.protobuf" version "0.9.4" 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() 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') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@@ -32,10 +30,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
dependencies { dependencies {
implementation 'com.google.protobuf:protobuf-javalite:3.20.1' implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
} }
@@ -57,7 +51,7 @@ protobuf {
} }
android { android {
compileSdkVersion 33 compileSdkVersion 34
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@@ -105,6 +99,6 @@ flutter {
dependencies { dependencies {
implementation "androidx.media:media:1.6.0" implementation "androidx.media:media:1.6.0"
implementation 'com.github.getActivity:XXPermissions:18.5' 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'
} }
@@ -15,6 +15,13 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <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 <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="RustDesk" android:label="RustDesk"
@@ -81,6 +88,11 @@
android:name=".MainService" android:name=".MainService"
android:enabled="true" android:enabled="true"
android:foregroundServiceType="mediaProjection" /> android:foregroundServiceType="mediaProjection" />
<service
android:name=".FloatingWindowService"
android:enabled="true" />
<!-- <!--
Don't delete the meta-data below. Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
@@ -0,0 +1,193 @@
package com.carriez.flutter_hbb
import ffi.FFI
import android.Manifest
import android.content.Context
import android.media.*
import android.content.pm.PackageManager
import android.media.projection.MediaProjection
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import android.os.Build
import android.util.Log
import kotlin.concurrent.thread
const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_FLOAT // ENCODING_OPUS need API 30
const val AUDIO_SAMPLE_RATE = 48000
const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO
class AudioRecordHandle(private var context: Context, private var isVideoStart: ()->Boolean, private var isAudioStart: ()->Boolean) {
private val logTag = "LOG_AUDIO_RECORD_HANDLE"
private var audioRecorder: AudioRecord? = null
private var audioReader: AudioReader? = null
private var minBufferSize = 0
private var audioRecordStat = false
private var audioThread: Thread? = null
@RequiresApi(Build.VERSION_CODES.M)
fun createAudioRecorder(inVoiceCall: Boolean, mediaProjection: MediaProjection?): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return false
}
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
Log.d(logTag, "createAudioRecorder failed, no RECORD_AUDIO permission")
return false
}
var builder = AudioRecord.Builder()
.setAudioFormat(
AudioFormat.Builder()
.setEncoding(AUDIO_ENCODING)
.setSampleRate(AUDIO_SAMPLE_RATE)
.setChannelMask(AUDIO_CHANNEL_MASK).build()
);
if (inVoiceCall) {
builder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
} else {
mediaProjection?.let {
var apcc = AudioPlaybackCaptureConfiguration.Builder(it)
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
.addMatchingUsage(AudioAttributes.USAGE_ALARM)
.addMatchingUsage(AudioAttributes.USAGE_GAME)
.addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build();
builder.setAudioPlaybackCaptureConfig(apcc);
} ?: let {
Log.d(logTag, "createAudioRecorder failed, mediaProjection null")
return false
}
}
audioRecorder = builder.build()
Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize")
return true
}
@RequiresApi(Build.VERSION_CODES.M)
private fun checkAudioReader() {
if (audioReader != null && minBufferSize != 0) {
return
}
// read f32 to byte , length * 4
minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize(
AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL_MASK,
AUDIO_ENCODING
)
if (minBufferSize == 0) {
Log.d(logTag, "get min buffer size fail!")
return
}
audioReader = AudioReader(minBufferSize, 4)
Log.d(logTag, "init audioData len:$minBufferSize")
}
@RequiresApi(Build.VERSION_CODES.M)
fun startAudioRecorder() {
checkAudioReader()
if (audioReader != null && audioRecorder != null && minBufferSize != 0) {
try {
FFI.setFrameRawEnable("audio", true)
audioRecorder!!.startRecording()
audioRecordStat = true
audioThread = thread {
while (audioRecordStat) {
audioReader!!.readSync(audioRecorder!!)?.let {
FFI.onAudioFrameUpdate(it)
}
}
// let's release here rather than onDestroy to avoid threading issue
audioRecorder?.release()
audioRecorder = null
minBufferSize = 0
FFI.setFrameRawEnable("audio", false)
Log.d(logTag, "Exit audio thread")
}
} catch (e: Exception) {
Log.d(logTag, "startAudioRecorder fail:$e")
}
} else {
Log.d(logTag, "startAudioRecorder fail")
}
}
fun onVoiceCallStarted(mediaProjection: MediaProjection?): Boolean {
if (!isSupportVoiceCall()) {
return false
}
// No need to check if video or audio is started here.
if (!switchToVoiceCall(mediaProjection)) {
return false
}
return true
}
fun onVoiceCallClosed(mediaProjection: MediaProjection?): Boolean {
// Return true if not supported, because is was not started.
if (!isSupportVoiceCall()) {
return true
}
if (isVideoStart()) {
switchOutVoiceCall(mediaProjection)
}
tryReleaseAudio()
return true
}
@RequiresApi(Build.VERSION_CODES.M)
fun switchToVoiceCall(mediaProjection: MediaProjection?): Boolean {
audioRecorder?.let {
if (it.getAudioSource() == MediaRecorder.AudioSource.VOICE_COMMUNICATION) {
return true
}
}
audioRecordStat = false
audioThread?.join()
audioThread = null
if (!createAudioRecorder(true, mediaProjection)) {
Log.e(logTag, "createAudioRecorder fail")
return false
}
startAudioRecorder()
return true
}
@RequiresApi(Build.VERSION_CODES.M)
fun switchOutVoiceCall(mediaProjection: MediaProjection?): Boolean {
audioRecorder?.let {
if (it.getAudioSource() != MediaRecorder.AudioSource.VOICE_COMMUNICATION) {
return true
}
}
audioRecordStat = false
audioThread?.join()
if (!createAudioRecorder(false, mediaProjection)) {
Log.e(logTag, "createAudioRecorder fail")
return false
}
startAudioRecorder()
return true
}
fun tryReleaseAudio() {
if (isAudioStart() || isVideoStart()) {
return
}
audioRecordStat = false
audioThread?.join()
audioThread = null
}
fun destroy() {
Log.d(logTag, "destroy audio record handle")
audioRecordStat = false
audioThread?.join()
}
}
@@ -0,0 +1,392 @@
package com.carriez.flutter_hbb
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.PixelFormat
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
import android.widget.ImageView
import android.widget.PopupMenu
import com.caverock.androidsvg.SVG
import ffi.FFI
import kotlin.math.abs
class FloatingWindowService : Service(), View.OnTouchListener {
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var floatingView: ImageView
private lateinit var originalDrawable: Drawable
private lateinit var leftHalfDrawable: Drawable
private lateinit var rightHalfDrawable: Drawable
private var dragging = false
private var lastDownX = 0f
private var lastDownY = 0f
private var viewCreated = false;
private var keepScreenOn = KeepScreenOn.DURING_CONTROLLED
companion object {
private val logTag = "floatingService"
private var firstCreate = true
private var viewWidth = 120
private var viewHeight = 120
private const val MIN_VIEW_SIZE = 32 // size 0 does not help prevent the service from being killed
private const val MAX_VIEW_SIZE = 320
private var viewUntouchable = false
private var viewTransparency = 1f // 0 means invisible but can help prevent the service from being killed
private var customSvg = ""
private var lastLayoutX = 0
private var lastLayoutY = 0
private var lastOrientation = Configuration.ORIENTATION_UNDEFINED
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
try {
if (firstCreate) {
firstCreate = false
onFirstCreate(windowManager)
}
Log.d(logTag, "floating window size: $viewWidth x $viewHeight, transparency: $viewTransparency, lastLayoutX: $lastLayoutX, lastLayoutY: $lastLayoutY, customSvg: $customSvg")
createView(windowManager)
handler.postDelayed(runnable, 1000)
Log.d(logTag, "onCreate success")
} catch (e: Exception) {
Log.d(logTag, "onCreate failed: $e")
}
}
override fun onDestroy() {
super.onDestroy()
if (viewCreated) {
windowManager.removeView(floatingView)
}
handler.removeCallbacks(runnable)
}
@SuppressLint("ClickableViewAccessibility")
private fun createView(windowManager: WindowManager) {
floatingView = ImageView(this)
viewCreated = true
originalDrawable = resources.getDrawable(R.drawable.floating_window, null)
if (customSvg.isNotEmpty()) {
try {
val svg = SVG.getFromString(customSvg)
Log.d(logTag, "custom svg info: ${svg.documentWidth} x ${svg.documentHeight}");
// This make the svg render clear
svg.documentWidth = viewWidth * 1f
svg.documentHeight = viewHeight * 1f
originalDrawable = svg.renderToPicture().let {
BitmapDrawable(
resources,
Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888)
.also { bitmap ->
it.draw(Canvas(bitmap))
})
}
floatingView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Log.d(logTag, "custom svg loaded")
} catch (e: Exception) {
e.printStackTrace()
}
}
val originalBitmap = Bitmap.createBitmap(
originalDrawable.intrinsicWidth,
originalDrawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(originalBitmap)
originalDrawable.setBounds(
0,
0,
originalDrawable.intrinsicWidth,
originalDrawable.intrinsicHeight
)
originalDrawable.draw(canvas)
val leftHalfBitmap = Bitmap.createBitmap(
originalBitmap,
0,
0,
originalDrawable.intrinsicWidth / 2,
originalDrawable.intrinsicHeight
)
val rightHalfBitmap = Bitmap.createBitmap(
originalBitmap,
originalDrawable.intrinsicWidth / 2,
0,
originalDrawable.intrinsicWidth / 2,
originalDrawable.intrinsicHeight
)
leftHalfDrawable = BitmapDrawable(resources, leftHalfBitmap)
rightHalfDrawable = BitmapDrawable(resources, rightHalfBitmap)
floatingView.setImageDrawable(rightHalfDrawable)
floatingView.setOnTouchListener(this)
floatingView.alpha = viewTransparency * 1f
var flags = FLAG_LAYOUT_IN_SCREEN or FLAG_NOT_TOUCH_MODAL or FLAG_NOT_FOCUSABLE
if (viewUntouchable || viewTransparency == 0f) {
flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
}
layoutParams = WindowManager.LayoutParams(
viewWidth / 2,
viewHeight,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE,
flags,
PixelFormat.TRANSLUCENT
)
layoutParams.gravity = Gravity.TOP or Gravity.START
layoutParams.x = lastLayoutX
layoutParams.y = lastLayoutY
val keepScreenOnOption = FFI.getLocalOption("keep-screen-on").lowercase()
keepScreenOn = when (keepScreenOnOption) {
"never" -> KeepScreenOn.NEVER
"service-on" -> KeepScreenOn.SERVICE_ON
else -> KeepScreenOn.DURING_CONTROLLED
}
Log.d(logTag, "keepScreenOn option: $keepScreenOnOption, value: $keepScreenOn")
updateKeepScreenOnLayoutParams()
windowManager.addView(floatingView, layoutParams)
moveToScreenSide()
}
private fun onFirstCreate(windowManager: WindowManager) {
val wh = getScreenSize(windowManager)
val w = wh.first
val h = wh.second
// size
FFI.getLocalOption("floating-window-size").let {
if (it.isNotEmpty()) {
try {
val size = it.toInt()
if (size in MIN_VIEW_SIZE..MAX_VIEW_SIZE && size <= w / 2 && size <= h / 2) {
viewWidth = size
viewHeight = size
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// untouchable
viewUntouchable = FFI.getLocalOption("floating-window-untouchable") == "Y"
// transparency
FFI.getLocalOption("floating-window-transparency").let {
if (it.isNotEmpty()) {
try {
val transparency = it.toInt()
if (transparency in 0..10) {
viewTransparency = transparency * 1f / 10
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
// custom svg
FFI.getLocalOption("floating-window-svg").let {
if (it.isNotEmpty()) {
customSvg = it
}
}
// position
lastLayoutX = 0
lastLayoutY = (wh.second - viewHeight) / 2
lastOrientation = resources.configuration.orientation
}
private fun performClick() {
showPopupMenu()
}
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
dragging = false
lastDownX = event.rawX
lastDownY = event.rawY
}
MotionEvent.ACTION_UP -> {
val clickDragTolerance = 10f
if (abs(event.rawX - lastDownX) < clickDragTolerance && abs(event.rawY - lastDownY) < clickDragTolerance) {
performClick()
} else {
moveToScreenSide()
}
}
MotionEvent.ACTION_MOVE -> {
val dx = event.rawX - lastDownX
val dy = event.rawY - lastDownY
// ignore too small fist start moving(some time is click)
if (!dragging && dx*dx+dy*dy < 25) {
return false
}
dragging = true
layoutParams.x = event.rawX.toInt()
layoutParams.y = event.rawY.toInt()
layoutParams.width = viewWidth
floatingView.setImageDrawable(originalDrawable)
windowManager.updateViewLayout(view, layoutParams)
lastLayoutX = layoutParams.x
lastLayoutY = layoutParams.y
}
}
return false
}
private fun moveToScreenSide(center: Boolean = false) {
val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val wh = getScreenSize(windowManager)
val w = wh.first
if (layoutParams.x < w / 2) {
layoutParams.x = 0
floatingView.setImageDrawable(rightHalfDrawable)
} else {
layoutParams.x = w - viewWidth / 2
floatingView.setImageDrawable(leftHalfDrawable)
}
if (center) {
layoutParams.y = (wh.second - viewHeight) / 2
}
layoutParams.width = viewWidth / 2
windowManager.updateViewLayout(floatingView, layoutParams)
lastLayoutX = layoutParams.x
lastLayoutY = layoutParams.y
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation != lastOrientation) {
lastOrientation = newConfig.orientation
val wh = getScreenSize(windowManager)
Log.d(logTag, "orientation: $lastOrientation, screen size: ${wh.first} x ${wh.second}")
val newW = wh.first
val newH = wh.second
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
// Proportional change
layoutParams.x = (layoutParams.x.toFloat() / newH.toFloat() * newW.toFloat()).toInt()
layoutParams.y = (layoutParams.y.toFloat() / newW.toFloat() * newH.toFloat()).toInt()
}
moveToScreenSide()
}
}
private fun showPopupMenu() {
val popupMenu = PopupMenu(this, floatingView)
val idShowRustDesk = 0
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
// 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) {
idShowRustDesk -> {
openMainActivity()
true
}
idSyncClipboard -> {
syncClipboard()
true
}
idStopService -> {
stopMainService()
true
}
else -> false
}
}
popupMenu.setOnDismissListener {
moveToScreenSide()
}
popupMenu.show()
}
private fun openMainActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
)
try {
pendingIntent.send()
} catch (e: PendingIntent.CanceledException) {
e.printStackTrace()
}
}
private fun syncClipboard() {
MainActivity.rdClipboardManager?.syncClipboard(false)
}
private fun stopMainService() {
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
}
enum class KeepScreenOn {
NEVER,
DURING_CONTROLLED,
SERVICE_ON,
}
private val handler = Handler(Looper.getMainLooper())
private val runnable = object : Runnable {
override fun run() {
if (updateKeepScreenOnLayoutParams()) {
windowManager.updateViewLayout(floatingView, layoutParams)
}
handler.postDelayed(this, 1000) // 1000 milliseconds = 1 second
}
}
private fun updateKeepScreenOnLayoutParams(): Boolean {
val oldOn = layoutParams.flags and FLAG_KEEP_SCREEN_ON != 0
val newOn = keepScreenOn == KeepScreenOn.SERVICE_ON || (keepScreenOn == KeepScreenOn.DURING_CONTROLLED && MainService.isStart)
if (oldOn != newOn) {
Log.d(logTag, "change keep screen on to $newOn")
if (newOn) {
layoutParams.flags = layoutParams.flags or FLAG_KEEP_SCREEN_ON
} else {
layoutParams.flags = layoutParams.flags and FLAG_KEEP_SCREEN_ON.inv()
}
return true
}
return false
}
}
@@ -18,7 +18,10 @@ import android.widget.EditText
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo
import android.view.KeyEvent as KeyEventAndroid
import android.view.ViewConfiguration
import android.graphics.Rect import android.graphics.Rect
import android.media.AudioManager
import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.AccessibilityServiceInfo
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR import android.accessibilityservice.AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR
import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS import android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
@@ -32,10 +35,15 @@ import hbb.MessageOuterClass.KeyEvent
import hbb.MessageOuterClass.KeyboardMode import hbb.MessageOuterClass.KeyboardMode
import hbb.KeyEventConverter import hbb.KeyEventConverter
const val LIFT_DOWN = 9 // const val BUTTON_UP = 2
const val LIFT_MOVE = 8 // const val BUTTON_BACK = 0x08
const val LIFT_UP = 10
const val LEFT_DOWN = 9
const val LEFT_MOVE = 8
const val LEFT_UP = 10
const val RIGHT_UP = 18 const val RIGHT_UP = 18
// (BUTTON_BACK << 3) | BUTTON_UP
const val BACK_UP = 66
const val WHEEL_BUTTON_DOWN = 33 const val WHEEL_BUTTON_DOWN = 33
const val WHEEL_BUTTON_UP = 34 const val WHEEL_BUTTON_UP = 34
const val WHEEL_DOWN = 523331 const val WHEEL_DOWN = 523331
@@ -62,12 +70,15 @@ class InputService : AccessibilityService() {
private val logTag = "input service" private val logTag = "input service"
private var leftIsDown = false private var leftIsDown = false
private var touchPath = Path() private val touchPath = Path()
private var stroke: GestureDescription.StrokeDescription? = null
private var lastTouchGestureStartTime = 0L private var lastTouchGestureStartTime = 0L
private var mouseX = 0 private var mouseX = 0
private var mouseY = 0 private var mouseY = 0
private var timer = Timer() private var timer = Timer()
private var recentActionTask: TimerTask? = null private var recentActionTask: TimerTask? = null
// 100(tap timeout) + 400(long press timeout)
private val longPressDuration = ViewConfiguration.getTapTimeout().toLong() + ViewConfiguration.getLongPressTimeout().toLong()
private val wheelActionsQueue = LinkedList<GestureDescription>() private val wheelActionsQueue = LinkedList<GestureDescription>()
private var isWheelActionsPolling = false private var isWheelActionsPolling = false
@@ -75,12 +86,17 @@ class InputService : AccessibilityService() {
private var fakeEditTextForTextStateCalculation: EditText? = null private var fakeEditTextForTextStateCalculation: EditText? = null
private var lastX = 0
private var lastY = 0
private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) }
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
fun onMouseInput(mask: Int, _x: Int, _y: Int) { fun onMouseInput(mask: Int, _x: Int, _y: Int) {
val x = max(0, _x) val x = max(0, _x)
val y = max(0, _y) val y = max(0, _y)
if (mask == 0 || mask == LIFT_MOVE) { if (mask == 0 || mask == LEFT_MOVE) {
val oldX = mouseX val oldX = mouseX
val oldY = mouseY val oldY = mouseY
mouseX = x * SCREEN_INFO.scale mouseX = x * SCREEN_INFO.scale
@@ -94,31 +110,30 @@ class InputService : AccessibilityService() {
} }
} }
// left button down ,was up // left button down, was up
if (mask == LIFT_DOWN) { if (mask == LEFT_DOWN) {
isWaitingLongPress = true isWaitingLongPress = true
timer.schedule(object : TimerTask() { timer.schedule(object : TimerTask() {
override fun run() { override fun run() {
if (isWaitingLongPress) { if (isWaitingLongPress) {
isWaitingLongPress = false isWaitingLongPress = false
leftIsDown = false continueGesture(mouseX, mouseY)
endGesture(mouseX, mouseY)
} }
} }
}, LONG_TAP_DELAY * 4) }, longPressDuration)
leftIsDown = true leftIsDown = true
startGesture(mouseX, mouseY) startGesture(mouseX, mouseY)
return return
} }
// left down ,was down // left down, was down
if (leftIsDown) { if (leftIsDown) {
continueGesture(mouseX, mouseY) continueGesture(mouseX, mouseY)
} }
// left up ,was down // left up, was down
if (mask == LIFT_UP) { if (mask == LEFT_UP) {
if (leftIsDown) { if (leftIsDown) {
leftIsDown = false leftIsDown = false
isWaitingLongPress = false isWaitingLongPress = false
@@ -128,6 +143,11 @@ class InputService : AccessibilityService() {
} }
if (mask == RIGHT_UP) { if (mask == RIGHT_UP) {
longPress(mouseX, mouseY)
return
}
if (mask == BACK_UP) {
performGlobalAction(GLOBAL_ACTION_BACK) performGlobalAction(GLOBAL_ACTION_BACK)
return return
} }
@@ -237,36 +257,78 @@ class InputService : AccessibilityService() {
} }
} }
private fun startGesture(x: Int, y: Int) { @RequiresApi(Build.VERSION_CODES.N)
touchPath = Path() private fun performClick(x: Int, y: Int, duration: Long) {
touchPath.moveTo(x.toFloat(), y.toFloat()) val path = Path()
lastTouchGestureStartTime = System.currentTimeMillis() path.moveTo(x.toFloat(), y.toFloat())
try {
val longPressStroke = GestureDescription.StrokeDescription(path, 0, duration)
val builder = GestureDescription.Builder()
builder.addStroke(longPressStroke)
Log.d(logTag, "performClick x:$x y:$y time:$duration")
dispatchGesture(builder.build(), null, null)
} catch (e: Exception) {
Log.e(logTag, "performClick, error:$e")
}
} }
private fun continueGesture(x: Int, y: Int) { @RequiresApi(Build.VERSION_CODES.N)
private fun longPress(x: Int, y: Int) {
performClick(x, y, longPressDuration)
}
private fun startGesture(x: Int, y: Int) {
touchPath.reset()
touchPath.moveTo(x.toFloat(), y.toFloat())
lastTouchGestureStartTime = System.currentTimeMillis()
lastX = x
lastY = y
}
@RequiresApi(Build.VERSION_CODES.N)
private fun doDispatchGesture(x: Int, y: Int, willContinue: Boolean) {
touchPath.lineTo(x.toFloat(), y.toFloat()) touchPath.lineTo(x.toFloat(), y.toFloat())
var duration = System.currentTimeMillis() - lastTouchGestureStartTime
if (duration <= 0) {
duration = 1
}
try {
if (stroke == null) {
stroke = GestureDescription.StrokeDescription(
touchPath,
0,
duration,
willContinue
)
} else {
stroke = stroke?.continueStroke(touchPath, 0, duration, willContinue)
}
stroke?.let {
val builder = GestureDescription.Builder()
builder.addStroke(it)
Log.d(logTag, "doDispatchGesture x:$x y:$y time:$duration")
dispatchGesture(builder.build(), null, null)
}
} catch (e: Exception) {
Log.e(logTag, "doDispatchGesture, willContinue:$willContinue, error:$e")
}
}
@RequiresApi(Build.VERSION_CODES.N)
private fun continueGesture(x: Int, y: Int) {
doDispatchGesture(x, y, true)
touchPath.reset()
touchPath.moveTo(x.toFloat(), y.toFloat())
lastTouchGestureStartTime = System.currentTimeMillis()
lastX = x
lastY = y
} }
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
private fun endGesture(x: Int, y: Int) { private fun endGesture(x: Int, y: Int) {
try { doDispatchGesture(x, y, false)
touchPath.lineTo(x.toFloat(), y.toFloat()) touchPath.reset()
var duration = System.currentTimeMillis() - lastTouchGestureStartTime stroke = null
if (duration <= 0) {
duration = 1
}
val stroke = GestureDescription.StrokeDescription(
touchPath,
0,
duration
)
val builder = GestureDescription.Builder()
builder.addStroke(stroke)
Log.d(logTag, "end gesture x:$x y:$y time:$duration")
dispatchGesture(builder.build(), null, null)
} catch (e: Exception) {
Log.e(logTag, "endGesture error:$e")
}
} }
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
@@ -276,24 +338,36 @@ class InputService : AccessibilityService() {
var textToCommit: String? = null var textToCommit: String? = null
if (keyboardMode == KeyboardMode.Legacy) { // [down] indicates the key's state(down or up).
if (keyEvent.hasChr() && keyEvent.getDown()) { // [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() val chr = keyEvent.getChr()
if (chr != null) { if (chr != null) {
textToCommit = String(Character.toChars(chr)) textToCommit = String(Character.toChars(chr))
} }
} }
} else if (keyboardMode == KeyboardMode.Translate) { } else if (keyboardMode == KeyboardMode.Translate) {
if (keyEvent.hasSeq() && keyEvent.getDown()) { } else {
val seq = keyEvent.getSeq()
if (seq != null) {
textToCommit = seq
}
}
} }
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit") Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit")
var ke: KeyEventAndroid? = null
if (Build.VERSION.SDK_INT < 33 || textToCommit == null) {
ke = KeyEventConverter.toAndroidKeyEvent(keyEvent)
}
ke?.let { event ->
if (tryHandleVolumeKeyEvent(event)) {
return
} else if (tryHandlePowerKeyEvent(event)) {
return
}
}
if (Build.VERSION.SDK_INT >= 33) { if (Build.VERSION.SDK_INT >= 33) {
getInputMethod()?.let { inputMethod -> getInputMethod()?.let { inputMethod ->
inputMethod.getCurrentInputConnection()?.let { inputConnection -> inputMethod.getCurrentInputConnection()?.let { inputConnection ->
@@ -302,8 +376,12 @@ class InputService : AccessibilityService() {
inputConnection.commitText(text, 1, null) inputConnection.commitText(text, 1, null)
} }
} else { } else {
KeyEventConverter.toAndroidKeyEvent(keyEvent).let { event -> ke?.let { event ->
inputConnection.sendKeyEvent(event) inputConnection.sendKeyEvent(event)
if (keyEvent.getPress()) {
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
inputConnection.sendKeyEvent(actionUpEvent)
}
} }
} }
} }
@@ -311,12 +389,16 @@ class InputService : AccessibilityService() {
} else { } else {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
handler.post { handler.post {
KeyEventConverter.toAndroidKeyEvent(keyEvent)?.let { event -> ke?.let { event ->
val possibleNodes = possibleAccessibiltyNodes() val possibleNodes = possibleAccessibiltyNodes()
Log.d(logTag, "possibleNodes:$possibleNodes") Log.d(logTag, "possibleNodes:$possibleNodes")
for (item in possibleNodes) { for (item in possibleNodes) {
val success = trySendKeyEvent(event, item, textToCommit) val success = trySendKeyEvent(event, item, textToCommit)
if (success) { if (success) {
if (keyEvent.getPress()) {
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
trySendKeyEvent(actionUpEvent, item, textToCommit)
}
break break
} }
} }
@@ -325,6 +407,43 @@ class InputService : AccessibilityService() {
} }
} }
private fun tryHandleVolumeKeyEvent(event: KeyEventAndroid): Boolean {
when (event.keyCode) {
KeyEventAndroid.KEYCODE_VOLUME_UP -> {
if (event.action == KeyEventAndroid.ACTION_DOWN) {
volumeController.raiseVolume(null, true, AudioManager.STREAM_SYSTEM)
}
return true
}
KeyEventAndroid.KEYCODE_VOLUME_DOWN -> {
if (event.action == KeyEventAndroid.ACTION_DOWN) {
volumeController.lowerVolume(null, true, AudioManager.STREAM_SYSTEM)
}
return true
}
KeyEventAndroid.KEYCODE_VOLUME_MUTE -> {
if (event.action == KeyEventAndroid.ACTION_DOWN) {
volumeController.toggleMute(true, AudioManager.STREAM_SYSTEM)
}
return true
}
else -> {
return false
}
}
}
private fun tryHandlePowerKeyEvent(event: KeyEventAndroid): Boolean {
if (event.keyCode == KeyEventAndroid.KEYCODE_POWER) {
// Perform power dialog action when action is up
if (event.action == KeyEventAndroid.ACTION_UP) {
performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
}
return true
}
return false
}
private fun insertAccessibilityNode(list: LinkedList<AccessibilityNodeInfo>, node: AccessibilityNodeInfo) { private fun insertAccessibilityNode(list: LinkedList<AccessibilityNodeInfo>, node: AccessibilityNodeInfo) {
if (node == null) { if (node == null) {
return return
@@ -422,7 +541,7 @@ class InputService : AccessibilityService() {
return linkedList return linkedList
} }
private fun trySendKeyEvent(event: android.view.KeyEvent, node: AccessibilityNodeInfo, textToCommit: String?): Boolean { private fun trySendKeyEvent(event: KeyEventAndroid, node: AccessibilityNodeInfo, textToCommit: String?): Boolean {
node.refresh() node.refresh()
this.fakeEditTextForTextStateCalculation?.setSelection(0,0) this.fakeEditTextForTextStateCalculation?.setSelection(0,0)
this.fakeEditTextForTextStateCalculation?.setText(null) this.fakeEditTextForTextStateCalculation?.setText(null)
@@ -487,10 +606,10 @@ class InputService : AccessibilityService() {
it.layout(rect.left, rect.top, rect.right, rect.bottom) it.layout(rect.left, rect.top, rect.right, rect.bottom)
it.onPreDraw() it.onPreDraw()
if (event.action == android.view.KeyEvent.ACTION_DOWN) { if (event.action == KeyEventAndroid.ACTION_DOWN) {
val succ = it.onKeyDown(event.getKeyCode(), event) val succ = it.onKeyDown(event.getKeyCode(), event)
Log.d(logTag, "onKeyDown $succ") Log.d(logTag, "onKeyDown $succ")
} else if (event.action == android.view.KeyEvent.ACTION_UP) { } else if (event.action == KeyEventAndroid.ACTION_UP) {
val success = it.onKeyUp(event.getKeyCode(), event) val success = it.onKeyUp(event.getKeyCode(), event)
Log.d(logTag, "keyup $success") Log.d(logTag, "keyup $success")
} else {} } else {}
@@ -31,7 +31,7 @@ object KeyEventConverter {
} }
var action = 0 var action = 0
if (keyEventProto.getDown()) { if (keyEventProto.getDown() || keyEventProto.getPress()) {
action = KeyEvent.ACTION_DOWN action = KeyEvent.ACTION_DOWN
} else { } else {
action = KeyEvent.ACTION_UP action = KeyEvent.ACTION_UP
@@ -112,6 +112,10 @@ object KeyEventConverter {
ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL ControlKey.Delete -> KeyEvent.KEYCODE_FORWARD_DEL
ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR ControlKey.Clear -> KeyEvent.KEYCODE_CLEAR
ControlKey.Pause -> KeyEvent.KEYCODE_BREAK ControlKey.Pause -> KeyEvent.KEYCODE_BREAK
ControlKey.VolumeMute -> KeyEvent.KEYCODE_VOLUME_MUTE
ControlKey.VolumeUp -> KeyEvent.KEYCODE_VOLUME_UP
ControlKey.VolumeDown -> KeyEvent.KEYCODE_VOLUME_DOWN
ControlKey.Power -> KeyEvent.KEYCODE_POWER
else -> 0 // Default to unknown. else -> 0 // Default to unknown.
} }
} }
@@ -7,29 +7,49 @@ package com.carriez.flutter_hbb
* Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG
*/ */
import ffi.FFI
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.ClipboardManager
import android.os.Bundle
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import android.view.WindowManager import android.view.WindowManager
import android.media.MediaCodecInfo
import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
import android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
import android.media.MediaCodecList
import android.media.MediaFormat
import android.util.DisplayMetrics
import androidx.annotation.RequiresApi
import org.json.JSONArray
import org.json.JSONObject
import com.hjq.permissions.XXPermissions import com.hjq.permissions.XXPermissions
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlin.concurrent.thread
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
companion object { companion object {
var flutterMethodChannel: MethodChannel? = null var flutterMethodChannel: MethodChannel? = null
private var _rdClipboardManager: RdClipboardManager? = null
val rdClipboardManager: RdClipboardManager?
get() = _rdClipboardManager;
} }
private val channelTag = "mChannel" private val channelTag = "mChannel"
private val logTag = "mMainActivity" private val logTag = "mMainActivity"
private var mainService: MainService? = null private var mainService: MainService? = null
private var isAudioStart = false
private val audioRecordHandle = AudioRecordHandle(this, { false }, { isAudioStart })
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
if (MainService.isReady) { if (MainService.isReady) {
@@ -42,6 +62,7 @@ class MainActivity : FlutterActivity() {
channelTag channelTag
) )
initFlutterChannel(flutterMethodChannel!!) initFlutterChannel(flutterMethodChannel!!)
thread { setCodecInfo() }
} }
override fun onResume() { override fun onResume() {
@@ -69,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() { override fun onDestroy() {
Log.e(logTag, "onDestroy") Log.e(logTag, "onDestroy")
mainService?.let { mainService?.let {
@@ -191,6 +220,10 @@ class MainActivity : FlutterActivity() {
result.success(true) result.success(true)
} }
"try_sync_clipboard" -> {
rdClipboardManager?.syncClipboard(true)
result.success(true)
}
GET_START_ON_BOOT_OPT -> { GET_START_ON_BOOT_OPT -> {
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE) val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false)) result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
@@ -217,10 +250,159 @@ class MainActivity : FlutterActivity() {
result.success(false) result.success(false)
} }
} }
GET_VALUE -> {
if (call.arguments is String) {
if (call.arguments == KEY_IS_SUPPORT_VOICE_CALL) {
result.success(isSupportVoiceCall())
} else {
result.error("-1", "No such key", null)
}
} else {
result.success(null)
}
}
"on_voice_call_started" -> {
onVoiceCallStarted()
}
"on_voice_call_closed" -> {
onVoiceCallClosed()
}
else -> { else -> {
result.error("-1", "No such method", null) result.error("-1", "No such method", null)
} }
} }
} }
} }
private fun setCodecInfo() {
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
val codecs = codecList.codecInfos
val codecArray = JSONArray()
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val wh = getScreenSize(windowManager)
var w = wh.first
var h = wh.second
val align = 64
w = (w + align - 1) / align * align
h = (h + align - 1) / align * align
codecs.forEach { codec ->
val codecObject = JSONObject()
codecObject.put("name", codec.name)
codecObject.put("is_encoder", codec.isEncoder)
var hw: Boolean? = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
hw = codec.isHardwareAccelerated
} else {
// https://chromium.googlesource.com/external/webrtc/+/HEAD/sdk/android/src/java/org/webrtc/MediaCodecUtils.java#29
// https://chromium.googlesource.com/external/webrtc/+/master/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java#229
if (listOf("OMX.google.", "OMX.SEC.", "c2.android").any { codec.name.startsWith(it, true) }) {
hw = false
} else if (listOf("c2.qti", "OMX.qcom.video", "OMX.Exynos", "OMX.hisi", "OMX.MTK", "OMX.Intel", "OMX.Nvidia").any { codec.name.startsWith(it, true) }) {
hw = true
}
}
if (hw != true) {
return@forEach
}
codecObject.put("hw", hw)
var mime_type = ""
codec.supportedTypes.forEach { type ->
if (listOf("video/avc", "video/hevc").contains(type)) { // "video/x-vnd.on2.vp8", "video/x-vnd.on2.vp9", "video/av01"
mime_type = type;
}
}
if (mime_type.isNotEmpty()) {
codecObject.put("mime_type", mime_type)
val caps = codec.getCapabilitiesForType(mime_type)
if (codec.isEncoder) {
// Encoders max_height and max_width are interchangeable
if (!caps.videoCapabilities.isSizeSupported(w,h) && !caps.videoCapabilities.isSizeSupported(h,w)) {
return@forEach
}
}
codecObject.put("min_width", caps.videoCapabilities.supportedWidths.lower)
codecObject.put("max_width", caps.videoCapabilities.supportedWidths.upper)
codecObject.put("min_height", caps.videoCapabilities.supportedHeights.lower)
codecObject.put("max_height", caps.videoCapabilities.supportedHeights.upper)
val surface = caps.colorFormats.contains(COLOR_FormatSurface);
codecObject.put("surface", surface)
val nv12 = caps.colorFormats.contains(COLOR_FormatYUV420SemiPlanar)
codecObject.put("nv12", nv12)
if (!(nv12 || surface)) {
return@forEach
}
codecObject.put("min_bitrate", caps.videoCapabilities.bitrateRange.lower / 1000)
codecObject.put("max_bitrate", caps.videoCapabilities.bitrateRange.upper / 1000)
if (!codec.isEncoder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
codecObject.put("low_latency", caps.isFeatureSupported(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency))
}
}
if (!codec.isEncoder) {
return@forEach
}
codecArray.put(codecObject)
}
}
val result = JSONObject()
result.put("version", Build.VERSION.SDK_INT)
result.put("w", w)
result.put("h", h)
result.put("codecs", codecArray)
FFI.setCodecInfo(result.toString())
}
private fun onVoiceCallStarted() {
var ok = false
mainService?.let {
ok = it.onVoiceCallStarted()
} ?: let {
isAudioStart = true
ok = audioRecordHandle.onVoiceCallStarted(null)
}
if (!ok) {
// Rarely happens, So we just add log and msgbox here.
Log.e(logTag, "onVoiceCallStarted fail")
flutterMethodChannel?.invokeMethod("msgbox", mapOf(
"type" to "custom-nook-nocancel-hasclose-error",
"title" to "Voice call",
"text" to "Failed to start voice call."))
} else {
Log.d(logTag, "onVoiceCallStarted success")
}
}
private fun onVoiceCallClosed() {
var ok = false
mainService?.let {
ok = it.onVoiceCallClosed()
} ?: let {
isAudioStart = false
ok = audioRecordHandle.onVoiceCallClosed(null)
}
if (!ok) {
// Rarely happens, So we just add log and msgbox here.
Log.e(logTag, "onVoiceCallClosed fail")
flutterMethodChannel?.invokeMethod("msgbox", mapOf(
"type" to "custom-nook-nocancel-hasclose-error",
"title" to "Voice call",
"text" to "Failed to stop voice call."))
} else {
Log.d(logTag, "onVoiceCallClosed success")
}
}
override fun onStop() {
super.onStop()
val disableFloatingWindow = FFI.getLocalOption("disable-floating-window") == "Y"
if (!disableFloatingWindow && MainService.isReady) {
startService(Intent(this, FloatingWindowService::class.java))
}
}
override fun onStart() {
super.onStart()
stopService(Intent(this, FloatingWindowService::class.java))
}
} }
@@ -55,21 +55,18 @@ const val MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_VP9
// video const // video const
const val MAX_SCREEN_SIZE = 1200
const val VIDEO_KEY_BIT_RATE = 1024_000 const val VIDEO_KEY_BIT_RATE = 1024_000
const val VIDEO_KEY_FRAME_RATE = 30 const val VIDEO_KEY_FRAME_RATE = 30
// audio const
const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_FLOAT // ENCODING_OPUS need API 30
const val AUDIO_SAMPLE_RATE = 48000
const val AUDIO_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO
class MainService : Service() { class MainService : Service() {
@Keep @Keep
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N)
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) { fun rustPointerInput(kind: Int, mask: Int, x: Int, y: Int) {
// turn on screen with LIFT_DOWN when screen off // turn on screen with LEFT_DOWN when screen off
if (!powerManager.isInteractive && (kind == "touch" || mask == LIFT_DOWN)) { if (!powerManager.isInteractive && (kind == 0 || mask == LEFT_DOWN)) {
if (wakeLock.isHeld) { if (wakeLock.isHeld) {
Log.d(logTag, "Turn on Screen, WakeLock release") Log.d(logTag, "Turn on Screen, WakeLock release")
wakeLock.release() wakeLock.release()
@@ -78,10 +75,10 @@ class MainService : Service() {
wakeLock.acquire(5000) wakeLock.acquire(5000)
} else { } else {
when (kind) { when (kind) {
"touch" -> { 0 -> { // touch
InputService.ctx?.onTouchInput(mask, x, y) InputService.ctx?.onTouchInput(mask, x, y)
} }
"mouse" -> { 1 -> { // mouse
InputService.ctx?.onMouseInput(mask, x, y) InputService.ctx?.onMouseInput(mask, x, y)
} }
else -> { else -> {
@@ -106,6 +103,9 @@ class MainService : Service() {
put("scale",SCREEN_INFO.scale) put("scale",SCREEN_INFO.scale)
}.toString() }.toString()
} }
"is_start" -> {
isStart.toString()
}
else -> "" else -> ""
} }
} }
@@ -138,10 +138,51 @@ class MainService : Service() {
e.printStackTrace() e.printStackTrace()
} }
} }
"update_voice_call_state" -> {
try {
val jsonObject = JSONObject(arg1)
val id = jsonObject["id"] as Int
val username = jsonObject["name"] as String
val peerId = jsonObject["peer_id"] as String
val inVoiceCall = jsonObject["in_voice_call"] as Boolean
val incomingVoiceCall = jsonObject["incoming_voice_call"] as Boolean
if (!inVoiceCall) {
if (incomingVoiceCall) {
voiceCallRequestNotification(id, "Voice Call Request", username, peerId)
} else {
if (!audioRecordHandle.switchOutVoiceCall(mediaProjection)) {
Log.e(logTag, "switchOutVoiceCall fail")
MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf(
"type" to "custom-nook-nocancel-hasclose-error",
"title" to "Voice call",
"text" to "Failed to switch out voice call."))
}
}
} else {
if (!audioRecordHandle.switchToVoiceCall(mediaProjection)) {
Log.e(logTag, "switchToVoiceCall fail")
MainActivity.flutterMethodChannel?.invokeMethod("msgbox", mapOf(
"type" to "custom-nook-nocancel-hasclose-error",
"title" to "Voice call",
"text" to "Failed to switch to voice call."))
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
"stop_capture" -> { "stop_capture" -> {
Log.d(logTag, "from rust:stop_capture") Log.d(logTag, "from rust:stop_capture")
stopCapture() stopCapture()
} }
"half_scale" -> {
val halfScale = arg1.toBoolean()
if (isHalfScale != halfScale) {
isHalfScale = halfScale
updateScreenInfo(resources.configuration.orientation)
}
}
else -> { else -> {
} }
} }
@@ -153,18 +194,16 @@ class MainService : Service() {
private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager } private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager }
private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")} private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")}
private fun translate(input: String): String {
Log.d(logTag, "translate:$LOCAL_NAME")
return FFI.translateLocale(LOCAL_NAME, input)
}
companion object { companion object {
private var _isReady = false // media permission ready status private var _isReady = false // media permission ready status
private var _isStart = false // screen capture start status private var _isStart = false // screen capture start status
private var _isAudioStart = false // audio capture start status
val isReady: Boolean val isReady: Boolean
get() = _isReady get() = _isReady
val isStart: Boolean val isStart: Boolean
get() = _isStart get() = _isStart
val isAudioStart: Boolean
get() = _isAudioStart
} }
private val logTag = "LOG_SERVICE" private val logTag = "LOG_SERVICE"
@@ -182,10 +221,7 @@ class MainService : Service() {
private var virtualDisplay: VirtualDisplay? = null private var virtualDisplay: VirtualDisplay? = null
// audio // audio
private var audioRecorder: AudioRecord? = null private val audioRecordHandle = AudioRecordHandle(this, { isStart }, { isAudioStart })
private var audioReader: AudioReader? = null
private var minBufferSize = 0
private var audioRecordStat = false
// notification // notification
private lateinit var notificationManager: NotificationManager private lateinit var notificationManager: NotificationManager
@@ -214,9 +250,11 @@ class MainService : Service() {
override fun onDestroy() { override fun onDestroy() {
checkMediaPermission() checkMediaPermission()
stopService(Intent(this, FloatingWindowService::class.java))
super.onDestroy() super.onDestroy()
} }
private var isHalfScale: Boolean? = null;
private fun updateScreenInfo(orientation: Int) { private fun updateScreenInfo(orientation: Int) {
var w: Int var w: Int
var h: Int var h: Int
@@ -249,6 +287,12 @@ class MainService : Service() {
Log.d(logTag,"updateScreenInfo:w:$w,h:$h") Log.d(logTag,"updateScreenInfo:w:$w,h:$h")
var scale = 1 var scale = 1
if (w != 0 && h != 0) { if (w != 0 && h != 0) {
if (isHalfScale == true && (w > MAX_SCREEN_SIZE || h > MAX_SCREEN_SIZE)) {
scale = 2
w /= scale
h /= scale
dpi /= scale
}
if (SCREEN_INFO.width != w) { if (SCREEN_INFO.width != w) {
SCREEN_INFO.width = w SCREEN_INFO.width = w
SCREEN_INFO.height = h SCREEN_INFO.height = h
@@ -258,6 +302,8 @@ class MainService : Service() {
stopCapture() stopCapture()
FFI.refreshScreen() FFI.refreshScreen()
startCapture() startCapture()
} else {
FFI.refreshScreen()
} }
} }
@@ -349,6 +395,14 @@ class MainService : Service() {
} }
} }
fun onVoiceCallStarted(): Boolean {
return audioRecordHandle.onVoiceCallStarted(mediaProjection)
}
fun onVoiceCallClosed(): Boolean {
return audioRecordHandle.onVoiceCallClosed(mediaProjection)
}
fun startCapture(): Boolean { fun startCapture(): Boolean {
if (isStart) { if (isStart) {
return true return true
@@ -369,12 +423,17 @@ class MainService : Service() {
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startAudioRecorder() if (!audioRecordHandle.createAudioRecorder(false, mediaProjection)) {
Log.d(logTag, "createAudioRecorder fail")
} else {
Log.d(logTag, "audio recorder start")
audioRecordHandle.startAudioRecorder()
}
} }
checkMediaPermission() checkMediaPermission()
_isStart = true _isStart = true
FFI.setFrameRawEnable("video",true) FFI.setFrameRawEnable("video",true)
FFI.setFrameRawEnable("audio",true) MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
return true return true
} }
@@ -382,8 +441,8 @@ class MainService : Service() {
fun stopCapture() { fun stopCapture() {
Log.d(logTag, "Stop Capture") Log.d(logTag, "Stop Capture")
FFI.setFrameRawEnable("video",false) FFI.setFrameRawEnable("video",false)
FFI.setFrameRawEnable("audio",false)
_isStart = false _isStart = false
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
// release video // release video
if (reuseVirtualDisplay) { if (reuseVirtualDisplay) {
// The virtual display video projection can be paused by calling `setSurface(null)`. // The virtual display video projection can be paused by calling `setSurface(null)`.
@@ -411,12 +470,14 @@ class MainService : Service() {
surface?.release() surface?.release()
// release audio // release audio
audioRecordStat = false _isAudioStart = false
audioRecordHandle.tryReleaseAudio()
} }
fun destroy() { fun destroy() {
Log.d(logTag, "destroy service") Log.d(logTag, "destroy service")
_isReady = false _isReady = false
_isAudioStart = false
stopCapture() stopCapture()
@@ -428,6 +489,7 @@ class MainService : Service() {
mediaProjection = null mediaProjection = null
checkMediaPermission() checkMediaPermission()
stopForeground(true) stopForeground(true)
stopService(Intent(this, FloatingWindowService::class.java))
stopSelf() stopSelf()
} }
@@ -514,7 +576,6 @@ class MainService : Service() {
} }
} }
private fun createMediaCodec() { private fun createMediaCodec() {
Log.d(logTag, "MediaFormat.MIMETYPE_VIDEO_VP9 :$MIME_TYPE") Log.d(logTag, "MediaFormat.MIMETYPE_VIDEO_VP9 :$MIME_TYPE")
videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE) videoEncoder = MediaCodec.createEncoderByType(MIME_TYPE)
@@ -534,80 +595,6 @@ class MainService : Service() {
} }
} }
@RequiresApi(Build.VERSION_CODES.M)
private fun startAudioRecorder() {
checkAudioRecorder()
if (audioReader != null && audioRecorder != null && minBufferSize != 0) {
try {
audioRecorder!!.startRecording()
audioRecordStat = true
thread {
while (audioRecordStat) {
audioReader!!.readSync(audioRecorder!!)?.let {
FFI.onAudioFrameUpdate(it)
}
}
// let's release here rather than onDestroy to avoid threading issue
audioRecorder?.release()
audioRecorder = null
minBufferSize = 0
Log.d(logTag, "Exit audio thread")
}
} catch (e: Exception) {
Log.d(logTag, "startAudioRecorder fail:$e")
}
} else {
Log.d(logTag, "startAudioRecorder fail")
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun checkAudioRecorder() {
if (audioRecorder != null && audioRecorder != null && minBufferSize != 0) {
return
}
// read f32 to byte , length * 4
minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize(
AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL_MASK,
AUDIO_ENCODING
)
if (minBufferSize == 0) {
Log.d(logTag, "get min buffer size fail!")
return
}
audioReader = AudioReader(minBufferSize, 4)
Log.d(logTag, "init audioData len:$minBufferSize")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let {
val apcc = AudioPlaybackCaptureConfiguration.Builder(it)
.addMatchingUsage(AudioAttributes.USAGE_MEDIA)
.addMatchingUsage(AudioAttributes.USAGE_ALARM)
.addMatchingUsage(AudioAttributes.USAGE_GAME)
.addMatchingUsage(AudioAttributes.USAGE_UNKNOWN).build()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
return
}
audioRecorder = AudioRecord.Builder()
.setAudioFormat(
AudioFormat.Builder()
.setEncoding(AUDIO_ENCODING)
.setSampleRate(AUDIO_SAMPLE_RATE)
.setChannelMask(AUDIO_CHANNEL_MASK).build()
)
.setAudioPlaybackCaptureConfig(apcc)
.setBufferSizeInBytes(minBufferSize).build()
Log.d(logTag, "createAudioRecorder done,minBufferSize:$minBufferSize")
return
}
}
Log.d(logTag, "createAudioRecorder fail")
}
private fun initNotification() { private fun initNotification() {
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -692,6 +679,21 @@ class MainService : Service() {
notificationManager.notify(getClientNotifyID(clientID), notification) notificationManager.notify(getClientNotifyID(clientID), notification)
} }
private fun voiceCallRequestNotification(
clientID: Int,
type: String,
username: String,
peerId: String
) {
val notification = notificationBuilder
.setOngoing(false)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setContentTitle(translate("Do you accept?"))
.setContentText("$type:$username-$peerId")
.build()
notificationManager.notify(getClientNotifyID(clientID), notification)
}
private fun getClientNotifyID(clientID: Int): Int { private fun getClientNotifyID(clientID: Int): Int {
return clientID + NOTIFY_ID_OFFSET return clientID + NOTIFY_ID_OFFSET
} }
@@ -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)
}
}
@@ -0,0 +1,78 @@
package com.carriez.flutter_hbb
// Inspired by https://github.com/yosemiteyss/flutter_volume_controller/blob/main/android/src/main/kotlin/com/yosemiteyss/flutter_volume_controller/VolumeController.kt
import android.media.AudioManager
import android.os.Build
import android.util.Log
class VolumeController(private val audioManager: AudioManager) {
private val logTag = "volume controller"
fun getVolume(streamType: Int): Double {
val current = audioManager.getStreamVolume(streamType)
val max = audioManager.getStreamMaxVolume(streamType)
return current.toDouble() / max
}
fun setVolume(volume: Double, showSystemUI: Boolean, streamType: Int) {
val max = audioManager.getStreamMaxVolume(streamType)
audioManager.setStreamVolume(
streamType,
(max * volume).toInt(),
if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0
)
}
fun raiseVolume(step: Double?, showSystemUI: Boolean, streamType: Int) {
if (step == null) {
audioManager.adjustStreamVolume(
streamType,
AudioManager.ADJUST_RAISE,
if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0
)
} else {
val target = getVolume(streamType) + step
setVolume(target, showSystemUI, streamType)
}
}
fun lowerVolume(step: Double?, showSystemUI: Boolean, streamType: Int) {
if (step == null) {
audioManager.adjustStreamVolume(
streamType,
AudioManager.ADJUST_LOWER,
if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0
)
} else {
val target = getVolume(streamType) - step
setVolume(target, showSystemUI, streamType)
}
}
fun getMute(streamType: Int): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
audioManager.isStreamMute(streamType)
} else {
audioManager.getStreamVolume(streamType) == 0
}
}
private fun setMute(isMuted: Boolean, showSystemUI: Boolean, streamType: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
audioManager.adjustStreamVolume(
streamType,
if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
if (showSystemUI) AudioManager.FLAG_SHOW_UI else 0
)
} else {
audioManager.setStreamMute(streamType, isMuted)
}
}
fun toggleMute(showSystemUI: Boolean, streamType: Int) {
val isMuted = getMute(streamType)
setMute(!isMuted, showSystemUI, streamType)
}
}
@@ -15,10 +15,14 @@ import android.os.Looper
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
import android.provider.Settings.* import android.provider.Settings.*
import android.util.DisplayMetrics
import android.util.Log
import android.view.WindowManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions import com.hjq.permissions.XXPermissions
import ffi.FFI
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.* import java.util.*
@@ -43,6 +47,9 @@ const val START_ACTION = "start_action"
const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt" const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt"
const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt" const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt"
const val SYNC_APP_DIR_CONFIG_PATH = "sync_app_dir" const val SYNC_APP_DIR_CONFIG_PATH = "sync_app_dir"
const val GET_VALUE = "get_value"
const val KEY_IS_SUPPORT_VOICE_CALL = "KEY_IS_SUPPORT_VOICE_CALL"
const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES" const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES"
const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT" const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT"
@@ -56,6 +63,11 @@ data class Info(
var width: Int, var height: Int, var scale: Int, var dpi: Int var width: Int, var height: Int, var scale: Int, var dpi: Int
) )
fun isSupportVoiceCall(): Boolean {
// https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_COMMUNICATION
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
}
fun requestPermission(context: Context, type: String) { fun requestPermission(context: Context, type: String) {
XXPermissions.with(context) XXPermissions.with(context)
.permission(type) .permission(type)
@@ -120,3 +132,26 @@ class AudioReader(val bufSize: Int, private val maxFrames: Int) {
} }
} }
} }
fun getScreenSize(windowManager: WindowManager) : Pair<Int, Int>{
var w = 0
var h = 0
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val m = windowManager.maximumWindowMetrics
w = m.bounds.width()
h = m.bounds.height()
} else {
val dm = DisplayMetrics()
windowManager.defaultDisplay.getRealMetrics(dm)
w = dm.widthPixels
h = dm.heightPixels
}
return Pair(w, h)
}
fun translate(input: String): String {
Log.d("common", "translate:$LOCAL_NAME")
return FFI.translateLocale(LOCAL_NAME, input)
}
@@ -5,12 +5,15 @@ package ffi
import android.content.Context import android.content.Context
import java.nio.ByteBuffer import java.nio.ByteBuffer
import com.carriez.flutter_hbb.RdClipboardManager
object FFI { object FFI {
init { init {
System.loadLibrary("rustdesk") System.loadLibrary("rustdesk")
} }
external fun init(ctx: Context) external fun init(ctx: Context)
external fun setClipboardManager(clipboardManager: RdClipboardManager)
external fun startServer(app_dir: String, custom_client_config: String) external fun startServer(app_dir: String, custom_client_config: String)
external fun startService() external fun startService()
external fun onVideoFrameUpdate(buf: ByteBuffer) external fun onVideoFrameUpdate(buf: ByteBuffer)
@@ -18,4 +21,8 @@ object FFI {
external fun translateLocale(localeName: String, input: String): String external fun translateLocale(localeName: String, input: String): String
external fun refreshScreen() external fun refreshScreen()
external fun setFrameRawEnable(name: String, value: Boolean) 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
} }
@@ -0,0 +1,7 @@
<vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android" android:height="320dp" android:viewportHeight="32" android:viewportWidth="32" android:width="320dp">
<path android:fillColor="#ffffff" android:pathData="M16,0L16,0A16,16 0,0 1,32 16L32,16A16,16 0,0 1,16 32L16,32A16,16 0,0 1,0 16L0,16A16,16 0,0 1,16 0z" android:strokeColor="#00000000" android:strokeWidth="1"/>
<path android:fillColor="#1a1a1a" android:pathData="m23.89,10.135 l-1.807,1.795c-0.318,0.285 -0.472,0.744 -0.293,1.131 1.204,2.518 0.747,5.52 -1.228,7.494 -1.976,1.973 -4.981,2.429 -7.502,1.226 -0.371,-0.166 -0.807,-0.025 -1.093,0.265l-1.836,1.833c-0.216,0.211 -0.322,0.51 -0.288,0.809 0.034,0.3 0.206,0.567 0.463,0.723 4.326,2.618 9.882,1.951 13.463,-1.618 3.581,-3.568 4.264,-9.115 1.655,-13.443 -0.15,-0.263 -0.414,-0.442 -0.714,-0.484 -0.3,-0.043 -0.603,0.058 -0.819,0.269zM8.265,8.184c-3.599,3.554 -4.304,9.103 -1.709,13.441 0.15,0.264 0.413,0.443 0.714,0.485 0.3,0.042 0.603,-0.058 0.82,-0.27l1.797,-1.785c0.325,-0.285 0.484,-0.749 0.303,-1.141 -1.204,-2.518 -0.748,-5.52 1.228,-7.493 1.975,-1.973 4.981,-2.429 7.502,-1.227 0.367,0.165 0.797,0.028 1.084,-0.254l1.846,-1.844c0.216,-0.211 0.322,-0.509 0.288,-0.809 -0.035,-0.299 -0.206,-0.566 -0.463,-0.723 -4.334,-2.596 -9.881,-1.908 -13.448,1.668z" android:strokeWidth="0.987992"/>
</vector>
+2 -15
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 { allprojects {
repositories { repositories {
google() google()
@@ -24,6 +9,8 @@ allprojects {
rootProject.buildDir = '../build' rootProject.buildDir = '../build'
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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
+22 -8
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") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def properties = new Properties()
assert localPropertiesFile.exists() repositories {
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } google()
mavenCentral()
gradlePluginPortal()
}
}
def flutterSdkPath = properties.getProperty("flutter.sdk") plugins {
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
}
include ":app"
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="-4 -4 32 32" width="24px" fill="#5f6368"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/></svg>

After

Width:  |  Height:  |  Size: 277 B

+1
View File
@@ -68,6 +68,7 @@ function build {
pushd "$SCRIPTDIR/.." pushd "$SCRIPTDIR/.."
$VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed" $VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed"
popd popd
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-$VCPKG_TARGET-rel-out.log" || true
echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies" echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies"
if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then
+624
View File
@@ -0,0 +1,624 @@
#!/bin/bash
#
# Script to build F-Droid release of RustDesk
#
# Copyright (C) 2024, The RustDesk Authors
# 2024, Vasyl Gello <vasek.gello@gmail.com>
#
# The script is invoked by F-Droid builder system ste-by-step.
#
# It accepts the following arguments:
#
# - versionName from https://github.com/rustdesk/rustdesk/releases/download/fdroid-version/rustdesk-version.txt
# - versionCode from https://github.com/rustdesk/rustdesk/releases/download/fdroid-version/rustdesk-version.txt
# - Android architecture to build APK for: armeabi-v7a arm64-v8av x86 x86_64
# - The build step to execute:
#
# + sudo-deps: as root, install needed Debian packages into builder VM
# + prebuild: patch sources and do other stuff before the build
# + 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}"
VERCODE="${2}"
ANDROID_ABI="${3}"
BUILDSTEP="${4}"
if [ -z "${VERNAME}" ] || [ -z "${VERCODE}" ] || [ -z "${ANDROID_ABI}" ] ||
[ -z "${BUILDSTEP}" ]; then
echo "ERROR: Command-line arguments are all required to be non-empty!" >&2
exit 1
fi
# Set various architecture-specific identifiers
case "${ANDROID_ABI}" in
arm64-v8a)
FLUTTER_TARGET=android-arm64
NDK_TARGET=aarch64-linux-android
RUST_TARGET=aarch64-linux-android
RUSTDESK_FEATURES='flutter,hwcodec'
;;
armeabi-v7a)
FLUTTER_TARGET=android-arm
NDK_TARGET=arm-linux-androideabi
RUST_TARGET=armv7-linux-androideabi
RUSTDESK_FEATURES='flutter,hwcodec'
;;
x86_64)
FLUTTER_TARGET=android-x64
NDK_TARGET=x86_64-linux-android
RUST_TARGET=x86_64-linux-android
RUSTDESK_FEATURES='flutter'
;;
x86)
FLUTTER_TARGET=android-x86
NDK_TARGET=i686-linux-android
RUST_TARGET=i686-linux-android
RUSTDESK_FEATURES='flutter'
;;
*)
echo "ERROR: Unknown Android ABI '${ANDROID_ABI}'!" >&2
exit 1
;;
esac
# Check ANDROID_SDK_ROOT and sdkmanager present on PATH
if [ ! -d "${ANDROID_SDK_ROOT}" ] || ! command -v sdkmanager 1>/dev/null; then
echo "ERROR: Can not find Android SDK!" >&2
exit 1
fi
# Export necessary variables
export PATH="${PATH}:${HOME}/flutter/bin:${HOME}/depot_tools"
export VCPKG_ROOT="${HOME}/vcpkg"
# Now act depending on build step
# NOTE: F-Droid maintainers require explicit declaration of dependencies
# as root via `Builds.sudo` F-Droid metadata directive:
# https://gitlab.com/fdroid/fdroiddata/-/merge_requests/15343#note_1988918695
case "${BUILDSTEP}" in
prebuild)
# prebuild: patch sources and do other stuff before the build
#
# Extract required versions for NDK, Rust, Flutter from
# '.github/workflows/flutter-build.yml'
#
CARGO_NDK_VERSION="$(yq -r \
.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
CARGO_EXPAND_VERSION="$(yq -r \
.env.CARGO_EXPAND_VERSION \
.github/workflows/bridge.yml)"
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/bridge.yml)"
NDK_VERSION="$(yq -r \
.env.NDK_VERSION \
.github/workflows/flutter-build.yml)"
RUST_VERSION="$(yq -r \
.env.RUST_VERSION \
.github/workflows/flutter-build.yml)"
VCPKG_COMMIT_ID="$(yq -r \
.env.VCPKG_COMMIT_ID \
.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
echo "ERROR: Can not identify all required versions!" >&2
exit 1
fi
# Map NDK version to revision
NDK_VERSION="$(wget \
-qO- \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
'https://api.github.com/repos/android/ndk/releases' |
jq -r ".[] | select(.tag_name == \"${NDK_VERSION}\") | .body | match(\"ndkVersion \\\"(.*)\\\"\").captures[0].string")"
if [ -z "${NDK_VERSION}" ]; then
echo "ERROR: Can not map Android NDK codename to revision!" >&2
exit 1
fi
export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}"
export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}"
#
# Install the components
#
set -e
# Install Android NDK
if [ ! -d "${ANDROID_NDK_ROOT}" ]; then
sdkmanager --install "ndk;${NDK_VERSION}"
fi
# Install Rust
if [ ! -f "${HOME}/rustup/rustup-init.sh" ]; then
pushd "${HOME}"
git clone --depth 1 https://github.com/rust-lang/rustup
popd # ${HOME}
fi
pushd "${HOME}/rustup"
bash rustup-init.sh -y \
--target "${RUST_TARGET}" \
--default-toolchain "${RUST_VERSION}"
popd
if ! command -v cargo 1>/dev/null 2>&1; then
. "${HOME}/.cargo/env"
fi
# Install cargo-ndk
cargo install \
cargo-ndk \
--version "${CARGO_NDK_VERSION}" \
--locked
# Install rust bridge generator
cargo install \
cargo-expand \
--version "${CARGO_EXPAND_VERSION}" \
--locked
cargo install flutter_rust_bridge_codegen \
--version "${FLUTTER_RUST_BRIDGE_VERSION}" \
--features "uuid" \
--locked
# Populate native vcpkg dependencies
if [ ! -d "${VCPKG_ROOT}" ]; then
pushd "${HOME}"
git clone \
https://github.com/Microsoft/vcpkg.git
git clone \
https://github.com/Microsoft/vcpkg-tool.git
pushd vcpkg-tool
mkdir build
pushd build
cmake \
-DCMAKE_BUILD_TYPE=Release \
-G 'Ninja' \
-DVCPKG_DEVELOPMENT_WARNINGS=OFF \
..
cmake --build .
popd # build
popd # vcpkg-tool
pushd vcpkg
git reset --hard "${VCPKG_COMMIT_ID}"
cp -a ../vcpkg-tool/build/vcpkg vcpkg
# disable telemetry
touch "vcpkg.disable-metrics"
popd # vcpkg
popd # ${HOME}
fi
# Install depot-tools for x86
if [ "${ANDROID_ABI}" = "x86" ]; then
if [ ! -d "${HOME}/depot_tools" ]; then
pushd "${HOME}"
git clone \
--depth 1 \
https://chromium.googlesource.com/chromium/tools/depot_tools.git
popd # ${HOME}
fi
fi
# Patch the RustDesk sources
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' \
flutter/pubspec.yaml
sed \
-i \
-e '/ firebase/,/ version/d' \
flutter/pubspec.lock
sed \
-i \
-e '/firebase/Id' \
flutter/lib/main.dart
;;
build)
# build: perform actual build of APK file
set -e
#
# Extract required versions for NDK, Rust, Flutter from
# '.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
NDK_VERSION="$(yq -r \
.env.NDK_VERSION \
.github/workflows/flutter-build.yml)"
# Map NDK version to revision
NDK_VERSION="$(wget \
-qO- \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
'https://api.github.com/repos/android/ndk/releases' |
jq -r ".[] | select(.tag_name == \"${NDK_VERSION}\") | .body | match(\"ndkVersion \\\"(.*)\\\"\").captures[0].string")"
if [ -z "${NDK_VERSION}" ]; then
echo "ERROR: Can not map Android NDK codename to revision!" >&2
exit 1
fi
export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}"
export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION}"
if ! command -v cargo 1>/dev/null 2>&1; then
. "${HOME}/.cargo/env"
fi
# Download Flutter dependencies
pushd flutter
flutter clean
flutter packages pub get
popd # flutter
# Build host android deps
bash flutter/build_android_deps.sh "${ANDROID_ABI}"
# Build rustdesk lib
cargo ndk \
--platform 21 \
--target "${RUST_TARGET}" \
--bindgen \
build \
--release \
--features "${RUSTDESK_FEATURES}"
mkdir -p "flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"
cp "target/${RUST_TARGET}/release/liblibrustdesk.so" \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
cp "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/${NDK_TARGET}/libc++_shared.so" \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/"
"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip" \
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"/*
# Build flutter-jit-release for x86
if [ "${ANDROID_ABI}" = "x86" ]; then
pushd flutter-sdk
echo "## Sync flutter engine sources"
echo "### We need fakeroot because chromium base image is unpacked with weird uid/gid ownership"
sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_VERSION}/" .gclient
export FAKEROOTDONTTRYCHOWN=1
fakeroot gclient sync
unset FAKEROOTDONTTRYCHOWN
pushd src
echo "## Patch away Google Play dependencies"
rm \
flutter/shell/platform/android/io/flutter/app/FlutterPlayStoreSplitApplication.java \
flutter/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java flutter/shell/platform/android/io/flutter/embedding/android/FlutterPlayStoreSplitApplication.java
sed \
-i \
-e '/PlayStore/d' \
flutter/tools/android_lint/project.xml \
flutter/shell/platform/android/BUILD.gn
sed \
-i \
-e '/com.google.android.play/d' \
flutter/tools/androidx/files.json
echo "## Configure android engine build"
flutter/tools/gn \
--android --android-cpu x86 --runtime-mode=jit_release \
--no-goma --no-enable-unittests
echo "## Perform android engine build"
ninja -C out/android_jit_release_x86
echo "## Configure host engine build"
flutter/tools/gn \
--android-cpu x86 --runtime-mode=jit_release \
--no-goma --no-enable-unittests
echo "## Perform android engine build"
ninja -C out/host_jit_release_x86
echo "## Rename host engine"
mv out/host_jit_release_x86 out/host_jit_release
echo "## Mimic jit_release engine to debug to use with flutter build apk"
pushd out/android_jit_release_x86
sed \
-e 's/jit_release/debug/' \
flutter_embedding_jit_release.maven-metadata.xml \
1>flutter_embedding_debug.maven-metadata.xml
sed \
-e 's/jit_release/debug/' \
flutter_embedding_jit_release.pom \
1>flutter_embedding_debug.pom
sed \
-e 's/jit_release/debug/' \
x86_jit_release.maven-metadata.xml \
1>x86_debug.maven-metadata.xml
sed \
-e 's/jit_release/debug/' \
x86_jit_release.pom \
1>x86_debug.pom
cp -a \
flutter_embedding_jit_release-sources.jar \
flutter_embedding_debug-sources.jar
cp -a \
flutter_embedding_jit_release.jar \
flutter_embedding_debug.jar
cp -a \
x86_jit_release.jar \
x86_debug.jar
popd # out/android_jit_release_x86
popd # src
popd # flutter-sdk
echo "# Clean up intermediate engine files and show free space"
rm -rf \
flutter-sdk/src/out/android_jit_release_x86/obj \
flutter-sdk/src/out/host_jit_release/obj
mv flutter-sdk/src/out flutter-out
rm -rf flutter-sdk
mkdir -p flutter-sdk/src/
mv flutter-out flutter-sdk/src/out
fi
# Build the apk
pushd flutter
if [ "${ANDROID_ABI}" = "x86" ]; then
flutter build apk \
--local-engine-src-path="$(readlink -mf "../flutter-sdk/src")" \
--local-engine=android_jit_release_x86 \
--debug \
--build-number="${VERCODE}" \
--build-name="${VERNAME}" \
--target-platform "${FLUTTER_TARGET}"
else
flutter build apk \
--release \
--build-number="${VERCODE}" \
--build-name="${VERNAME}" \
--target-platform "${FLUTTER_TARGET}"
fi
popd # flutter
rm -rf flutter-sdk
# Special step for fdroiddata CI builds to remove .gitconfig
rm -f /home/vagrant/.gitconfig
;;
*)
echo "ERROR: Unknown build step '${BUILDSTEP}'!" >&2
exit 1
;;
esac
# Report success
echo "All done!"
+2
View File
@@ -2,4 +2,6 @@
# https://docs.flutter.dev/deployment/ios # https://docs.flutter.dev/deployment/ios
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info # flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
# no obfuscate, because no easy to check errors # 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 flutter build ipa --release
-14
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 -
+1 -1
View File
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>12.0</string> <string>13.0</string>
</dict> </dict>
</plist> </plist>
+1 -4
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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
platform :ios, '12.0' platform :ios, '13.0'
project 'Runner', { project 'Runner', {
'Debug' => :debug, 'Debug' => :debug,
+2 -2
View File
@@ -133,10 +133,10 @@ SPEC CHECKSUMS:
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156 PODFILE CHECKSUM: 83d1b0fb6fc8613d8312a03b8e1540d37cfc5d2c
COCOAPODS: 1.15.2 COCOAPODS: 1.15.2
+3 -3
View File
@@ -347,7 +347,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@@ -491,7 +491,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -541,7 +541,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
+1 -1
View File
@@ -1,7 +1,7 @@
import UIKit import UIKit
import Flutter import Flutter
@UIApplicationMain @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
+2
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.networking.wifi-info</key> <key>com.apple.developer.networking.wifi-info</key>
<true/> <true/>
</dict> </dict>
+3 -1
View File
@@ -1,2 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cargo build --features flutter --release --target aarch64-apple-ios --lib 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
+640 -201
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -67,6 +67,7 @@ class PeerPayload {
int? status; int? status;
String user = ''; String user = '';
String user_name = ''; String user_name = '';
String? device_group_name;
String note = ''; String note = '';
PeerPayload.fromJson(Map<String, dynamic> json) PeerPayload.fromJson(Map<String, dynamic> json)
@@ -75,6 +76,7 @@ class PeerPayload {
status = json['status'], status = json['status'],
user = json['user'] ?? '', user = json['user'] ?? '',
user_name = json['user_name'] ?? '', user_name = json['user_name'] ?? '',
device_group_name = json['device_group_name'] ?? '',
note = json['note'] ?? ''; note = json['note'] ?? '';
static Peer toPeer(PeerPayload p) { static Peer toPeer(PeerPayload p) {
@@ -84,6 +86,7 @@ class PeerPayload {
"username": p.info['username'] ?? '', "username": p.info['username'] ?? '',
"platform": _platform(p.info['os']), "platform": _platform(p.info['os']),
"hostname": p.info['device_name'], "hostname": p.info['device_name'],
"device_group_name": p.device_group_name,
}); });
} }
@@ -265,3 +268,19 @@ class AbTag {
: name = json['name'] ?? '', : name = json['name'] ?? '',
color = json['color'] ?? ''; color = json['color'] ?? '';
} }
class DeviceGroupPayload {
String name;
DeviceGroupPayload(this.name);
DeviceGroupPayload.fromJson(Map<String, dynamic> json)
: name = json['name'] ?? '';
Map<String, dynamic> toGroupCacheJson() {
final Map<String, dynamic> map = {
'name': name,
};
return map;
}
}
+54 -54
View File
@@ -10,16 +10,16 @@ class PrivacyModeState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxString>(tag: key)) {
final RxString state = ''.obs; final RxString state = ''.obs;
Get.put(state, tag: key); Get.put<RxString>(state, tag: key);
} }
} }
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxString>(tag: key)) {
Get.delete(tag: key); Get.delete<RxString>(tag: key);
} else { } else {
Get.find<RxString>(tag: key).value = ''; Get.find<RxString>(tag: key).value = '';
} }
@@ -33,9 +33,9 @@ class BlockInputState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
final RxBool state = false.obs; final RxBool state = false.obs;
Get.put(state, tag: key); Get.put<RxBool>(state, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = false; Get.find<RxBool>(tag: key).value = false;
} }
@@ -43,8 +43,8 @@ class BlockInputState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -56,9 +56,9 @@ class CurrentDisplayState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxInt>(tag: key)) {
final RxInt state = RxInt(0); final RxInt state = RxInt(0);
Get.put(state, tag: key); Get.put<RxInt>(state, tag: key);
} else { } else {
Get.find<RxInt>(tag: key).value = 0; Get.find<RxInt>(tag: key).value = 0;
} }
@@ -66,8 +66,8 @@ class CurrentDisplayState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxInt>(tag: key)) {
Get.delete(tag: key); Get.delete<RxInt>(tag: key);
} }
} }
@@ -105,16 +105,16 @@ class ConnectionTypeState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<ConnectionType>(tag: key)) {
final ConnectionType collectionType = ConnectionType(); final ConnectionType collectionType = ConnectionType();
Get.put(collectionType, tag: key); Get.put<ConnectionType>(collectionType, tag: key);
} }
} }
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<ConnectionType>(tag: key)) {
Get.delete(tag: key); Get.delete<ConnectionType>(tag: key);
} }
} }
@@ -127,9 +127,9 @@ class FingerprintState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxString>(tag: key)) {
final RxString state = ''.obs; final RxString state = ''.obs;
Get.put(state, tag: key); Get.put<RxString>(state, tag: key);
} else { } else {
Get.find<RxString>(tag: key).value = ''; Get.find<RxString>(tag: key).value = '';
} }
@@ -137,8 +137,8 @@ class FingerprintState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxString>(tag: key)) {
Get.delete(tag: key); Get.delete<RxString>(tag: key);
} }
} }
@@ -150,9 +150,9 @@ class ShowRemoteCursorState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
final RxBool state = false.obs; final RxBool state = false.obs;
Get.put(state, tag: key); Get.put<RxBool>(state, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = false; Get.find<RxBool>(tag: key).value = false;
} }
@@ -160,8 +160,8 @@ class ShowRemoteCursorState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -173,9 +173,9 @@ class ShowRemoteCursorLockState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
final RxBool state = false.obs; final RxBool state = false.obs;
Get.put(state, tag: key); Get.put<RxBool>(state, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = false; Get.find<RxBool>(tag: key).value = false;
} }
@@ -183,8 +183,8 @@ class ShowRemoteCursorLockState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -196,10 +196,10 @@ class KeyboardEnabledState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
// Server side, default true // Server side, default true
final RxBool state = true.obs; final RxBool state = true.obs;
Get.put(state, tag: key); Get.put<RxBool>(state, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = true; Get.find<RxBool>(tag: key).value = true;
} }
@@ -207,8 +207,8 @@ class KeyboardEnabledState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -220,9 +220,9 @@ class RemoteCursorMovedState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
final RxBool state = false.obs; final RxBool state = false.obs;
Get.put(state, tag: key); Get.put<RxBool>(state, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = false; Get.find<RxBool>(tag: key).value = false;
} }
@@ -230,8 +230,8 @@ class RemoteCursorMovedState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -243,9 +243,9 @@ class RemoteCountState {
static void init() { static void init() {
final key = tag(); final key = tag();
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxInt>(tag: key)) {
final RxInt state = 1.obs; final RxInt state = 1.obs;
Get.put(state, tag: key); Get.put<RxInt>(state, tag: key);
} else { } else {
Get.find<RxInt>(tag: key).value = 1; Get.find<RxInt>(tag: key).value = 1;
} }
@@ -253,8 +253,8 @@ class RemoteCountState {
static void delete() { static void delete() {
final key = tag(); final key = tag();
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxInt>(tag: key)) {
Get.delete(tag: key); Get.delete<RxInt>(tag: key);
} }
} }
@@ -266,9 +266,9 @@ class PeerBoolOption {
static void init(String id, String opt, bool Function() init_getter) { static void init(String id, String opt, bool Function() init_getter) {
final key = tag(id, opt); final key = tag(id, opt);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxBool>(tag: key)) {
final RxBool value = RxBool(init_getter()); final RxBool value = RxBool(init_getter());
Get.put(value, tag: key); Get.put<RxBool>(value, tag: key);
} else { } else {
Get.find<RxBool>(tag: key).value = init_getter(); Get.find<RxBool>(tag: key).value = init_getter();
} }
@@ -276,8 +276,8 @@ class PeerBoolOption {
static void delete(String id, String opt) { static void delete(String id, String opt) {
final key = tag(id, opt); final key = tag(id, opt);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxBool>(tag: key)) {
Get.delete(tag: key); Get.delete<RxBool>(tag: key);
} }
} }
@@ -290,9 +290,9 @@ class PeerStringOption {
static void init(String id, String opt, String Function() init_getter) { static void init(String id, String opt, String Function() init_getter) {
final key = tag(id, opt); final key = tag(id, opt);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxString>(tag: key)) {
final RxString value = RxString(init_getter()); final RxString value = RxString(init_getter());
Get.put(value, tag: key); Get.put<RxString>(value, tag: key);
} else { } else {
Get.find<RxString>(tag: key).value = init_getter(); Get.find<RxString>(tag: key).value = init_getter();
} }
@@ -300,8 +300,8 @@ class PeerStringOption {
static void delete(String id, String opt) { static void delete(String id, String opt) {
final key = tag(id, opt); final key = tag(id, opt);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxString>(tag: key)) {
Get.delete(tag: key); Get.delete<RxString>(tag: key);
} }
} }
@@ -314,9 +314,9 @@ class UnreadChatCountState {
static void init(String id) { static void init(String id) {
final key = tag(id); final key = tag(id);
if (!Get.isRegistered(tag: key)) { if (!Get.isRegistered<RxInt>(tag: key)) {
final RxInt state = RxInt(0); final RxInt state = RxInt(0);
Get.put(state, tag: key); Get.put<RxInt>(state, tag: key);
} else { } else {
Get.find<RxInt>(tag: key).value = 0; Get.find<RxInt>(tag: key).value = 0;
} }
@@ -324,8 +324,8 @@ class UnreadChatCountState {
static void delete(String id) { static void delete(String id) {
final key = tag(id); final key = tag(id);
if (Get.isRegistered(tag: key)) { if (Get.isRegistered<RxInt>(tag: key)) {
Get.delete(tag: key); Get.delete<RxInt>(tag: key);
} }
} }
@@ -341,7 +341,7 @@ initSharedStates(String id) {
ShowRemoteCursorLockState.init(id); ShowRemoteCursorLockState.init(id);
RemoteCursorMovedState.init(id); RemoteCursorMovedState.init(id);
FingerprintState.init(id); FingerprintState.init(id);
PeerBoolOption.init(id, 'zoom-cursor', () => false); PeerBoolOption.init(id, kOptionZoomCursor, () => false);
UnreadChatCountState.init(id); UnreadChatCountState.init(id);
if (isMobile) ConnectionTypeState.init(id); // desktop in other places if (isMobile) ConnectionTypeState.init(id); // desktop in other places
} }
@@ -355,7 +355,7 @@ removeSharedStates(String id) {
KeyboardEnabledState.delete(id); KeyboardEnabledState.delete(id);
RemoteCursorMovedState.delete(id); RemoteCursorMovedState.delete(id);
FingerprintState.delete(id); FingerprintState.delete(id);
PeerBoolOption.delete(id, 'zoom-cursor'); PeerBoolOption.delete(id, kOptionZoomCursor);
UnreadChatCountState.delete(id); UnreadChatCountState.delete(id);
if (isMobile) ConnectionTypeState.delete(id); if (isMobile) ConnectionTypeState.delete(id);
} }
+174 -110
View File
@@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:bot_toast/bot_toast.dart';
import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:dynamic_layouts/dynamic_layouts.dart'; import 'package:dynamic_layouts/dynamic_layouts.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -7,9 +8,11 @@ import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/ab_model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -34,17 +37,14 @@ class AddressBook extends StatefulWidget {
class _AddressBookState extends State<AddressBook> { class _AddressBookState extends State<AddressBook> {
var menuPos = RelativeRect.fill; var menuPos = RelativeRect.fill;
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) => Obx(() { Widget build(BuildContext context) => Obx(() {
if (!gFFI.userModel.isLogin) { if (!gFFI.userModel.isLogin) {
return Center( return Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: loginDialog, child: Text(translate("Login")))); onPressed: loginDialog, child: Text(translate("Login"))));
} else if (gFFI.userModel.networkError.isNotEmpty) {
return netWorkErrorWidget();
} else { } else {
return Column( return Column(
children: [ children: [
@@ -63,15 +63,16 @@ class _AddressBookState extends State<AddressBook> {
retry: null, // remove retry retry: null, // remove retry
close: () => gFFI.abModel.currentAbPushError.value = ''), close: () => gFFI.abModel.currentAbPushError.value = ''),
Expanded( Expanded(
child: (isDesktop || isWebDesktop) child: Obx(() => stateGlobal.isPortrait.isTrue
? _buildAddressBookDesktop() ? _buildAddressBookPortrait()
: _buildAddressBookMobile()) : _buildAddressBookLandscape()),
),
], ],
); );
} }
}); });
Widget _buildAddressBookDesktop() { Widget _buildAddressBookLandscape() {
return Row( return Row(
children: [ children: [
Offstage( Offstage(
@@ -108,7 +109,8 @@ class _AddressBookState extends State<AddressBook> {
); );
} }
Widget _buildAddressBookMobile() { Widget _buildAddressBookPortrait() {
const padding = 8.0;
return Column( return Column(
children: [ children: [
Offstage( Offstage(
@@ -119,7 +121,8 @@ class _AddressBookState extends State<AddressBook> {
border: Border.all( border: Border.all(
color: Theme.of(context).colorScheme.background)), color: Theme.of(context).colorScheme.background)),
child: Container( child: Container(
padding: const EdgeInsets.all(8.0), padding:
const EdgeInsets.fromLTRB(padding, 0, padding, padding),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -129,7 +132,6 @@ class _AddressBookState extends State<AddressBook> {
width: double.infinity, width: double.infinity,
child: _buildTags(), child: _buildTags(),
), ),
_buildAbPermission(),
], ],
), ),
), ),
@@ -189,42 +191,74 @@ class _AddressBookState extends State<AddressBook> {
if (!names.contains(gFFI.abModel.currentName.value)) { if (!names.contains(gFFI.abModel.currentName.value)) {
return Offstage(); return Offstage();
} }
// order: personal, divider, character order
// https://pub.dev/packages/dropdown_button2#3-dropdownbutton2-with-items-of-different-heights-like-dividers
final personalAddressBookName = gFFI.abModel.personalAddressBookName();
bool contains = names.remove(personalAddressBookName);
names.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
if (contains) {
names.insert(0, personalAddressBookName);
}
Row buildItem(String e, {bool button = false}) {
return Row(
children: [
Expanded(
child: Tooltip(
waitDuration: Duration(milliseconds: 500),
message: gFFI.abModel.translatedName(e),
child: Text(
gFFI.abModel.translatedName(e),
style: button ? null : TextStyle(fontSize: 14.0),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: button ? TextAlign.center : null,
)),
),
],
);
}
final items = names
.map((e) => DropdownMenuItem(value: e, child: buildItem(e)))
.toList();
var menuItemStyleData = MenuItemStyleData(height: 36);
if (contains && items.length > 1) {
items.insert(1, DropdownMenuItem(enabled: false, child: Divider()));
List<double> customHeights = List.filled(items.length, 36);
customHeights[1] = 4;
menuItemStyleData = MenuItemStyleData(customHeights: customHeights);
}
final TextEditingController textEditingController = TextEditingController(); final TextEditingController textEditingController = TextEditingController();
final isOptFixed = isOptionFixed(kOptionCurrentAbName);
return DropdownButton2<String>( return DropdownButton2<String>(
value: gFFI.abModel.currentName.value, value: gFFI.abModel.currentName.value,
onChanged: (value) { onChanged: isOptFixed
if (value != null) { ? null
gFFI.abModel.setCurrentName(value); : (value) {
bind.setLocalFlutterOption(k: 'current-ab-name', v: value); if (value != null) {
} gFFI.abModel.setCurrentName(value);
}, bind.setLocalFlutterOption(k: kOptionCurrentAbName, v: value);
}
},
customButton: Obx(() => Container(
height: stateGlobal.isPortrait.isFalse ? 48 : 40,
child: Row(children: [
Expanded(
child:
buildItem(gFFI.abModel.currentName.value, button: true)),
Icon(Icons.arrow_drop_down),
]),
)),
underline: Container( underline: Container(
height: 0.7, height: 0.7,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
), ),
buttonStyleData: ButtonStyleData(height: 48), menuItemStyleData: menuItemStyleData,
menuItemStyleData: MenuItemStyleData(height: 36), items: items,
items: names
.map((e) => DropdownMenuItem(
value: e,
child: Row(
children: [
Expanded(
child: Tooltip(
waitDuration: Duration(milliseconds: 500),
message: gFFI.abModel.translatedName(e),
child: Text(
gFFI.abModel.translatedName(e),
style: TextStyle(fontSize: 14.0),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)),
),
],
)))
.toList(),
isExpanded: true, isExpanded: true,
isDense: true,
dropdownSearchData: DropdownSearchData( dropdownSearchData: DropdownSearchData(
searchController: textEditingController, searchController: textEditingController,
searchInnerWidgetHeight: 50, searchInnerWidgetHeight: 50,
@@ -252,7 +286,7 @@ class _AddressBookState extends State<AddressBook> {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
), ).workaroundFreezeLinuxMint(),
), ),
searchMatchFn: (item, searchValue) { searchMatchFn: (item, searchValue) {
return item.value return item.value
@@ -283,13 +317,14 @@ class _AddressBookState extends State<AddressBook> {
Widget _buildTags() { Widget _buildTags() {
return Obx(() { return Obx(() {
final List tags; List tags;
if (gFFI.abModel.sortTags.value) { if (gFFI.abModel.sortTags.value) {
tags = gFFI.abModel.currentAbTags.toList(); tags = gFFI.abModel.currentAbTags.toList();
tags.sort(); tags.sort();
} else { } else {
tags = gFFI.abModel.currentAbTags; tags = gFFI.abModel.currentAbTags.toList();
} }
tags = [kUntagged, ...tags].toList();
final editPermission = gFFI.abModel.current.canWrite(); final editPermission = gFFI.abModel.current.canWrite();
tagBuilder(String e) { tagBuilder(String e) {
return AddressBookTag( return AddressBookTag(
@@ -305,8 +340,8 @@ class _AddressBookState extends State<AddressBook> {
showActionMenu: editPermission); showActionMenu: editPermission);
} }
final gridView = DynamicGridView.builder( gridView(bool isPortrait) => DynamicGridView.builder(
shrinkWrap: isMobile, shrinkWrap: isPortrait,
gridDelegate: SliverGridDelegateWithWrapping(), gridDelegate: SliverGridDelegateWithWrapping(),
itemCount: tags.length, itemCount: tags.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
@@ -314,9 +349,9 @@ class _AddressBookState extends State<AddressBook> {
return tagBuilder(e); return tagBuilder(e);
}); });
final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); final maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
return (isDesktop || isWebDesktop) return Obx(() => stateGlobal.isPortrait.isFalse
? gridView ? gridView(false)
: LimitedBox(maxHeight: maxHeight, child: gridView); : LimitedBox(maxHeight: maxHeight, child: gridView(true)));
}); });
} }
@@ -326,13 +361,13 @@ class _AddressBookState extends State<AddressBook> {
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: AddressBookPeersView( child: AddressBookPeersView(
menuPadding: widget.menuPadding, menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.abModel.currentAbPeers,
)), )),
); );
} }
@protected @protected
MenuEntryBase<String> syncMenuItem() { MenuEntryBase<String> syncMenuItem() {
final isOptFixed = isOptionFixed(syncAbOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Sync with recent sessions'), text: translate('Sync with recent sessions'),
@@ -343,11 +378,13 @@ class _AddressBookState extends State<AddressBook> {
gFFI.abModel.setShouldAsync(v); gFFI.abModel.setShouldAsync(v);
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }
@protected @protected
MenuEntryBase<String> sortMenuItem() { MenuEntryBase<String> sortMenuItem() {
final isOptFixed = isOptionFixed(sortAbTagsOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Sort tags'), text: translate('Sort tags'),
@@ -355,15 +392,18 @@ class _AddressBookState extends State<AddressBook> {
return shouldSortTags(); return shouldSortTags();
}, },
setter: (bool v) async { setter: (bool v) async {
bind.mainSetLocalOption(key: sortAbTagsOption, value: v ? 'Y' : ''); bind.mainSetLocalOption(
key: sortAbTagsOption, value: v ? 'Y' : defaultOptionNo);
gFFI.abModel.sortTags.value = v; gFFI.abModel.sortTags.value = v;
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }
@protected @protected
MenuEntryBase<String> filterMenuItem() { MenuEntryBase<String> filterMenuItem() {
final isOptFixed = isOptionFixed(filterAbTagOption);
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox, switchType: SwitchType.scheckbox,
text: translate('Filter by intersection'), text: translate('Filter by intersection'),
@@ -371,10 +411,12 @@ class _AddressBookState extends State<AddressBook> {
return filterAbTagByIntersection(); return filterAbTagByIntersection();
}, },
setter: (bool v) async { setter: (bool v) async {
bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : ''); bind.mainSetLocalOption(
key: filterAbTagOption, value: v ? 'Y' : defaultOptionNo);
gFFI.abModel.filterByIntersection.value = v; gFFI.abModel.filterByIntersection.value = v;
}, },
dismissOnClicked: true, dismissOnClicked: true,
enabled: (!isOptFixed).obs,
); );
} }
@@ -384,7 +426,8 @@ class _AddressBookState extends State<AddressBook> {
if (canWrite) getEntry(translate("Add ID"), addIdToCurrentAb), if (canWrite) getEntry(translate("Add ID"), addIdToCurrentAb),
if (canWrite) getEntry(translate("Add Tag"), abAddTag), if (canWrite) getEntry(translate("Add Tag"), abAddTag),
getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags), getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags),
sortMenuItem(), if (gFFI.abModel.legacyMode.value)
sortMenuItem(), // It's already sorted after pulling down
if (canWrite) syncMenuItem(), if (canWrite) syncMenuItem(),
filterMenuItem(), filterMenuItem(),
if (!gFFI.abModel.legacyMode.value && canWrite) if (!gFFI.abModel.legacyMode.value && canWrite)
@@ -467,20 +510,21 @@ class _AddressBookState extends State<AddressBook> {
double marginBottom = 4; double marginBottom = 4;
row({required Widget lable, required Widget input}) { row({required Widget lable, required Widget input}) {
return Row( makeChild(bool isPortrait) => Row(
children: [ children: [
!isMobile !isPortrait
? ConstrainedBox( ? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: lable.marginOnly(right: 10)) child: lable.marginOnly(right: 10))
: SizedBox.shrink(), : SizedBox.shrink(),
Expanded( Expanded(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 200), constraints: const BoxConstraints(minWidth: 200),
child: input), child: input),
), ),
], ],
).marginOnly(bottom: !isMobile ? 8 : 0); ).marginOnly(bottom: !isPortrait ? 8 : 0);
return Obx(() => makeChild(stateGlobal.isPortrait.isTrue));
} }
return CustomAlertDialog( return CustomAlertDialog(
@@ -503,24 +547,29 @@ class _AddressBookState extends State<AddressBook> {
), ),
], ],
), ),
input: TextField( input: Obx(() => TextField(
controller: idController, controller: idController,
inputFormatters: [IDTextInputFormatter()], inputFormatters: [IDTextInputFormatter()],
decoration: InputDecoration( decoration: InputDecoration(
labelText: !isMobile ? null : translate('ID'), labelText: stateGlobal.isPortrait.isFalse
errorText: errorMsg, ? null
errorMaxLines: 5), : translate('ID'),
)), errorText: errorMsg,
errorMaxLines: 5),
).workaroundFreezeLinuxMint())),
row( row(
lable: Text( lable: Text(
translate('Alias'), translate('Alias'),
style: style, style: style,
), ),
input: TextField( input: Obx(() => TextField(
controller: aliasController, controller: aliasController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: !isMobile ? null : translate('Alias'), labelText: stateGlobal.isPortrait.isFalse
)), ? null
: translate('Alias'),
),
).workaroundFreezeLinuxMint()),
), ),
if (isCurrentAbShared) if (isCurrentAbShared)
row( row(
@@ -528,24 +577,28 @@ class _AddressBookState extends State<AddressBook> {
translate('Password'), translate('Password'),
style: style, style: style,
), ),
input: TextField( input: Obx(
controller: passwordController, () => TextField(
obscureText: !passwordVisible, controller: passwordController,
decoration: InputDecoration( obscureText: !passwordVisible,
labelText: !isMobile ? null : translate('Password'), decoration: InputDecoration(
suffixIcon: IconButton( labelText: stateGlobal.isPortrait.isFalse
icon: Icon( ? null
passwordVisible : translate('Password'),
? Icons.visibility suffixIcon: IconButton(
: Icons.visibility_off, icon: Icon(
color: MyTheme.lightTheme.primaryColor), passwordVisible
onPressed: () { ? Icons.visibility
setState(() { : Icons.visibility_off,
passwordVisible = !passwordVisible; color: MyTheme.lightTheme.primaryColor),
}); onPressed: () {
}, setState(() {
passwordVisible = !passwordVisible;
});
},
),
), ),
), ).workaroundFreezeLinuxMint(),
)), )),
if (gFFI.abModel.currentAbTags.isNotEmpty) if (gFFI.abModel.currentAbTags.isNotEmpty)
Align( Align(
@@ -618,6 +671,14 @@ class _AddressBookState extends State<AddressBook> {
} else { } else {
final tags = field.trim().split(RegExp(r"[\s,;\n]+")); final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
field = tags.join(','); 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); gFFI.abModel.addTags(tags);
// final currentPeers // final currentPeers
} }
@@ -643,7 +704,7 @@ class _AddressBookState extends State<AddressBook> {
), ),
controller: controller, controller: controller,
autofocus: true, autofocus: true,
), ).workaroundFreezeLinuxMint(),
), ),
], ],
), ),
@@ -690,12 +751,14 @@ class AddressBookTag extends StatelessWidget {
} }
const double radius = 8; const double radius = 8;
final isUnTagged = name == kUntagged;
final showAction = showActionMenu && !isUnTagged;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
onTapDown: showActionMenu ? setPosition : null, onTapDown: showAction ? setPosition : null,
onSecondaryTapDown: showActionMenu ? setPosition : null, onSecondaryTapDown: showAction ? setPosition : null,
onSecondaryTap: showActionMenu ? () => _showMenu(context, pos) : null, onSecondaryTap: showAction ? () => _showMenu(context, pos) : null,
onLongPress: showActionMenu ? () => _showMenu(context, pos) : null, onLongPress: showAction ? () => _showMenu(context, pos) : null,
child: Obx(() => Container( child: Obx(() => Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: tags.contains(name) color: tags.contains(name)
@@ -707,17 +770,18 @@ class AddressBookTag extends StatelessWidget {
child: IntrinsicWidth( child: IntrinsicWidth(
child: Row( child: Row(
children: [ children: [
Container( if (!isUnTagged)
width: radius, Container(
height: radius, width: radius,
decoration: BoxDecoration( height: radius,
shape: BoxShape.circle, decoration: BoxDecoration(
color: tags.contains(name) shape: BoxShape.circle,
? Colors.white color: tags.contains(name)
: gFFI.abModel.getCurrentAbTagColor(name)), ? Colors.white
).marginOnly(right: radius / 2), : gFFI.abModel.getCurrentAbTagColor(name)),
).marginOnly(right: radius / 2),
Expanded( Expanded(
child: Text(name, child: Text(isUnTagged ? translate(name) : name,
style: TextStyle( style: TextStyle(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
color: tags.contains(name) ? Colors.white : null)), color: tags.contains(name) ? Colors.white : null)),
+37 -12
View File
@@ -2,22 +2,39 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
const _kSystemSound = 'System Sound';
typedef AudioINputSetDevice = void Function(String device); typedef AudioINputSetDevice = void Function(String device);
typedef AudioInputBuilder = Widget Function( typedef AudioInputBuilder = Widget Function(
List<String> devices, String currentDevice, AudioINputSetDevice setDevice); List<String> devices, String currentDevice, AudioINputSetDevice setDevice);
class AudioInput extends StatelessWidget { class AudioInput extends StatelessWidget {
final AudioInputBuilder builder; final AudioInputBuilder builder;
final bool isCm;
final bool isVoiceCall;
const AudioInput({Key? key, required this.builder}) : super(key: key); const AudioInput(
{Key? key,
required this.builder,
required this.isCm,
required this.isVoiceCall})
: super(key: key);
static String getDefault() { static String getDefault() {
if (isWindows) return translate('System Sound'); if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound);
return ''; return '';
} }
static Future<String> getValue() async { static Future<String> getAudioInput(bool isCm, bool isVoiceCall) {
String device = await bind.mainGetOption(key: 'audio-input'); if (isVoiceCall) {
return bind.getVoiceCallInputDevice(isCm: isCm);
} else {
return bind.mainGetOption(key: 'audio-input');
}
}
static Future<String> getValue(bool isCm, bool isVoiceCall) async {
String device = await getAudioInput(isCm, isVoiceCall);
if (device.isNotEmpty) { if (device.isNotEmpty) {
return device; return device;
} else { } else {
@@ -25,31 +42,39 @@ class AudioInput extends StatelessWidget {
} }
} }
static Future<void> setDevice(String device) async { static Future<void> setDevice(
String device, bool isCm, bool isVoiceCall) async {
if (device == getDefault()) device = ''; if (device == getDefault()) device = '';
await bind.mainSetOption(key: 'audio-input', value: device); if (isVoiceCall) {
await bind.setVoiceCallInputDevice(isCm: isCm, device: device);
} else {
await bind.mainSetOption(key: 'audio-input', value: device);
}
} }
static Future<Map<String, Object>> getDevicesInfo() async { static Future<Map<String, Object>> getDevicesInfo(
bool isCm, bool isVoiceCall) async {
List<String> devices = (await bind.mainGetSoundInputs()).toList(); List<String> devices = (await bind.mainGetSoundInputs()).toList();
if (isWindows) { if (bind.mainAudioSupportLoopback()) {
devices.insert(0, translate('System Sound')); devices.insert(0, translate(_kSystemSound));
} }
String current = await getValue(); String current = await getValue(isCm, isVoiceCall);
return {'devices': devices, 'current': current}; return {'devices': devices, 'current': current};
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return futureBuilder( return futureBuilder(
future: getDevicesInfo(), future: getDevicesInfo(isCm, isVoiceCall),
hasData: (data) { hasData: (data) {
String currentDevice = data['current']; String currentDevice = data['current'];
List<String> devices = data['devices'] as List<String>; List<String> devices = data['devices'] as List<String>;
if (devices.isEmpty) { if (devices.isEmpty) {
return const Offstage(); return const Offstage();
} }
return builder(devices, currentDevice, setDevice); return builder(devices, currentDevice, (devices) {
setDevice(devices, isCm, isVoiceCall);
});
}, },
); );
} }
+89 -42
View File
@@ -1,4 +1,3 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import '../../../models/platform_model.dart'; import '../../../models/platform_model.dart';
@@ -6,56 +5,104 @@ import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
Future<List<Peer>> getAllPeers() async { class AllPeersLoader {
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync()); List<Peer> peers = [];
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
Map<String, dynamic> combinedPeers = {};
void mergePeers(Map<String, dynamic> peers) { bool _isPeersLoading = false;
if (peers.containsKey("peers")) { bool _isPeersLoaded = false;
dynamic peerData = peers["peers"];
if (peerData is String) { final String _listenerKey = 'AllPeersLoader';
try {
peerData = jsonDecode(peerData);
} catch (e) {
print("Error decoding peers: $e");
return;
}
}
if (peerData is List) { late void Function(VoidCallback) setState;
for (var peer in peerData) {
if (peer is Map && peer.containsKey("id")) { bool get needLoad => !_isPeersLoaded && !_isPeersLoading;
String id = peer["id"]; bool get isPeersLoaded => _isPeersLoaded;
if (!combinedPeers.containsKey(id)) {
combinedPeers[id] = peer; AllPeersLoader();
}
} void init(void Function(VoidCallback) setState) {
} this.setState = setState;
gFFI.recentPeersModel.addListener(_mergeAllPeers);
gFFI.lanPeersModel.addListener(_mergeAllPeers);
gFFI.abModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers);
gFFI.groupModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers);
}
void clear() {
gFFI.recentPeersModel.removeListener(_mergeAllPeers);
gFFI.lanPeersModel.removeListener(_mergeAllPeers);
gFFI.abModel.removePeerUpdateListener(_listenerKey);
gFFI.groupModel.removePeerUpdateListener(_listenerKey);
}
Future<void> getAllPeers() async {
if (!needLoad) {
return;
}
_isPeersLoading = true;
if (gFFI.recentPeersModel.peers.isEmpty) {
bind.mainLoadRecentPeers();
}
if (gFFI.lanPeersModel.peers.isEmpty) {
bind.mainLoadLanPeers();
}
// No need to care about peers from abModel, and group model.
// Because they will pull data in `refreshCurrentUser()` on startup.
final startTime = DateTime.now();
_mergeAllPeers();
final diffTime = DateTime.now().difference(startTime).inMilliseconds;
if (diffTime < 100) {
await Future.delayed(Duration(milliseconds: diffTime));
}
}
void _mergeAllPeers() {
Map<String, dynamic> combinedPeers = {};
for (var p in gFFI.abModel.allPeers()) {
if (!combinedPeers.containsKey(p.id)) {
combinedPeers[p.id] = p.toJson();
} }
} }
} for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
if (!combinedPeers.containsKey(p.id)) {
mergePeers(recentPeers); combinedPeers[p.id] = p.toJson();
mergePeers(lanPeers); }
for (var p in gFFI.abModel.allPeers()) {
if (!combinedPeers.containsKey(p.id)) {
combinedPeers[p.id] = p.toJson();
} }
}
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) { List<Peer> parsedPeers = [];
if (!combinedPeers.containsKey(p.id)) { for (var peer in combinedPeers.values) {
combinedPeers[p.id] = p.toJson(); parsedPeers.add(Peer.fromJson(peer));
} }
}
List<Peer> parsedPeers = []; Set<String> peerIds = combinedPeers.keys.toSet();
for (final peer in gFFI.lanPeersModel.peers) {
if (!peerIds.contains(peer.id)) {
parsedPeers.add(peer);
peerIds.add(peer.id);
}
}
for (var peer in combinedPeers.values) { for (final peer in gFFI.recentPeersModel.peers) {
parsedPeers.add(Peer.fromJson(peer)); if (!peerIds.contains(peer.id)) {
parsedPeers.add(peer);
peerIds.add(peer.id);
}
}
for (final id in gFFI.recentPeersModel.restPeerIds) {
if (!peerIds.contains(id)) {
parsedPeers.add(Peer.fromJson({'id': id}));
peerIds.add(id);
}
}
peers = parsedPeers;
setState(() {
_isPeersLoading = false;
_isPeersLoaded = true;
});
} }
return parsedPeers;
} }
class AutocompletePeerTile extends StatefulWidget { class AutocompletePeerTile extends StatefulWidget {
@@ -189,7 +236,7 @@ class AutocompletePeerTileState extends State<AutocompletePeerTile> {
.map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .map((e) => gFFI.abModel.getCurrentAbTagColor(e))
.toList(); .toList();
return Tooltip( return Tooltip(
message: isMobile message: !(isDesktop || isWebDesktop)
? '' ? ''
: widget.peer.tags.isNotEmpty : widget.peer.tags.isNotEmpty
? '${translate('Tags')}: ${widget.peer.tags.join(', ')}' ? '${translate('Tags')}: ${widget.peer.tags.join(', ')}'
+1 -1
View File
@@ -167,7 +167,7 @@ class ChatPage extends StatelessWidget implements PageShape {
); );
}, },
), ),
); ).workaroundFreezeLinuxMint();
return SelectionArea(child: chat); return SelectionArea(child: chat);
}), }),
], ],
@@ -0,0 +1,38 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../common.dart';
Widget getConnectionPageTitle(BuildContext context, bool isWeb) {
return Row(
children: [
Expanded(
child: Row(
children: [
AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
).marginOnly(right: 4),
Tooltip(
waitDuration: Duration(milliseconds: 300),
message: translate(isWeb ? "web_id_input_tip" : "id_input_tip"),
child: Icon(
Icons.help_outline_outlined,
size: 16,
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
),
),
],
)),
],
);
}
@@ -14,7 +14,11 @@ class UppercaseValidationRule extends ValidationRule {
String get name => translate('uppercase'); String get name => translate('uppercase');
@override @override
bool validate(String value) { bool validate(String value) {
return value.contains(RegExp(r'[A-Z]')); return value.runes.any((int rune) {
var character = String.fromCharCode(rune);
return character.toUpperCase() == character &&
character.toLowerCase() != character;
});
} }
} }
@@ -24,7 +28,11 @@ class LowercaseValidationRule extends ValidationRule {
@override @override
bool validate(String value) { bool validate(String value) {
return value.contains(RegExp(r'[a-z]')); return value.runes.any((int rune) {
var character = String.fromCharCode(rune);
return character.toLowerCase() == character &&
character.toUpperCase() != character;
});
} }
} }
+473 -88
View File
@@ -4,11 +4,13 @@ import 'dart:convert';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
@@ -69,7 +71,7 @@ void changeIdDialog() {
final rules = [ final rules = [
RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')), RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')),
LengthRangeValidationRule(6, 16), LengthRangeValidationRule(6, 16),
RegexValidationRule('allowed characters', RegExp(r'^\w*$')) RegexValidationRule('allowed characters', RegExp(r'^[\w-]*$'))
]; ];
gFFI.dialogManager.show((setState, close, context) { gFFI.dialogManager.show((setState, close, context) {
@@ -138,7 +140,7 @@ void changeIdDialog() {
msg = ''; msg = '';
}); });
}, },
), ).workaroundFreezeLinuxMint(),
const SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
@@ -177,11 +179,14 @@ void changeIdDialog() {
} }
void changeWhiteList({Function()? callback}) async { void changeWhiteList({Function()? callback}) async {
var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(','); final curWhiteList = await bind.mainGetOption(key: kOptionWhitelist);
var newWhiteListField = newWhiteList.join('\n'); var newWhiteListField = curWhiteList == defaultOptionWhitelist
? ''
: curWhiteList.split(',').join('\n');
var controller = TextEditingController(text: newWhiteListField); var controller = TextEditingController(text: newWhiteListField);
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
final isOptFixed = isOptionFixed(kOptionWhitelist);
gFFI.dialogManager.show((setState, close, context) { gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("IP Whitelisting")), title: Text(translate("IP Whitelisting")),
@@ -196,12 +201,14 @@ void changeWhiteList({Function()? callback}) async {
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
maxLines: null, maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
errorText: msg.isEmpty ? null : translate(msg), errorText: msg.isEmpty ? null : translate(msg),
), ),
controller: controller, controller: controller,
autofocus: true), enabled: !isOptFixed,
autofocus: true)
.workaroundFreezeLinuxMint(),
), ),
], ],
), ),
@@ -214,45 +221,53 @@ void changeWhiteList({Function()? callback}) async {
), ),
actions: [ actions: [
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("Clear", onPressed: () async { if (!isOptFixed)
await bind.mainSetOption(key: 'whitelist', value: ''); dialogButton("Clear", onPressed: () async {
callback?.call(); await bind.mainSetOption(
close(); key: kOptionWhitelist, value: defaultOptionWhitelist);
}, isOutline: true),
dialogButton(
"OK",
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips = newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(
r"^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$");
final ipv6Match = RegExp(
r"^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip) && !ipv6Match.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
await bind.mainSetOption(key: 'whitelist', value: newWhiteList);
callback?.call(); callback?.call();
close(); close();
}, }, isOutline: true),
), if (!isOptFixed)
dialogButton(
"OK",
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips =
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(
r"^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)(\/([1-9]|[1-2][0-9]|3[0-2])){0,1}$");
final ipv6Match = RegExp(
r"^(((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))*((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7})(\/([1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])){0,1}$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip) && !ipv6Match.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
if (newWhiteList.trim().isEmpty) {
newWhiteList = defaultOptionWhitelist;
}
await bind.mainSetOption(
key: kOptionWhitelist, value: newWhiteList);
callback?.call();
close();
},
),
], ],
onCancel: close, onCancel: close,
); );
@@ -273,22 +288,23 @@ Future<String> changeDirectAccessPort(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
maxLines: null, maxLines: null,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '21118', hintText: '21118',
isCollapsed: true, isCollapsed: true,
prefix: Text('$currentIP : '), prefix: Text('$currentIP : '),
suffix: IconButton( suffix: IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16), icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())), onPressed: () => controller.clear())),
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( 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])$')), 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, controller: controller,
autofocus: true), autofocus: true)
.workaroundFreezeLinuxMint(),
), ),
], ],
), ),
@@ -298,7 +314,7 @@ Future<String> changeDirectAccessPort(
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () async { dialogButton("OK", onPressed: () async {
await bind.mainSetOption( await bind.mainSetOption(
key: 'direct-access-port', value: controller.text); key: kOptionDirectAccessPort, value: controller.text);
close(); close();
}), }),
], ],
@@ -321,21 +337,22 @@ Future<String> changeAutoDisconnectTimeout(String old) async {
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
maxLines: null, maxLines: null,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: InputDecoration( decoration: InputDecoration(
hintText: '10', hintText: '10',
isCollapsed: true, isCollapsed: true,
suffix: IconButton( suffix: IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Icons.clear, size: 16), icon: const Icon(Icons.clear, size: 16),
onPressed: () => controller.clear())), onPressed: () => controller.clear())),
inputFormatters: [ inputFormatters: [
FilteringTextInputFormatter.allow(RegExp( 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])$')), 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, controller: controller,
autofocus: true), autofocus: true)
.workaroundFreezeLinuxMint(),
), ),
], ],
), ),
@@ -345,7 +362,7 @@ Future<String> changeAutoDisconnectTimeout(String old) async {
dialogButton("Cancel", onPressed: close, isOutline: true), dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: () async { dialogButton("OK", onPressed: () async {
await bind.mainSetOption( await bind.mainSetOption(
key: 'auto-disconnect-timeout', value: controller.text); key: kOptionAutoDisconnectTimeout, value: controller.text);
close(); close();
}), }),
], ],
@@ -367,6 +384,7 @@ class DialogTextField extends StatelessWidget {
final FocusNode? focusNode; final FocusNode? focusNode;
final TextInputType? keyboardType; final TextInputType? keyboardType;
final List<TextInputFormatter>? inputFormatters; final List<TextInputFormatter>? inputFormatters;
final int? maxLength;
static const kUsernameTitle = 'Username'; static const kUsernameTitle = 'Username';
static const kUsernameIcon = Icon(Icons.account_circle_outlined); static const kUsernameIcon = Icon(Icons.account_circle_outlined);
@@ -384,6 +402,7 @@ class DialogTextField extends StatelessWidget {
this.hintText, this.hintText,
this.keyboardType, this.keyboardType,
this.inputFormatters, this.inputFormatters,
this.maxLength,
required this.title, required this.title,
required this.controller}) required this.controller})
: super(key: key); : super(key: key);
@@ -410,7 +429,8 @@ class DialogTextField extends StatelessWidget {
obscureText: obscureText, obscureText: obscureText,
keyboardType: keyboardType, keyboardType: keyboardType,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
), maxLength: maxLength,
).workaroundFreezeLinuxMint(),
), ),
], ],
).paddingSymmetric(vertical: 4.0); ).paddingSymmetric(vertical: 4.0);
@@ -666,6 +686,8 @@ class PasswordWidget extends StatefulWidget {
this.reRequestFocus = false, this.reRequestFocus = false,
this.hintText, this.hintText,
this.errorText, this.errorText,
this.title,
this.maxLength,
}) : super(key: key); }) : super(key: key);
final TextEditingController controller; final TextEditingController controller;
@@ -673,6 +695,8 @@ class PasswordWidget extends StatefulWidget {
final bool reRequestFocus; final bool reRequestFocus;
final String? hintText; final String? hintText;
final String? errorText; final String? errorText;
final String? title;
final int? maxLength;
@override @override
State<PasswordWidget> createState() => _PasswordWidgetState(); State<PasswordWidget> createState() => _PasswordWidgetState();
@@ -716,7 +740,7 @@ class _PasswordWidgetState extends State<PasswordWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DialogTextField( return DialogTextField(
title: translate(DialogTextField.kPasswordTitle), title: translate(widget.title ?? DialogTextField.kPasswordTitle),
hintText: translate(widget.hintText ?? 'Enter your password'), hintText: translate(widget.hintText ?? 'Enter your password'),
controller: widget.controller, controller: widget.controller,
prefixIcon: DialogTextField.kPasswordIcon, prefixIcon: DialogTextField.kPasswordIcon,
@@ -735,6 +759,7 @@ class _PasswordWidgetState extends State<PasswordWidget> {
obscureText: !_passwordVisible, obscureText: !_passwordVisible,
errorText: widget.errorText, errorText: widget.errorText,
focusNode: _focusNode, focusNode: _focusNode,
maxLength: widget.maxLength,
); );
} }
} }
@@ -1108,7 +1133,7 @@ void showRequestElevationDialog(
errorText: errPwd.isEmpty ? null : errPwd.value, errorText: errPwd.isEmpty ? null : errPwd.value,
), ),
], ],
).marginOnly(left: (isDesktop || isWebDesktop) ? 35 : 0), ).marginOnly(left: stateGlobal.isPortrait.isFalse ? 35 : 0),
).marginOnly(top: 10), ).marginOnly(top: 10),
], ],
), ),
@@ -1479,7 +1504,7 @@ showAuditDialog(FFI ffi) async {
maxLength: 256, maxLength: 256,
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
)), ).workaroundFreezeLinuxMint()),
actions: [ actions: [
dialogButton('Cancel', onPressed: close, isOutline: true), dialogButton('Cancel', onPressed: close, isOutline: true),
dialogButton('OK', onPressed: submit) dialogButton('OK', onPressed: submit)
@@ -1726,7 +1751,7 @@ void renameDialog(
autofocus: true, autofocus: true,
decoration: InputDecoration(labelText: translate('Name')), decoration: InputDecoration(labelText: translate('Name')),
validator: validator, validator: validator,
), ).workaroundFreezeLinuxMint(),
), ),
), ),
// NOT use Offstage to wrap LinearProgressIndicator // NOT use Offstage to wrap LinearProgressIndicator
@@ -1753,9 +1778,70 @@ void renameDialog(
}); });
} }
void changeBot({Function()? callback}) async {
if (bind.mainHasValidBotSync()) {
await bind.mainSetOption(key: "bot", value: "");
callback?.call();
return;
}
String errorText = '';
bool loading = false;
final controller = TextEditingController();
gFFI.dialogManager.show((setState, close, context) {
onVerify() async {
final token = controller.text.trim();
if (token == "") return;
loading = true;
errorText = '';
setState(() {});
final error = await bind.mainVerifyBot(token: token);
if (error == "") {
callback?.call();
close();
} else {
errorText = translate(error);
loading = false;
setState(() {});
}
}
final codeField = TextField(
autofocus: true,
controller: controller,
decoration: InputDecoration(
hintText: translate('Token'),
),
).workaroundFreezeLinuxMint();
return CustomAlertDialog(
title: Text(translate("Telegram bot")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(translate("enable-bot-desc"),
style: TextStyle(fontSize: 12))
.marginOnly(bottom: 12),
Row(children: [Expanded(child: codeField)]),
if (errorText != '')
Text(errorText, style: TextStyle(color: Colors.red))
.marginOnly(top: 12),
],
),
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
loading
? CircularProgressIndicator()
: dialogButton("OK", onPressed: onVerify),
],
onCancel: close,
);
});
}
void change2fa({Function()? callback}) async { void change2fa({Function()? callback}) async {
if (bind.mainHasValid2FaSync()) { if (bind.mainHasValid2FaSync()) {
await bind.mainSetOption(key: "2fa", value: ""); await bind.mainSetOption(key: "2fa", value: "");
await bind.mainClearTrustedDevices();
callback?.call(); callback?.call();
return; return;
} }
@@ -1823,6 +1909,7 @@ void enter2FaDialog(
SessionID sessionId, OverlayDialogManager dialogManager) async { SessionID sessionId, OverlayDialogManager dialogManager) async {
final controller = TextEditingController(); final controller = TextEditingController();
final RxBool submitReady = false.obs; final RxBool submitReady = false.obs;
final RxBool trustThisDevice = false.obs;
dialogManager.dismissAll(); dialogManager.dismissAll();
dialogManager.show((setState, close, context) { dialogManager.show((setState, close, context) {
@@ -1832,7 +1919,7 @@ void enter2FaDialog(
} }
submit() { submit() {
gFFI.send2FA(sessionId, controller.text.trim()); gFFI.send2FA(sessionId, controller.text.trim(), trustThisDevice.value);
close(); close();
dialogManager.showLoading(translate('Logging in...'), dialogManager.showLoading(translate('Logging in...'),
onCancel: closeConnection); onCancel: closeConnection);
@@ -1846,9 +1933,27 @@ void enter2FaDialog(
onChanged: () => submitReady.value = codeField.isReady, onChanged: () => submitReady.value = codeField.isReady,
); );
final trustField = Obx(() => CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
controlAffinity: ListTileControlAffinity.leading,
title: Text(translate("Trust this device")),
value: trustThisDevice.value,
onChanged: (value) {
if (value == null) return;
trustThisDevice.value = value;
},
));
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('enter-2fa-title')), title: Text(translate('enter-2fa-title')),
content: codeField, content: Column(
children: [
codeField,
if (bind.sessionGetEnableTrustedDevices(sessionId: sessionId))
trustField,
],
),
actions: [ actions: [
dialogButton('Cancel', dialogButton('Cancel',
onPressed: cancel, onPressed: cancel,
@@ -2076,7 +2181,7 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
}, },
), ),
), ),
), ).workaroundFreezeLinuxMint(),
if (!gFFI.abModel.current.isPersonal()) if (!gFFI.abModel.current.isPersonal())
Row(children: [ Row(children: [
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4), Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
@@ -2115,3 +2220,283 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
); );
}); });
} }
void CommonConfirmDialog(OverlayDialogManager dialogManager, String content,
VoidCallback onConfirm) {
dialogManager.show((setState, close, context) {
submit() {
close();
onConfirm.call();
}
return CustomAlertDialog(
content: Row(
children: [
Expanded(
child: Text(content,
style: const TextStyle(fontSize: 15),
textAlign: TextAlign.start),
),
],
).marginOnly(bottom: 12),
actions: [
dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
dialogButton(translate("OK"), onPressed: submit),
],
onSubmit: submit,
onCancel: close,
);
});
}
void changeUnlockPinDialog(String oldPin, Function() callback) {
final pinController = TextEditingController(text: oldPin);
final confirmController = TextEditingController(text: oldPin);
String? pinErrorText;
String? confirmationErrorText;
final maxLength = bind.mainMaxEncryptLen();
gFFI.dialogManager.show((setState, close, context) {
submit() async {
pinErrorText = null;
confirmationErrorText = null;
final pin = pinController.text.trim();
final confirm = confirmController.text.trim();
if (pin != confirm) {
setState(() {
confirmationErrorText =
translate('The confirmation is not identical.');
});
return;
}
final errorMsg = bind.mainSetUnlockPin(pin: pin);
if (errorMsg != '') {
setState(() {
pinErrorText = translate(errorMsg);
});
return;
}
callback.call();
close();
}
return CustomAlertDialog(
title: Text(translate("Set PIN")),
content: Column(
children: [
DialogTextField(
title: 'PIN',
controller: pinController,
obscureText: true,
errorText: pinErrorText,
maxLength: maxLength,
),
DialogTextField(
title: translate('Confirmation'),
controller: confirmController,
obscureText: true,
errorText: confirmationErrorText,
maxLength: maxLength,
)
],
).marginOnly(bottom: 12),
actions: [
dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
dialogButton(translate("OK"), onPressed: submit),
],
onSubmit: submit,
onCancel: close,
);
});
}
void checkUnlockPinDialog(String correctPin, Function() passCallback) {
final controller = TextEditingController();
String? errorText;
gFFI.dialogManager.show((setState, close, context) {
submit() async {
final pin = controller.text.trim();
if (correctPin != pin) {
setState(() {
errorText = translate('Wrong PIN');
});
return;
}
passCallback.call();
close();
}
return CustomAlertDialog(
content: Row(
children: [
Expanded(
child: PasswordWidget(
title: 'PIN',
controller: controller,
errorText: errorText,
hintText: '',
))
],
).marginOnly(bottom: 12),
actions: [
dialogButton(translate("Cancel"), onPressed: close, isOutline: true),
dialogButton(translate("OK"), onPressed: submit),
],
onSubmit: submit,
onCancel: close,
);
});
}
void confrimDeleteTrustedDevicesDialog(
RxList<TrustedDevice> trustedDevices, RxList<Uint8List> selectedDevices) {
CommonConfirmDialog(gFFI.dialogManager, '${translate('Confirm Delete')}?',
() async {
if (selectedDevices.isEmpty) return;
if (selectedDevices.length == trustedDevices.length) {
await bind.mainClearTrustedDevices();
trustedDevices.clear();
selectedDevices.clear();
} else {
final json = jsonEncode(selectedDevices.map((e) => e.toList()).toList());
await bind.mainRemoveTrustedDevices(json: json);
trustedDevices.removeWhere((element) {
return selectedDevices.contains(element.hwid);
});
selectedDevices.clear();
}
});
}
void manageTrustedDeviceDialog() async {
RxList<TrustedDevice> trustedDevices = (await TrustedDevice.get()).obs;
RxList<Uint8List> selectedDevices = RxList.empty();
gFFI.dialogManager.show((setState, close, context) {
return CustomAlertDialog(
title: Text(translate("Manage trusted devices")),
content: trustedDevicesTable(trustedDevices, selectedDevices),
actions: [
Obx(() => dialogButton(translate("Delete"),
onPressed: selectedDevices.isEmpty
? null
: () {
confrimDeleteTrustedDevicesDialog(
trustedDevices,
selectedDevices,
);
},
isOutline: false)
.marginOnly(top: 12)),
dialogButton(translate("Close"), onPressed: close, isOutline: true)
.marginOnly(top: 12),
],
onCancel: close,
);
});
}
class TrustedDevice {
late final Uint8List hwid;
late final int time;
late final String id;
late final String name;
late final String platform;
TrustedDevice.fromJson(Map<String, dynamic> json) {
final hwidList = json['hwid'] as List<dynamic>;
hwid = Uint8List.fromList(hwidList.cast<int>());
time = json['time'];
id = json['id'];
name = json['name'];
platform = json['platform'];
}
String daysRemaining() {
final expiry = time + 90 * 24 * 60 * 60 * 1000;
final remaining = expiry - DateTime.now().millisecondsSinceEpoch;
if (remaining < 0) {
return '0';
}
return (remaining / (24 * 60 * 60 * 1000)).toStringAsFixed(0);
}
static Future<List<TrustedDevice>> get() async {
final List<TrustedDevice> devices = List.empty(growable: true);
try {
final devicesJson = await bind.mainGetTrustedDevices();
if (devicesJson.isNotEmpty) {
final devicesList = json.decode(devicesJson);
if (devicesList is List) {
for (var device in devicesList) {
devices.add(TrustedDevice.fromJson(device));
}
}
}
} catch (e) {
print(e.toString());
}
devices.sort((a, b) => b.time.compareTo(a.time));
return devices;
}
}
Widget trustedDevicesTable(
RxList<TrustedDevice> devices, RxList<Uint8List> selectedDevices) {
RxBool selectAll = false.obs;
setSelectAll() {
if (selectedDevices.isNotEmpty &&
selectedDevices.length == devices.length) {
selectAll.value = true;
} else {
selectAll.value = false;
}
}
devices.listen((_) {
setSelectAll();
});
selectedDevices.listen((_) {
setSelectAll();
});
return FittedBox(
child: Obx(() => DataTable(
columns: [
DataColumn(
label: Checkbox(
value: selectAll.value,
onChanged: (value) {
if (value == true) {
selectedDevices.clear();
selectedDevices.addAll(devices.map((e) => e.hwid));
} else {
selectedDevices.clear();
}
},
)),
DataColumn(label: Text(translate('Platform'))),
DataColumn(label: Text(translate('ID'))),
DataColumn(label: Text(translate('Username'))),
DataColumn(label: Text(translate('Days remaining'))),
],
rows: devices.map((device) {
return DataRow(cells: [
DataCell(Checkbox(
value: selectedDevices.contains(device.hwid),
onChanged: (value) {
if (value == null) return;
if (value) {
selectedDevices.remove(device.hwid);
selectedDevices.add(device.hwid);
} else {
selectedDevices.remove(device.hwid);
}
},
)),
DataCell(Text(device.platform)),
DataCell(Text(device.id)),
DataCell(Text(device.name)),
DataCell(Text(device.daysRemaining())),
]);
}).toList(),
)),
);
}
+3 -1
View File
@@ -86,7 +86,7 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
// end // end
switch (_currentState) { switch (_currentState) {
case GestureState.oneFingerPan: case GestureState.oneFingerPan:
debugPrint("TwoFingerState.pan onEnd"); debugPrint("OneFingerState.pan onEnd");
if (onOneFingerPanEnd != null) { if (onOneFingerPanEnd != null) {
onOneFingerPanEnd!(_getDragEndDetails(d)); onOneFingerPanEnd!(_getDragEndDetails(d));
} }
@@ -112,6 +112,8 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
}; };
} }
// FIXME: This debounce logic is not working properly.
// If we move our finger very fast, we won't be able to detect the "oneFingerPan" event sometimes.
void onOneFingerStartDebounce(ScaleUpdateDetails d) { void onOneFingerStartDebounce(ScaleUpdateDetails d) {
start(ScaleUpdateDetails d) { start(ScaleUpdateDetails d) {
_currentState = GestureState.oneFingerPan; _currentState = GestureState.oneFingerPan;
+7 -7
View File
@@ -142,11 +142,6 @@ class _WidgetOPState extends State<WidgetOP> {
String _failedMsg = ''; String _failedMsg = '';
String _url = ''; String _url = '';
@override
void initState() {
super.initState();
}
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
@@ -455,7 +450,7 @@ Future<bool?> loginDialog() async {
} }
if (isEmailVerification != null) { if (isEmailVerification != null) {
if (isMobile) { if (isMobile) {
if (close != null) close(false); if (close != null) close(null);
verificationCodeDialog( verificationCodeDialog(
resp.user, resp.secret, isEmailVerification); resp.user, resp.secret, isEmailVerification);
} else { } else {
@@ -683,7 +678,7 @@ Future<bool?> verificationCodeDialog(
labelText: "Email", prefixIcon: Icon(Icons.email)), labelText: "Email", prefixIcon: Icon(Icons.email)),
readOnly: true, readOnly: true,
controller: TextEditingController(text: user?.email), controller: TextEditingController(text: user?.email),
)), ).workaroundFreezeLinuxMint()),
isEmailVerification ? const SizedBox(height: 8) : const Offstage(), isEmailVerification ? const SizedBox(height: 8) : const Offstage(),
codeField, codeField,
/* /*
@@ -712,6 +707,11 @@ Future<bool?> verificationCodeDialog(
dialogButton("Verify", onPressed: getOnSubmit()), dialogButton("Verify", onPressed: getOnSubmit()),
]); ]);
}); });
// For verification code, desktop update other models in login dialog, mobile need to close login dialog first,
// otherwise the soft keyboard will jump out on each key press, so mobile update in verification code dialog.
if (isMobile && res == true) {
await UserModel.updateOtherModels();
}
return res; return res;
} }
+88 -35
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/hbbs/hbbs.dart'; import 'package:flutter_hbb/common/hbbs/hbbs.dart';
import 'package:flutter_hbb/common/widgets/login.dart'; import 'package:flutter_hbb/common/widgets/login.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
@@ -19,15 +20,13 @@ class MyGroup extends StatefulWidget {
} }
class _MyGroupState extends State<MyGroup> { class _MyGroupState extends State<MyGroup> {
RxString get selectedUser => gFFI.groupModel.selectedUser; RxBool get isSelectedDeviceGroup => gFFI.groupModel.isSelectedDeviceGroup;
RxString get searchUserText => gFFI.groupModel.searchUserText; RxString get selectedAccessibleItemName =>
gFFI.groupModel.selectedAccessibleItemName;
RxString get searchAccessibleItemNameText =>
gFFI.groupModel.searchAccessibleItemNameText;
static TextEditingController searchUserController = TextEditingController(); static TextEditingController searchUserController = TextEditingController();
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return Obx(() {
@@ -35,6 +34,8 @@ class _MyGroupState extends State<MyGroup> {
return Center( return Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: loginDialog, child: Text(translate("Login")))); onPressed: loginDialog, child: Text(translate("Login"))));
} else if (gFFI.userModel.networkError.isNotEmpty) {
return netWorkErrorWidget();
} else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) { } else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
@@ -48,15 +49,15 @@ class _MyGroupState extends State<MyGroup> {
retry: null, retry: null,
close: () => gFFI.groupModel.groupLoadError.value = ''), close: () => gFFI.groupModel.groupLoadError.value = ''),
Expanded( Expanded(
child: (isDesktop || isWebDesktop) child: Obx(() => stateGlobal.isPortrait.isTrue
? _buildDesktop() ? _buildPortrait()
: _buildMobile()) : _buildLandscape())),
], ],
); );
}); });
} }
Widget _buildDesktop() { Widget _buildLandscape() {
return Row( return Row(
children: [ children: [
Container( Container(
@@ -74,7 +75,7 @@ class _MyGroupState extends State<MyGroup> {
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
child: _buildUserContacts(), child: _buildLeftList(),
), ),
) )
], ],
@@ -85,14 +86,14 @@ class _MyGroupState extends State<MyGroup> {
child: Align( child: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: MyGroupPeerView( child: MyGroupPeerView(
menuPadding: widget.menuPadding, menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.groupModel.peers)), )),
) )
], ],
); );
} }
Widget _buildMobile() { Widget _buildPortrait() {
return Column( return Column(
children: [ children: [
Container( Container(
@@ -107,7 +108,7 @@ class _MyGroupState extends State<MyGroup> {
_buildLeftHeader(), _buildLeftHeader(),
Container( Container(
width: double.infinity, width: double.infinity,
child: _buildUserContacts(), child: _buildLeftList(),
) )
], ],
), ),
@@ -117,8 +118,8 @@ class _MyGroupState extends State<MyGroup> {
child: Align( child: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: MyGroupPeerView( child: MyGroupPeerView(
menuPadding: widget.menuPadding, menuPadding: widget.menuPadding,
getInitPeers: () => gFFI.groupModel.peers)), )),
) )
], ],
); );
@@ -132,7 +133,8 @@ class _MyGroupState extends State<MyGroup> {
child: TextField( child: TextField(
controller: searchUserController, controller: searchUserController,
onChanged: (value) { onChanged: (value) {
searchUserText.value = value; searchAccessibleItemNameText.value = value;
selectedAccessibleItemName.value = '';
}, },
textAlignVertical: TextAlignVertical.center, textAlignVertical: TextAlignVertical.center,
style: TextStyle(fontSize: fontSize), style: TextStyle(fontSize: fontSize),
@@ -147,43 +149,55 @@ class _MyGroupState extends State<MyGroup> {
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
), ),
)), ).workaroundFreezeLinuxMint()),
], ],
); );
} }
Widget _buildUserContacts() { Widget _buildLeftList() {
return Obx(() { return Obx(() {
final items = gFFI.groupModel.users.where((p0) { final userItems = gFFI.groupModel.users.where((p0) {
if (searchUserText.isNotEmpty) { if (searchAccessibleItemNameText.isNotEmpty) {
return p0.name return p0.name
.toLowerCase() .toLowerCase()
.contains(searchUserText.value.toLowerCase()); .contains(searchAccessibleItemNameText.value.toLowerCase());
} }
return true; return true;
}).toList(); }).toList();
final listView = ListView.builder( final deviceGroupItems = gFFI.groupModel.deviceGroups.where((p0) {
shrinkWrap: isMobile, if (searchAccessibleItemNameText.isNotEmpty) {
itemCount: items.length, return p0.name
itemBuilder: (context, index) => _buildUserItem(items[index])); .toLowerCase()
.contains(searchAccessibleItemNameText.value.toLowerCase());
}
return true;
}).toList();
listView(bool isPortrait) => ListView.builder(
shrinkWrap: isPortrait,
itemCount: deviceGroupItems.length + userItems.length,
itemBuilder: (context, index) => index < deviceGroupItems.length
? _buildDeviceGroupItem(deviceGroupItems[index])
: _buildUserItem(userItems[index - deviceGroupItems.length]));
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0); var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
return (isDesktop || isWebDesktop) return Obx(() => stateGlobal.isPortrait.isFalse
? listView ? listView(false)
: LimitedBox(maxHeight: maxHeight, child: listView); : LimitedBox(maxHeight: maxHeight, child: listView(true)));
}); });
} }
Widget _buildUserItem(UserPayload user) { Widget _buildUserItem(UserPayload user) {
final username = user.name; final username = user.name;
return InkWell(onTap: () { return InkWell(onTap: () {
if (selectedUser.value != username) { isSelectedDeviceGroup.value = false;
selectedUser.value = username; if (selectedAccessibleItemName.value != username) {
selectedAccessibleItemName.value = username;
} else { } else {
selectedUser.value = ''; selectedAccessibleItemName.value = '';
} }
}, child: Obx( }, child: Obx(
() { () {
bool selected = selectedUser.value == username; bool selected = !isSelectedDeviceGroup.value &&
selectedAccessibleItemName.value == username;
final isMe = username == gFFI.userModel.userName.value; final isMe = username == gFFI.userModel.userName.value;
final colorMe = MyTheme.color(context).me!; final colorMe = MyTheme.color(context).me!;
return Container( return Container(
@@ -240,4 +254,43 @@ class _MyGroupState extends State<MyGroup> {
}, },
)).marginSymmetric(horizontal: 12).marginOnly(bottom: 6); )).marginSymmetric(horizontal: 12).marginOnly(bottom: 6);
} }
Widget _buildDeviceGroupItem(DeviceGroupPayload deviceGroup) {
final name = deviceGroup.name;
return InkWell(onTap: () {
isSelectedDeviceGroup.value = true;
if (selectedAccessibleItemName.value != name) {
selectedAccessibleItemName.value = name;
} else {
selectedAccessibleItemName.value = '';
}
}, child: Obx(
() {
bool selected = isSelectedDeviceGroup.value &&
selectedAccessibleItemName.value == name;
return Container(
decoration: BoxDecoration(
color: selected ? MyTheme.color(context).highlight : null,
border: Border(
bottom: BorderSide(
width: 0.7,
color: Theme.of(context).dividerColor.withOpacity(0.1))),
),
child: Container(
child: Row(
children: [
Container(
width: 20,
height: 20,
child: Icon(IconFont.deviceGroupOutline,
color: MyTheme.accent, size: 19),
).marginOnly(right: 4),
Expanded(child: Text(name)),
],
).paddingSymmetric(vertical: 4),
),
);
},
)).marginSymmetric(horizontal: 12).marginOnly(bottom: 6);
}
} }
+154 -57
View File
@@ -1,6 +1,8 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:debounce_throttle/debounce_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -26,9 +28,12 @@ class DraggableChatWindow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (draggablePositions.chatWindow.isInvalid()) {
draggablePositions.chatWindow.update(position);
}
return isIOS return isIOS
? IOSDraggable( ? IOSDraggable(
position: position, position: draggablePositions.chatWindow,
chatModel: chatModel, chatModel: chatModel,
width: width, width: width,
height: height, height: height,
@@ -45,7 +50,7 @@ class DraggableChatWindow extends StatelessWidget {
) )
: Draggable( : Draggable(
checkKeyboard: true, checkKeyboard: true,
position: position, position: draggablePositions.chatWindow,
width: width, width: width,
height: height, height: height,
chatModel: chatModel, chatModel: chatModel,
@@ -166,15 +171,17 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
/// floating buttons of back/home/recent actions for android /// floating buttons of back/home/recent actions for android
class DraggableMobileActions extends StatelessWidget { class DraggableMobileActions extends StatelessWidget {
DraggableMobileActions( DraggableMobileActions(
{this.position = Offset.zero, {this.onBackPressed,
this.onBackPressed,
this.onRecentPressed, this.onRecentPressed,
this.onHomePressed, this.onHomePressed,
this.onHidePressed, this.onHidePressed,
required this.position,
required this.width, required this.width,
required this.height}); required this.height,
required this.scale});
final Offset position; final double scale;
final DraggableKeyPosition position;
final double width; final double width;
final double height; final double height;
final VoidCallback? onBackPressed; final VoidCallback? onBackPressed;
@@ -186,8 +193,8 @@ class DraggableMobileActions extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Draggable( return Draggable(
position: position, position: position,
width: width, width: scale * width,
height: height, height: scale * height,
builder: (_, onPanUpdate) { builder: (_, onPanUpdate) {
return GestureDetector( return GestureDetector(
onPanUpdate: onPanUpdate, onPanUpdate: onPanUpdate,
@@ -197,7 +204,8 @@ class DraggableMobileActions extends StatelessWidget {
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: MyTheme.accent.withOpacity(0.4), color: MyTheme.accent.withOpacity(0.4),
borderRadius: BorderRadius.all(Radius.circular(15))), borderRadius:
BorderRadius.all(Radius.circular(15 * scale))),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
@@ -205,17 +213,20 @@ class DraggableMobileActions extends StatelessWidget {
color: Colors.white, color: Colors.white,
onPressed: onBackPressed, onPressed: onBackPressed,
splashRadius: kDesktopIconButtonSplashRadius, splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.arrow_back)), icon: const Icon(Icons.arrow_back),
iconSize: 24 * scale),
IconButton( IconButton(
color: Colors.white, color: Colors.white,
onPressed: onHomePressed, onPressed: onHomePressed,
splashRadius: kDesktopIconButtonSplashRadius, splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.home)), icon: const Icon(Icons.home),
iconSize: 24 * scale),
IconButton( IconButton(
color: Colors.white, color: Colors.white,
onPressed: onRecentPressed, onPressed: onRecentPressed,
splashRadius: kDesktopIconButtonSplashRadius, splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.more_horiz)), icon: const Icon(Icons.more_horiz),
iconSize: 24 * scale),
const VerticalDivider( const VerticalDivider(
width: 0, width: 0,
thickness: 2, thickness: 2,
@@ -226,7 +237,8 @@ class DraggableMobileActions extends StatelessWidget {
color: Colors.white, color: Colors.white,
onPressed: onHidePressed, onPressed: onHidePressed,
splashRadius: kDesktopIconButtonSplashRadius, splashRadius: kDesktopIconButtonSplashRadius,
icon: const Icon(Icons.keyboard_arrow_down)), icon: const Icon(Icons.keyboard_arrow_down),
iconSize: 24 * scale),
], ],
), ),
))); )));
@@ -234,12 +246,98 @@ class DraggableMobileActions extends StatelessWidget {
} }
} }
class DraggableKeyPosition {
final String key;
Offset _pos;
late Debouncer<int> _debouncerStore;
DraggableKeyPosition(this.key)
: _pos = DraggablePositions.kInvalidDraggablePosition;
get pos => _pos;
_loadPosition(String k) {
final value = bind.getLocalFlutterOption(k: k);
if (value.isNotEmpty) {
final parts = value.split(',');
if (parts.length == 2) {
return Offset(double.parse(parts[0]), double.parse(parts[1]));
}
}
return DraggablePositions.kInvalidDraggablePosition;
}
load() {
_pos = _loadPosition(key);
_debouncerStore = Debouncer<int>(const Duration(milliseconds: 500),
onChanged: (v) => _store(), initialValue: 0);
}
update(Offset pos) {
_pos = pos;
_triggerStore();
}
// Adjust position to keep it in the screen
// Only used for desktop and web desktop
tryAdjust(double w, double h, double scale) {
final size = MediaQuery.of(Get.context!).size;
w = w * scale;
h = h * scale;
double x = _pos.dx;
double y = _pos.dy;
if (x + w > size.width) {
x = size.width - w;
}
final tabBarHeight = isDesktop ? kDesktopRemoteTabBarHeight : 0;
if (y + h > (size.height - tabBarHeight)) {
y = size.height - tabBarHeight - h;
}
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x != _pos.dx || y != _pos.dy) {
update(Offset(x, y));
}
}
isInvalid() {
return _pos == DraggablePositions.kInvalidDraggablePosition;
}
_triggerStore() => _debouncerStore.value = _debouncerStore.value + 1;
_store() {
bind.setLocalFlutterOption(k: key, v: '${_pos.dx},${_pos.dy}');
}
}
class DraggablePositions {
static const kChatWindow = 'draggablePositionChat';
static const kMobileActions = 'draggablePositionMobile';
static const kIOSDraggable = 'draggablePositionIOS';
static const kInvalidDraggablePosition = Offset(-999999, -999999);
final chatWindow = DraggableKeyPosition(kChatWindow);
final mobileActions = DraggableKeyPosition(kMobileActions);
final iOSDraggable = DraggableKeyPosition(kIOSDraggable);
load() {
chatWindow.load();
mobileActions.load();
iOSDraggable.load();
}
}
DraggablePositions draggablePositions = DraggablePositions();
class Draggable extends StatefulWidget { class Draggable extends StatefulWidget {
const Draggable( Draggable(
{Key? key, {Key? key,
this.checkKeyboard = false, this.checkKeyboard = false,
this.checkScreenSize = false, this.checkScreenSize = false,
this.position = Offset.zero, required this.position,
required this.width, required this.width,
required this.height, required this.height,
this.chatModel, this.chatModel,
@@ -248,55 +346,53 @@ class Draggable extends StatefulWidget {
final bool checkKeyboard; final bool checkKeyboard;
final bool checkScreenSize; final bool checkScreenSize;
final Offset position; final DraggableKeyPosition position;
final double width; final double width;
final double height; final double height;
final ChatModel? chatModel; final ChatModel? chatModel;
final Widget Function(BuildContext, GestureDragUpdateCallback) builder; final Widget Function(BuildContext, GestureDragUpdateCallback) builder;
@override @override
State<StatefulWidget> createState() => _DraggableState(); State<StatefulWidget> createState() => _DraggableState(chatModel);
} }
class _DraggableState extends State<Draggable> { class _DraggableState extends State<Draggable> {
late Offset _position;
late ChatModel? _chatModel; late ChatModel? _chatModel;
bool _keyboardVisible = false; bool _keyboardVisible = false;
double _saveHeight = 0; double _saveHeight = 0;
double _lastBottomHeight = 0; double _lastBottomHeight = 0;
@override _DraggableState(ChatModel? chatModel) {
void initState() { _chatModel = chatModel;
super.initState();
_position = widget.position;
_chatModel = widget.chatModel;
} }
get position => widget.position.pos;
void onPanUpdate(DragUpdateDetails d) { void onPanUpdate(DragUpdateDetails d) {
final offset = d.delta; final offset = d.delta;
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
double x = 0; double x = 0;
double y = 0; double y = 0;
if (_position.dx + offset.dx + widget.width > size.width) { if (position.dx + offset.dx + widget.width > size.width) {
x = size.width - widget.width; x = size.width - widget.width;
} else if (_position.dx + offset.dx < 0) { } else if (position.dx + offset.dx < 0) {
x = 0; x = 0;
} else { } else {
x = _position.dx + offset.dx; x = position.dx + offset.dx;
} }
if (_position.dy + offset.dy + widget.height > size.height) { if (position.dy + offset.dy + widget.height > size.height) {
y = size.height - widget.height; y = size.height - widget.height;
} else if (_position.dy + offset.dy < 0) { } else if (position.dy + offset.dy < 0) {
y = 0; y = 0;
} else { } else {
y = _position.dy + offset.dy; y = position.dy + offset.dy;
} }
setState(() { setState(() {
_position = Offset(x, y); widget.position.update(Offset(x, y));
}); });
_chatModel?.setChatWindowPosition(_position); _chatModel?.setChatWindowPosition(position);
} }
checkScreenSize() {} checkScreenSize() {}
@@ -307,13 +403,13 @@ class _DraggableState extends State<Draggable> {
// save // save
if (!_keyboardVisible && currentVisible) { if (!_keyboardVisible && currentVisible) {
_saveHeight = _position.dy; _saveHeight = position.dy;
} }
// reset // reset
if (_lastBottomHeight > 0 && bottomHeight == 0) { if (_lastBottomHeight > 0 && bottomHeight == 0) {
setState(() { setState(() {
_position = Offset(_position.dx, _saveHeight); widget.position.update(Offset(position.dx, _saveHeight));
}); });
} }
@@ -321,10 +417,10 @@ class _DraggableState extends State<Draggable> {
if (_keyboardVisible && currentVisible) { if (_keyboardVisible && currentVisible) {
final sumHeight = bottomHeight + widget.height; final sumHeight = bottomHeight + widget.height;
final contextHeight = MediaQuery.of(context).size.height; final contextHeight = MediaQuery.of(context).size.height;
if (sumHeight + _position.dy > contextHeight) { if (sumHeight + position.dy > contextHeight) {
final y = contextHeight - sumHeight; final y = contextHeight - sumHeight;
setState(() { setState(() {
_position = Offset(_position.dx, y); widget.position.update(Offset(position.dx, y));
}); });
} }
} }
@@ -343,8 +439,8 @@ class _DraggableState extends State<Draggable> {
} }
return Stack(children: [ return Stack(children: [
Positioned( Positioned(
top: _position.dy, top: position.dy,
left: _position.dx, left: position.dx,
width: widget.width, width: widget.width,
height: widget.height, height: widget.height,
child: widget.builder(context, onPanUpdate)) child: widget.builder(context, onPanUpdate))
@@ -355,25 +451,25 @@ class _DraggableState extends State<Draggable> {
class IOSDraggable extends StatefulWidget { class IOSDraggable extends StatefulWidget {
const IOSDraggable( const IOSDraggable(
{Key? key, {Key? key,
this.position = Offset.zero,
this.chatModel, this.chatModel,
required this.position,
required this.width, required this.width,
required this.height, required this.height,
required this.builder}) required this.builder})
: super(key: key); : super(key: key);
final Offset position; final DraggableKeyPosition position;
final ChatModel? chatModel; final ChatModel? chatModel;
final double width; final double width;
final double height; final double height;
final Widget Function(BuildContext) builder; final Widget Function(BuildContext) builder;
@override @override
IOSDraggableState createState() => IOSDraggableState(); IOSDraggableState createState() =>
IOSDraggableState(chatModel, width, height);
} }
class IOSDraggableState extends State<IOSDraggable> { class IOSDraggableState extends State<IOSDraggable> {
late Offset _position;
late ChatModel? _chatModel; late ChatModel? _chatModel;
late double _width; late double _width;
late double _height; late double _height;
@@ -381,28 +477,27 @@ class IOSDraggableState extends State<IOSDraggable> {
double _saveHeight = 0; double _saveHeight = 0;
double _lastBottomHeight = 0; double _lastBottomHeight = 0;
@override IOSDraggableState(ChatModel? chatModel, double w, double h) {
void initState() { _chatModel = chatModel;
super.initState(); _width = w;
_position = widget.position; _height = h;
_chatModel = widget.chatModel;
_width = widget.width;
_height = widget.height;
} }
DraggableKeyPosition get position => widget.position;
checkKeyboard() { checkKeyboard() {
final bottomHeight = MediaQuery.of(context).viewInsets.bottom; final bottomHeight = MediaQuery.of(context).viewInsets.bottom;
final currentVisible = bottomHeight != 0; final currentVisible = bottomHeight != 0;
// save // save
if (!_keyboardVisible && currentVisible) { if (!_keyboardVisible && currentVisible) {
_saveHeight = _position.dy; _saveHeight = position.pos.dy;
} }
// reset // reset
if (_lastBottomHeight > 0 && bottomHeight == 0) { if (_lastBottomHeight > 0 && bottomHeight == 0) {
setState(() { setState(() {
_position = Offset(_position.dx, _saveHeight); position.update(Offset(position.pos.dx, _saveHeight));
}); });
} }
@@ -410,10 +505,10 @@ class IOSDraggableState extends State<IOSDraggable> {
if (_keyboardVisible && currentVisible) { if (_keyboardVisible && currentVisible) {
final sumHeight = bottomHeight + _height; final sumHeight = bottomHeight + _height;
final contextHeight = MediaQuery.of(context).size.height; final contextHeight = MediaQuery.of(context).size.height;
if (sumHeight + _position.dy > contextHeight) { if (sumHeight + position.pos.dy > contextHeight) {
final y = contextHeight - sumHeight; final y = contextHeight - sumHeight;
setState(() { setState(() {
_position = Offset(_position.dx, y); position.update(Offset(position.pos.dx, y));
}); });
} }
} }
@@ -428,14 +523,14 @@ class IOSDraggableState extends State<IOSDraggable> {
return Stack( return Stack(
children: [ children: [
Positioned( Positioned(
left: _position.dx, left: position.pos.dx,
top: _position.dy, top: position.pos.dy,
child: GestureDetector( child: GestureDetector(
onPanUpdate: (details) { onPanUpdate: (details) {
setState(() { setState(() {
_position += details.delta; position.update(position.pos + details.delta);
}); });
_chatModel?.setChatWindowPosition(_position); _chatModel?.setChatWindowPosition(position.pos);
}, },
child: Material( child: Material(
child: Container( child: Container(
@@ -491,8 +586,10 @@ class QualityMonitor extends StatelessWidget {
children: [ children: [
_row("Speed", qualityMonitorModel.data.speed ?? '-'), _row("Speed", qualityMonitorModel.data.speed ?? '-'),
_row("FPS", qualityMonitorModel.data.fps ?? '-'), _row("FPS", qualityMonitorModel.data.fps ?? '-'),
// let delay be 0 if fps is 0
_row( _row(
"Delay", "${qualityMonitorModel.data.delay ?? '-'}ms", "Delay",
"${qualityMonitorModel.data.delay == null ? '-' : (qualityMonitorModel.data.fps ?? "").replaceAll(' ', '').replaceAll('0', '').isEmpty ? 0 : qualityMonitorModel.data.delay}ms",
rightColor: Colors.green), rightColor: Colors.green),
_row("Target Bitrate", _row("Target Bitrate",
"${qualityMonitorModel.data.targetBitrate ?? '-'}kb"), "${qualityMonitorModel.data.targetBitrate ?? '-'}kb"),
+139 -127
View File
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -22,6 +23,8 @@ enum PeerUiType { grid, tile, list }
final peerCardUiType = PeerUiType.grid.obs; final peerCardUiType = PeerUiType.grid.obs;
bool? hideUsernameOnCard;
class _PeerCard extends StatefulWidget { class _PeerCard extends StatefulWidget {
final Peer peer; final Peer peer;
final PeerTabIndex tab; final PeerTabIndex tab;
@@ -51,42 +54,44 @@ class _PeerCardState extends State<_PeerCard>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
if (isDesktop || isWebDesktop) { return Obx(() =>
return _buildDesktop(); stateGlobal.isPortrait.isTrue ? _buildPortrait() : _buildLandscape());
} else {
return _buildMobile();
}
} }
Widget _buildMobile() { Widget gestureDetector({required Widget child}) {
final peer = super.widget.peer;
final PeerTabModel peerTabModel = Provider.of(context); final PeerTabModel peerTabModel = Provider.of(context);
final peer = super.widget.peer;
return GestureDetector(
onDoubleTap: peerTabModel.multiSelectionMode
? null
: () => widget.connect(context, peer.id),
onTap: () {
if (peerTabModel.multiSelectionMode) {
peerTabModel.select(peer);
} else {
if (isMobile) {
widget.connect(context, peer.id);
} else {
peerTabModel.select(peer);
}
}
},
onLongPress: () => peerTabModel.select(peer),
child: child);
}
Widget _buildPortrait() {
final peer = super.widget.peer;
return Card( return Card(
margin: EdgeInsets.symmetric(horizontal: 2), margin: EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector( child: gestureDetector(
onTap: () {
if (peerTabModel.multiSelectionMode) {
peerTabModel.select(peer);
} else {
if (!isWebDesktop) {
connectInPeerTab(context, peer, widget.tab);
}
}
},
onDoubleTap: isWebDesktop
? () => connectInPeerTab(context, peer, widget.tab)
: null,
onLongPress: () {
peerTabModel.select(peer);
},
child: Container( child: Container(
padding: EdgeInsets.only(left: 12, top: 8, bottom: 8), padding: EdgeInsets.only(left: 12, top: 8, bottom: 8),
child: _buildPeerTile(context, peer, null)), child: _buildPeerTile(context, peer, null)),
)); ));
} }
Widget _buildDesktop() { Widget _buildLandscape() {
final PeerTabModel peerTabModel = Provider.of(context);
final peer = super.widget.peer; final peer = super.widget.peer;
var deco = Rx<BoxDecoration?>( var deco = Rx<BoxDecoration?>(
BoxDecoration( BoxDecoration(
@@ -115,33 +120,27 @@ class _PeerCardState extends State<_PeerCard>
), ),
); );
}, },
child: GestureDetector( child: gestureDetector(
onDoubleTap:
peerTabModel.multiSelectionMode || peerTabModel.isShiftDown
? null
: () => widget.connect(context, peer.id),
onTap: () => peerTabModel.select(peer),
onLongPress: () => peerTabModel.select(peer),
child: Obx(() => peerCardUiType.value == PeerUiType.grid child: Obx(() => peerCardUiType.value == PeerUiType.grid
? _buildPeerCard(context, peer, deco) ? _buildPeerCard(context, peer, deco)
: _buildPeerTile(context, peer, deco))), : _buildPeerTile(context, peer, deco))),
); );
} }
Widget _buildPeerTile( makeChild(bool isPortrait, Peer peer) {
BuildContext context, Peer peer, Rx<BoxDecoration?>? deco) { final name = hideUsernameOnCard == true
final name = ? peer.hostname
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; : '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
final greyStyle = TextStyle( final greyStyle = TextStyle(
fontSize: 11, fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6)); color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final child = Row( return Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f), color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: isMobile borderRadius: isPortrait
? BorderRadius.circular(_tileRadius) ? BorderRadius.circular(_tileRadius)
: BorderRadius.only( : BorderRadius.only(
topLeft: Radius.circular(_tileRadius), topLeft: Radius.circular(_tileRadius),
@@ -149,11 +148,11 @@ class _PeerCardState extends State<_PeerCard>
), ),
), ),
alignment: Alignment.center, alignment: Alignment.center,
width: isMobile ? 50 : 42, width: isPortrait ? 50 : 42,
height: isMobile ? 50 : null, height: isPortrait ? 50 : null,
child: Stack( child: Stack(
children: [ children: [
getPlatformImage(peer.platform, size: isMobile ? 38 : 30) getPlatformImage(peer.platform, size: isPortrait ? 38 : 30)
.paddingAll(6), .paddingAll(6),
if (_shouldBuildPasswordIcon(peer)) if (_shouldBuildPasswordIcon(peer))
Positioned( Positioned(
@@ -178,19 +177,19 @@ class _PeerCardState extends State<_PeerCard>
child: Column( child: Column(
children: [ children: [
Row(children: [ Row(children: [
getOnline(isMobile ? 4 : 8, peer.online), getOnline(isPortrait ? 4 : 8, peer.online),
Expanded( Expanded(
child: Text( child: Text(
peer.alias.isEmpty ? formatID(peer.id) : peer.alias, peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
)), )),
]).marginOnly(top: isMobile ? 0 : 2), ]).marginOnly(top: isPortrait ? 0 : 2),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
name, name,
style: isMobile ? null : greyStyle, style: isPortrait ? null : greyStyle,
textAlign: TextAlign.start, textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -198,53 +197,65 @@ class _PeerCardState extends State<_PeerCard>
], ],
).marginOnly(top: 2), ).marginOnly(top: 2),
), ),
isMobile isPortrait
? checkBoxOrActionMoreMobile(peer) ? checkBoxOrActionMorePortrait(peer)
: checkBoxOrActionMoreDesktop(peer, isTile: true), : checkBoxOrActionMoreLandscape(peer, isTile: true),
], ],
).paddingOnly(left: 10.0, top: 3.0), ).paddingOnly(left: 10.0, top: 3.0),
), ),
) )
], ],
); );
}
Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?>? deco) {
hideUsernameOnCard ??=
bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y';
final colors = _frontN(peer.tags, 25) final colors = _frontN(peer.tags, 25)
.map((e) => gFFI.abModel.getCurrentAbTagColor(e)) .map((e) => gFFI.abModel.getCurrentAbTagColor(e))
.toList(); .toList();
return Tooltip( return Tooltip(
message: isMobile message: !(isDesktop || isWebDesktop)
? '' ? ''
: peer.tags.isNotEmpty : peer.tags.isNotEmpty
? '${translate('Tags')}: ${peer.tags.join(', ')}' ? '${translate('Tags')}: ${peer.tags.join(', ')}'
: '', : '',
child: Stack(children: [ child: Stack(children: [
deco == null Obx(
? child () => deco == null
: Obx( ? makeChild(stateGlobal.isPortrait.isTrue, peer)
() => Container( : Container(
foregroundDecoration: deco.value, foregroundDecoration: deco.value,
child: child, child: makeChild(stateGlobal.isPortrait.isTrue, peer),
), ),
), ),
if (colors.isNotEmpty) if (colors.isNotEmpty)
Positioned( Obx(() => Positioned(
top: 2, top: 2,
right: isMobile ? 20 : 10, right: stateGlobal.isPortrait.isTrue ? 20 : 10,
child: CustomPaint( child: CustomPaint(
painter: TagPainter(radius: 3, colors: colors), painter: TagPainter(radius: 3, colors: colors),
), ),
) ))
]), ]),
); );
} }
Widget _buildPeerCard( Widget _buildPeerCard(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final name = hideUsernameOnCard ??=
'${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}'; bind.mainGetBuildinOption(key: kHideUsernameOnCard) == 'Y';
final name = hideUsernameOnCard == true
? peer.hostname
: '${peer.username}${peer.username.isNotEmpty && peer.hostname.isNotEmpty ? '@' : ''}${peer.hostname}';
final child = Card( final child = Card(
color: Colors.transparent, color: Colors.transparent,
elevation: 0, elevation: 0,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
// to-do: memory leak here, more investigation needed.
// Continious rebuilds of `Obx()` will cause memory leak here.
// The simple demo does not have this issue.
child: Obx( child: Obx(
() => Container( () => Container(
foregroundDecoration: deco.value, foregroundDecoration: deco.value,
@@ -308,7 +319,7 @@ class _PeerCardState extends State<_PeerCard>
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
)), )),
]).paddingSymmetric(vertical: 8)), ]).paddingSymmetric(vertical: 8)),
checkBoxOrActionMoreDesktop(peer, isTile: false), checkBoxOrActionMoreLandscape(peer, isTile: false),
], ],
).paddingSymmetric(horizontal: 12.0), ).paddingSymmetric(horizontal: 12.0),
) )
@@ -354,7 +365,7 @@ class _PeerCardState extends State<_PeerCard>
} }
} }
Widget checkBoxOrActionMoreMobile(Peer peer) { Widget checkBoxOrActionMorePortrait(Peer peer) {
final PeerTabModel peerTabModel = Provider.of(context); final PeerTabModel peerTabModel = Provider.of(context);
final selected = peerTabModel.isPeerSelected(peer.id); final selected = peerTabModel.isPeerSelected(peer.id);
if (peerTabModel.multiSelectionMode) { if (peerTabModel.multiSelectionMode) {
@@ -382,7 +393,7 @@ class _PeerCardState extends State<_PeerCard>
} }
} }
Widget checkBoxOrActionMoreDesktop(Peer peer, {required bool isTile}) { Widget checkBoxOrActionMoreLandscape(Peer peer, {required bool isTile}) {
final PeerTabModel peerTabModel = Provider.of(context); final PeerTabModel peerTabModel = Provider.of(context);
final selected = peerTabModel.isPeerSelected(peer.id); final selected = peerTabModel.isPeerSelected(peer.id);
if (peerTabModel.multiSelectionMode) { if (peerTabModel.multiSelectionMode) {
@@ -628,8 +639,8 @@ abstract class BasePeerCard extends StatelessWidget {
@protected @protected
Future<bool> _isForceAlwaysRelay(String id) async { Future<bool> _isForceAlwaysRelay(String id) async {
return (await bind.mainGetPeerOption(id: id, key: kOptionForceAlwaysRelay)) return option2bool(kOptionForceAlwaysRelay,
.isNotEmpty; (await bind.mainGetPeerOption(id: id, key: kOptionForceAlwaysRelay)));
} }
@protected @protected
@@ -705,18 +716,18 @@ abstract class BasePeerCard extends StatelessWidget {
switch (tab) { switch (tab) {
case PeerTabIndex.recent: case PeerTabIndex.recent:
await bind.mainRemovePeer(id: id); await bind.mainRemovePeer(id: id);
await bind.mainLoadRecentPeers(); bind.mainLoadRecentPeers();
break; break;
case PeerTabIndex.fav: case PeerTabIndex.fav:
final favs = (await bind.mainGetFav()).toList(); final favs = (await bind.mainGetFav()).toList();
if (favs.remove(id)) { if (favs.remove(id)) {
await bind.mainStoreFav(favs: favs); await bind.mainStoreFav(favs: favs);
await bind.mainLoadFavPeers(); bind.mainLoadFavPeers();
} }
break; break;
case PeerTabIndex.lan: case PeerTabIndex.lan:
await bind.mainRemoveDiscovered(id: id); await bind.mainRemoveDiscovered(id: id);
await bind.mainLoadLanPeers(); bind.mainLoadLanPeers();
break; break;
case PeerTabIndex.ab: case PeerTabIndex.ab:
await gFFI.abModel.deletePeers([id]); await gFFI.abModel.deletePeers([id]);
@@ -868,7 +879,7 @@ class RecentPeerCard extends BasePeerCard {
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context), _connectAction(context),
if (!isWeb) _transferFileAction(context), _transferFileAction(context),
]; ];
final List favs = (await bind.mainGetFav()).toList(); final List favs = (await bind.mainGetFav()).toList();
@@ -887,7 +898,7 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id)); menuItems.add(_createShortCutAction(peer.id));
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
if (isDesktop || isWebDesktop) { if (isMobile || isDesktop || isWebDesktop) {
menuItems.add(_renameAction(peer.id)); menuItems.add(_renameAction(peer.id));
} }
if (await bind.mainPeerHasPassword(id: peer.id)) { if (await bind.mainPeerHasPassword(id: peer.id)) {
@@ -927,7 +938,7 @@ class FavoritePeerCard extends BasePeerCard {
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context), _connectAction(context),
if (!isWeb) _transferFileAction(context), _transferFileAction(context),
]; ];
if (isDesktop && peer.platform != kPeerPlatformAndroid) { if (isDesktop && peer.platform != kPeerPlatformAndroid) {
menuItems.add(_tcpTunnelingAction(context)); menuItems.add(_tcpTunnelingAction(context));
@@ -943,7 +954,7 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id)); menuItems.add(_createShortCutAction(peer.id));
} }
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
if (isDesktop || isWebDesktop) { if (isMobile || isDesktop || isWebDesktop) {
menuItems.add(_renameAction(peer.id)); menuItems.add(_renameAction(peer.id));
} }
if (await bind.mainPeerHasPassword(id: peer.id)) { if (await bind.mainPeerHasPassword(id: peer.id)) {
@@ -980,7 +991,7 @@ class DiscoveredPeerCard extends BasePeerCard {
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context), _connectAction(context),
if (!isWeb) _transferFileAction(context), _transferFileAction(context),
]; ];
final List favs = (await bind.mainGetFav()).toList(); final List favs = (await bind.mainGetFav()).toList();
@@ -1033,7 +1044,7 @@ class AddressBookPeerCard extends BasePeerCard {
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context), _connectAction(context),
if (!isWeb) _transferFileAction(context), _transferFileAction(context),
]; ];
if (isDesktop && peer.platform != kPeerPlatformAndroid) { if (isDesktop && peer.platform != kPeerPlatformAndroid) {
menuItems.add(_tcpTunnelingAction(context)); menuItems.add(_tcpTunnelingAction(context));
@@ -1048,7 +1059,7 @@ class AddressBookPeerCard extends BasePeerCard {
} }
if (gFFI.abModel.current.canWrite()) { if (gFFI.abModel.current.canWrite()) {
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
if (isDesktop || isWebDesktop) { if (isMobile || isDesktop || isWebDesktop) {
menuItems.add(_renameAction(peer.id)); menuItems.add(_renameAction(peer.id));
} }
if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) { if (gFFI.abModel.current.isPersonal() && peer.hash.isNotEmpty) {
@@ -1165,7 +1176,7 @@ class MyGroupPeerCard extends BasePeerCard {
BuildContext context) async { BuildContext context) async {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context), _connectAction(context),
if (!isWeb) _transferFileAction(context), _transferFileAction(context),
]; ];
if (isDesktop && peer.platform != kPeerPlatformAndroid) { if (isDesktop && peer.platform != kPeerPlatformAndroid) {
menuItems.add(_tcpTunnelingAction(context)); menuItems.add(_tcpTunnelingAction(context));
@@ -1195,6 +1206,7 @@ class MyGroupPeerCard extends BasePeerCard {
} }
void _rdpDialog(String id) async { void _rdpDialog(String id) async {
final maxLength = bind.mainMaxEncryptLen();
final port = await bind.mainGetPeerOption(id: id, key: 'rdp_port'); final port = await bind.mainGetPeerOption(id: id, key: 'rdp_port');
final username = await bind.mainGetPeerOption(id: id, key: 'rdp_username'); final username = await bind.mainGetPeerOption(id: id, key: 'rdp_username');
final portController = TextEditingController(text: port); final portController = TextEditingController(text: port);
@@ -1245,58 +1257,58 @@ void _rdpDialog(String id) async {
hintText: '3389'), hintText: '3389'),
controller: portController, controller: portController,
autofocus: true, autofocus: true,
), ).workaroundFreezeLinuxMint(),
), ),
], ],
).marginOnly(bottom: isDesktop ? 8 : 0), ).marginOnly(bottom: isDesktop ? 8 : 0),
Row( Obx(() => Row(
children: [ children: [
(isDesktop || isWebDesktop) stateGlobal.isPortrait.isFalse
? ConstrainedBox( ? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 140), constraints: const BoxConstraints(minWidth: 140),
child: Text( child: Text(
"${translate('Username')}:", "${translate('Username')}:",
textAlign: TextAlign.right, textAlign: TextAlign.right,
).marginOnly(right: 10)) ).marginOnly(right: 10))
: SizedBox.shrink(), : SizedBox.shrink(),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration(
labelText: (isDesktop || isWebDesktop)
? null
: translate('Username')),
controller: userController,
),
),
],
).marginOnly(bottom: (isDesktop || isWebDesktop) ? 8 : 0),
Row(
children: [
(isDesktop || isWebDesktop)
? ConstrainedBox(
constraints: const BoxConstraints(minWidth: 140),
child: Text(
"${translate('Password')}:",
textAlign: TextAlign.right,
).marginOnly(right: 10))
: SizedBox.shrink(),
Expanded(
child: Obx(() => TextField(
obscureText: secure.value,
decoration: InputDecoration( decoration: InputDecoration(
labelText: (isDesktop || isWebDesktop) labelText:
? null isDesktop ? null : translate('Username')),
: translate('Password'), controller: userController,
suffixIcon: IconButton( ).workaroundFreezeLinuxMint(),
onPressed: () => secure.value = !secure.value, ),
icon: Icon(secure.value ],
? Icons.visibility_off ).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)),
: Icons.visibility))), Obx(() => Row(
controller: passwordController, children: [
)), stateGlobal.isPortrait.isFalse
), ? ConstrainedBox(
], constraints: const BoxConstraints(minWidth: 140),
) child: Text(
"${translate('Password')}:",
textAlign: TextAlign.right,
).marginOnly(right: 10))
: SizedBox.shrink(),
Expanded(
child: Obx(() => TextField(
obscureText: secure.value,
maxLength: maxLength,
decoration: InputDecoration(
labelText:
isDesktop ? null : translate('Password'),
suffixIcon: IconButton(
onPressed: () =>
secure.value = !secure.value,
icon: Icon(secure.value
? Icons.visibility_off
: Icons.visibility))),
controller: passwordController,
).workaroundFreezeLinuxMint()),
),
],
))
], ],
), ),
), ),
+135 -113
View File
@@ -16,6 +16,7 @@ import 'package:flutter_hbb/models/ab_model.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/peer_tab_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -32,8 +33,8 @@ class PeerTabPage extends StatefulWidget {
class _TabEntry { class _TabEntry {
final Widget widget; final Widget widget;
final Function({dynamic hint}) load; final Function({dynamic hint})? load;
_TabEntry(this.widget, this.load); _TabEntry(this.widget, [this.load]);
} }
EdgeInsets? _menuPadding() { EdgeInsets? _menuPadding() {
@@ -43,21 +44,15 @@ EdgeInsets? _menuPadding() {
class _PeerTabPageState extends State<PeerTabPage> class _PeerTabPageState extends State<PeerTabPage>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
final List<_TabEntry> entries = [ final List<_TabEntry> entries = [
_TabEntry( _TabEntry(RecentPeersView(
RecentPeersView( menuPadding: _menuPadding(),
menuPadding: _menuPadding(), )),
), _TabEntry(FavoritePeersView(
bind.mainLoadRecentPeers), menuPadding: _menuPadding(),
_TabEntry( )),
FavoritePeersView( _TabEntry(DiscoveredPeersView(
menuPadding: _menuPadding(), menuPadding: _menuPadding(),
), )),
bind.mainLoadFavPeers),
_TabEntry(
DiscoveredPeersView(
menuPadding: _menuPadding(),
),
bind.mainDiscover),
_TabEntry( _TabEntry(
AddressBook( AddressBook(
menuPadding: _menuPadding(), menuPadding: _menuPadding(),
@@ -74,9 +69,14 @@ class _PeerTabPageState extends State<PeerTabPage>
]; ];
RelativeRect? mobileTabContextMenuPos; RelativeRect? mobileTabContextMenuPos;
@override final isOptVisiableFixed = isOptionFixed(kOptionPeerTabVisible);
void initState() {
final uiType = bind.getLocalFlutterOption(k: 'peer-card-ui-type'); _PeerTabPageState() {
_loadLocalOptions();
}
void _loadLocalOptions() {
final uiType = bind.getLocalFlutterOption(k: kOptionPeerCardUiType);
if (uiType != '') { if (uiType != '') {
peerCardUiType.value = int.parse(uiType) == 0 peerCardUiType.value = int.parse(uiType) == 0
? PeerUiType.grid ? PeerUiType.grid
@@ -85,8 +85,7 @@ class _PeerTabPageState extends State<PeerTabPage>
: PeerUiType.list; : PeerUiType.list;
} }
hideAbTagsPanel.value = hideAbTagsPanel.value =
bind.mainGetLocalOption(key: "hideAbTagsPanel").isNotEmpty; bind.mainGetLocalOption(key: kOptionHideAbTagsPanel) == 'Y';
super.initState();
} }
Future<void> handleTabSelection(int tabIndex) async { Future<void> handleTabSelection(int tabIndex) async {
@@ -95,7 +94,7 @@ class _PeerTabPageState extends State<PeerTabPage>
gFFI.peerTabModel.setCurrentTabCachedPeers([]); gFFI.peerTabModel.setCurrentTabCachedPeers([]);
} }
gFFI.peerTabModel.setCurrentTab(tabIndex); gFFI.peerTabModel.setCurrentTab(tabIndex);
entries[tabIndex].load(hint: false); entries[tabIndex].load?.call(hint: false);
} }
} }
@@ -103,33 +102,33 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final model = Provider.of<PeerTabModel>(context); final model = Provider.of<PeerTabModel>(context);
Widget selectionWrap(Widget widget) { Widget selectionWrap(Widget widget) {
return model.multiSelectionMode ? createMultiSelectionBar() : widget; return model.multiSelectionMode ? createMultiSelectionBar(model) : widget;
} }
return Column( return Column(
textBaseline: TextBaseline.ideographic, textBaseline: TextBaseline.ideographic,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( Obx(() => SizedBox(
height: 32, height: 32,
child: Container( child: Container(
padding: (isDesktop || isWebDesktop) padding: stateGlobal.isPortrait.isTrue
? null ? EdgeInsets.symmetric(horizontal: 2)
: EdgeInsets.symmetric(horizontal: 2), : null,
child: selectionWrap(Row( child: selectionWrap(Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: child: visibleContextMenuListener(
visibleContextMenuListener(_createSwitchBar(context))), _createSwitchBar(context))),
if (isMobile) if (stateGlobal.isPortrait.isTrue)
..._mobileRightActions(context) ..._portraitRightActions(context)
else else
..._desktopRightActions(context) ..._landscapeRightActions(context)
], ],
)), )),
), ),
).paddingOnly(right: (isDesktop || isWebDesktop) ? 12 : 0), ).paddingOnly(right: stateGlobal.isPortrait.isTrue ? 0 : 12)),
_createPeersView(), _createPeersView(),
], ],
); );
@@ -173,11 +172,13 @@ class _PeerTabPageState extends State<PeerTabPage>
child: Icon(model.tabIcon(t), color: color) child: Icon(model.tabIcon(t), color: color)
.paddingSymmetric(horizontal: 4), .paddingSymmetric(horizontal: 4),
).paddingSymmetric(horizontal: 4), ).paddingSymmetric(horizontal: 4),
onTap: () async { onTap: isOptionFixed(kOptionPeerTabIndex)
await handleTabSelection(t); ? null
await bind.setLocalFlutterOption( : () async {
k: PeerTabModel.kPeerTabIndex, v: t.toString()); await handleTabSelection(t);
}, await bind.setLocalFlutterOption(
k: kOptionPeerTabIndex, v: t.toString());
},
onHover: (value) => hover.value = value, onHover: (value) => hover.value = value,
), ),
))); )));
@@ -218,7 +219,7 @@ class _PeerTabPageState extends State<PeerTabPage>
child: RefreshWidget( child: RefreshWidget(
onPressed: () { onPressed: () {
if (gFFI.peerTabModel.currentTab < entries.length) { if (gFFI.peerTabModel.currentTab < entries.length) {
entries[gFFI.peerTabModel.currentTab].load(); entries[gFFI.peerTabModel.currentTab].load?.call();
} }
}, },
spinning: loading, spinning: loading,
@@ -265,17 +266,22 @@ class _PeerTabPageState extends State<PeerTabPage>
if (!model.isEnabled[i]) continue; if (!model.isEnabled[i]) continue;
items.add(PopupMenuItem( items.add(PopupMenuItem(
height: kMinInteractiveDimension * 0.8, height: kMinInteractiveDimension * 0.8,
onTap: () => model.setTabVisible(i, !model.isVisibleEnabled[i]), onTap: isOptVisiableFixed
? null
: () => model.setTabVisible(i, !model.isVisibleEnabled[i]),
enabled: !isOptVisiableFixed,
child: Row( child: Row(
children: [ children: [
Checkbox( Checkbox(
value: model.isVisibleEnabled[i], value: model.isVisibleEnabled[i],
onChanged: (_) { onChanged: isOptVisiableFixed
model.setTabVisible(i, !model.isVisibleEnabled[i]); ? null
if (Navigator.canPop(context)) { : (_) {
Navigator.pop(context); model.setTabVisible(i, !model.isVisibleEnabled[i]);
} if (Navigator.canPop(context)) {
}), Navigator.pop(context);
}
}),
Expanded(child: Text(model.tabTooltip(i))), Expanded(child: Text(model.tabTooltip(i))),
], ],
), ),
@@ -288,7 +294,7 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget visibleContextMenuListener(Widget child) { Widget visibleContextMenuListener(Widget child) {
if (isMobile) { if (!(isDesktop || isWebDesktop)) {
return GestureDetector( return GestureDetector(
onLongPressDown: (e) { onLongPressDown: (e) {
final x = e.globalPosition.dx; final x = e.globalPosition.dx;
@@ -332,8 +338,10 @@ class _PeerTabPageState extends State<PeerTabPage>
currentValue: model.isVisibleEnabled[tabIndex], currentValue: model.isVisibleEnabled[tabIndex],
setter: (show) async { setter: (show) async {
model.setTabVisible(tabIndex, show); model.setTabVisible(tabIndex, show);
cancelFunc(); // Do not hide the current menu (checkbox)
})); // cancelFunc();
},
enabled: (!isOptVisiableFixed).obs));
} }
return mod_menu.PopupMenu( return mod_menu.PopupMenu(
items: menu items: menu
@@ -348,8 +356,7 @@ class _PeerTabPageState extends State<PeerTabPage>
.toList()); .toList());
} }
Widget createMultiSelectionBar() { Widget createMultiSelectionBar(PeerTabModel model) {
final model = Provider.of<PeerTabModel>(context);
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -367,7 +374,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Row( Row(
children: [ children: [
selectionCount(model.selectedPeers.length), selectionCount(model.selectedPeers.length),
selectAll(), selectAll(model),
closeSelection(), closeSelection(),
], ],
) )
@@ -388,10 +395,10 @@ class _PeerTabPageState extends State<PeerTabPage>
final peers = model.selectedPeers; final peers = model.selectedPeers;
switch (model.currentTab) { switch (model.currentTab) {
case 0: case 0:
peers.map((p) async { for (var p in peers) {
await bind.mainRemovePeer(id: p.id); await bind.mainRemovePeer(id: p.id);
}).toList(); }
await bind.mainLoadRecentPeers(); bind.mainLoadRecentPeers();
break; break;
case 1: case 1:
final favs = (await bind.mainGetFav()).toList(); final favs = (await bind.mainGetFav()).toList();
@@ -399,13 +406,13 @@ class _PeerTabPageState extends State<PeerTabPage>
favs.remove(p.id); favs.remove(p.id);
}).toList(); }).toList();
await bind.mainStoreFav(favs: favs); await bind.mainStoreFav(favs: favs);
await bind.mainLoadFavPeers(); bind.mainLoadFavPeers();
break; break;
case 2: case 2:
peers.map((p) async { for (var p in peers) {
await bind.mainRemoveDiscovered(id: p.id); await bind.mainRemoveDiscovered(id: p.id);
}).toList(); }
await bind.mainLoadLanPeers(); bind.mainLoadLanPeers();
break; break;
case 3: case 3:
await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList()); await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
@@ -443,7 +450,7 @@ class _PeerTabPageState extends State<PeerTabPage>
showToast(translate('Successful')); showToast(translate('Successful'));
}, },
child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]), child: Icon(PeerTabModel.icons[PeerTabIndex.fav.index]),
).marginOnly(left: isMobile ? 11 : 6), ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6),
); );
} }
@@ -464,7 +471,7 @@ class _PeerTabPageState extends State<PeerTabPage>
model.setMultiSelectionMode(false); model.setMultiSelectionMode(false);
}, },
child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]), child: Icon(PeerTabModel.icons[PeerTabIndex.ab.index]),
).marginOnly(left: isMobile ? 11 : 6), ).marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6),
); );
} }
@@ -487,7 +494,7 @@ class _PeerTabPageState extends State<PeerTabPage>
}); });
}, },
child: Icon(Icons.tag)) child: Icon(Icons.tag))
.marginOnly(left: isMobile ? 11 : 6), .marginOnly(left: !(isDesktop || isWebDesktop) ? 11 : 6),
); );
} }
@@ -498,8 +505,7 @@ class _PeerTabPageState extends State<PeerTabPage>
); );
} }
Widget selectAll() { Widget selectAll(PeerTabModel model) {
final model = Provider.of<PeerTabModel>(context);
return Offstage( return Offstage(
offstage: offstage:
model.selectedPeers.length >= model.currentTabCachedPeers.length, model.selectedPeers.length >= model.currentTabCachedPeers.length,
@@ -529,7 +535,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget _toggleTags() { Widget _toggleTags() {
return _hoverAction( return _hoverAction(
context: context, context: context,
toolTip: translate('Toggle tags'), toolTip: translate('Toggle Tags'),
hoverableWhenfalse: hideAbTagsPanel, hoverableWhenfalse: hideAbTagsPanel,
child: Icon( child: Icon(
Icons.tag_rounded, Icons.tag_rounded,
@@ -537,15 +543,16 @@ class _PeerTabPageState extends State<PeerTabPage>
), ),
onTap: () async { onTap: () async {
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: "hideAbTagsPanel", value: hideAbTagsPanel.value ? "" : "Y"); key: kOptionHideAbTagsPanel,
value: hideAbTagsPanel.value ? defaultOptionNo : "Y");
hideAbTagsPanel.value = !hideAbTagsPanel.value; hideAbTagsPanel.value = !hideAbTagsPanel.value;
}); });
} }
List<Widget> _desktopRightActions(BuildContext context) { List<Widget> _landscapeRightActions(BuildContext context) {
final model = Provider.of<PeerTabModel>(context); final model = Provider.of<PeerTabModel>(context);
return [ return [
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13), const PeerSearchBar().marginOnly(right: 13),
_createRefresh( _createRefresh(
index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading), index: PeerTabIndex.ab, loading: gFFI.abModel.currentAbLoading),
_createRefresh( _createRefresh(
@@ -566,7 +573,7 @@ class _PeerTabPageState extends State<PeerTabPage>
]; ];
} }
List<Widget> _mobileRightActions(BuildContext context) { List<Widget> _portraitRightActions(BuildContext context) {
final model = Provider.of<PeerTabModel>(context); final model = Provider.of<PeerTabModel>(context);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final leftIconSize = Theme.of(context).iconTheme.size ?? 24; final leftIconSize = Theme.of(context).iconTheme.size ?? 24;
@@ -687,13 +694,13 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
baseOffset: 0, baseOffset: 0,
extentOffset: peerSearchTextController.value.text.length); extentOffset: peerSearchTextController.value.text.length);
}); });
return Container( return Obx(() => Container(
width: isMobile ? 120 : 140, width: stateGlobal.isPortrait.isTrue ? 120 : 140,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Obx(() => Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Row( child: Row(
@@ -730,7 +737,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
), ),
), ).workaroundFreezeLinuxMint(),
), ),
// Icon(Icons.close), // Icon(Icons.close),
IconButton( IconButton(
@@ -754,8 +761,8 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
), ),
) )
], ],
)), ),
); ));
} }
} }
@@ -790,25 +797,38 @@ class _PeerViewDropdownState extends State<PeerViewDropdown> {
child: SizedBox( child: SizedBox(
height: 36, height: 36,
child: getRadio<PeerUiType>( child: getRadio<PeerUiType>(
Text( Tooltip(
translate(types.indexOf(e) == 0 message: translate(types.indexOf(e) == 0
? 'Big tiles' ? 'Big tiles'
: types.indexOf(e) == 1 : types.indexOf(e) == 1
? 'Small tiles' ? 'Small tiles'
: 'List'), : 'List'),
style: style), child: Icon(
e == PeerUiType.grid
? Icons.grid_view_rounded
: e == PeerUiType.list
? Icons.view_list_rounded
: Icons.view_agenda_rounded,
size: 18,
)),
e, e,
peerCardUiType.value, peerCardUiType.value,
dense: true, (PeerUiType? v) async { dense: true,
if (v != null) { isOptionFixed(kOptionPeerCardUiType)
peerCardUiType.value = v; ? null
setState(() {}); : (PeerUiType? v) async {
await bind.setLocalFlutterOption( if (v != null) {
k: "peer-card-ui-type", peerCardUiType.value = v;
v: peerCardUiType.value.index.toString(), setState(() {});
); await bind.setLocalFlutterOption(
} k: kOptionPeerCardUiType,
}), v: peerCardUiType.value.index.toString(),
);
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}
}),
), ),
)))); ))));
} }
@@ -820,7 +840,7 @@ class _PeerViewDropdownState extends State<PeerViewDropdown> {
child: Icon( child: Icon(
peerCardUiType.value == PeerUiType.grid peerCardUiType.value == PeerUiType.grid
? Icons.grid_view_rounded ? Icons.grid_view_rounded
: peerCardUiType.value == PeerUiType.tile : peerCardUiType.value == PeerUiType.list
? Icons.view_list_rounded ? Icons.view_list_rounded
: Icons.view_agenda_rounded, : Icons.view_agenda_rounded,
size: 18, size: 18,
@@ -847,16 +867,18 @@ class PeerSortDropdown extends StatefulWidget {
} }
class _PeerSortDropdownState extends State<PeerSortDropdown> { class _PeerSortDropdownState extends State<PeerSortDropdown> {
@override _PeerSortDropdownState() {
void initState() {
if (!PeerSortType.values.contains(peerSort.value)) { if (!PeerSortType.values.contains(peerSort.value)) {
peerSort.value = PeerSortType.remoteId; _loadLocalOptions();
bind.setLocalFlutterOption(
k: "peer-sorting",
v: peerSort.value,
);
} }
super.initState(); }
void _loadLocalOptions() {
peerSort.value = PeerSortType.remoteId;
bind.setLocalFlutterOption(
k: kOptionPeerSorting,
v: peerSort.value,
);
} }
@override @override
@@ -882,7 +904,7 @@ class _PeerSortDropdownState extends State<PeerSortDropdown> {
if (v != null) { if (v != null) {
peerSort.value = v; peerSort.value = v;
await bind.setLocalFlutterOption( await bind.setLocalFlutterOption(
k: "peer-sorting", k: kOptionPeerSorting,
v: peerSort.value, v: peerSort.value,
); );
} }
+181 -103
View File
@@ -4,12 +4,14 @@ import 'dart:collection';
import 'package:dynamic_layouts/dynamic_layouts.dart'; import 'package:dynamic_layouts/dynamic_layouts.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart'; import 'package:flutter_hbb/consts.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'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
@@ -23,13 +25,13 @@ class PeerSortType {
static const String remoteId = 'Remote ID'; static const String remoteId = 'Remote ID';
static const String remoteHost = 'Remote Host'; static const String remoteHost = 'Remote Host';
static const String username = 'Username'; static const String username = 'Username';
// static const String status = 'Status'; static const String status = 'Status';
static List<String> values = [ static List<String> values = [
PeerSortType.remoteId, PeerSortType.remoteId,
PeerSortType.remoteHost, PeerSortType.remoteHost,
PeerSortType.username, PeerSortType.username,
// PeerSortType.status PeerSortType.status
]; ];
} }
@@ -41,14 +43,26 @@ class LoadEvent {
static const String group = 'load_group_peers'; static const String group = 'load_group_peers';
} }
class PeersModelName {
static const String recent = 'recent peer';
static const String favorite = 'fav peer';
static const String lan = 'discovered peer';
static const String addressBook = 'address book peer';
static const String group = 'group peer';
}
/// for peer search text, global obs value /// for peer search text, global obs value
final peerSearchText = "".obs; final peerSearchText = "".obs;
/// for peer sort, global obs value /// for peer sort, global obs value
final peerSort = bind.getLocalFlutterOption(k: 'peer-sorting').obs; RxString? _peerSort;
RxString get peerSort {
_peerSort ??= bind.getLocalFlutterOption(k: kOptionPeerSorting).obs;
return _peerSort!;
}
// list for listener // list for listener
final obslist = [peerSearchText, peerSort].obs; RxList<RxString> get obslist => [peerSearchText, peerSort].obs;
final peerSearchTextController = final peerSearchTextController =
TextEditingController(text: peerSearchText.value); TextEditingController(text: peerSearchText.value);
@@ -70,7 +84,8 @@ class _PeersView extends StatefulWidget {
} }
/// State for the peer widget. /// State for the peer widget.
class _PeersViewState extends State<_PeersView> with WindowListener { class _PeersViewState extends State<_PeersView>
with WindowListener, WidgetsBindingObserver {
static const int _maxQueryCount = 3; static const int _maxQueryCount = 3;
final HashMap<String, String> _emptyMessages = HashMap.from({ final HashMap<String, String> _emptyMessages = HashMap.from({
LoadEvent.recent: 'empty_recent_tip', LoadEvent.recent: 'empty_recent_tip',
@@ -82,9 +97,11 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
final _curPeers = <String>{}; final _curPeers = <String>{};
var _lastChangeTime = DateTime.now(); var _lastChangeTime = DateTime.now();
var _lastQueryPeers = <String>{}; var _lastQueryPeers = <String>{};
var _lastQueryTime = DateTime.now().add(const Duration(seconds: 30)); var _lastQueryTime = DateTime.now();
var _lastWindowRestoreTime = DateTime.now();
var _queryCount = 0; var _queryCount = 0;
var _exit = false; var _exit = false;
bool _isActive = true;
final _scrollController = ScrollController(); final _scrollController = ScrollController();
@@ -95,12 +112,14 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
@override @override
void initState() { void initState() {
windowManager.addListener(this); windowManager.addListener(this);
WidgetsBinding.instance.addObserver(this);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
windowManager.removeListener(this); windowManager.removeListener(this);
WidgetsBinding.instance.removeObserver(this);
_exit = true; _exit = true;
super.dispose(); super.dispose();
} }
@@ -108,17 +127,61 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
@override @override
void onWindowFocus() { void onWindowFocus() {
_queryCount = 0; _queryCount = 0;
_isActive = true;
}
@override
void onWindowBlur() {
// We need this comparison because window restore (on Windows) also triggers `onWindowBlur()`.
// Maybe it's a bug of the window manager, but the source code seems to be correct.
//
// Although `onWindowRestore()` is called after `onWindowBlur()` in my test,
// we need the following comparison to ensure that `_isActive` is true in the end.
if (isWindows &&
DateTime.now().difference(_lastWindowRestoreTime) <
const Duration(milliseconds: 300)) {
return;
}
_queryCount = _maxQueryCount;
_isActive = false;
}
@override
void onWindowRestore() {
// Window restore (on MacOS and Linux) also triggers `onWindowFocus()`.
// But on Windows, it triggers `onWindowBlur()`, mybe it's a bug of the window manager.
if (!isWindows) return;
_queryCount = 0;
_isActive = true;
_lastWindowRestoreTime = DateTime.now();
} }
@override @override
void onWindowMinimize() { void onWindowMinimize() {
_queryCount = _maxQueryCount; // Window minimize also triggers `onWindowBlur()`.
}
// This function is required for mobile.
// `onWindowFocus` works fine for desktop.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (isDesktop || isWebDesktop) return;
if (state == AppLifecycleState.resumed) {
_isActive = true;
_queryCount = 0;
} else if (state == AppLifecycleState.inactive) {
_isActive = false;
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider<Peers>( // We should avoid too many rebuilds. MacOS(m1, 14.6.1) on Flutter 3.19.6.
create: (context) => widget.peers, // Continious rebuilds of `ChangeNotifierProvider` will cause memory leak.
// Simple demo can reproduce this issue.
return ChangeNotifierProvider<Peers>.value(
value: widget.peers,
child: Consumer<Peers>(builder: (context, peers, child) { child: Consumer<Peers>(builder: (context, peers, child) {
if (peers.peers.isEmpty) { if (peers.peers.isEmpty) {
gFFI.peerTabModel.setCurrentTabCachedPeers([]); gFFI.peerTabModel.setCurrentTabCachedPeers([]);
@@ -172,7 +235,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
var peers = snapshot.data!; var peers = snapshot.data!;
if (peers.length > 1000) peers = peers.sublist(0, 1000); if (peers.length > 1000) peers = peers.sublist(0, 1000);
gFFI.peerTabModel.setCurrentTabCachedPeers(peers); gFFI.peerTabModel.setCurrentTabCachedPeers(peers);
buildOnePeer(Peer peer) { buildOnePeer(Peer peer, bool isPortrait) {
final visibilityChild = VisibilityDetector( final visibilityChild = VisibilityDetector(
key: ValueKey(_cardId(peer.id)), key: ValueKey(_cardId(peer.id)),
onVisibilityChanged: onVisibilityChanged, onVisibilityChanged: onVisibilityChanged,
@@ -184,7 +247,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
// No need to listen the currentTab change event. // No need to listen the currentTab change event.
// Because the currentTab change event will trigger the peers change event, // Because the currentTab change event will trigger the peers change event,
// and the peers change event will trigger _buildPeersView(). // and the peers change event will trigger _buildPeersView().
return (isDesktop || isWebDesktop) return !isPortrait
? Obx(() => peerCardUiType.value == PeerUiType.list ? Obx(() => peerCardUiType.value == PeerUiType.list
? Container(height: 45, child: visibilityChild) ? Container(height: 45, child: visibilityChild)
: peerCardUiType.value == PeerUiType.grid : peerCardUiType.value == PeerUiType.grid
@@ -195,44 +258,36 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
: Container(child: visibilityChild); : Container(child: visibilityChild);
} }
final Widget child; // We should avoid too many rebuilds. Win10(Some machines) on Flutter 3.19.6.
if (isMobile) { // Continious rebuilds of `ListView.builder` will cause memory leak.
child = ListView.builder( // Simple demo can reproduce this issue.
itemCount: peers.length, final Widget child = Obx(() => stateGlobal.isPortrait.isTrue
itemBuilder: (BuildContext context, int index) { ? ListView.builder(
return buildOnePeer(peers[index]).marginOnly( itemCount: peers.length,
top: index == 0 ? 0 : space / 2, bottom: space / 2); itemBuilder: (BuildContext context, int index) {
}, return buildOnePeer(peers[index], true).marginOnly(
); top: index == 0 ? 0 : space / 2, bottom: space / 2);
} else { },
child = Obx(() => peerCardUiType.value == PeerUiType.list )
? DesktopScrollWrapper( : peerCardUiType.value == PeerUiType.list
scrollController: _scrollController, ? ListView.builder(
child: ListView.builder( controller: _scrollController,
controller: _scrollController, itemCount: peers.length,
physics: DraggableNeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) {
itemCount: peers.length, return buildOnePeer(peers[index], false).marginOnly(
itemBuilder: (BuildContext context, int index) { right: space,
return buildOnePeer(peers[index]).marginOnly( top: index == 0 ? 0 : space / 2,
right: space, bottom: space / 2);
top: index == 0 ? 0 : space / 2, },
bottom: space / 2); )
}), : DynamicGridView.builder(
) gridDelegate: SliverGridDelegateWithWrapping(
: DesktopScrollWrapper( mainAxisSpacing: space / 2,
scrollController: _scrollController, crossAxisSpacing: space),
child: DynamicGridView.builder( itemCount: peers.length,
controller: _scrollController, itemBuilder: (BuildContext context, int index) {
physics: DraggableNeverScrollableScrollPhysics(), return buildOnePeer(peers[index], false);
gridDelegate: SliverGridDelegateWithWrapping( }));
mainAxisSpacing: space / 2,
crossAxisSpacing: space),
itemCount: peers.length,
itemBuilder: (BuildContext context, int index) {
return buildOnePeer(peers[index]);
}),
));
}
if (updateEvent == UpdateEvent.load) { if (updateEvent == UpdateEvent.load) {
_curPeers.clear(); _curPeers.clear();
@@ -253,10 +308,14 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
return body; return body;
} }
final _queryInterval = const Duration(seconds: 20); var _queryInterval = const Duration(seconds: 20);
void _startCheckOnlines() { void _startCheckOnlines() {
() async { () async {
final p = await bind.mainIsUsingPublicServer();
if (!p) {
_queryInterval = const Duration(seconds: 6);
}
while (!_exit) { while (!_exit) {
final now = DateTime.now(); final now = DateTime.now();
if (!setEquals(_curPeers, _lastQueryPeers)) { if (!setEquals(_curPeers, _lastQueryPeers)) {
@@ -264,7 +323,12 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
_queryOnlines(false); _queryOnlines(false);
} }
} else { } else {
if (_queryCount < _maxQueryCount) { final skipIfIsWeb =
isWeb && !(stateGlobal.isWebVisible && stateGlobal.isInMainPage);
final skipIfMobile =
(isAndroid || isIOS) && !stateGlobal.isInMainPage;
final skipIfNotActive = skipIfIsWeb || skipIfMobile || !_isActive;
if (!skipIfNotActive && (_queryCount < _maxQueryCount || !p)) {
if (now.difference(_lastQueryTime) >= _queryInterval) { if (now.difference(_lastQueryTime) >= _queryInterval) {
if (_curPeers.isNotEmpty) { if (_curPeers.isNotEmpty) {
bind.queryOnlines(ids: _curPeers.toList(growable: false)); bind.queryOnlines(ids: _curPeers.toList(growable: false));
@@ -282,14 +346,14 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
_queryOnlines(bool isLoadEvent) { _queryOnlines(bool isLoadEvent) {
if (_curPeers.isNotEmpty) { if (_curPeers.isNotEmpty) {
bind.queryOnlines(ids: _curPeers.toList(growable: false)); bind.queryOnlines(ids: _curPeers.toList(growable: false));
_lastQueryPeers = {..._curPeers};
if (isLoadEvent) {
_lastChangeTime = DateTime.now();
} else {
_lastQueryTime = DateTime.now().subtract(_queryInterval);
}
_queryCount = 0; _queryCount = 0;
} }
_lastQueryPeers = {..._curPeers};
if (isLoadEvent) {
_lastChangeTime = DateTime.now();
} else {
_lastQueryTime = DateTime.now().subtract(_queryInterval);
}
} }
Future<List<Peer>>? matchPeers( Future<List<Peer>>? matchPeers(
@@ -302,7 +366,7 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
if (!PeerSortType.values.contains(sortedBy)) { if (!PeerSortType.values.contains(sortedBy)) {
sortedBy = PeerSortType.remoteId; sortedBy = PeerSortType.remoteId;
bind.setLocalFlutterOption( bind.setLocalFlutterOption(
k: "peer-sorting", k: kOptionPeerSorting,
v: sortedBy, v: sortedBy,
); );
} }
@@ -320,9 +384,9 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
peers.sort((p1, p2) => peers.sort((p1, p2) =>
p1.username.toLowerCase().compareTo(p2.username.toLowerCase())); p1.username.toLowerCase().compareTo(p2.username.toLowerCase()));
break; break;
// case PeerSortType.status: case PeerSortType.status:
// peers.sort((p1, p2) => p1.online ? -1 : 1); peers.sort((p1, p2) => p1.online ? -1 : 1);
// break; break;
} }
} }
@@ -345,28 +409,39 @@ class _PeersViewState extends State<_PeersView> with WindowListener {
} }
abstract class BasePeersView extends StatelessWidget { abstract class BasePeersView extends StatelessWidget {
final String name; final PeerTabIndex peerTabIndex;
final String loadEvent;
final PeerFilter? peerFilter; final PeerFilter? peerFilter;
final PeerCardBuilder peerCardBuilder; final PeerCardBuilder peerCardBuilder;
final GetInitPeers? getInitPeers;
const BasePeersView({ const BasePeersView({
Key? key, Key? key,
required this.name, required this.peerTabIndex,
required this.loadEvent,
this.peerFilter, this.peerFilter,
required this.peerCardBuilder, required this.peerCardBuilder,
required this.getInitPeers,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Peers peers;
switch (peerTabIndex) {
case PeerTabIndex.recent:
peers = gFFI.recentPeersModel;
break;
case PeerTabIndex.fav:
peers = gFFI.favoritePeersModel;
break;
case PeerTabIndex.lan:
peers = gFFI.lanPeersModel;
break;
case PeerTabIndex.ab:
peers = gFFI.abModel.peersModel;
break;
case PeerTabIndex.group:
peers = gFFI.groupModel.peersModel;
break;
}
return _PeersView( return _PeersView(
peers: peers: peers, peerFilter: peerFilter, peerCardBuilder: peerCardBuilder);
Peers(name: name, loadEvent: loadEvent, getInitPeers: getInitPeers),
peerFilter: peerFilter,
peerCardBuilder: peerCardBuilder);
} }
} }
@@ -375,13 +450,11 @@ class RecentPeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super( : super(
key: key, key: key,
name: 'recent peer', peerTabIndex: PeerTabIndex.recent,
loadEvent: LoadEvent.recent,
peerCardBuilder: (Peer peer) => RecentPeerCard( peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding, menuPadding: menuPadding,
), ),
getInitPeers: null,
); );
@override @override
@@ -397,13 +470,11 @@ class FavoritePeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super( : super(
key: key, key: key,
name: 'favorite peer', peerTabIndex: PeerTabIndex.fav,
loadEvent: LoadEvent.favorite,
peerCardBuilder: (Peer peer) => FavoritePeerCard( peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding, menuPadding: menuPadding,
), ),
getInitPeers: null,
); );
@override @override
@@ -419,13 +490,11 @@ class DiscoveredPeersView extends BasePeersView {
{Key? key, EdgeInsets? menuPadding, ScrollController? scrollController}) {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
: super( : super(
key: key, key: key,
name: 'discovered peer', peerTabIndex: PeerTabIndex.lan,
loadEvent: LoadEvent.lan,
peerCardBuilder: (Peer peer) => DiscoveredPeerCard( peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding, menuPadding: menuPadding,
), ),
getInitPeers: null,
); );
@override @override
@@ -438,36 +507,38 @@ class DiscoveredPeersView extends BasePeersView {
class AddressBookPeersView extends BasePeersView { class AddressBookPeersView extends BasePeersView {
AddressBookPeersView( AddressBookPeersView(
{Key? key, {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
EdgeInsets? menuPadding,
ScrollController? scrollController,
required GetInitPeers getInitPeers})
: super( : super(
key: key, key: key,
name: 'address book peer', peerTabIndex: PeerTabIndex.ab,
loadEvent: LoadEvent.addressBook,
peerFilter: (Peer peer) => peerFilter: (Peer peer) =>
_hitTag(gFFI.abModel.selectedTags, peer.tags), _hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardBuilder: (Peer peer) => AddressBookPeerCard( peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding, menuPadding: menuPadding,
), ),
getInitPeers: getInitPeers,
); );
static bool _hitTag(List<dynamic> selectedTags, List<dynamic> idents) { static bool _hitTag(List<dynamic> selectedTags, List<dynamic> idents) {
if (selectedTags.isEmpty) { if (selectedTags.isEmpty) {
return true; 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) { if (gFFI.abModel.filterByIntersection.value) {
for (final tag in selectedTags) { for (final tag in selectedNormalTags) {
if (!idents.contains(tag)) { if (!idents.contains(tag)) {
return false; return false;
} }
} }
return true; return true;
} else { } else {
for (final tag in selectedTags) { for (final tag in selectedNormalTags) {
if (idents.contains(tag)) { if (idents.contains(tag)) {
return true; return true;
} }
@@ -479,31 +550,38 @@ class AddressBookPeersView extends BasePeersView {
class MyGroupPeerView extends BasePeersView { class MyGroupPeerView extends BasePeersView {
MyGroupPeerView( MyGroupPeerView(
{Key? key, {Key? key, EdgeInsets? menuPadding, ScrollController? scrollController})
EdgeInsets? menuPadding,
ScrollController? scrollController,
required GetInitPeers getInitPeers})
: super( : super(
key: key, key: key,
name: 'group peer', peerTabIndex: PeerTabIndex.group,
loadEvent: LoadEvent.group,
peerFilter: filter, peerFilter: filter,
peerCardBuilder: (Peer peer) => MyGroupPeerCard( peerCardBuilder: (Peer peer) => MyGroupPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding, menuPadding: menuPadding,
), ),
getInitPeers: getInitPeers,
); );
static bool filter(Peer peer) { static bool filter(Peer peer) {
if (gFFI.groupModel.searchUserText.isNotEmpty) { final model = gFFI.groupModel;
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) { if (model.searchAccessibleItemNameText.isNotEmpty) {
final text = model.searchAccessibleItemNameText.value;
final searchPeersOfUser = peer.loginName.contains(text) &&
model.users.any((user) => user.name == peer.loginName);
final searchPeersOfDeviceGroup = peer.device_group_name.contains(text) &&
model.deviceGroups.any((g) => g.name == peer.device_group_name);
if (!searchPeersOfUser && !searchPeersOfDeviceGroup) {
return false; return false;
} }
} }
if (gFFI.groupModel.selectedUser.isNotEmpty) { if (model.selectedAccessibleItemName.isNotEmpty) {
if (gFFI.groupModel.selectedUser.value != peer.loginName) { if (model.isSelectedDeviceGroup.value) {
return false; if (model.selectedAccessibleItemName.value != peer.device_group_name) {
return false;
}
} else {
if (model.selectedAccessibleItemName.value != peer.loginName) {
return false;
}
} }
} }
return true; return true;
+162 -51
View File
@@ -27,6 +27,10 @@ class RawKeyFocusScope extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// https://github.com/flutter/flutter/issues/154053
final useRawKeyEvents = isLinux && !isWeb;
// FIXME: On Windows, `AltGr` will generate `Alt` and `Control` key events,
// while `Alt` and `Control` are seperated key events for en-US input method.
return FocusScope( return FocusScope(
autofocus: true, autofocus: true,
child: Focus( child: Focus(
@@ -34,8 +38,14 @@ class RawKeyFocusScope extends StatelessWidget {
canRequestFocus: true, canRequestFocus: true,
focusNode: focusNode, focusNode: focusNode,
onFocusChange: onFocusChange, onFocusChange: onFocusChange,
onKey: (FocusNode data, RawKeyEvent e) => onKey: useRawKeyEvents
inputModel.handleRawKeyEvent(e), ? (FocusNode data, RawKeyEvent event) =>
inputModel.handleRawKeyEvent(event)
: null,
onKeyEvent: useRawKeyEvents
? null
: (FocusNode node, KeyEvent event) =>
inputModel.handleKeyEvent(event),
child: child)); child: child));
} }
} }
@@ -69,11 +79,22 @@ class RawTouchGestureDetectorRegion extends StatefulWidget {
class _RawTouchGestureDetectorRegionState class _RawTouchGestureDetectorRegionState
extends State<RawTouchGestureDetectorRegion> { extends State<RawTouchGestureDetectorRegion> {
Offset _cacheLongPressPosition = Offset(0, 0); Offset _cacheLongPressPosition = Offset(0, 0);
// Timestamp of the last long press event.
int _cacheLongPressPositionTs = 0;
double _mouseScrollIntegral = 0; // mouse scroll speed controller double _mouseScrollIntegral = 0; // mouse scroll speed controller
double _scale = 1; double _scale = 1;
// Workaround tap down event when two fingers are used to scale(mobile)
TapDownDetails? _lastTapDownDetails;
PointerDeviceKind? lastDeviceKind; 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; FFI get ffi => widget.ffi;
FfiModel get ffiModel => widget.ffiModel; FfiModel get ffiModel => widget.ffiModel;
InputModel get inputModel => widget.inputModel; InputModel get inputModel => widget.inputModel;
@@ -88,145 +109,223 @@ class _RawTouchGestureDetectorRegionState
); );
} }
onTapDown(TapDownDetails d) { onTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { if (handleTouch) {
_lastPosOfDoubleTapDown = d.localPosition;
// Desktop or mobile "Touch mode" // Desktop or mobile "Touch mode"
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); _lastTapDownDetails = d;
inputModel.tapDown(MouseButtons.left);
} }
} }
onTapUp(TapUpDetails d) { onTapUp(TapUpDetails d) async {
final TapDownDetails? lastTapDownDetails = _lastTapDownDetails;
_lastTapDownDetails = null;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { if (handleTouch) {
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); final isMoved =
inputModel.tapUp(MouseButtons.left); 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) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (!handleTouch) { if (!handleTouch) {
// Mobile, "Mouse mode" // Mobile, "Mouse mode"
inputModel.tap(MouseButtons.left); await inputModel.tap(MouseButtons.left);
} }
} }
onDoubleTapDown(TapDownDetails d) { onDoubleTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { 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) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
inputModel.tap(MouseButtons.left); if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) {
inputModel.tap(MouseButtons.left); return;
}
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; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { if (handleTouch) {
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); _lastPosOfDoubleTapDown = d.localPosition;
_cacheLongPressPosition = d.localPosition; _cacheLongPressPosition = d.localPosition;
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
return;
}
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
if (ffiModel.isPeerMobile) {
await ffi.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
await inputModel.tapDown(MouseButtons.left);
}
} }
} }
onLongPressUp() { onLongPressUp() async {
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { if (handleTouch) {
inputModel.tapUp(MouseButtons.left); await inputModel.tapUp(MouseButtons.left);
} }
} }
// for mobiles // for mobiles
onLongPress() { onLongPress() async {
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (!ffi.ffiModel.isPeerMobile) {
if (handleTouch) {
final isMoved = await ffi.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
if (!isMoved) {
return;
}
}
await inputModel.tap(MouseButtons.right);
} else {
// It's better to send a message to tell the controlled device that the long press event is triggered.
// We're now using a `TimerTask` in `InputService.kt` to decide whether to trigger the long press event.
// It's not accurate and it's better to use the same detection logic in the controlling side.
}
}
onLongPressMoveUpdate(LongPressMoveUpdateDetails d) async {
if (!ffiModel.isPeerMobile || lastDeviceKind != PointerDeviceKind.touch) {
return;
}
if (handleTouch) { if (handleTouch) {
ffi.cursorModel if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); return;
}
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
} }
inputModel.tap(MouseButtons.right);
} }
onDoubleFinerTapDown(TapDownDetails d) { onDoubleFinerTapDown(TapDownDetails d) async {
lastDeviceKind = d.kind; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
_doubleFinerTapPosition = d.localPosition;
// ignore for desktop and mobile // ignore for desktop and mobile
} }
onDoubleFinerTap(TapDownDetails d) { onDoubleFinerTap(TapDownDetails d) async {
lastDeviceKind = d.kind; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; 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; lastDeviceKind = d.kind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (!handleTouch) { if (!handleTouch) {
inputModel.sendMouse('down', MouseButtons.left); await inputModel.sendMouse('down', MouseButtons.left);
} }
} }
onHoldDragUpdate(DragUpdateDetails d) { onHoldDragUpdate(DragUpdateDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (!handleTouch) { 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) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (!handleTouch) { 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; lastDeviceKind = d.kind ?? lastDeviceKind;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (handleTouch) { if (handleTouch) {
if (isDesktop) { 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(); ffi.cursorModel.trySetRemoteWindowCoords();
} }
inputModel.sendMouse('down', MouseButtons.left);
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); // 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.
//
// TODO: We should find a better way to send the first pan event as soon as possible.
if (DateTime.now().millisecondsSinceEpoch - _cacheLongPressPositionTs <
500) {
await ffi.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
}
await inputModel.sendMouse('down', MouseButtons.left);
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
} else { } else {
final offset = ffi.cursorModel.offset; final offset = ffi.cursorModel.offset;
final cursorX = offset.dx; final cursorX = offset.dx;
@@ -235,36 +334,46 @@ class _RawTouchGestureDetectorRegionState
ffi.cursorModel.getVisibleRect().inflate(1); // extend edges ffi.cursorModel.getVisibleRect().inflate(1); // extend edges
final size = MediaQueryData.fromView(View.of(context)).size; final size = MediaQueryData.fromView(View.of(context)).size;
if (!visible.contains(Offset(cursorX, cursorY))) { 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) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch); if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
return;
}
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) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if (isDesktop) { if (isDesktop || isWebDesktop) {
ffi.cursorModel.clearRemoteWindowCoords(); ffi.cursorModel.clearRemoteWindowCoords();
} }
inputModel.sendMouse('up', MouseButtons.left); if (handleTouch) {
await inputModel.sendMouse('up', MouseButtons.left);
}
} }
// scale + pan event // scale + pan event
onTwoFingerScaleStart(ScaleStartDetails d) { onTwoFingerScaleStart(ScaleStartDetails d) {
_lastTapDownDetails = null;
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
} }
onTwoFingerScaleUpdate(ScaleUpdateDetails d) { onTwoFingerScaleUpdate(ScaleUpdateDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
@@ -273,7 +382,7 @@ class _RawTouchGestureDetectorRegionState
_scale = d.scale; _scale = d.scale;
if (scale != 0) { if (scale != 0) {
bind.sessionSendPointer( await bind.sessionSendPointer(
sessionId: sessionId, sessionId: sessionId,
msg: json.encode( msg: json.encode(
PointerEventToRust(kPointerEventKindTouch, 'scale', scale) PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
@@ -281,28 +390,29 @@ class _RawTouchGestureDetectorRegionState
} }
} else { } else {
// mobile // mobile
ffi.canvasModel.updateScale(d.scale / _scale); ffi.canvasModel.updateScale(d.scale / _scale, d.focalPoint);
_scale = d.scale; _scale = d.scale;
ffi.canvasModel.panX(d.focalPointDelta.dx); ffi.canvasModel.panX(d.focalPointDelta.dx);
ffi.canvasModel.panY(d.focalPointDelta.dy); ffi.canvasModel.panY(d.focalPointDelta.dy);
} }
} }
onTwoFingerScaleEnd(ScaleEndDetails d) { onTwoFingerScaleEnd(ScaleEndDetails d) async {
if (lastDeviceKind != PointerDeviceKind.touch) { if (lastDeviceKind != PointerDeviceKind.touch) {
return; return;
} }
if ((isDesktop || isWebDesktop)) { if ((isDesktop || isWebDesktop)) {
bind.sessionSendPointer( await bind.sessionSendPointer(
sessionId: sessionId, sessionId: sessionId,
msg: json.encode( msg: json.encode(
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson())); PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
} else { } else {
// mobile // mobile
_scale = 1; _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; get onHoldDragCancel => null;
@@ -343,7 +453,8 @@ class _RawTouchGestureDetectorRegionState
instance instance
..onLongPressDown = onLongPressDown ..onLongPressDown = onLongPressDown
..onLongPressUp = onLongPressUp ..onLongPressUp = onLongPressUp
..onLongPress = onLongPress; ..onLongPress = onLongPress
..onLongPressMoveUpdate = onLongPressMoveUpdate;
}), }),
// Customized // Customized
HoldTapMoveGestureRecognizer: HoldTapMoveGestureRecognizer:
+53 -44
View File
@@ -3,15 +3,14 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
customImageQualityWidget( customImageQualityWidget(
{required double initQuality, {required double initQuality,
required double initFps, required double initFps,
required Function(double) setQuality, required Function(double)? setQuality,
required Function(double) setFps, required Function(double)? setFps,
required bool showFps, required bool showFps,
required bool showMoreQuality}) { required bool showMoreQuality}) {
if (initQuality < kMinQuality || if (initQuality < kMinQuality ||
@@ -27,16 +26,12 @@ customImageQualityWidget(
final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality); final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality);
final debouncerQuality = Debouncer<double>( final debouncerQuality = Debouncer<double>(
Duration(milliseconds: 1000), Duration(milliseconds: 1000),
onChanged: (double v) { onChanged: setQuality,
setQuality(v);
},
initialValue: qualityValue.value, initialValue: qualityValue.value,
); );
final debouncerFps = Debouncer<double>( final debouncerFps = Debouncer<double>(
Duration(milliseconds: 1000), Duration(milliseconds: 1000),
onChanged: (double v) { onChanged: setFps,
setFps(v);
},
initialValue: fpsValue.value, initialValue: fpsValue.value,
); );
@@ -62,10 +57,12 @@ customImageQualityWidget(
divisions: moreQualityChecked.value divisions: moreQualityChecked.value
? ((kMaxMoreQuality - kMinQuality) / 10).round() ? ((kMaxMoreQuality - kMinQuality) / 10).round()
: ((kMaxQuality - kMinQuality) / 5).round(), : ((kMaxQuality - kMinQuality) / 5).round(),
onChanged: (double value) async { onChanged: setQuality == null
qualityValue.value = value; ? null
debouncerQuality.value = value; : (double value) async {
}, qualityValue.value = value;
debouncerQuality.value = value;
},
), ),
), ),
Expanded( Expanded(
@@ -124,10 +121,12 @@ customImageQualityWidget(
min: kMinFps, min: kMinFps,
max: kMaxFps, max: kMaxFps,
divisions: ((kMaxFps - kMinFps) / 5).round(), divisions: ((kMaxFps - kMinFps) / 5).round(),
onChanged: (double value) async { onChanged: setFps == null
fpsValue.value = value; ? null
debouncerFps.value = value; : (double value) async {
}, fpsValue.value = value;
debouncerFps.value = value;
},
), ),
), ),
Expanded( Expanded(
@@ -152,21 +151,29 @@ customImageQualitySetting() {
final qualityKey = 'custom_image_quality'; final qualityKey = 'custom_image_quality';
final fpsKey = 'custom-fps'; final fpsKey = 'custom-fps';
var initQuality = final initQuality =
(double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
kDefaultQuality); kDefaultQuality);
var initFps = (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? final isQuanlityFixed = isOptionFixed(qualityKey);
kDefaultFps); final initFps =
(double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ??
kDefaultFps);
final isFpsFixed = isOptionFixed(fpsKey);
return customImageQualityWidget( return customImageQualityWidget(
initQuality: initQuality, initQuality: initQuality,
initFps: initFps, initFps: initFps,
setQuality: (v) { setQuality: isQuanlityFixed
bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString()); ? null
}, : (v) {
setFps: (v) { bind.mainSetUserDefaultOption(
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString()); key: qualityKey, value: v.toString());
}, },
setFps: isFpsFixed
? null
: (v) {
bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
},
showFps: true, showFps: true,
showMoreQuality: true); showMoreQuality: true);
} }
@@ -208,29 +215,31 @@ List<Widget> ServerConfigImportExportWidgets(
List<(String, String)> otherDefaultSettings() { List<(String, String)> otherDefaultSettings() {
List<(String, String)> v = [ List<(String, String)> v = [
('View Mode', 'view_only'), ('View Mode', kOptionViewOnly),
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar), if ((isDesktop || isWebDesktop))
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'), ('show_monitors_tip', kKeyShowMonitorsToolbar),
('Show remote cursor', 'show_remote_cursor'), if ((isDesktop || isWebDesktop))
('Follow remote cursor', 'follow_remote_cursor'), ('Collapse toolbar', kOptionCollapseToolbar),
('Follow remote window focus', 'follow_remote_window'), ('Show remote cursor', kOptionShowRemoteCursor),
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'), ('Follow remote cursor', kOptionFollowRemoteCursor),
('Show quality monitor', 'show_quality_monitor'), ('Follow remote window focus', kOptionFollowRemoteWindow),
('Mute', 'disable_audio'), if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor),
if (isDesktop) ('Enable file copy and paste', 'enable_file_transfer'), ('Show quality monitor', kOptionShowQualityMonitor),
('Disable clipboard', 'disable_clipboard'), ('Mute', kOptionDisableAudio),
('Lock after session end', 'lock_after_session_end'), if (isDesktop) ('Enable file copy and paste', kOptionEnableFileCopyPaste),
('Privacy mode', 'privacy_mode'), ('Disable clipboard', kOptionDisableClipboard),
if (isMobile) ('Touch mode', 'touch-mode'), ('Lock after session end', kOptionLockAfterSessionEnd),
('True color (4:4:4)', 'i444'), ('Privacy mode', kOptionPrivacyMode),
if (isMobile) ('Touch mode', kOptionTouchMode),
('True color (4:4:4)', kOptionI444),
('Reverse mouse wheel', kKeyReverseMouseWheel), ('Reverse mouse wheel', kKeyReverseMouseWheel),
('swap-left-right-mouse', 'swap-left-right-mouse'), ('swap-left-right-mouse', kOptionSwapLeftRightMouse),
if (isDesktop && useTextureRender) if (isDesktop)
( (
'Show displays as individual windows', 'Show displays as individual windows',
kKeyShowDisplaysAsIndividualWindows kKeyShowDisplaysAsIndividualWindows
), ),
if (isDesktop && useTextureRender) if (isDesktop)
( (
'Use all my displays for the remote session', 'Use all my displays for the remote session',
kKeyUseAllMyDisplaysForTheRemoteSession kKeyUseAllMyDisplaysForTheRemoteSession
+160 -20
View File
@@ -6,9 +6,9 @@ import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/shared_state.dart'; import 'package:flutter_hbb/common/shared_state.dart';
import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/common/widgets/dialog.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
bool isEditOsPassword = false; bool isEditOsPassword = false;
@@ -23,6 +23,20 @@ class TTextMenu {
required this.onPressed, required this.onPressed,
this.trailingIcon, this.trailingIcon,
this.divider = false}); this.divider = false});
Widget getChild() {
if (trailingIcon != null) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
child,
trailingIcon!,
],
);
} else {
return child;
}
}
} }
class TRadioMenu<T> { class TRadioMenu<T> {
@@ -116,9 +130,9 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
); );
} }
// paste // paste
if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) { if (pi.platform != kPeerPlatformAndroid && perms['keyboard'] != false) {
v.add(TTextMenu( v.add(TTextMenu(
child: Text(translate('Paste')), child: Text(translate('Send clipboard keystrokes')),
onPressed: () async { onPressed: () async {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) { if (data != null && data.text != null) {
@@ -133,12 +147,23 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
child: Text(translate('Reset canvas')), child: Text(translate('Reset canvas')),
onPressed: () => ffi.cursorModel.reset())); onPressed: () => ffi.cursorModel.reset()));
} }
connectWithToken(
{required bool isFileTransfer, required bool isTcpTunneling}) {
final connToken = bind.sessionGetConnToken(sessionId: ffi.sessionId);
connect(context, id,
isFileTransfer: isFileTransfer,
isTcpTunneling: isTcpTunneling,
connToken: connToken);
}
// transferFile // transferFile
if (isDesktop) { if (isDesktop) {
v.add( v.add(
TTextMenu( TTextMenu(
child: Text(translate('Transfer file')), child: Text(translate('Transfer file')),
onPressed: () => connect(context, id, isFileTransfer: true)), onPressed: () =>
connectWithToken(isFileTransfer: true, isTcpTunneling: false)),
); );
} }
// tcpTunneling // tcpTunneling
@@ -146,7 +171,8 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
v.add( v.add(
TTextMenu( TTextMenu(
child: Text(translate('TCP tunneling')), child: Text(translate('TCP tunneling')),
onPressed: () => connect(context, id, isTcpTunneling: true)), onPressed: () =>
connectWithToken(isFileTransfer: false, isTcpTunneling: true)),
); );
} }
// note // note
@@ -169,7 +195,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
(pi.platform == kPeerPlatformLinux || pi.sasEnabled)) { (pi.platform == kPeerPlatformLinux || pi.sasEnabled)) {
v.add( v.add(
TTextMenu( TTextMenu(
child: Text('${translate("Insert")} Ctrl + Alt + Del'), child: Text('${translate("Insert Ctrl + Alt + Del")}'),
onPressed: () => bind.sessionCtrlAltDel(sessionId: sessionId)), onPressed: () => bind.sessionCtrlAltDel(sessionId: sessionId)),
); );
} }
@@ -327,7 +353,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
final alternativeCodecs = final alternativeCodecs =
await bind.sessionAlternativeCodecs(sessionId: sessionId); await bind.sessionAlternativeCodecs(sessionId: sessionId);
final groupValue = await bind.sessionGetOption( final groupValue = await bind.sessionGetOption(
sessionId: sessionId, arg: 'codec-preference') ?? sessionId: sessionId, arg: kOptionCodecPreference) ??
''; '';
final List<bool> codecs = []; final List<bool> codecs = [];
try { try {
@@ -349,7 +375,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
onChanged(String? value) async { onChanged(String? value) async {
if (value == null) return; if (value == null) return;
await bind.sessionPeerOption( await bind.sessionPeerOption(
sessionId: sessionId, name: 'codec-preference', value: value); sessionId: sessionId, name: kOptionCodecPreference, value: value);
bind.sessionChangePreferCodec(sessionId: sessionId); bind.sessionChangePreferCodec(sessionId: sessionId);
} }
@@ -362,7 +388,8 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
} }
var autoLabel = translate('Auto'); var autoLabel = translate('Auto');
if (groupValue == 'auto') { if (groupValue == 'auto' &&
ffi.qualityMonitorModel.data.codecFormat != null) {
autoLabel = '$autoLabel (${ffi.qualityMonitorModel.data.codecFormat})'; autoLabel = '$autoLabel (${ffi.qualityMonitorModel.data.codecFormat})';
} }
return [ return [
@@ -380,7 +407,6 @@ Future<List<TToggleMenu>> toolbarCursor(
List<TToggleMenu> v = []; List<TToggleMenu> v = [];
final ffiModel = ffi.ffiModel; final ffiModel = ffi.ffiModel;
final pi = ffiModel.pi; final pi = ffiModel.pi;
final perms = ffiModel.permissions;
final sessionId = ffi.sessionId; final sessionId = ffi.sessionId;
// show remote cursor // show remote cursor
@@ -534,15 +560,15 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
perms['file'] != false && perms['file'] != false &&
(isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) { (isSupportIfPeer_1_2_3 || isSupportIfPeer_1_2_4)) {
final enabled = !ffiModel.viewOnly; final enabled = !ffiModel.viewOnly;
final option = 'enable-file-transfer'; final value = bind.sessionGetToggleOptionSync(
final value = sessionId: sessionId, arg: kOptionEnableFileCopyPaste);
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
v.add(TToggleMenu( v.add(TToggleMenu(
value: value, value: value,
onChanged: enabled onChanged: enabled
? (value) { ? (value) {
if (value == null) return; if (value == null) return;
bind.sessionToggleOption(sessionId: sessionId, value: option); bind.sessionToggleOption(
sessionId: sessionId, value: kOptionEnableFileCopyPaste);
} }
: null, : null,
child: Text(translate('Enable file copy and paste')))); child: Text(translate('Enable file copy and paste'))));
@@ -565,7 +591,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Disable clipboard')))); child: Text(translate('Disable clipboard'))));
} }
// lock after session end // lock after session end
if (ffiModel.keyboard) { if (ffiModel.keyboard && !ffiModel.isPeerAndroid) {
final enabled = !ffiModel.viewOnly; final enabled = !ffiModel.viewOnly;
final option = 'lock-after-session-end'; final option = 'lock-after-session-end';
final value = final value =
@@ -581,8 +607,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Lock after session end')))); child: Text(translate('Lock after session end'))));
} }
if (useTextureRender && if (pi.isSupportMultiDisplay &&
pi.isSupportMultiDisplay &&
PrivacyModeState.find(id).isEmpty && PrivacyModeState.find(id).isEmpty &&
pi.displaysCount.value > 1 && pi.displaysCount.value > 1 &&
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
@@ -594,13 +619,13 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
bind.sessionSetDisplaysAsIndividualWindows( bind.sessionSetDisplaysAsIndividualWindows(
sessionId: sessionId, value: value ? 'Y' : ''); sessionId: sessionId, value: value ? 'Y' : 'N');
}, },
child: Text(translate('Show displays as individual windows')))); child: Text(translate('Show displays as individual windows'))));
} }
final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1; final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1;
if (useTextureRender && pi.isSupportMultiDisplay && isMultiScreens) { if (pi.isSupportMultiDisplay && isMultiScreens) {
final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession( final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
sessionId: ffi.sessionId) == sessionId: ffi.sessionId) ==
'Y'; 'Y';
@@ -609,7 +634,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
bind.sessionSetUseAllMyDisplaysForTheRemoteSession( bind.sessionSetUseAllMyDisplaysForTheRemoteSession(
sessionId: sessionId, value: value ? 'Y' : ''); sessionId: sessionId, value: value ? 'Y' : 'N');
}, },
child: Text(translate('Use all my displays for the remote session')))); child: Text(translate('Use all my displays for the remote session'))));
} }
@@ -635,6 +660,18 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
v.addAll(toolbarKeyboardToggles(ffi)); v.addAll(toolbarKeyboardToggles(ffi));
} }
// view mode (mobile only, desktop is in keyboard menu)
if (isMobile && versionCmp(pi.version, '1.2.0') >= 0) {
v.add(TToggleMenu(
value: ffiModel.viewOnly,
onChanged: (value) async {
if (value == null) return;
await bind.sessionToggleOption(
sessionId: ffi.sessionId, value: kOptionToggleViewOnly);
ffiModel.setViewOnly(id, value);
},
child: Text(translate('View Mode'))));
}
return v; return v;
} }
@@ -775,3 +812,106 @@ List<TToggleMenu> toolbarKeyboardToggles(FFI ffi) {
} }
return v; return v;
} }
bool showVirtualDisplayMenu(FFI ffi) {
if (ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
return false;
}
if (!ffi.ffiModel.pi.isInstalled) {
return false;
}
if (ffi.ffiModel.pi.isRustDeskIdd || ffi.ffiModel.pi.isAmyuniIdd) {
return true;
}
return false;
}
List<Widget> getVirtualDisplayMenuChildren(
FFI ffi, String id, VoidCallback? clickCallBack) {
if (!showVirtualDisplayMenu(ffi)) {
return [];
}
final pi = ffi.ffiModel.pi;
final privacyModeState = PrivacyModeState.find(id);
if (pi.isRustDeskIdd) {
final virtualDisplays = ffi.ffiModel.pi.RustDeskVirtualDisplays;
final children = <Widget>[];
for (var i = 0; i < kMaxVirtualDisplayCount; i++) {
children.add(Obx(() => CkbMenuButton(
value: virtualDisplays.contains(i + 1),
onChanged: privacyModeState.isNotEmpty
? null
: (bool? value) async {
if (value != null) {
bind.sessionToggleVirtualDisplay(
sessionId: ffi.sessionId, index: i + 1, on: value);
clickCallBack?.call();
}
},
child: Text('${translate('Virtual display')} ${i + 1}'),
ffi: ffi,
)));
}
children.add(Divider());
children.add(Obx(() => MenuButton(
onPressed: privacyModeState.isNotEmpty
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: ffi.sessionId,
index: kAllVirtualDisplay,
on: false);
clickCallBack?.call();
},
ffi: ffi,
child: Text(translate('Plug out all')),
)));
return children;
}
if (pi.isAmyuniIdd) {
final count = ffi.ffiModel.pi.amyuniVirtualDisplayCount;
final children = <Widget>[
Obx(() => Row(
children: [
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: ffi.sessionId, index: 0, on: false);
clickCallBack?.call();
},
child: Icon(Icons.remove),
),
Text(count.toString()),
TextButton(
onPressed: privacyModeState.isNotEmpty || count == 4
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: ffi.sessionId, index: 0, on: true);
clickCallBack?.call();
},
child: Icon(Icons.add),
),
],
)),
Divider(),
Obx(() => MenuButton(
onPressed: privacyModeState.isNotEmpty || count == 0
? null
: () {
bind.sessionToggleVirtualDisplay(
sessionId: ffi.sessionId,
index: kAllVirtualDisplay,
on: false);
clickCallBack?.call();
},
ffi: ffi,
child: Text(translate('Plug out all')),
)),
];
return children;
}
return [];
}
+102 -14
View File
@@ -32,6 +32,7 @@ const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformMacOS = "Mac OS";
const String kPeerPlatformAndroid = "Android"; const String kPeerPlatformAndroid = "Android";
const String kPeerPlatformWebDesktop = "WebDesktop";
const double kScrollbarThickness = 12.0; const double kScrollbarThickness = 12.0;
@@ -63,16 +64,99 @@ const String kWindowEventActiveDisplaySession = "active_display_session";
const String kWindowEventGetRemoteList = "get_remote_list"; const String kWindowEventGetRemoteList = "get_remote_list";
const String kWindowEventGetSessionIdList = "get_session_id_list"; const String kWindowEventGetSessionIdList = "get_session_id_list";
const String kWindowEventRemoteWindowCoords = "remote_window_coords"; const String kWindowEventRemoteWindowCoords = "remote_window_coords";
const String kWindowEventSetFullscreen = "set_fullscreen";
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
const String kWindowEventGetCachedSessionData = "get_cached_session_data"; const String kWindowEventGetCachedSessionData = "get_cached_session_data";
const String kWindowEventOpenMonitorSession = "open_monitor_session"; const String kWindowEventOpenMonitorSession = "open_monitor_session";
const String kOptionViewStyle = "view_style";
const String kOptionScrollStyle = "scroll_style";
const String kOptionImageQuality = "image_quality";
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
const String kOptionTextureRender = "use-texture-render";
const String kOptionOpenInTabs = "allow-open-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs";
const String kOptionOpenInWindows = "allow-open-in-windows"; const String kOptionOpenInWindows = "allow-open-in-windows";
const String kOptionForceAlwaysRelay = "force-always-relay"; const String kOptionForceAlwaysRelay = "force-always-relay";
const String kOptionViewOnly = "view-only"; const String kOptionViewOnly = "view_only";
const String kOptionEnableLanDiscovery = "enable-lan-discovery";
const String kOptionWhitelist = "whitelist";
const String kOptionEnableAbr = "enable-abr";
const String kOptionEnableRecordSession = "enable-record-session";
const String kOptionDirectServer = "direct-server";
const String kOptionDirectAccessPort = "direct-access-port";
const String kOptionAllowAutoDisconnect = "allow-auto-disconnect";
const String kOptionAutoDisconnectTimeout = "auto-disconnect-timeout";
const String kOptionEnableHwcodec = "enable-hwcodec";
const String kOptionAllowAutoRecordIncoming = "allow-auto-record-incoming";
const String kOptionAllowAutoRecordOutgoing = "allow-auto-record-outgoing";
const String kOptionVideoSaveDirectory = "video-save-directory";
const String kOptionAccessMode = "access-mode";
const String kOptionEnableKeyboard = "enable-keyboard";
// "Settings -> Security -> Permissions"
const String kOptionEnableClipboard = "enable-clipboard";
const String kOptionEnableFileTransfer = "enable-file-transfer";
const String kOptionEnableAudio = "enable-audio";
const String kOptionEnableTunnel = "enable-tunnel";
const String kOptionEnableRemoteRestart = "enable-remote-restart";
const String kOptionEnableBlockInput = "enable-block-input";
const String kOptionAllowRemoteConfigModification =
"allow-remote-config-modification";
const String kOptionVerificationMethod = "verification-method";
const String kOptionApproveMode = "approve-mode";
const String kOptionCollapseToolbar = "collapse_toolbar";
const String kOptionShowRemoteCursor = "show_remote_cursor";
const String kOptionFollowRemoteCursor = "follow_remote_cursor";
const String kOptionFollowRemoteWindow = "follow_remote_window";
const String kOptionZoomCursor = "zoom-cursor";
const String kOptionShowQualityMonitor = "show_quality_monitor";
const String kOptionDisableAudio = "disable_audio";
const String kOptionEnableFileCopyPaste = "enable-file-copy-paste";
// "Settings -> Display -> Other default options"
const String kOptionDisableClipboard = "disable_clipboard";
const String kOptionLockAfterSessionEnd = "lock_after_session_end";
const String kOptionPrivacyMode = "privacy_mode";
const String kOptionTouchMode = "touch-mode";
const String kOptionI444 = "i444";
const String kOptionSwapLeftRightMouse = "swap-left-right-mouse";
const String kOptionCodecPreference = "codec-preference";
const String kOptionRemoteMenubarDragLeft = "remote-menubar-drag-left";
const String kOptionRemoteMenubarDragRight = "remote-menubar-drag-right";
const String kOptionHideAbTagsPanel = "hideAbTagsPanel";
const String kOptionRemoteMenubarState = "remoteMenubarState";
const String kOptionPeerSorting = "peer-sorting";
const String kOptionPeerTabIndex = "peer-tab-index";
const String kOptionPeerTabOrder = "peer-tab-order";
const String kOptionPeerTabVisible = "peer-tab-visible";
const String kOptionPeerCardUiType = "peer-card-ui-type";
const String kOptionCurrentAbName = "current-ab-name";
const String kOptionEnableConfirmClosingTabs = "enable-confirm-closing-tabs";
const String kOptionAllowAlwaysSoftwareRender = "allow-always-software-render";
const String kOptionEnableCheckUpdate = "enable-check-update";
const String kOptionAllowLinuxHeadless = "allow-linux-headless";
const String kOptionAllowRemoveWallpaper = "allow-remove-wallpaper";
const String kOptionStopService = "stop-service";
const String kOptionDirectxCapture = "enable-directx-capture";
const String kOptionAllowRemoteCmModification = "allow-remote-cm-modification";
const String kOptionEnableTrustedDevices = "enable-trusted-devices";
// buildin opitons
const String kOptionHideServerSetting = "hide-server-settings";
const String kOptionHideProxySetting = "hide-proxy-settings";
const String kOptionHideSecuritySetting = "hide-security-settings";
const String kOptionHideNetworkSetting = "hide-network-settings";
const String kOptionRemovePresetPasswordWarning =
"remove-preset-password-warning";
const kHideUsernameOnCard = "hide-username-on-card";
const String kOptionHideHelpCards = "hide-help-cards";
const String kOptionToggleViewOnly = "view-only";
const String kOptionDisableFloatingWindow = "disable-floating-window";
const String kOptionKeepScreenOn = "keep-screen-on";
const String kOptionShowMobileAction = "showMobileActions";
const String kUrlActionClose = "close"; const String kUrlActionClose = "close";
@@ -85,6 +169,15 @@ const int kWindowMainId = 0;
const String kPointerEventKindTouch = "touch"; const String kPointerEventKindTouch = "touch";
const String kPointerEventKindMouse = "mouse"; 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 = const String kKeyShowDisplaysAsIndividualWindows =
'displays_as_individual_windows'; 'displays_as_individual_windows';
const String kKeyUseAllMyDisplaysForTheRemoteSession = const String kKeyUseAllMyDisplaysForTheRemoteSession =
@@ -92,10 +185,13 @@ const String kKeyUseAllMyDisplaysForTheRemoteSession =
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar'; const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
const String kKeyReverseMouseWheel = "reverse_mouse_wheel"; const String kKeyReverseMouseWheel = "reverse_mouse_wheel";
const String kMsgboxTextWaitingForImage = 'Connected, waiting for image...';
// the executable name of the portable version // the executable name of the portable version
const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59); const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
const Color kColorCanvas = Colors.black;
const int kMobileDefaultDisplayWidth = 720; const int kMobileDefaultDisplayWidth = 720;
const int kMobileDefaultDisplayHeight = 1280; const int kMobileDefaultDisplayHeight = 1280;
@@ -148,15 +244,11 @@ const double kDesktopIconButtonSplashRadius = 20;
/// [kMinCursorSize] indicates min cursor (w, h) /// [kMinCursorSize] indicates min cursor (w, h)
const int kMinCursorSize = 12; 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 kFullScreenEdgeSize = 0.0;
const kMaximizeEdgeSize = 0.0; const kMaximizeEdgeSize = 0.0;
// Do not use kWindowEdgeSize directly. Use `windowEdgeSize` in `common.dart` instead. // Do not use kWindowResizeEdgeSize directly. Use `windowResizeEdgeSize` in `common.dart` instead.
final kWindowEdgeSize = isWindows ? 1.0 : 5.0; const kWindowResizeEdgeSize = 5.0;
final kWindowBorderWidth = isLinux ? 1.0 : 0.0; final kWindowBorderWidth = isWindows ? 0.0 : 1.0;
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0); const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
const kFrameBorderRadius = 12.0; const kFrameBorderRadius = 12.0;
const kFrameClipRRectBorderRadius = 12.0; const kFrameClipRRectBorderRadius = 12.0;
@@ -208,12 +300,6 @@ const kRemoteImageQualityLow = 'low';
/// [kRemoteImageQualityCustom] Custom image quality. /// [kRemoteImageQualityCustom] Custom image quality.
const kRemoteImageQualityCustom = 'custom'; const kRemoteImageQualityCustom = 'custom';
/// [kRemoteAudioGuestToHost] Guest to host audio mode(default).
const kRemoteAudioGuestToHost = 'guest-to-host';
/// [kRemoteAudioDualWay] dual-way audio mode(default).
const kRemoteAudioDualWay = 'dual-way';
const kIgnoreDpi = true; const kIgnoreDpi = true;
// ================================ mobile ================================ // ================================ mobile ================================
@@ -488,3 +574,5 @@ enum WindowsTarget {
extension WindowsTargetExt on int { extension WindowsTargetExt on int {
WindowsTarget get windowsVersion => getWindowsTarget(this); WindowsTarget get windowsVersion => getWindowsTarget(this);
} }
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';
+54 -76
View File
@@ -3,8 +3,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/connection_page_title.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -39,7 +39,7 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
double? get height => bind.isIncomingOnly() ? null : em * 3; double? get height => bind.isIncomingOnly() ? null : em * 3;
void onUsePublicServerGuide() { void onUsePublicServerGuide() {
const url = "https://rustdesk.com/pricing.html"; const url = "https://rustdesk.com/pricing";
canLaunchUrlString(url).then((can) { canLaunchUrlString(url).then((can) {
if (can) { if (can) {
launchUrlString(url); launchUrlString(url);
@@ -169,20 +169,19 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
final status = final status =
jsonDecode(await bind.mainGetConnectStatus()) as Map<String, dynamic>; jsonDecode(await bind.mainGetConnectStatus()) as Map<String, dynamic>;
final statusNum = status['status_num'] as int; final statusNum = status['status_num'] as int;
final preStatus = stateGlobal.svcStatus.value;
if (statusNum == 0) { if (statusNum == 0) {
stateGlobal.svcStatus.value = SvcStatus.connecting; stateGlobal.svcStatus.value = SvcStatus.connecting;
} else if (statusNum == -1) { } else if (statusNum == -1) {
stateGlobal.svcStatus.value = SvcStatus.notReady; stateGlobal.svcStatus.value = SvcStatus.notReady;
} else if (statusNum == 1) { } else if (statusNum == 1) {
stateGlobal.svcStatus.value = SvcStatus.ready; stateGlobal.svcStatus.value = SvcStatus.ready;
if (preStatus != SvcStatus.ready) {
gFFI.userModel.refreshCurrentUser();
}
} else { } else {
stateGlobal.svcStatus.value = SvcStatus.notReady; stateGlobal.svcStatus.value = SvcStatus.notReady;
} }
_svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer(); _svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
try {
stateGlobal.videoConnCount.value = status['video_conn_count'] as int;
} catch (_) {}
} }
} }
@@ -201,26 +200,32 @@ class _ConnectionPageState extends State<ConnectionPage>
final _idController = IDTextEditingController(); final _idController = IDTextEditingController();
final RxBool _idInputFocused = false.obs; final RxBool _idInputFocused = false.obs;
final FocusNode _idFocusNode = FocusNode();
final TextEditingController _idEditingController = TextEditingController();
bool isWindowMinimized = false; bool isWindowMinimized = false;
List<Peer> peers = [];
bool isPeersLoading = false; final AllPeersLoader _allPeersLoader = AllPeersLoader();
bool isPeersLoaded = false;
// https://github.com/flutter/flutter/issues/157244
Iterable<Peer> _autocompleteOpts = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_allPeersLoader.init(setState);
_idFocusNode.addListener(onFocusChanged);
if (_idController.text.isEmpty) { if (_idController.text.isEmpty) {
() async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final lastRemoteId = await bind.mainGetLastRemoteId(); final lastRemoteId = await bind.mainGetLastRemoteId();
if (lastRemoteId != _idController.id) { if (lastRemoteId != _idController.id) {
setState(() { setState(() {
_idController.id = lastRemoteId; _idController.id = lastRemoteId;
}); });
} }
}(); });
} }
Get.put<TextEditingController>(_idEditingController);
Get.put<IDTextEditingController>(_idController); Get.put<IDTextEditingController>(_idController);
windowManager.addListener(this); windowManager.addListener(this);
} }
@@ -229,6 +234,10 @@ class _ConnectionPageState extends State<ConnectionPage>
void dispose() { void dispose() {
_idController.dispose(); _idController.dispose();
windowManager.removeListener(this); windowManager.removeListener(this);
_allPeersLoader.clear();
_idFocusNode.removeListener(onFocusChanged);
_idFocusNode.dispose();
_idEditingController.dispose();
if (Get.isRegistered<IDTextEditingController>()) { if (Get.isRegistered<IDTextEditingController>()) {
Get.delete<IDTextEditingController>(); Get.delete<IDTextEditingController>();
} }
@@ -261,8 +270,9 @@ class _ConnectionPageState extends State<ConnectionPage>
@override @override
void onWindowLeaveFullScreen() { void onWindowLeaveFullScreen() {
// Restore edge border to default edge size. // Restore edge border to default edge size.
stateGlobal.resizeEdgeSize.value = stateGlobal.resizeEdgeSize.value = stateGlobal.isMaximized.isTrue
stateGlobal.isMaximized.isTrue ? kMaximizeEdgeSize : windowEdgeSize; ? kMaximizeEdgeSize
: windowResizeEdgeSize;
} }
@override @override
@@ -271,6 +281,20 @@ class _ConnectionPageState extends State<ConnectionPage>
bind.mainOnMainWindowClose(); bind.mainOnMainWindowClose();
} }
void onFocusChanged() {
_idInputFocused.value = _idFocusNode.hasFocus;
if (_idFocusNode.hasFocus) {
if (_allPeersLoader.needLoad) {
_allPeersLoader.getAllPeers();
}
final textLength = _idEditingController.value.text.length;
// Select all to facilitate removing text, just following the behavior of address input of chrome.
_idEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: textLength);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isOutgoingOnly = bind.isOutgoingOnly(); final isOutgoingOnly = bind.isOutgoingOnly();
@@ -302,18 +326,6 @@ class _ConnectionPageState extends State<ConnectionPage>
connect(context, id, isFileTransfer: isFileTransfer); connect(context, id, isFileTransfer: isFileTransfer);
} }
Future<void> _fetchPeers() async {
setState(() {
isPeersLoading = true;
});
await Future.delayed(Duration(milliseconds: 100));
peers = await getAllPeers();
setState(() {
isPeersLoading = false;
isPeersLoaded = true;
});
}
/// UI for the remote ID TextField. /// UI for the remote ID TextField.
/// Search for a peer. /// Search for a peer.
Widget _buildRemoteIDTextField(BuildContext context) { Widget _buildRemoteIDTextField(BuildContext context) {
@@ -326,44 +338,16 @@ class _ConnectionPageState extends State<ConnectionPage>
child: Ink( child: Ink(
child: Column( child: Column(
children: [ children: [
getConnectionPageTitle(context, false).marginOnly(bottom: 15),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: Row( child: RawAutocomplete<Peer>(
children: [
AutoSizeText(
translate('Control Remote Desktop'),
maxLines: 1,
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
).marginOnly(right: 4),
Tooltip(
waitDuration: Duration(milliseconds: 0),
message: translate("id_input_tip"),
child: Icon(
Icons.help_outline_outlined,
size: 16,
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
),
),
],
)),
],
).marginOnly(bottom: 15),
Row(
children: [
Expanded(
child: Autocomplete<Peer>(
optionsBuilder: (TextEditingValue textEditingValue) { optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') { if (textEditingValue.text == '') {
return const Iterable<Peer>.empty(); _autocompleteOpts = const Iterable<Peer>.empty();
} else if (peers.isEmpty && !isPeersLoaded) { } else if (_allPeersLoader.peers.isEmpty &&
!_allPeersLoader.isPeersLoaded) {
Peer emptyPeer = Peer( Peer emptyPeer = Peer(
id: '', id: '',
username: '', username: '',
@@ -377,8 +361,9 @@ class _ConnectionPageState extends State<ConnectionPage>
rdpPort: '', rdpPort: '',
rdpUsername: '', rdpUsername: '',
loginName: '', loginName: '',
device_group_name: '',
); );
return [emptyPeer]; _autocompleteOpts = [emptyPeer];
} else { } else {
String textWithoutSpaces = String textWithoutSpaces =
textEditingValue.text.replaceAll(" ", ""); textEditingValue.text.replaceAll(" ", "");
@@ -389,8 +374,7 @@ class _ConnectionPageState extends State<ConnectionPage>
); );
} }
String textToFind = textEditingValue.text.toLowerCase(); String textToFind = textEditingValue.text.toLowerCase();
_autocompleteOpts = _allPeersLoader.peers
return peers
.where((peer) => .where((peer) =>
peer.id.toLowerCase().contains(textToFind) || peer.id.toLowerCase().contains(textToFind) ||
peer.username peer.username
@@ -402,26 +386,18 @@ class _ConnectionPageState extends State<ConnectionPage>
peer.alias.toLowerCase().contains(textToFind)) peer.alias.toLowerCase().contains(textToFind))
.toList(); .toList();
} }
return _autocompleteOpts;
}, },
focusNode: _idFocusNode,
textEditingController: _idEditingController,
fieldViewBuilder: ( fieldViewBuilder: (
BuildContext context, BuildContext context,
TextEditingController fieldTextEditingController, TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode, FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted, VoidCallback onFieldSubmitted,
) { ) {
fieldTextEditingController.text = _idController.text; updateTextAndPreserveSelection(
Get.put<TextEditingController>(fieldTextEditingController); fieldTextEditingController, _idController.text);
fieldFocusNode.addListener(() async {
_idInputFocused.value = fieldFocusNode.hasFocus;
if (fieldFocusNode.hasFocus && !isPeersLoading) {
_fetchPeers();
}
});
final textLength =
fieldTextEditingController.value.text.length;
// select all to facilitate removing text, just following the behavior of address input of chrome
fieldTextEditingController.selection =
TextSelection(baseOffset: 0, extentOffset: textLength);
return Obx(() => TextField( return Obx(() => TextField(
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
@@ -451,7 +427,7 @@ class _ConnectionPageState extends State<ConnectionPage>
onSubmitted: (_) { onSubmitted: (_) {
onConnect(); onConnect();
}, },
)); ).workaroundFreezeLinuxMint());
}, },
onSelected: (option) { onSelected: (option) {
setState(() { setState(() {
@@ -462,6 +438,7 @@ class _ConnectionPageState extends State<ConnectionPage>
optionsViewBuilder: (BuildContext context, optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<Peer> onSelected, AutocompleteOnSelected<Peer> onSelected,
Iterable<Peer> options) { Iterable<Peer> options) {
options = _autocompleteOpts;
double maxHeight = options.length * 50; double maxHeight = options.length * 50;
if (options.length == 1) { if (options.length == 1) {
maxHeight = 52; maxHeight = 52;
@@ -493,7 +470,8 @@ class _ConnectionPageState extends State<ConnectionPage>
maxHeight: maxHeight, maxHeight: maxHeight,
maxWidth: 319, maxWidth: 319,
), ),
child: peers.isEmpty && isPeersLoading child: _allPeersLoader.peers.isEmpty &&
!_allPeersLoader.isPeersLoaded
? Container( ? Container(
height: 80, height: 80,
child: Center( child: Center(
+110 -78
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/connection_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_setting_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/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/platform_model.dart';
import 'package:flutter_hbb/models/server_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/plugin/ui_manager.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -35,12 +35,11 @@ class DesktopHomePage extends StatefulWidget {
const borderColor = Color(0xFF2F65BA); const borderColor = Color(0xFF2F65BA);
class _DesktopHomePageState extends State<DesktopHomePage> class _DesktopHomePageState extends State<DesktopHomePage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
final _leftPaneScrollController = ScrollController(); final _leftPaneScrollController = ScrollController();
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
var updateUrl = '';
var systemError = ''; var systemError = '';
StreamSubscription? _uniLinksSubscription; StreamSubscription? _uniLinksSubscription;
var svcStopped = false.obs; var svcStopped = false.obs;
@@ -52,6 +51,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
bool isCardClosed = false; bool isCardClosed = false;
final RxBool _editHover = false.obs; final RxBool _editHover = false.obs;
final RxBool _block = false.obs;
final GlobalKey _childKey = GlobalKey(); final GlobalKey _childKey = GlobalKey();
@@ -59,14 +59,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final isIncomingOnly = bind.isIncomingOnly(); final isIncomingOnly = bind.isIncomingOnly();
return Row( return _buildBlock(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
buildLeftPane(context), buildLeftPane(context),
if (!isIncomingOnly) const VerticalDivider(width: 1), if (!isIncomingOnly) const VerticalDivider(width: 1),
if (!isIncomingOnly) Expanded(child: buildRightPane(context)), 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) { Widget buildLeftPane(BuildContext context) {
@@ -87,7 +93,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
if (!isOutgoingOnly) buildIDBoard(context), if (!isOutgoingOnly) buildIDBoard(context),
if (!isOutgoingOnly) buildPasswordBoard(context), if (!isOutgoingOnly) buildPasswordBoard(context),
FutureBuilder<Widget>( FutureBuilder<Widget>(
future: buildHelpCards(), future: Future.value(
Obx(() => buildHelpCards(stateGlobal.updateUrl.value))),
builder: (_, data) { builder: (_, data) {
if (data.hasData) { if (data.hasData) {
if (isIncomingOnly) { if (isIncomingOnly) {
@@ -125,47 +132,43 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: Container( child: Container(
width: isIncomingOnly ? 280.0 : 200.0, width: isIncomingOnly ? 280.0 : 200.0,
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: DesktopScrollWrapper( child: Stack(
scrollController: _leftPaneScrollController, children: [
child: Stack( SingleChildScrollView(
children: [ controller: _leftPaneScrollController,
SingleChildScrollView( child: Column(
controller: _leftPaneScrollController, key: _childKey,
physics: DraggableNeverScrollableScrollPhysics(), children: children,
child: Column(
key: _childKey,
children: children,
),
), ),
if (isOutgoingOnly) ),
Positioned( if (isOutgoingOnly)
bottom: 6, Positioned(
left: 12, bottom: 6,
child: Align( left: 12,
alignment: Alignment.centerLeft, child: Align(
child: InkWell( alignment: Alignment.centerLeft,
child: Obx( child: InkWell(
() => Icon( child: Obx(
Icons.settings, () => Icon(
color: _editHover.value Icons.settings,
? textColor color: _editHover.value
: Colors.grey.withOpacity(0.5), ? textColor
size: 22, : 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( style: TextStyle(
fontSize: 22, fontSize: 22,
), ),
), ).workaroundFreezeLinuxMint(),
), ),
) )
], ],
@@ -272,10 +275,21 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
buildPasswordBoard(BuildContext context) { 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 refreshHover = false.obs;
RxBool editHover = false.obs; RxBool editHover = false.obs;
final textColor = Theme.of(context).textTheme.titleLarge?.color; final textColor = Theme.of(context).textTheme.titleLarge?.color;
final showOneTime = model.approveMode != 'click' &&
model.verificationMethod != kUsePermanentPassword;
return Container( return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13), margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row( child: Row(
@@ -304,8 +318,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
onDoubleTap: () { onDoubleTap: () {
if (model.verificationMethod != if (showOneTime) {
kUsePermanentPassword) {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: model.serverPasswd.text)); ClipboardData(text: model.serverPasswd.text));
showToast(translate("Copied")); showToast(translate("Copied"));
@@ -320,25 +333,26 @@ class _DesktopHomePageState extends State<DesktopHomePage>
EdgeInsets.only(top: 14, bottom: 10), EdgeInsets.only(top: 14, bottom: 10),
), ),
style: TextStyle(fontSize: 15), style: TextStyle(fontSize: 15),
), ).workaroundFreezeLinuxMint(),
), ),
), ),
AnimatedRotationWidget( if (showOneTime)
onPressed: () => bind.mainUpdateTemporaryPassword(), AnimatedRotationWidget(
child: Tooltip( onPressed: () => bind.mainUpdateTemporaryPassword(),
message: translate('Refresh Password'), child: Tooltip(
child: Obx(() => RotatedBox( message: translate('Refresh Password'),
quarterTurns: 2, child: Obx(() => RotatedBox(
child: Icon( quarterTurns: 2,
Icons.refresh, child: Icon(
color: refreshHover.value Icons.refresh,
? textColor color: refreshHover.value
: Color(0xFFDDDDDD), ? textColor
size: 22, : Color(0xFFDDDDDD),
))), size: 22,
), ))),
onHover: (value) => refreshHover.value = value, ),
).marginOnly(right: 8, top: 4), onHover: (value) => refreshHover.value = value,
).marginOnly(right: 8, top: 4),
if (!bind.isDisableSettings()) if (!bind.isDisableSettings())
InkWell( InkWell(
child: Tooltip( child: Tooltip(
@@ -409,14 +423,14 @@ class _DesktopHomePageState extends State<DesktopHomePage>
); );
} }
Future<Widget> buildHelpCards() async { Widget buildHelpCards(String updateUrl) {
if (!bind.isCustomClient() && if (!bind.isCustomClient() &&
updateUrl.isNotEmpty && updateUrl.isNotEmpty &&
!isCardClosed && !isCardClosed &&
bind.mainUriPrefixSync().contains('rustdesk')) { bind.mainUriPrefixSync().contains('rustdesk')) {
return buildInstallCard( return buildInstallCard(
"Status", "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 { "Click to download", () async {
final Uri url = Uri.parse('https://rustdesk.com/download'); final Uri url = Uri.parse('https://rustdesk.com/download');
await launchUrl(url); await launchUrl(url);
@@ -443,14 +457,14 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}); });
} }
} else if (isMacOS) { } else if (isMacOS) {
if (!(bind.isOutgoingOnly() || final isOutgoingOnly = bind.isOutgoingOnly();
bind.mainIsCanScreenRecording(prompt: false))) { if (!(isOutgoingOnly || bind.mainIsCanScreenRecording(prompt: false))) {
return buildInstallCard("Permissions", "config_screen", "Configure", return buildInstallCard("Permissions", "config_screen", "Configure",
() async { () async {
bind.mainIsCanScreenRecording(prompt: true); bind.mainIsCanScreenRecording(prompt: true);
watchIsCanScreenRecording = true; watchIsCanScreenRecording = true;
}, help: 'Help', link: translate("doc_mac_permission")); }, help: 'Help', link: translate("doc_mac_permission"));
} else if (!bind.mainIsProcessTrusted(prompt: false)) { } else if (!isOutgoingOnly && !bind.mainIsProcessTrusted(prompt: false)) {
return buildInstallCard("Permissions", "config_acc", "Configure", return buildInstallCard("Permissions", "config_acc", "Configure",
() async { () async {
bind.mainIsProcessTrusted(prompt: true); bind.mainIsProcessTrusted(prompt: true);
@@ -462,7 +476,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
bind.mainIsCanInputMonitoring(prompt: true); bind.mainIsCanInputMonitoring(prompt: true);
watchIsInputMonitoring = true; watchIsInputMonitoring = true;
}, help: 'Help', link: translate("doc_mac_permission")); }, help: 'Help', link: translate("doc_mac_permission"));
} else if (!svcStopped.value && } else if (!isOutgoingOnly &&
!svcStopped.value &&
bind.mainIsInstalled() && bind.mainIsInstalled() &&
!bind.mainIsInstalledDaemon(prompt: false)) { !bind.mainIsInstalledDaemon(prompt: false)) {
return buildInstallCard("", "install_daemon_tip", "Install", () async { return buildInstallCard("", "install_daemon_tip", "Install", () async {
@@ -545,6 +560,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
String? link, String? link,
bool? closeButton, bool? closeButton,
String? closeOption}) { String? closeOption}) {
if (bind.mainGetBuildinOption(key: kOptionHideHelpCards) == 'Y' &&
content != 'install_daemon_tip') {
return const SizedBox();
}
void closeCard() async { void closeCard() async {
if (closeOption != null) { if (closeOption != null) {
await bind.mainSetLocalOption(key: closeOption, value: 'N'); await bind.mainSetLocalOption(key: closeOption, value: 'N');
@@ -658,10 +677,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Timer(const Duration(seconds: 1), () async {
updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (updateUrl.isNotEmpty) setState(() {});
});
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async { _updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
await gFFI.serverModel.fetchID(); await gFFI.serverModel.fetchID();
final error = await bind.mainGetError(); final error = await bind.mainGetError();
@@ -669,7 +684,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
systemError = error; systemError = error;
setState(() {}); setState(() {});
} }
final v = await bind.mainGetOption(key: "stop-service") == "Y"; final v = await mainGetBoolOption(kOptionStopService);
if (v != svcStopped.value) { if (v != svcStopped.value) {
svcStopped.value = v; svcStopped.value = v;
setState(() {}); setState(() {});
@@ -759,6 +774,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
isRDP: call.arguments['isRDP'], isRDP: call.arguments['isRDP'],
password: call.arguments['password'], password: call.arguments['password'],
forceRelay: call.arguments['forceRelay'], forceRelay: call.arguments['forceRelay'],
connToken: call.arguments['connToken'],
); );
} else if (call.method == kWindowEventMoveTabToNewWindow) { } else if (call.method == kWindowEventMoveTabToNewWindow) {
final args = call.arguments.split(','); final args = call.arguments.split(',');
@@ -796,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_updateWindowSize(); _updateWindowSize();
}); });
} }
WidgetsBinding.instance.addObserver(this);
} }
_updateWindowSize() { _updateWindowSize() {
@@ -817,9 +834,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
_uniLinksSubscription?.cancel(); _uniLinksSubscription?.cancel();
Get.delete<RxBool>(tag: 'stop-service'); Get.delete<RxBool>(tag: 'stop-service');
_updateTimer?.cancel(); _updateTimer?.cancel();
WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
shouldBeBlocked(_block, canBeBlocked);
}
}
Widget buildPluginEntry() { Widget buildPluginEntry() {
final entries = PluginUiManager.instance.entries.entries; final entries = PluginUiManager.instance.entries.entries;
return Offstage( return Offstage(
@@ -836,7 +862,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
} }
void setPasswordDialog() async { void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
final pw = await bind.mainGetPermanentPassword(); final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw); final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw); final p1 = TextEditingController(text: pw);
@@ -850,6 +876,7 @@ void setPasswordDialog() async {
// SpecialCharacterValidationRule(), // SpecialCharacterValidationRule(),
MinCharactersValidationRule(8), MinCharactersValidationRule(8),
]; ];
final maxLength = bind.mainMaxEncryptLen();
gFFI.dialogManager.show((setState, close, context) { gFFI.dialogManager.show((setState, close, context) {
submit() { submit() {
@@ -876,6 +903,9 @@ void setPasswordDialog() async {
return; return;
} }
bind.mainSetPermanentPassword(password: pass); bind.mainSetPermanentPassword(password: pass);
if (pass.isNotEmpty) {
notEmptyCallback?.call();
}
close(); close();
} }
@@ -905,7 +935,8 @@ void setPasswordDialog() async {
errMsg0 = ''; errMsg0 = '';
}); });
}, },
), maxLength: maxLength,
).workaroundFreezeLinuxMint(),
), ),
], ],
), ),
@@ -931,7 +962,8 @@ void setPasswordDialog() async {
errMsg1 = ''; errMsg1 = '';
}); });
}, },
), maxLength: maxLength,
).workaroundFreezeLinuxMint(),
), ),
], ],
), ),
File diff suppressed because it is too large Load Diff

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