mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-22 09:38:32 +08:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9f0223ea3 | ||
|
|
9577439a58 | ||
|
|
d5f615f8d6 | ||
|
|
99801258a2 | ||
|
|
018f551d13 | ||
|
|
63cb39f365 | ||
|
|
6bb6535d3e | ||
|
|
25011331ed | ||
|
|
b3f799a7dd | ||
|
|
9a110c703e | ||
|
|
c076d4a17c | ||
|
|
de36ec5b2c | ||
|
|
5ce9037310 | ||
|
|
e7ac67effc | ||
|
|
17dbd27cfd | ||
|
|
dcc0a1425e | ||
|
|
97359d6eae | ||
|
|
cfcfd127b2 | ||
|
|
21a172478c | ||
|
|
3db0f90af9 | ||
|
|
d7ef3df2b3 | ||
|
|
e927d49aa2 | ||
|
|
4b8017292e | ||
|
|
6b8d2a33c0 | ||
|
|
3c5e27b0ca | ||
|
|
fcf6b5466a | ||
|
|
745f33a8c1 | ||
|
|
193426a3c1 | ||
|
|
e4f0a7b1d5 | ||
|
|
48a11cd8ca | ||
|
|
a96eb236c1 | ||
|
|
2acb5d58cf | ||
|
|
c890cbf611 | ||
|
|
4e19777ba0 | ||
|
|
22994ee8f2 | ||
|
|
142813a7e7 | ||
|
|
21c1c06a20 | ||
|
|
8028b23086 | ||
|
|
6c6f6c081e | ||
|
|
e1bd925877 | ||
|
|
8a2d702348 | ||
|
|
13abacf6e0 | ||
|
|
2bc41bfd90 | ||
|
|
65529b10b6 | ||
|
|
f5d0cece43 | ||
|
|
99d25002f7 | ||
|
|
be4cc32e2f | ||
|
|
02c9d3fe2c | ||
|
|
317bc21a1b | ||
|
|
3148ab214a | ||
|
|
e21502e6a5 | ||
|
|
147391eaa1 | ||
|
|
081ac7b4d1 | ||
|
|
f2a02c154c | ||
|
|
6762ef220f | ||
|
|
89b3e68788 | ||
|
|
e80da4af0d | ||
|
|
32c349dd8b | ||
|
|
8bb851704a | ||
|
|
1648895156 | ||
|
|
84d7115e36 | ||
|
|
9f1d4e8ba6 | ||
|
|
8c186edd18 | ||
|
|
94763e9fe0 | ||
|
|
91c53b1db4 | ||
|
|
ebe0681748 | ||
|
|
76987105c1 | ||
|
|
b0f22d8693 | ||
|
|
778f46bbb2 | ||
|
|
afd77181ff | ||
|
|
685d960b1e | ||
|
|
53fdddb1a9 | ||
|
|
54d03b2ecf | ||
|
|
beb14f90b9 | ||
|
|
daa59af5be | ||
|
|
1f137b3542 | ||
|
|
12149bf3e3 | ||
|
|
b85cb81d9e | ||
|
|
1954f905af | ||
|
|
e5dad3467b | ||
|
|
5a9d50db46 | ||
|
|
6c8c3b4a7a | ||
|
|
d6709e069c | ||
|
|
a5675c06e7 | ||
|
|
099a5edef3 | ||
|
|
3dfe8b27e1 | ||
|
|
3bb9417a9d | ||
|
|
b225494137 | ||
|
|
4bde36e186 | ||
|
|
87e06e974e | ||
|
|
7c4c69aa75 | ||
|
|
769e46d3e6 | ||
|
|
dce35ff881 | ||
|
|
16ecbf117c | ||
|
|
dc45952558 | ||
|
|
4707e0ed2e | ||
|
|
1ea009ff61 | ||
|
|
bf4854a444 | ||
|
|
a3024fdd14 | ||
|
|
92a4d88532 | ||
|
|
5cef21fd40 | ||
|
|
b52661083c | ||
|
|
b41c6be341 | ||
|
|
07c21ced70 | ||
|
|
533fc082bd | ||
|
|
9ad9cb8ff2 | ||
|
|
f47a5600b1 | ||
|
|
31a7d817da | ||
|
|
65924cb134 | ||
|
|
437b5f2ca3 | ||
|
|
b75edd9710 | ||
|
|
06af880b12 | ||
|
|
bb79abffe4 | ||
|
|
89933bb2bd |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -13,8 +13,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "docs/**"
|
||||
|
||||
11
.github/workflows/flutter-build.yml
vendored
11
.github/workflows/flutter-build.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
upload-artifact:
|
||||
type: boolean
|
||||
default: true
|
||||
upload-tag:
|
||||
type: string
|
||||
default: "nightly"
|
||||
|
||||
env:
|
||||
CARGO_NDK_VERSION: "3.1.2"
|
||||
@@ -15,11 +18,11 @@ env:
|
||||
# for arm64 linux
|
||||
FLUTTER_ELINUX_VERSION: "3.10.5"
|
||||
FLUTTER_ELINUX_COMMIT_ID: "410b3ca42f2cd0c485edf517a1666652bab442d4"
|
||||
TAG_NAME: "nightly"
|
||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||
# vcpkg version: 2023.04.15
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
|
||||
VERSION: "1.2.0"
|
||||
VERSION: "1.2.1"
|
||||
NDK_VERSION: "r25c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}'
|
||||
@@ -374,6 +377,7 @@ jobs:
|
||||
uses: ./.github/workflows/bridge.yml
|
||||
|
||||
build-rustdesk-ios:
|
||||
if: ${{ inputs.upload-artifact == 'true' }}
|
||||
needs: [generate-bridge-linux]
|
||||
name: build rustdesk ios ipa ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
@@ -779,6 +783,7 @@ jobs:
|
||||
path: target/release/liblibrustdesk.so
|
||||
|
||||
build-rustdesk-lib-linux-arm:
|
||||
if: ${{ inputs.upload-artifact == 'true' }}
|
||||
needs: [generate-bridge-linux, build-vcpkg-deps-linux]
|
||||
name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
@@ -941,6 +946,7 @@ jobs:
|
||||
path: target/release/liblibrustdesk.so
|
||||
|
||||
build-rustdesk-sciter-arm:
|
||||
if: ${{ inputs.upload-artifact == 'true' }}
|
||||
needs: [build-vcpkg-deps-linux]
|
||||
name: build-rustdesk(sciter) ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
@@ -1099,6 +1105,7 @@ jobs:
|
||||
path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb
|
||||
|
||||
build-rustdesk-linux-arm:
|
||||
if: ${{ inputs.upload-artifact == 'true' }}
|
||||
needs: [build-rustdesk-lib-linux-arm]
|
||||
name: build-rustdesk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||
runs-on: ubuntu-20.04 # 20.04 has more performance on arm build
|
||||
|
||||
4
.github/workflows/flutter-ci.yml
vendored
4
.github/workflows/flutter-ci.yml
vendored
@@ -9,8 +9,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "docs/**"
|
||||
@@ -21,4 +19,4 @@ jobs:
|
||||
uses: ./.github/workflows/flutter-build.yml
|
||||
with:
|
||||
upload-artifact: false
|
||||
|
||||
|
||||
|
||||
1
.github/workflows/flutter-nightly.yml
vendored
1
.github/workflows/flutter-nightly.yml
vendored
@@ -12,3 +12,4 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "nightly"
|
||||
|
||||
18
.github/workflows/flutter-tag.yml
vendored
Normal file
18
.github/workflows/flutter-tag.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Flutter Tag Build
|
||||
|
||||
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:
|
||||
run-flutter-tag-build:
|
||||
uses: ./.github/workflows/flutter-build.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
upload-artifact: true
|
||||
upload-tag: "1.2.1"
|
||||
2
.github/workflows/history.yml
vendored
2
.github/workflows/history.yml
vendored
@@ -10,7 +10,7 @@ env:
|
||||
# vcpkg version: 2022.05.10
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
|
||||
VERSION: "1.2.0"
|
||||
VERSION: "1.2.1"
|
||||
|
||||
jobs:
|
||||
build-for-windows-2022-12-05:
|
||||
|
||||
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -1054,7 +1054,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.4.0-2"
|
||||
source = "git+https://github.com/open-trade/confy#9f231b2039cf8a8f8cdf6b829c5ac0016e146077"
|
||||
source = "git+https://github.com/open-trade/confy#7855cd3c32b1a60b44e5076ee8f6b4131da10350"
|
||||
dependencies = [
|
||||
"directories-next",
|
||||
"serde 1.0.163",
|
||||
@@ -5146,7 +5146,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"arboard",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
|
||||
@@ -34,7 +34,6 @@ RustDesk welcomes contribution from everyone. See [CONTRIBUTING.md](docs/CONTRIB
|
||||
Below are the servers you are using for free, they may change over time. If you are not close to one of these, your network may be slow.
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| South Korea (Seoul) | [AWS lightsail](https://aws.amazon.com) | 1 vCPU / 0.5 GB RAM |
|
||||
| Germany | [Hetzner](https://www.hetzner.com) | 2 vCPU / 4 GB RAM |
|
||||
| Germany | [Codext](https://codext.de) | 4 vCPU / 8 GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.0.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.1.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@@ -17,7 +17,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.0
|
||||
version: 1.2.1
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
@@ -50,6 +50,7 @@ AppDir:
|
||||
- libva-x11-2
|
||||
- libvdpau1
|
||||
- libgstreamer-plugins-base1.0-0
|
||||
- gstreamer1.0-pipewire
|
||||
- libwayland-cursor0
|
||||
- libwayland-egl1
|
||||
- libpulse0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
version: 1
|
||||
script:
|
||||
- rm -rf ./AppDir || true
|
||||
- bsdtar -zxvf ../rustdesk-1.2.0.deb
|
||||
- bsdtar -zxvf ../rustdesk-1.2.1.deb
|
||||
- tar -xvf ./data.tar.xz
|
||||
- mkdir ./AppDir
|
||||
- mv ./usr ./AppDir/usr
|
||||
@@ -17,7 +17,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.0
|
||||
version: 1.2.1
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
@@ -52,6 +52,7 @@ AppDir:
|
||||
- libva-x11-2
|
||||
- libvdpau1
|
||||
- libgstreamer-plugins-base1.0-0
|
||||
- gstreamer1.0-pipewire
|
||||
- libwayland-cursor0
|
||||
- libwayland-egl1
|
||||
- libpulse0
|
||||
|
||||
2
build.py
2
build.py
@@ -285,7 +285,7 @@ Version: %s
|
||||
Architecture: %s
|
||||
Maintainer: rustdesk <info@rustdesk.com>
|
||||
Homepage: https://rustdesk.com
|
||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g
|
||||
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
|
||||
Description: A remote control software.
|
||||
|
||||
""" % (version, get_arch())
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
فيما يلي الخوادم التي تستخدمها مجانًا، وقد تتغير طوال الوقت. إذا لم تكن قريبًا من أحد هؤلاء، فقد تكون شبكتك بطيئة.
|
||||
| الموقع | المورد | المواصفات |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
|
||||
|
||||
## التبعيات
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ Projekt RustDesk vítá přiložení ruky k dílu od každého. Jak začít se d
|
||||
Níže jsou uvedeny servery zdarma k vašemu použití (údaje se mohou v čase měnit). Pokud se nenacházíte v oblastech světa poblíž nich, spojení může být pomalé.
|
||||
| umístění | dodavatel | parametry |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
|
||||
|
||||
## Softwarové součásti, na kterých závisí
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ Nedenfor er de servere, du bruger gratis, det kan ændre sig med tiden. Hvis du
|
||||
|
||||
| Beliggenhed | Udbyder | Specifikation |
|
||||
| ---------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
|
||||
## Afhængigheder
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ RustDesk heißt jegliche Mitarbeit willkommen. Schauen Sie sich [CONTRIBUTING-DE
|
||||
Nachfolgend sind die Server gelistet, die Sie kostenlos nutzen können. Es kann sein, dass sich diese Liste immer mal wieder ändert. Falls Sie nicht in der Nähe einer dieser Server sind, kann es sein, dass Ihre Verbindung langsam sein wird.
|
||||
| Standort | Anbieter | Spezifikation |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Südkorea (Seoul) | [AWS lightsail](https://aws.amazon.com/de/) | 1 vCPU / 0,5 GB RAM |
|
||||
| Deutschland | [Hetzner](https://www.hetzner.com/de/) | 2 vCPU / 4 GB RAM |
|
||||
| Deutschland | [Codext](https://codext.de/) | 4 vCPU / 8 GB RAM |
|
||||
| Ukraine (Kiew) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
|
||||
|
||||
@@ -24,7 +24,6 @@ RustDesk bonvenigas kontribuon de ĉiuj. Vidu [`docs/CONTRIBUTING.md`](CONTRIBUT
|
||||
Malsupre estas la serviloj, kiuj vi uzas senpage, ĝi povas ŝanĝi laŭlonge de la tempo. Se vi ne estas proksima de unu de tiuj, via reto povas esti malrapida.
|
||||
| Situo | Vendanto | Detaloj |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -31,7 +31,6 @@ A continuación se muestran los servidores gratuitos, pueden cambiar a medida qu
|
||||
|
||||
| Ubicación | Compañía | Especificación |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
شما ميتوانید از سرورهای زیر به رایگان استفاده کنید. این لیست ممکن است به مرور زمان تغییر میکند. اگر به این سرورها نزدیک نیستید، ممکن است اتصال شما کند باشد.
|
||||
| موقعیت | سرویس دهنده | مشخصات |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| کرهی جنوبی، سئول | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| آلمان | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| آلمان | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ RustDesk toivottaa avustukset tervetulleiksi kaikilta. Katso lisätietoja [`docs
|
||||
Alla on palvelimia, joita voit käyttää ilmaiseksi, ne saattavat muuttua ajan mittaan. Jos et ole lähellä yhtä näistä, verkkosi voi olla hidas.
|
||||
| Sijainti | Myyjä | Määrittely |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -25,7 +25,6 @@ Ci-dessous se trouvent les serveurs que vous utilisez gratuitement, cela peut ch
|
||||
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
Παρακάτω είναι οι διακομιστές που χρησιμοποιούνται δωρεάν, ενδέχεται να αλλάξουν με την πάροδο του χρόνου. Εάν δεν είστε κοντά σε ένα από αυτούς, το δίκτυό σας ίσως να είναι αργό.
|
||||
| Περιοχή | Πάροχος | Προδιαγραφές |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Σεούλ | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Γερμανία | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Γερμανία | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ουκρανία (Κίεβο) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -32,7 +32,6 @@ A RustDesk szívesen fogad minden contributiont, támogatást mindenkitől. Lás
|
||||
Ezalatt az üzenet alatt találhatóak azok a publikus szerverek, amelyeket ingyen használhatsz. Ezek a szerverek változhatnak a jövőben, illetve a hálózatuk lehet hogy lassú lehet.
|
||||
| Hely | Host | Specifikáció |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -24,7 +24,6 @@ RustDesk menyambut baik kontribusi dari semua orang. Lihat [`docs/CONTRIBUTING.m
|
||||
Di bawah ini adalah server yang bisa Anda gunakan secara gratis, dapat berubah seiring waktu. Jika Anda tidak dekat dengan salah satu dari ini, jaringan Anda mungkin lambat.
|
||||
| Lokasi | Vendor | Spesifikasi |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -24,7 +24,6 @@ RustDesk accoglie il contributo di tutti. Per ulteriori informazioni su come ini
|
||||
Qui sotto trovate i server che possono essere usati gratuitamente, la lista potrebbe cambiare nel tempo. Se non si è vicini a uno di questi server, la vostra connessione potrebbe essere lenta.
|
||||
| Posizione | Vendor | Specifiche |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -29,7 +29,6 @@ RustDeskは誰からの貢献も歓迎します。 貢献するには [`docs/CON
|
||||
下記のサーバーは、無料で使用できますが、後々変更されることがあります。これらのサーバーから遠い場合、接続が遅い可能性があります。
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`docs/C
|
||||
표에 있는 서버는 무료로 사용할 수 있지만 추후 변경될 수도 있습니다. 이 서버에서 멀다면, 네트워크가 느려질 가능성도 있습니다.
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
നിങ്ങൾ സൗജന്യമായി ഉപയോഗിക്കുന്ന സെർവറുകൾ ചുവടെയുണ്ട്, അത് സമയത്തിനനുസരിച്ച് മാറിയേക്കാം. നിങ്ങൾ ഇവയിലൊന്നിനോട് അടുത്തല്ലെങ്കിൽ, നിങ്ങളുടെ നെറ്റ്വർക്ക് സ്ലോ ആയേക്കാം.
|
||||
| സ്ഥാനം | കച്ചവടക്കാരൻ | വിവരണം |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ RustDesk verwelkomt bijdragen van iedereen. Zie [`docs/CONTRIBUTING.md`](CONTRIB
|
||||
Hieronder staan de servers die u gratis gebruikt, ze kunnen in de loop van de tijd veranderen. Als u niet in de buurt van een van deze servers bevindt, kan uw vervinding langzamer zijn.
|
||||
| Locatie | Aanbieder | Specificaties |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Duitsland | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Duitsland | Codext | 4 vCPU / 8GB RAM |
|
||||
| Oekraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -34,7 +34,6 @@ RustDesk zaprasza do współpracy każdego. Zobacz [`docs/CONTRIBUTING-PL.md`](C
|
||||
Poniżej znajdują się serwery, z których można korzystać za darmo, może się to zmienić z upływem czasu. Jeśli nie znajdujesz się w pobliżu jednego z nich, Twoja prędkość połączenia może być niska.
|
||||
| Lokalizacja | Dostawca | Specyfikacja |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Korea Płd. (Seul) | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Niemcy | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Niemcy | Codext | 4 vCPU / 8GB RAM |
|
||||
| Ukraina (Kijów) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -25,7 +25,6 @@ Abaixo estão os servidores que você está utilizando de graça, ele pode mudar
|
||||
|
||||
| Localização | Fornecedor | Especificações |
|
||||
| ----------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -33,11 +33,9 @@ RustDesk приветствует вклад каждого. Ознакомьт
|
||||
Ниже приведены бесплатные публичные сервера, используемые по умолчанию. Имейте ввиду, они могут меняться со временем. Также стоит отметить, что скорость работы сети зависит от вашего местоположения и расстояния до серверов. Подключение происходит к ближайшему доступному.
|
||||
| Расположение | Поставщик | Технические характеристики |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Сеул | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Сингапур | Vultr | 1 vCPU / 1GB RAM |
|
||||
| Даллас | Vultr | 1 vCPU / 1GB RAM |
|
||||
| Германия | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Германия | Codext | 4 vCPU / 8GB RAM |
|
||||
| Россия (Москва) | [nt-vps](https://nt-vps.ru) | 8 vCPU / 8GB RAM |
|
||||
|
||||
## Зависимости
|
||||
|
||||
|
||||
@@ -34,9 +34,6 @@ RustDesk вітає внесок кожного. Дивіться [`docs/CONTRIB
|
||||
Нижче наведені сервери, для безкоштовного використання, вони можуть змінюватися з часом. Якщо ви не перебуваєте поруч з одним із них, ваша мережа може працювати повільно.
|
||||
| Місцезнаходження | Постачальник | Технічні характеристики |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Південна Корея (Сеул) | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Сінгапур | Vultr | 1 vCPU / 1GB RAM |
|
||||
| США (Даллас) | Vultr | 1 vCPU / 1GB RAM
|
||||
| Німеччина | Hetzner | 2 VCPU / 4GB RAM |
|
||||
| Німеччина | Codext | 4 vCPU / 8GB RAM |
|
||||
| Україна (Київ) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |
|
||||
|
||||
@@ -33,7 +33,6 @@ Dưới đây là những máy chủ mà bạn có thể sử dụng mà không
|
||||
|
||||
| Địa điểm | Nhà cung cấp | Cấu hình |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https:
|
||||
|
||||
| Location | Vendor | Specification |
|
||||
| --------- | ------------- | ------------------ |
|
||||
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
|
||||
| Germany | Hetzner | 2 vCPU / 4GB RAM |
|
||||
| Germany | Codext | 4 vCPU / 8GB RAM |
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"name": "rustdesk",
|
||||
"buildsystem": "simple",
|
||||
"build-commands": [
|
||||
"bsdtar -zxvf rustdesk-1.2.0.deb",
|
||||
"bsdtar -zxvf rustdesk-1.2.1.deb",
|
||||
"tar -xvf ./data.tar.xz",
|
||||
"cp -r ./usr/* /app/",
|
||||
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
|
||||
@@ -25,7 +25,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"path": "../rustdesk-1.2.0.deb"
|
||||
"path": "../rustdesk-1.2.1.deb"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
|
||||
@@ -176,6 +176,10 @@ class MyTheme {
|
||||
static const Color dark = Colors.black87;
|
||||
static const Color button = Color(0xFF2C8CFF);
|
||||
static const Color hoverBorder = Color(0xFF999999);
|
||||
static const Color bordDark = Colors.white24;
|
||||
static const Color bordLight = Colors.black26;
|
||||
static const Color dividerDark = Colors.white38;
|
||||
static const Color dividerLight = Colors.black38;
|
||||
|
||||
// ListTile
|
||||
static const ListTileThemeData listTileTheme = ListTileThemeData(
|
||||
@@ -545,7 +549,7 @@ closeConnection({String? id}) {
|
||||
}
|
||||
}
|
||||
|
||||
void window_on_top(int? id) {
|
||||
void window_on_top(int? id) async {
|
||||
if (!isDesktop) {
|
||||
return;
|
||||
}
|
||||
@@ -1538,7 +1542,7 @@ Future<bool> initUniLinks() async {
|
||||
if (initialLink == null) {
|
||||
return false;
|
||||
}
|
||||
return parseRustdeskUri(initialLink);
|
||||
return handleUriLink(uriString: initialLink);
|
||||
} catch (err) {
|
||||
debugPrintStack(label: "$err");
|
||||
return false;
|
||||
@@ -1559,7 +1563,7 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
|
||||
debugPrint("A uri was received: $uri.");
|
||||
if (uri != null) {
|
||||
if (handleByFlutter) {
|
||||
callUniLinksUriHandler(uri);
|
||||
handleUriLink(uri: uri);
|
||||
} else {
|
||||
bind.sendUrlScheme(url: uri.toString());
|
||||
}
|
||||
@@ -1572,90 +1576,147 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
|
||||
return sub;
|
||||
}
|
||||
|
||||
/// Handle command line arguments
|
||||
///
|
||||
/// * Returns true if we successfully handle the startup arguments.
|
||||
bool checkArguments() {
|
||||
if (kBootArgs.isNotEmpty) {
|
||||
final ret = parseRustdeskUri(kBootArgs.first);
|
||||
if (ret) {
|
||||
return true;
|
||||
enum UriLinkType {
|
||||
remoteDesktop,
|
||||
fileTransfer,
|
||||
portForward,
|
||||
rdp,
|
||||
}
|
||||
|
||||
// uri link handler
|
||||
bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
|
||||
List<String>? args;
|
||||
if (cmdArgs != null) {
|
||||
args = cmdArgs;
|
||||
// rustdesk <uri link>
|
||||
if (args.isNotEmpty && args[0].startsWith(kUniLinksPrefix)) {
|
||||
final uri = Uri.tryParse(args[0]);
|
||||
if (uri != null) {
|
||||
args = urlLinkToCmdArgs(uri);
|
||||
}
|
||||
}
|
||||
} else if (uri != null) {
|
||||
args = urlLinkToCmdArgs(uri);
|
||||
} else if (uriString != null) {
|
||||
final uri = Uri.tryParse(uriString);
|
||||
if (uri != null) {
|
||||
args = urlLinkToCmdArgs(uri);
|
||||
}
|
||||
}
|
||||
// bootArgs:[--connect, 362587269, --switch_uuid, e3d531cc-5dce-41e0-bd06-5d4a2b1eec05]
|
||||
// check connect args
|
||||
var connectIndex = kBootArgs.indexOf("--connect");
|
||||
if (connectIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
String? id =
|
||||
kBootArgs.length <= connectIndex + 1 ? null : kBootArgs[connectIndex + 1];
|
||||
String? password =
|
||||
kBootArgs.length <= connectIndex + 2 ? null : kBootArgs[connectIndex + 2];
|
||||
if (password != null && password.startsWith("--")) {
|
||||
password = null;
|
||||
}
|
||||
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid");
|
||||
String? switchUuid = kBootArgs.length <= switchUuidIndex + 1
|
||||
? null
|
||||
: kBootArgs[switchUuidIndex + 1];
|
||||
if (id != null) {
|
||||
if (id.startsWith(kUniLinksPrefix)) {
|
||||
return parseRustdeskUri(id);
|
||||
} else {
|
||||
// remove "--connect xxx" in the `bootArgs` array
|
||||
kBootArgs.removeAt(connectIndex);
|
||||
kBootArgs.removeAt(connectIndex);
|
||||
// fallback to peer id
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newRemoteDesktop(id,
|
||||
password: password, switch_uuid: switchUuid);
|
||||
});
|
||||
return true;
|
||||
if (args == null) return false;
|
||||
|
||||
UriLinkType? type;
|
||||
String? id;
|
||||
String? password;
|
||||
String? switchUuid;
|
||||
bool? forceRelay;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case '--connect':
|
||||
case '--play':
|
||||
type = UriLinkType.remoteDesktop;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--file-transfer':
|
||||
type = UriLinkType.fileTransfer;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--port-forward':
|
||||
type = UriLinkType.portForward;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--rdp':
|
||||
type = UriLinkType.rdp;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--password':
|
||||
password = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--switch_uuid':
|
||||
switchUuid = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--relay':
|
||||
forceRelay = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type != null && id != null) {
|
||||
switch (type) {
|
||||
case UriLinkType.remoteDesktop:
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newRemoteDesktop(id!,
|
||||
password: password,
|
||||
switch_uuid: switchUuid,
|
||||
forceRelay: forceRelay);
|
||||
});
|
||||
break;
|
||||
case UriLinkType.fileTransfer:
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newFileTransfer(id!,
|
||||
password: password, forceRelay: forceRelay);
|
||||
});
|
||||
break;
|
||||
case UriLinkType.portForward:
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newPortForward(id!, false,
|
||||
password: password, forceRelay: forceRelay);
|
||||
});
|
||||
break;
|
||||
case UriLinkType.rdp:
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newPortForward(id!, true,
|
||||
password: password, forceRelay: forceRelay);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Parse `rustdesk://` unilinks
|
||||
///
|
||||
/// Returns true if we successfully handle the uri provided.
|
||||
/// [Functions]
|
||||
/// 1. New Connection: rustdesk://connection/new/your_peer_id
|
||||
bool parseRustdeskUri(String uriPath) {
|
||||
final uri = Uri.tryParse(uriPath);
|
||||
if (uri == null) {
|
||||
debugPrint("uri is not valid: $uriPath");
|
||||
return false;
|
||||
}
|
||||
return callUniLinksUriHandler(uri);
|
||||
}
|
||||
|
||||
/// uri handler
|
||||
///
|
||||
/// Returns true if we successfully handle the uri provided.
|
||||
bool callUniLinksUriHandler(Uri uri) {
|
||||
debugPrint("uni links called: $uri");
|
||||
// new connection
|
||||
String peerId;
|
||||
List<String>? urlLinkToCmdArgs(Uri uri) {
|
||||
String? command;
|
||||
String? id;
|
||||
if (uri.authority == "connection" && uri.path.startsWith("/new/")) {
|
||||
peerId = uri.path.substring("/new/".length);
|
||||
} else if (uri.authority == "connect") {
|
||||
peerId = uri.path.substring(1);
|
||||
// For compatibility
|
||||
command = '--connect';
|
||||
id = uri.path.substring("/new/".length);
|
||||
} else if (['connect', "play", 'file-transfer', 'port-forward', 'rdp']
|
||||
.contains(uri.authority)) {
|
||||
command = '--${uri.authority}';
|
||||
if (uri.path.length > 1) {
|
||||
id = uri.path.substring(1);
|
||||
}
|
||||
} else if (uri.authority.length > 2 && uri.path.length <= 1) {
|
||||
// "/" or ""
|
||||
peerId = uri.authority;
|
||||
} else {
|
||||
return false;
|
||||
// rustdesk://<connect-id>
|
||||
command = '--connect';
|
||||
id = uri.authority;
|
||||
}
|
||||
var param = uri.queryParameters;
|
||||
String? switch_uuid = param["switch_uuid"];
|
||||
String? password = param["password"];
|
||||
Future.delayed(Duration.zero, () {
|
||||
rustDeskWinManager.newRemoteDesktop(peerId,
|
||||
password: password, switch_uuid: switch_uuid);
|
||||
});
|
||||
return true;
|
||||
|
||||
List<String> args = List.empty(growable: true);
|
||||
if (command != null && id != null) {
|
||||
args.add(command);
|
||||
args.add(id);
|
||||
var param = uri.queryParameters;
|
||||
String? password = param["password"];
|
||||
if (password != null) args.addAll(['--password', password]);
|
||||
String? switch_uuid = param["switch_uuid"];
|
||||
if (switch_uuid != null) args.addAll(['--switch_uuid', switch_uuid]);
|
||||
if (param["relay"] != null) args.add("--relay");
|
||||
return args;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
connectMainDesktop(String id,
|
||||
@@ -2101,3 +2162,64 @@ Future<void> start_service(bool is_start) async {
|
||||
bind.mainSetOption(key: "stop-service", value: is_start ? "" : "Y");
|
||||
}
|
||||
}
|
||||
|
||||
typedef Future<bool> WhetherUseRemoteBlock();
|
||||
Widget buildRemoteBlock({required Widget child, WhetherUseRemoteBlock? use}) {
|
||||
var block = false.obs;
|
||||
return Obx(() => MouseRegion(
|
||||
onEnter: (_) async {
|
||||
if (use != null && !await use()) {
|
||||
block.value = false;
|
||||
return;
|
||||
}
|
||||
var time0 = DateTime.now().millisecondsSinceEpoch;
|
||||
await bind.mainCheckMouseTime();
|
||||
Timer(const Duration(milliseconds: 120), () async {
|
||||
var d = time0 - await bind.mainGetMouseTime();
|
||||
if (d < 120) {
|
||||
block.value = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
onExit: (event) => block.value = false,
|
||||
child: Stack(children: [
|
||||
child,
|
||||
Offstage(
|
||||
offstage: !block.value,
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
)),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
Widget unreadMessageCountBuilder(RxInt? count,
|
||||
{double? size, double? fontSize}) {
|
||||
return Obx(() => Offstage(
|
||||
offstage: !((count?.value ?? 0) > 0),
|
||||
child: Container(
|
||||
width: size ?? 16,
|
||||
height: size ?? 16,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text("${count?.value ?? 0}",
|
||||
maxLines: 1,
|
||||
style: TextStyle(color: Colors.white, fontSize: fontSize ?? 10)),
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
Widget unreadTopRightBuilder(RxInt? count, {Widget? icon}) {
|
||||
return Stack(
|
||||
children: [
|
||||
icon ?? Icon(Icons.chat),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: unreadMessageCountBuilder(count, size: 12, fontSize: 8))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,6 +285,29 @@ class PeerStringOption {
|
||||
Get.find<RxString>(tag: tag(id, opt));
|
||||
}
|
||||
|
||||
class UnreadChatCountState {
|
||||
static String tag(id) => 'unread_chat_count_$id';
|
||||
|
||||
static void init(String id) {
|
||||
final key = tag(id);
|
||||
if (!Get.isRegistered(tag: key)) {
|
||||
final RxInt state = RxInt(0);
|
||||
Get.put(state, tag: key);
|
||||
} else {
|
||||
Get.find<RxInt>(tag: key).value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void delete(String id) {
|
||||
final key = tag(id);
|
||||
if (Get.isRegistered(tag: key)) {
|
||||
Get.delete(tag: key);
|
||||
}
|
||||
}
|
||||
|
||||
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
|
||||
}
|
||||
|
||||
initSharedStates(String id) {
|
||||
PrivacyModeState.init(id);
|
||||
BlockInputState.init(id);
|
||||
@@ -294,6 +317,7 @@ initSharedStates(String id) {
|
||||
RemoteCursorMovedState.init(id);
|
||||
FingerprintState.init(id);
|
||||
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
||||
UnreadChatCountState.init(id);
|
||||
}
|
||||
|
||||
removeSharedStates(String id) {
|
||||
@@ -305,4 +329,5 @@ removeSharedStates(String id) {
|
||||
RemoteCursorMovedState.delete(id);
|
||||
FingerprintState.delete(id);
|
||||
PeerBoolOption.delete(id, 'zoom-cursor');
|
||||
UnreadChatCountState.delete(id);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,16 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../../mobile/pages/home_page.dart';
|
||||
|
||||
enum ChatPageType {
|
||||
mobileMain,
|
||||
desktopCM,
|
||||
}
|
||||
|
||||
class ChatPage extends StatelessWidget implements PageShape {
|
||||
late final ChatModel chatModel;
|
||||
final ChatPageType? type;
|
||||
|
||||
ChatPage({ChatModel? chatModel}) {
|
||||
ChatPage({ChatModel? chatModel, this.type}) {
|
||||
this.chatModel = chatModel ?? gFFI.chatModel;
|
||||
}
|
||||
|
||||
@@ -18,27 +24,53 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
final title = translate("Chat");
|
||||
|
||||
@override
|
||||
final icon = Icon(Icons.chat);
|
||||
final icon = unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum);
|
||||
|
||||
@override
|
||||
final appBarActions = [
|
||||
PopupMenuButton<int>(
|
||||
PopupMenuButton<MessageKey>(
|
||||
tooltip: "",
|
||||
icon: Icon(Icons.group),
|
||||
icon: unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum,
|
||||
icon: Icon(Icons.group)),
|
||||
itemBuilder: (context) {
|
||||
// only mobile need [appBarActions], just bind gFFI.chatModel
|
||||
final chatModel = gFFI.chatModel;
|
||||
return chatModel.messages.entries.map((entry) {
|
||||
final id = entry.key;
|
||||
final key = entry.key;
|
||||
final user = entry.value.chatUser;
|
||||
return PopupMenuItem<int>(
|
||||
child: Text("${user.firstName} ${user.id}"),
|
||||
value: id,
|
||||
final client = gFFI.serverModel.clients
|
||||
.firstWhereOrNull((e) => e.id == key.connId);
|
||||
final connected =
|
||||
gFFI.serverModel.clients.any((e) => e.id == key.connId);
|
||||
return PopupMenuItem<MessageKey>(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
key.isOut
|
||||
? Icons.call_made_rounded
|
||||
: Icons.call_received_rounded,
|
||||
color: MyTheme.accent)
|
||||
.marginOnly(right: 6),
|
||||
Text("${user.firstName} ${user.id}"),
|
||||
if (connected)
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Color.fromARGB(255, 46, 205, 139)),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
if (client != null)
|
||||
unreadMessageCountBuilder(client.unreadChatMessageCount)
|
||||
.marginOnly(left: 4)
|
||||
],
|
||||
),
|
||||
value: key,
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
onSelected: (id) {
|
||||
gFFI.chatModel.changeCurrentID(id);
|
||||
onSelected: (key) {
|
||||
gFFI.chatModel.changeCurrentKey(key);
|
||||
})
|
||||
];
|
||||
|
||||
@@ -50,16 +82,27 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Consumer<ChatModel>(
|
||||
builder: (context, chatModel, child) {
|
||||
final currentUser = chatModel.currentUser;
|
||||
final readOnly = type == ChatPageType.mobileMain &&
|
||||
(chatModel.currentKey.connId == ChatModel.clientModeID ||
|
||||
gFFI.serverModel.clients.every((e) =>
|
||||
e.id != chatModel.currentKey.connId ||
|
||||
chatModel.currentUser == null)) ||
|
||||
type == ChatPageType.desktopCM &&
|
||||
gFFI.serverModel.clients
|
||||
.firstWhereOrNull(
|
||||
(e) => e.id == chatModel.currentKey.connId)
|
||||
?.disconnected ==
|
||||
true;
|
||||
return Stack(
|
||||
children: [
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
final chat = DashChat(
|
||||
onSend: chatModel.send,
|
||||
currentUser: chatModel.me,
|
||||
messages:
|
||||
chatModel.messages[chatModel.currentID]?.chatMessages ??
|
||||
[],
|
||||
messages: chatModel
|
||||
.messages[chatModel.currentKey]?.chatMessages ??
|
||||
[],
|
||||
readOnly: readOnly,
|
||||
inputOptions: InputOptions(
|
||||
focusNode: chatModel.inputNode,
|
||||
textController: chatModel.textController,
|
||||
@@ -127,22 +170,6 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
);
|
||||
return SelectionArea(child: chat);
|
||||
}),
|
||||
desktopType == DesktopType.cm ||
|
||||
chatModel.currentID == ChatModel.clientModeID
|
||||
? SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.account_circle, color: MyTheme.accent80),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
"${currentUser.firstName} ${currentUser.id}",
|
||||
style: TextStyle(color: MyTheme.accent),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddingOnly(bottom: 8);
|
||||
},
|
||||
|
||||
@@ -943,16 +943,20 @@ showSetOSPassword(
|
||||
SessionID sessionId,
|
||||
bool login,
|
||||
OverlayDialogManager dialogManager,
|
||||
String? osPassword,
|
||||
Function()? closeCallback,
|
||||
) async {
|
||||
final controller = TextEditingController();
|
||||
var password =
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
||||
'';
|
||||
osPassword ??= await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ?? '';
|
||||
var autoLogin =
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') !=
|
||||
'';
|
||||
controller.text = password;
|
||||
controller.text = osPassword;
|
||||
dialogManager.show((setState, close, context) {
|
||||
closeWithCallback([dynamic]) {
|
||||
close();
|
||||
if (closeCallback != null) closeCallback();
|
||||
}
|
||||
submit() {
|
||||
var text = controller.text.trim();
|
||||
bind.sessionPeerOption(
|
||||
@@ -964,7 +968,7 @@ showSetOSPassword(
|
||||
if (text != '' && login) {
|
||||
bind.sessionInputOsPassword(sessionId: sessionId, value: text);
|
||||
}
|
||||
close();
|
||||
closeWithCallback();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
@@ -998,7 +1002,7 @@ showSetOSPassword(
|
||||
dialogButton(
|
||||
"Cancel",
|
||||
icon: Icon(Icons.close_rounded),
|
||||
onPressed: close,
|
||||
onPressed: closeWithCallback,
|
||||
isOutline: true,
|
||||
),
|
||||
dialogButton(
|
||||
@@ -1008,7 +1012,7 @@ showSetOSPassword(
|
||||
),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: close,
|
||||
onCancel: closeWithCallback,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,11 +434,16 @@ Future<bool?> loginDialog() async {
|
||||
}
|
||||
break;
|
||||
case HttpType.kAuthResTypeEmailCheck:
|
||||
setState(() => isInProgress = false);
|
||||
final res = await verificationCodeDialog(resp.user);
|
||||
if (res == true) {
|
||||
if (isMobile) {
|
||||
close(true);
|
||||
return;
|
||||
verificationCodeDialog(resp.user);
|
||||
} else {
|
||||
setState(() => isInProgress = false);
|
||||
final res = await verificationCodeDialog(resp.user);
|
||||
if (res == true) {
|
||||
close(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -512,7 +517,11 @@ Future<bool?> loginDialog() async {
|
||||
size: 25,
|
||||
// No need to handle the branch of null.
|
||||
// Because we can ensure the color is not null when debug.
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.55),
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color
|
||||
?.withOpacity(0.55),
|
||||
),
|
||||
onTap: onDialogCancel,
|
||||
hoverColor: Colors.red,
|
||||
|
||||
@@ -32,7 +32,7 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
width: width,
|
||||
height: height,
|
||||
builder: (context, onPanUpdate) {
|
||||
return isIOS
|
||||
final child = isIOS
|
||||
? ChatPage(chatModel: chatModel)
|
||||
: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
@@ -44,6 +44,10 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
),
|
||||
body: ChatPage(chatModel: chatModel),
|
||||
);
|
||||
return Container(
|
||||
decoration:
|
||||
BoxDecoration(border: Border.all(color: MyTheme.border)),
|
||||
child: child);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
bool isEditOsPassword = false;
|
||||
|
||||
class TTextMenu {
|
||||
final Widget child;
|
||||
final VoidCallback onPressed;
|
||||
@@ -44,6 +46,28 @@ class TToggleMenu {
|
||||
{required this.child, required this.value, required this.onChanged});
|
||||
}
|
||||
|
||||
handleOsPasswordEditIcon(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) {
|
||||
isEditOsPassword = true;
|
||||
showSetOSPassword(sessionId, false, dialogManager, null, () => isEditOsPassword = false);
|
||||
}
|
||||
|
||||
handleOsPasswordAction(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
||||
if (isEditOsPassword) {
|
||||
isEditOsPassword = false;
|
||||
return;
|
||||
}
|
||||
final password =
|
||||
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
|
||||
'';
|
||||
if (password.isEmpty) {
|
||||
showSetOSPassword(sessionId, true, dialogManager, password, () => isEditOsPassword = false);
|
||||
} else {
|
||||
bind.sessionInputOsPassword(sessionId: sessionId, value: password);
|
||||
}
|
||||
}
|
||||
|
||||
List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
final pi = ffiModel.pi;
|
||||
@@ -63,17 +87,26 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
|
||||
// osAccount / osPassword
|
||||
v.add(
|
||||
TTextMenu(
|
||||
child: Row(children: [
|
||||
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
||||
Offstage(
|
||||
offstage: isDesktop,
|
||||
child:
|
||||
Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12))
|
||||
]),
|
||||
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)),
|
||||
onPressed: () => pi.is_headless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: showSetOSPassword(sessionId, false, ffi.dialogManager)),
|
||||
child: Row(children: [
|
||||
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
|
||||
Offstage(
|
||||
offstage: isDesktop,
|
||||
child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
|
||||
)
|
||||
]),
|
||||
trailingIcon: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: InkWell(
|
||||
onTap: () => pi.is_headless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordEditIcon(sessionId, ffi.dialogManager),
|
||||
child: Icon(Icons.edit),
|
||||
),
|
||||
),
|
||||
onPressed: () => pi.is_headless
|
||||
? showSetOSAccount(sessionId, ffi.dialogManager)
|
||||
: handleOsPasswordAction(sessionId, ffi.dialogManager),
|
||||
),
|
||||
);
|
||||
// paste
|
||||
if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/models/user_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -1559,7 +1559,7 @@ class _AboutState extends State<_About> {
|
||||
.marginSymmetric(vertical: 4.0)),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
launchUrlString('https://rustdesk.com/privacy');
|
||||
launchUrlString('https://rustdesk.com/privacy.html');
|
||||
},
|
||||
child: Text(
|
||||
translate('Privacy Statement'),
|
||||
|
||||
@@ -52,10 +52,12 @@ class FileManagerPage extends StatefulWidget {
|
||||
const FileManagerPage(
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.password,
|
||||
required this.tabController,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final String? password;
|
||||
final bool? forceRelay;
|
||||
final DesktopTabController tabController;
|
||||
|
||||
@@ -79,7 +81,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ffi = FFI();
|
||||
_ffi.start(widget.id, isFileTransfer: true, forceRelay: widget.forceRelay);
|
||||
_ffi.start(widget.id,
|
||||
isFileTransfer: true,
|
||||
password: widget.password,
|
||||
forceRelay: widget.forceRelay);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_ffi.dialogManager
|
||||
.showLoading(translate('Connecting...'), onCancel: closeConnection);
|
||||
|
||||
@@ -44,6 +44,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
page: FileManagerPage(
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
password: params['password'],
|
||||
tabController: tabController,
|
||||
forceRelay: params['forceRelay'],
|
||||
)));
|
||||
@@ -72,6 +73,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
page: FileManagerPage(
|
||||
key: ValueKey(id),
|
||||
id: id,
|
||||
password: args['password'],
|
||||
tabController: tabController,
|
||||
forceRelay: args['forceRelay'],
|
||||
)));
|
||||
|
||||
@@ -183,9 +183,9 @@ class _InstallPageBodyState extends State<_InstallPageBody>
|
||||
InkWell(
|
||||
hoverColor: Colors.transparent,
|
||||
onTap: () =>
|
||||
launchUrlString('https://rustdesk.com/privacy'),
|
||||
launchUrlString('https://rustdesk.com/privacy.html'),
|
||||
child: Tooltip(
|
||||
message: 'https://rustdesk.com/privacy',
|
||||
message: 'https://rustdesk.com/privacy.html',
|
||||
child: Row(children: [
|
||||
Icon(Icons.launch_outlined, size: 16)
|
||||
.marginOnly(right: 5),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -8,7 +7,6 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
|
||||
const double _kColumn1Width = 30;
|
||||
const double _kColumn4Width = 100;
|
||||
@@ -30,11 +28,13 @@ class PortForwardPage extends StatefulWidget {
|
||||
const PortForwardPage(
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.password,
|
||||
required this.tabController,
|
||||
required this.isRDP,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
final String id;
|
||||
final String? password;
|
||||
final DesktopTabController tabController;
|
||||
final bool isRDP;
|
||||
final bool? forceRelay;
|
||||
@@ -57,12 +57,10 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
_ffi = FFI();
|
||||
_ffi.start(widget.id,
|
||||
isPortForward: true,
|
||||
password: widget.password,
|
||||
forceRelay: widget.forceRelay,
|
||||
isRdp: widget.isRDP);
|
||||
Get.put(_ffi, tag: 'pf_${widget.id}');
|
||||
if (!Platform.isLinux) {
|
||||
Wakelock.enable();
|
||||
}
|
||||
debugPrint("Port forward page init success with id ${widget.id}");
|
||||
widget.tabController.onSelected?.call(widget.id);
|
||||
}
|
||||
@@ -71,9 +69,6 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
void dispose() {
|
||||
_ffi.close();
|
||||
_ffi.dialogManager.dismissAll();
|
||||
if (!Platform.isLinux) {
|
||||
Wakelock.disable();
|
||||
}
|
||||
Get.delete<FFI>(tag: 'pf_${widget.id}');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
page: PortForwardPage(
|
||||
key: ValueKey(params['id']),
|
||||
id: params['id'],
|
||||
password: params['password'],
|
||||
tabController: tabController,
|
||||
isRDP: isRDP,
|
||||
forceRelay: params['forceRelay'],
|
||||
@@ -77,6 +78,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
page: PortForwardPage(
|
||||
key: ValueKey(args['id']),
|
||||
id: id,
|
||||
password: args['password'],
|
||||
isRDP: isRDP,
|
||||
tabController: tabController,
|
||||
forceRelay: args['forceRelay'],
|
||||
|
||||
@@ -56,15 +56,14 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
if (peerId != null) {
|
||||
ConnectionTypeState.init(peerId);
|
||||
tabController.onSelected = (id) {
|
||||
final remotePage = tabController.state.value.tabs
|
||||
.firstWhereOrNull((tab) => tab.key == id)
|
||||
?.page;
|
||||
final remotePage = tabController.widget(id);
|
||||
if (remotePage is RemotePage) {
|
||||
final ffi = remotePage.ffi;
|
||||
bind.setCurSessionId(sessionId: ffi.sessionId);
|
||||
}
|
||||
WindowController.fromWindowId(windowId())
|
||||
.setTitle(getWindowNameWithId(id));
|
||||
UnreadChatCountState.find(id).value = 0;
|
||||
};
|
||||
tabController.add(TabInfo(
|
||||
key: peerId,
|
||||
@@ -206,6 +205,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
).paddingOnly(right: 5),
|
||||
),
|
||||
label,
|
||||
unreadMessageCountBuilder(UnreadChatCountState.find(key))
|
||||
.marginOnly(left: 4),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -100,10 +100,16 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
gFFI.serverModel.tabController.onSelected = (client_id_str) {
|
||||
final client_id = int.tryParse(client_id_str);
|
||||
if (client_id != null) {
|
||||
gFFI.chatModel.changeCurrentID(client_id);
|
||||
final client =
|
||||
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == client_id);
|
||||
if (client != null) {
|
||||
gFFI.chatModel.changeCurrentKey(MessageKey(client.peerId, client.id));
|
||||
if (client.unreadChatMessageCount.value > 0) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
client.unreadChatMessageCount.value = 0;
|
||||
gFFI.chatModel.showChatPage(MessageKey(client.peerId, client.id));
|
||||
});
|
||||
}
|
||||
windowManager.setTitle(getWindowNameWithId(client.peerId));
|
||||
}
|
||||
}
|
||||
@@ -144,10 +150,11 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
showClose: true,
|
||||
onWindowCloseButton: handleWindowCloseButton,
|
||||
controller: serverModel.tabController,
|
||||
selectedBorderColor: MyTheme.accent,
|
||||
maxLabelWidth: 100,
|
||||
tail: buildScrollJumper(),
|
||||
selectedTabBackgroundColor:
|
||||
Theme.of(context).hintColor.withOpacity(0.2),
|
||||
Theme.of(context).hintColor.withOpacity(0),
|
||||
tabBuilder: (key, icon, label, themeConf) {
|
||||
final client = serverModel.clients
|
||||
.firstWhereOrNull((client) => client.id.toString() == key);
|
||||
@@ -158,10 +165,8 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
message: key,
|
||||
waitDuration: Duration(seconds: 1),
|
||||
child: label),
|
||||
Obx(() => Offstage(
|
||||
offstage:
|
||||
!(client?.hasUnreadChatMessage.value ?? false),
|
||||
child: Icon(Icons.circle, color: Colors.red, size: 10)))
|
||||
unreadMessageCountBuilder(client?.unreadChatMessageCount)
|
||||
.marginOnly(left: 4),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -170,7 +175,16 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
Consumer<ChatModel>(
|
||||
builder: (_, model, child) => model.isShowCMChatPage
|
||||
? Expanded(
|
||||
child: ChatPage(),
|
||||
child: buildRemoteBlock(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor))),
|
||||
child:
|
||||
ChatPage(type: ChatPageType.desktopCM)),
|
||||
),
|
||||
flex: (kConnectionManagerWindowSizeOpenChat.width -
|
||||
kConnectionManagerWindowSizeClosedChat
|
||||
.width)
|
||||
@@ -437,7 +451,8 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
child: IconButton(
|
||||
onPressed: () => checkClickTime(
|
||||
client.id,
|
||||
() => gFFI.chatModel.toggleCMChatPage(client.id),
|
||||
() => gFFI.chatModel
|
||||
.toggleCMChatPage(MessageKey(client.peerId, client.id)),
|
||||
),
|
||||
icon: SvgPicture.asset('assets/chat2.svg'),
|
||||
splashRadius: kDesktopIconButtonSplashRadius,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -111,6 +113,36 @@ class _ToolbarTheme {
|
||||
static const double buttonVMargin = 6;
|
||||
static const double iconRadius = 8;
|
||||
static const double elevation = 3;
|
||||
|
||||
static const Color bordDark = MyTheme.bordDark;
|
||||
static const Color bordLight = MyTheme.bordLight;
|
||||
|
||||
static const Color dividerDark = MyTheme.dividerDark;
|
||||
static const Color dividerLight = MyTheme.dividerLight;
|
||||
static double dividerSpaceToAction = Platform.isWindows ? 8 : 14;
|
||||
|
||||
static double menuBorderRadius = Platform.isWindows ? 5.0 : 7.0;
|
||||
static EdgeInsets menuPadding = Platform.isWindows
|
||||
? EdgeInsets.fromLTRB(4, 12, 4, 12)
|
||||
: EdgeInsets.fromLTRB(6, 14, 6, 14);
|
||||
static const double menuButtonBorderRadius = 3.0;
|
||||
|
||||
static final defaultMenuStyle = MenuStyle(
|
||||
side: MaterialStateProperty.all(BorderSide(
|
||||
width: 1,
|
||||
color: MyTheme.currentThemeMode() == ThemeMode.light
|
||||
? _ToolbarTheme.bordLight
|
||||
: _ToolbarTheme.bordDark,
|
||||
)),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(_ToolbarTheme.menuBorderRadius))),
|
||||
padding: MaterialStateProperty.all(_ToolbarTheme.menuPadding),
|
||||
);
|
||||
static final defaultMenuButtonStyle = ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Colors.transparent),
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
overlayColor: MaterialStatePropertyAll(Colors.transparent),
|
||||
);
|
||||
}
|
||||
|
||||
typedef DismissFunc = void Function();
|
||||
@@ -475,9 +507,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
textStyle: MaterialStatePropertyAll(
|
||||
TextStyle(fontWeight: FontWeight.normal),
|
||||
),
|
||||
shape: MaterialStatePropertyAll(RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(_ToolbarTheme.menuButtonBorderRadius))),
|
||||
),
|
||||
),
|
||||
dividerTheme: DividerThemeData(space: 4),
|
||||
dividerTheme: DividerThemeData(
|
||||
space: _ToolbarTheme.dividerSpaceToAction,
|
||||
color: MyTheme.currentThemeMode() == ThemeMode.light
|
||||
? _ToolbarTheme.dividerLight
|
||||
: _ToolbarTheme.dividerDark,
|
||||
),
|
||||
menuBarTheme: MenuBarThemeData(
|
||||
style: MenuStyle(
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
@@ -1413,7 +1453,8 @@ class _ChatMenuState extends State<_ChatMenu> {
|
||||
initPos = Offset(pos.dx, pos.dy + _ToolbarTheme.dividerHeight);
|
||||
}
|
||||
|
||||
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||
widget.ffi.chatModel.changeCurrentKey(
|
||||
MessageKey(widget.ffi.id, ChatModel.clientModeID));
|
||||
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
|
||||
});
|
||||
}
|
||||
@@ -1635,11 +1676,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
child: SubmenuButton(
|
||||
menuStyle: widget.menuStyle,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(Colors.transparent),
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
overlayColor: MaterialStatePropertyAll(Colors.transparent)),
|
||||
menuStyle: widget.menuStyle ?? _ToolbarTheme.defaultMenuStyle,
|
||||
style: _ToolbarTheme.defaultMenuButtonStyle,
|
||||
onHover: (value) => setState(() {
|
||||
hover = value;
|
||||
}),
|
||||
@@ -1681,6 +1719,7 @@ class _SubmenuButton extends StatelessWidget {
|
||||
child: child,
|
||||
menuChildren:
|
||||
menuChildren.map((e) => _buildPointerTrackWidget(e, ffi)).toList(),
|
||||
menuStyle: _ToolbarTheme.defaultMenuStyle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,10 @@ class DesktopTabController {
|
||||
state.value.tabs.clear();
|
||||
state.refresh();
|
||||
}
|
||||
|
||||
Widget? widget(String key) {
|
||||
return state.value.tabs.firstWhereOrNull((tab) => tab.key == key)?.page;
|
||||
}
|
||||
}
|
||||
|
||||
class TabThemeConf {
|
||||
@@ -221,6 +225,7 @@ class DesktopTab extends StatelessWidget {
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
final Color? selectedBorderColor;
|
||||
|
||||
final DesktopTabController controller;
|
||||
|
||||
@@ -248,6 +253,7 @@ class DesktopTab extends StatelessWidget {
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
this.selectedBorderColor,
|
||||
}) : super(key: key) {
|
||||
tabType = controller.tabType;
|
||||
isMainWindow = tabType == DesktopTabType.main ||
|
||||
@@ -295,37 +301,16 @@ class DesktopTab extends StatelessWidget {
|
||||
if (tabType != DesktopTabType.main) {
|
||||
return child;
|
||||
}
|
||||
var block = false.obs;
|
||||
return Obx(() => MouseRegion(
|
||||
onEnter: (_) async {
|
||||
var access_mode = await bind.mainGetOption(key: 'access-mode');
|
||||
var option = option2bool(
|
||||
'allow-remote-config-modification',
|
||||
await bind.mainGetOption(
|
||||
key: 'allow-remote-config-modification'));
|
||||
if (access_mode == 'view' || (access_mode.isEmpty && !option)) {
|
||||
var time0 = DateTime.now().millisecondsSinceEpoch;
|
||||
await bind.mainCheckMouseTime();
|
||||
Timer(const Duration(milliseconds: 120), () async {
|
||||
var d = time0 - await bind.mainGetMouseTime();
|
||||
if (d < 120) {
|
||||
block.value = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onExit: (_) => block.value = false,
|
||||
child: Stack(
|
||||
children: [
|
||||
child,
|
||||
Offstage(
|
||||
offstage: !block.value,
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
)),
|
||||
],
|
||||
),
|
||||
));
|
||||
return buildRemoteBlock(
|
||||
child: child,
|
||||
use: () async {
|
||||
var access_mode = await bind.mainGetOption(key: 'access-mode');
|
||||
var option = option2bool(
|
||||
'allow-remote-config-modification',
|
||||
await bind.mainGetOption(
|
||||
key: 'allow-remote-config-modification'));
|
||||
return access_mode == 'view' || (access_mode.isEmpty && !option);
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _tabWidgets = [];
|
||||
@@ -430,15 +415,17 @@ class DesktopTab extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: _ListView(
|
||||
controller: controller,
|
||||
tabBuilder: tabBuilder,
|
||||
tabMenuBuilder: tabMenuBuilder,
|
||||
labelGetter: labelGetter,
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor:
|
||||
selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor:
|
||||
unSelectedTabBackgroundColor))),
|
||||
controller: controller,
|
||||
tabBuilder: tabBuilder,
|
||||
tabMenuBuilder: tabMenuBuilder,
|
||||
labelGetter: labelGetter,
|
||||
maxLabelWidth: maxLabelWidth,
|
||||
selectedTabBackgroundColor:
|
||||
selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor:
|
||||
unSelectedTabBackgroundColor,
|
||||
selectedBorderColor: selectedBorderColor,
|
||||
))),
|
||||
],
|
||||
))),
|
||||
// hide simulated action buttons when we in compatible ui mode, because of reusing system title bar.
|
||||
@@ -741,6 +728,7 @@ class _ListView extends StatelessWidget {
|
||||
final LabelGetter? labelGetter;
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? selectedBorderColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
|
||||
Rx<DesktopTabState> get state => controller.state;
|
||||
@@ -753,6 +741,7 @@ class _ListView extends StatelessWidget {
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
this.selectedBorderColor,
|
||||
});
|
||||
|
||||
/// Check whether to show ListView
|
||||
@@ -805,6 +794,7 @@ class _ListView extends StatelessWidget {
|
||||
selectedTabBackgroundColor: selectedTabBackgroundColor ??
|
||||
MyTheme.tabbar(context).selectedTabBackgroundColor,
|
||||
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
|
||||
selectedBorderColor: selectedBorderColor,
|
||||
);
|
||||
}).toList()));
|
||||
}
|
||||
@@ -825,6 +815,7 @@ class _Tab extends StatefulWidget {
|
||||
final double? maxLabelWidth;
|
||||
final Color? selectedTabBackgroundColor;
|
||||
final Color? unSelectedTabBackgroundColor;
|
||||
final Color? selectedBorderColor;
|
||||
|
||||
const _Tab({
|
||||
Key? key,
|
||||
@@ -842,6 +833,7 @@ class _Tab extends StatefulWidget {
|
||||
this.maxLabelWidth,
|
||||
this.selectedTabBackgroundColor,
|
||||
this.unSelectedTabBackgroundColor,
|
||||
this.selectedBorderColor,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -932,35 +924,46 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
},
|
||||
onTap: () => widget.onTap(),
|
||||
child: Container(
|
||||
color: isSelected
|
||||
? widget.selectedTabBackgroundColor
|
||||
: widget.unSelectedTabBackgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: _kTabBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildTabContent(),
|
||||
Obx((() => _CloseButton(
|
||||
visible: hover.value && widget.closable,
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
])).paddingOnly(left: 10, right: 5),
|
||||
Offstage(
|
||||
offstage: !showDivider,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
indent: _kDividerIndent,
|
||||
endIndent: _kDividerIndent,
|
||||
color: MyTheme.tabbar(context).dividerColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
decoration: isSelected && widget.selectedBorderColor != null
|
||||
? BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: widget.selectedBorderColor!,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Container(
|
||||
color: isSelected
|
||||
? widget.selectedTabBackgroundColor
|
||||
: widget.unSelectedTabBackgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: _kTabBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_buildTabContent(),
|
||||
Obx((() => _CloseButton(
|
||||
visible: hover.value && widget.closable,
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
])).paddingOnly(left: 10, right: 5),
|
||||
Offstage(
|
||||
offstage: !showDivider,
|
||||
child: VerticalDivider(
|
||||
width: 1,
|
||||
indent: _kDividerIndent,
|
||||
endIndent: _kDividerIndent,
|
||||
color: MyTheme.tabbar(context).dividerColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1116,14 +1119,14 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
selectedIconColor: Color.fromARGB(255, 26, 26, 26),
|
||||
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
|
||||
dividerColor: Color.fromARGB(255, 238, 238, 238),
|
||||
hoverColor: Color.fromARGB(51, 158, 158, 158),
|
||||
closeHoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||
selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240));
|
||||
hoverColor: Colors.white54,
|
||||
closeHoverColor: Colors.white,
|
||||
selectedTabBackgroundColor: Colors.white54);
|
||||
|
||||
static const dark = TabbarTheme(
|
||||
selectedTabIconColor: MyTheme.accent,
|
||||
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
|
||||
selectedTextColor: Color.fromARGB(255, 255, 255, 255),
|
||||
selectedTextColor: Colors.white,
|
||||
unSelectedTextColor: Color.fromARGB(255, 192, 192, 192),
|
||||
selectedIconColor: Color.fromARGB(255, 192, 192, 192),
|
||||
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),
|
||||
|
||||
@@ -134,7 +134,7 @@ void runMainApp(bool startService) async {
|
||||
// Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
|
||||
final handledByUniLinks = await initUniLinks();
|
||||
debugPrint("handled by uni links: $handledByUniLinks");
|
||||
if (handledByUniLinks || checkArguments()) {
|
||||
if (handledByUniLinks || handleUriLink(cmdArgs: kBootArgs)) {
|
||||
windowManager.hide();
|
||||
} else {
|
||||
windowManager.show();
|
||||
@@ -225,6 +225,7 @@ void runConnectionManagerScreen(bool hide) async {
|
||||
} else {
|
||||
await showCmWindow(isStartup: true);
|
||||
}
|
||||
windowManager.setResizable(false);
|
||||
// Start the uni links handler and redirect links to Native, not for Flutter.
|
||||
listenUniLinks(handleByFlutter: false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_size_text_field/auto_size_text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -37,6 +38,7 @@ class ConnectionPage extends StatefulWidget implements PageShape {
|
||||
class _ConnectionPageState extends State<ConnectionPage> {
|
||||
/// Controller for the id input bar.
|
||||
final _idController = IDTextEditingController();
|
||||
final RxBool _idEmpty = true.obs;
|
||||
|
||||
/// Update url. If it's not null, means an update is available.
|
||||
var _updateUrl = '';
|
||||
@@ -60,6 +62,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
if (_updateUrl.isNotEmpty) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
_idController.addListener(() {
|
||||
_idEmpty.value = _idController.text.isEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -126,7 +132,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: TextField(
|
||||
child: AutoSizeTextField(
|
||||
minFontSize: 18,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
@@ -158,6 +165,14 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(() => Offstage(
|
||||
offstage: _idEmpty.value,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
_idController.clear();
|
||||
},
|
||||
icon: Icon(Icons.clear, color: MyTheme.darkGray)),
|
||||
)),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
height: 60,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/overlay.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/server_page.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/settings_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/chat_page.dart';
|
||||
import 'connection_page.dart';
|
||||
|
||||
abstract class PageShape extends Widget {
|
||||
final String title = "";
|
||||
final Icon icon = Icon(null);
|
||||
final Widget icon = Icon(null);
|
||||
final List<Widget> appBarActions = [];
|
||||
}
|
||||
|
||||
@@ -22,7 +24,9 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
var _selectedIndex = 0;
|
||||
int get selectedIndex => _selectedIndex;
|
||||
final List<PageShape> _pages = [];
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
void refreshPages() {
|
||||
setState(() {
|
||||
@@ -34,13 +38,14 @@ class _HomePageState extends State<HomePage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
initPages();
|
||||
_blockableOverlayState.applyFfi(gFFI);
|
||||
}
|
||||
|
||||
void initPages() {
|
||||
_pages.clear();
|
||||
_pages.add(ConnectionPage());
|
||||
if (isAndroid) {
|
||||
_pages.addAll([ChatPage(), ServerPage()]);
|
||||
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
|
||||
}
|
||||
_pages.add(SettingsPage());
|
||||
}
|
||||
@@ -62,7 +67,7 @@ class _HomePageState extends State<HomePage> {
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text("RustDesk"),
|
||||
title: appTitle(),
|
||||
actions: _pages.elementAt(_selectedIndex).appBarActions,
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
@@ -80,6 +85,8 @@ class _HomePageState extends State<HomePage> {
|
||||
if (index == 1 && _selectedIndex != index) {
|
||||
gFFI.chatModel.hideChatIconOverlay();
|
||||
gFFI.chatModel.hideChatWindowOverlay();
|
||||
gFFI.chatModel
|
||||
.mobileClearClientUnread(gFFI.chatModel.currentKey.connId);
|
||||
}
|
||||
_selectedIndex = index;
|
||||
}),
|
||||
@@ -87,6 +94,53 @@ class _HomePageState extends State<HomePage> {
|
||||
body: _pages.elementAt(_selectedIndex),
|
||||
));
|
||||
}
|
||||
|
||||
Widget appTitle() {
|
||||
final currentUser = gFFI.chatModel.currentUser;
|
||||
final currentKey = gFFI.chatModel.currentKey;
|
||||
if (_selectedIndex == 1 &&
|
||||
currentUser != null &&
|
||||
currentKey.peerId.isNotEmpty) {
|
||||
final connected =
|
||||
gFFI.serverModel.clients.any((e) => e.id == currentKey.connId);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Tooltip(
|
||||
message: currentKey.isOut
|
||||
? translate('Outgoing connection')
|
||||
: translate('Incoming connection'),
|
||||
child: Icon(
|
||||
currentKey.isOut
|
||||
? Icons.call_made_rounded
|
||||
: Icons.call_received_rounded,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"${currentUser.firstName} ${currentUser.id}",
|
||||
),
|
||||
if (connected)
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Color.fromARGB(255, 133, 246, 199)),
|
||||
).marginSymmetric(horizontal: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Text("RustDesk");
|
||||
}
|
||||
}
|
||||
|
||||
class WebHomePage extends StatelessWidget {
|
||||
|
||||
@@ -43,8 +43,6 @@ class _RemotePageState extends State<RemotePage> {
|
||||
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
||||
Orientation? _currentOrientation;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
final keyboardVisibilityController = KeyboardVisibilityController();
|
||||
late final StreamSubscription<bool> keyboardSubscription;
|
||||
final FocusNode _mobileFocusNode = FocusNode();
|
||||
@@ -70,8 +68,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
|
||||
keyboardSubscription =
|
||||
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
|
||||
_blockableOverlayState.applyFfi(gFFI);
|
||||
initSharedStates(widget.id);
|
||||
gFFI.chatModel
|
||||
.changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -351,8 +350,8 @@ class _RemotePageState extends State<RemotePage> {
|
||||
color: Colors.white,
|
||||
icon: Icon(Icons.message),
|
||||
onPressed: () {
|
||||
gFFI.chatModel
|
||||
.changeCurrentID(ChatModel.clientModeID);
|
||||
gFFI.chatModel.changeCurrentKey(MessageKey(
|
||||
widget.id, ChatModel.clientModeID));
|
||||
gFFI.chatModel.toggleChatOverlay();
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -419,14 +420,16 @@ class ConnectionManager extends StatelessWidget {
|
||||
? const SizedBox.shrink()
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
gFFI.chatModel.changeCurrentID(client.id);
|
||||
gFFI.chatModel.changeCurrentKey(
|
||||
MessageKey(client.peerId, client.id));
|
||||
final bar = navigationBarKey.currentWidget;
|
||||
if (bar != null) {
|
||||
bar as BottomNavigationBar;
|
||||
bar.onTap!(1);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.chat)))
|
||||
icon: unreadTopRightBuilder(
|
||||
client.unreadChatMessageCount)))
|
||||
],
|
||||
),
|
||||
client.authorized
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dash_chat_2/dash_chat_2.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/home_page.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
@@ -16,6 +22,24 @@ import '../common/widgets/overlay.dart';
|
||||
import '../main.dart';
|
||||
import 'model.dart';
|
||||
|
||||
class MessageKey {
|
||||
final String peerId;
|
||||
final int connId;
|
||||
bool get isOut => connId == ChatModel.clientModeID;
|
||||
|
||||
MessageKey(this.peerId, this.connId);
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return other is MessageKey &&
|
||||
other.peerId == peerId &&
|
||||
other.isOut == isOut;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => peerId.hashCode ^ isOut.hashCode;
|
||||
}
|
||||
|
||||
class MessageBody {
|
||||
ChatUser chatUser;
|
||||
List<ChatMessage> chatMessages;
|
||||
@@ -45,6 +69,8 @@ class ChatModel with ChangeNotifier {
|
||||
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
|
||||
|
||||
TextEditingController textController = TextEditingController();
|
||||
RxInt mobileUnreadSum = 0.obs;
|
||||
MessageKey? latestReceivedKey;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -53,19 +79,18 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
final ChatUser me = ChatUser(
|
||||
id: "",
|
||||
id: Uuid().v4().toString(),
|
||||
firstName: translate("Me"),
|
||||
);
|
||||
|
||||
late final Map<int, MessageBody> _messages = {}..[clientModeID] =
|
||||
MessageBody(me, []);
|
||||
late final Map<MessageKey, MessageBody> _messages = {};
|
||||
|
||||
var _currentID = clientModeID;
|
||||
MessageKey _currentKey = MessageKey('', -2); // -2 is invalid value
|
||||
late bool _isShowCMChatPage = false;
|
||||
|
||||
Map<int, MessageBody> get messages => _messages;
|
||||
Map<MessageKey, MessageBody> get messages => _messages;
|
||||
|
||||
int get currentID => _currentID;
|
||||
MessageKey get currentKey => _currentKey;
|
||||
|
||||
bool get isShowCMChatPage => _isShowCMChatPage;
|
||||
|
||||
@@ -115,15 +140,7 @@ class ChatModel with ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
ChatUser get currentUser {
|
||||
final user = messages[currentID]?.chatUser;
|
||||
if (user == null) {
|
||||
_currentID = clientModeID;
|
||||
return me;
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
ChatUser? get currentUser => _messages[_currentKey]?.chatUser;
|
||||
|
||||
showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
|
||||
if (chatIconOverlayEntry != null) {
|
||||
@@ -178,6 +195,12 @@ class ChatModel with ChangeNotifier {
|
||||
|
||||
final overlayState = _blockableOverlayState?.state;
|
||||
if (overlayState == null) return;
|
||||
if (isMobile &&
|
||||
!gFFI.chatModel.currentKey.isOut && // not in remote page
|
||||
gFFI.chatModel.latestReceivedKey != null) {
|
||||
gFFI.chatModel.changeCurrentKey(gFFI.chatModel.latestReceivedKey!);
|
||||
gFFI.chatModel.mobileClearClientUnread(gFFI.chatModel.currentKey.connId);
|
||||
}
|
||||
final overlay = OverlayEntry(builder: (context) {
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
@@ -229,21 +252,29 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
showChatPage(int id) async {
|
||||
if (isConnManager) {
|
||||
if (!_isShowCMChatPage) {
|
||||
await toggleCMChatPage(id);
|
||||
showChatPage(MessageKey key) async {
|
||||
if (isDesktop) {
|
||||
if (isConnManager) {
|
||||
if (!_isShowCMChatPage) {
|
||||
await toggleCMChatPage(key);
|
||||
}
|
||||
} else {
|
||||
if (_isChatOverlayHide()) {
|
||||
await toggleChatOverlay();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_isChatOverlayHide()) {
|
||||
await toggleChatOverlay();
|
||||
if (key.connId == clientModeID) {
|
||||
if (_isChatOverlayHide()) {
|
||||
await toggleChatOverlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleCMChatPage(int id) async {
|
||||
if (gFFI.chatModel.currentID != id) {
|
||||
gFFI.chatModel.changeCurrentID(id);
|
||||
toggleCMChatPage(MessageKey key) async {
|
||||
if (gFFI.chatModel.currentKey != key) {
|
||||
gFFI.chatModel.changeCurrentKey(key);
|
||||
}
|
||||
if (_isShowCMChatPage) {
|
||||
_isShowCMChatPage = !_isShowCMChatPage;
|
||||
@@ -261,25 +292,30 @@ class ChatModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
changeCurrentID(int id) {
|
||||
if (_messages.containsKey(id)) {
|
||||
_currentID = id;
|
||||
notifyListeners();
|
||||
changeCurrentKey(MessageKey key) {
|
||||
updateConnIdOfKey(key);
|
||||
String? peerName;
|
||||
if (key.connId == clientModeID) {
|
||||
peerName = parent.target?.ffiModel.pi.username;
|
||||
} else {
|
||||
final client = parent.target?.serverModel.clients
|
||||
.firstWhere((client) => client.id == id);
|
||||
if (client == null) {
|
||||
return debugPrint(
|
||||
"Failed to changeCurrentID,remote user doesn't exist");
|
||||
}
|
||||
final chatUser = ChatUser(
|
||||
id: client.peerId,
|
||||
firstName: client.name,
|
||||
);
|
||||
_messages[id] = MessageBody(chatUser, []);
|
||||
_currentID = id;
|
||||
notifyListeners();
|
||||
peerName = parent.target?.serverModel.clients
|
||||
.firstWhereOrNull((client) => client.peerId == key.peerId)
|
||||
?.name;
|
||||
}
|
||||
if (!_messages.containsKey(key)) {
|
||||
final chatUser = ChatUser(
|
||||
id: key.peerId,
|
||||
firstName: peerName,
|
||||
);
|
||||
_messages[key] = MessageBody(chatUser, []);
|
||||
} else {
|
||||
if (peerName != null && peerName.isNotEmpty) {
|
||||
_messages[key]?.chatUser.firstName = peerName;
|
||||
}
|
||||
}
|
||||
_currentKey = key;
|
||||
notifyListeners();
|
||||
mobileClearClientUnread(key.connId);
|
||||
}
|
||||
|
||||
receive(int id, String text) async {
|
||||
@@ -292,49 +328,90 @@ class ChatModel with ChangeNotifier {
|
||||
if (desktopType == DesktopType.cm) {
|
||||
await showCmWindow();
|
||||
}
|
||||
String? peerId;
|
||||
if (id == clientModeID) {
|
||||
peerId = session.id;
|
||||
} else {
|
||||
peerId = session.serverModel.clients
|
||||
.firstWhereOrNull((e) => e.id == id)
|
||||
?.peerId;
|
||||
}
|
||||
if (peerId == null) {
|
||||
debugPrint("Failed to receive msg, peerId is null");
|
||||
return;
|
||||
}
|
||||
|
||||
final messagekey = MessageKey(peerId, id);
|
||||
|
||||
// mobile: first message show overlay icon
|
||||
if (!isDesktop && chatIconOverlayEntry == null) {
|
||||
showChatIconOverlay();
|
||||
}
|
||||
// show chat page
|
||||
await showChatPage(id);
|
||||
|
||||
int toId = currentID;
|
||||
|
||||
await showChatPage(messagekey);
|
||||
late final ChatUser chatUser;
|
||||
if (id == clientModeID) {
|
||||
chatUser = ChatUser(
|
||||
firstName: session.ffiModel.pi.username,
|
||||
id: session.id,
|
||||
id: peerId,
|
||||
);
|
||||
toId = id;
|
||||
|
||||
if (isDesktop) {
|
||||
if (Get.isRegistered<DesktopTabController>()) {
|
||||
DesktopTabController tabController = Get.find<DesktopTabController>();
|
||||
var index = tabController.state.value.tabs
|
||||
.indexWhere((e) => e.key == session.id);
|
||||
final notSelected =
|
||||
index >= 0 && tabController.state.value.selected != index;
|
||||
// minisized: top and switch tab
|
||||
// not minisized: add count
|
||||
if (await WindowController.fromWindowId(stateGlobal.windowId)
|
||||
.isMinimized()) {
|
||||
window_on_top(stateGlobal.windowId);
|
||||
if (notSelected) {
|
||||
tabController.jumpTo(index);
|
||||
}
|
||||
} else {
|
||||
if (notSelected) {
|
||||
UnreadChatCountState.find(peerId).value += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final client =
|
||||
session.serverModel.clients.firstWhere((client) => client.id == id);
|
||||
final client = session.serverModel.clients
|
||||
.firstWhereOrNull((client) => client.id == id);
|
||||
if (client == null) {
|
||||
debugPrint("Failed to receive msg, client is null");
|
||||
return;
|
||||
}
|
||||
if (isDesktop) {
|
||||
window_on_top(null);
|
||||
// disable auto jumpTo other tab when hasFocus, and mark unread message
|
||||
final currentSelectedTab =
|
||||
session.serverModel.tabController.state.value.selectedTabInfo;
|
||||
if (currentSelectedTab.key != id.toString() && inputNode.hasFocus) {
|
||||
client.hasUnreadChatMessage.value = true;
|
||||
client.unreadChatMessageCount.value += 1;
|
||||
} else {
|
||||
parent.target?.serverModel.jumpTo(id);
|
||||
toId = id;
|
||||
}
|
||||
} else {
|
||||
toId = id;
|
||||
if (HomePage.homeKey.currentState?.selectedIndex != 1 ||
|
||||
_currentKey != messagekey) {
|
||||
client.unreadChatMessageCount.value += 1;
|
||||
mobileUpdateUnreadSum();
|
||||
}
|
||||
}
|
||||
chatUser = ChatUser(id: client.peerId, firstName: client.name);
|
||||
}
|
||||
|
||||
if (!_messages.containsKey(id)) {
|
||||
_messages[id] = MessageBody(chatUser, []);
|
||||
}
|
||||
_messages[id]!.insert(
|
||||
insertMessage(messagekey,
|
||||
ChatMessage(text: text, user: chatUser, createdAt: DateTime.now()));
|
||||
_currentID = toId;
|
||||
if (id == clientModeID || _currentKey.peerId.isEmpty) {
|
||||
// client or invalid
|
||||
_currentKey = messagekey;
|
||||
mobileClearClientUnread(messagekey.connId);
|
||||
}
|
||||
latestReceivedKey = messagekey;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -344,17 +421,63 @@ class ChatModel with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
message.text = trimmedText;
|
||||
_messages[_currentID]?.insert(message);
|
||||
if (_currentID == clientModeID && parent.target != null) {
|
||||
insertMessage(_currentKey, message);
|
||||
if (_currentKey.connId == clientModeID && parent.target != null) {
|
||||
bind.sessionSendChat(sessionId: sessionId, text: message.text);
|
||||
} else {
|
||||
bind.cmSendChat(connId: _currentID, msg: message.text);
|
||||
bind.cmSendChat(connId: _currentKey.connId, msg: message.text);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
inputNode.requestFocus();
|
||||
}
|
||||
|
||||
insertMessage(MessageKey key, ChatMessage message) {
|
||||
updateConnIdOfKey(key);
|
||||
if (!_messages.containsKey(key)) {
|
||||
_messages[key] = MessageBody(message.user, []);
|
||||
}
|
||||
_messages[key]?.insert(message);
|
||||
}
|
||||
|
||||
updateConnIdOfKey(MessageKey key) {
|
||||
if (_messages.keys
|
||||
.toList()
|
||||
.firstWhereOrNull((e) => e == key && e.connId != key.connId) !=
|
||||
null) {
|
||||
final value = _messages.remove(key);
|
||||
if (value != null) {
|
||||
_messages[key] = value;
|
||||
}
|
||||
}
|
||||
if (_currentKey == key || _currentKey.peerId.isEmpty) {
|
||||
_currentKey = key; // hash != assign
|
||||
}
|
||||
}
|
||||
|
||||
void mobileUpdateUnreadSum() {
|
||||
if (!isMobile) return;
|
||||
var sum = 0;
|
||||
parent.target?.serverModel.clients
|
||||
.map((e) => sum += e.unreadChatMessageCount.value)
|
||||
.toList();
|
||||
Future.delayed(Duration.zero, () {
|
||||
mobileUnreadSum.value = sum;
|
||||
});
|
||||
}
|
||||
|
||||
void mobileClearClientUnread(int id) {
|
||||
if (!isMobile) return;
|
||||
final client = parent.target?.serverModel.clients
|
||||
.firstWhereOrNull((client) => client.id == id);
|
||||
if (client != null) {
|
||||
Future.delayed(Duration.zero, () {
|
||||
client.unreadChatMessageCount.value = 0;
|
||||
mobileUpdateUnreadSum();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
hideChatIconOverlay();
|
||||
hideChatWindowOverlay();
|
||||
|
||||
@@ -248,7 +248,7 @@ class FfiModel with ChangeNotifier {
|
||||
|
||||
onUrlSchemeReceived(Map<String, dynamic> evt) {
|
||||
final url = evt['url'].toString().trim();
|
||||
if (url.startsWith(kUniLinksPrefix) && parseRustdeskUri(url)) {
|
||||
if (url.startsWith(kUniLinksPrefix) && handleUriLink(uriString: url)) {
|
||||
return;
|
||||
}
|
||||
switch (url) {
|
||||
|
||||
@@ -233,7 +233,7 @@ class PlatformFFI {
|
||||
'_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir');
|
||||
}
|
||||
if (desktopType == DesktopType.cm) {
|
||||
await _ffiBind.cmStartListenIpcThread();
|
||||
await _ffiBind.cmInit();
|
||||
}
|
||||
await _ffiBind.mainDeviceId(id: id);
|
||||
await _ffiBind.mainDeviceName(name: name);
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
@@ -462,13 +463,7 @@ class ServerModel with ChangeNotifier {
|
||||
key: client.id.toString(),
|
||||
label: client.name,
|
||||
closable: false,
|
||||
onTap: () {
|
||||
if (client.hasUnreadChatMessage.value) {
|
||||
client.hasUnreadChatMessage.value = false;
|
||||
final chatModel = parent.target!.chatModel;
|
||||
chatModel.showChatPage(client.id);
|
||||
}
|
||||
},
|
||||
onTap: () {},
|
||||
page: desktop.buildConnectionCard(client)));
|
||||
Future.delayed(Duration.zero, () async {
|
||||
if (!hideCm) window_on_top(null);
|
||||
@@ -480,6 +475,8 @@ class ServerModel with ChangeNotifier {
|
||||
cmHiddenTimer = null;
|
||||
});
|
||||
}
|
||||
parent.target?.chatModel
|
||||
.updateConnIdOfKey(MessageKey(client.peerId, client.id));
|
||||
}
|
||||
|
||||
void showLoginDialog(Client client) {
|
||||
@@ -643,7 +640,7 @@ class Client {
|
||||
bool inVoiceCall = false;
|
||||
bool incomingVoiceCall = false;
|
||||
|
||||
RxBool hasUnreadChatMessage = false.obs;
|
||||
RxInt unreadChatMessageCount = 0.obs;
|
||||
|
||||
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
|
||||
this.keyboard, this.clipboard, this.audio);
|
||||
|
||||
@@ -84,10 +84,12 @@ class RustDeskMultiWindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> newFileTransfer(String remoteId, {bool? forceRelay}) async {
|
||||
Future<dynamic> newFileTransfer(String remoteId,
|
||||
{String? password, bool? forceRelay}) async {
|
||||
var msg = jsonEncode({
|
||||
"type": WindowType.FileTransfer.index,
|
||||
"id": remoteId,
|
||||
"password": password,
|
||||
"forceRelay": forceRelay,
|
||||
});
|
||||
|
||||
@@ -117,11 +119,12 @@ class RustDeskMultiWindowManager {
|
||||
}
|
||||
|
||||
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
||||
{bool? forceRelay}) async {
|
||||
{String? password, bool? forceRelay}) async {
|
||||
final msg = jsonEncode({
|
||||
"type": WindowType.PortForward.index,
|
||||
"id": remoteId,
|
||||
"isRDP": isRDP,
|
||||
"password": password,
|
||||
"forceRelay": forceRelay,
|
||||
});
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ static void my_application_activate(GApplication* application) {
|
||||
int width = 800, height = 600;
|
||||
if (gIsConnectionManager) {
|
||||
width = 300;
|
||||
height = 400;
|
||||
height = 490;
|
||||
}
|
||||
gtk_window_set_default_size(window, width, height); // <-- comment this line
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
@@ -65,6 +65,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
auto_size_text_field:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: auto_size_text_field
|
||||
sha256: "8967129167193fefbb7a8707ade1bb71f9e52b9a5cf6da0132b7f6b7946c5a3f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
back_button_interceptor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -319,7 +327,7 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "30518303e28702bf6b8110465293c05d21bc4cd2"
|
||||
resolved-ref: aee670819f5fe7e8b0f05e0239dafb5c62f7a84b
|
||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.2.0
|
||||
version: 1.2.1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0"
|
||||
@@ -96,6 +96,7 @@ dependencies:
|
||||
percent_indicator: ^4.2.2
|
||||
dropdown_button2: ^2.0.0
|
||||
uuid: ^3.0.7
|
||||
auto_size_text_field: ^2.2.1
|
||||
|
||||
dev_dependencies:
|
||||
icons_launcher: ^2.0.4
|
||||
|
||||
@@ -84,7 +84,6 @@ const CHARS: &[char] = &[
|
||||
pub const RENDEZVOUS_SERVERS: &[&str] = &[
|
||||
"rs-ny.rustdesk.com",
|
||||
"rs-sg.rustdesk.com",
|
||||
"rs-cn.rustdesk.com",
|
||||
];
|
||||
|
||||
pub const RS_PUB_KEY: &str = match option_env!("RS_PUB_KEY") {
|
||||
@@ -1031,8 +1030,12 @@ impl PeerConfig {
|
||||
};
|
||||
|
||||
let c = PeerConfig::load(&id_decoded_string);
|
||||
if c.info.platform.is_empty() {
|
||||
fs::remove_file(p).ok();
|
||||
}
|
||||
(id_decoded_string, t, c)
|
||||
})
|
||||
.filter(|p| !p.2.info.platform.is_empty())
|
||||
.collect();
|
||||
peers.sort_unstable_by(|a, b| b.1.cmp(&a.1));
|
||||
return peers;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.2.0
|
||||
pkgver=1.2.1
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
||||
2
res/bump.sh
Normal file
2
res/bump.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#! /usr/bin/env bash
|
||||
sed -i "s/$1/$2/g" res/*spec res/PKGBUILD flutter/pubspec.yaml Cargo.toml .github/workflows/*yml flatpak/*json appimage/*yml
|
||||
14
res/osx-dist.sh
Executable file
14
res/osx-dist.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo $MACOS_CODESIGN_IDENTITY
|
||||
cargo install flutter_rust_bridge_codegen --version 1.75.3 --features uuid
|
||||
cd flutter; flutter pub get; cd -
|
||||
~/.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
|
||||
./build.py --flutter
|
||||
rm rustdesk-$VERSION.dmg
|
||||
# security find-identity -v
|
||||
codesign --force --options runtime -s $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-$VERSION.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||
codesign --force --options runtime -s $MACOS_CODESIGN_IDENTITY --deep --strict rustdesk-$VERSION.dmg -vvv
|
||||
# notarize the rustdesk-${{ env.VERSION }}.dmg
|
||||
rcodesign notary-submit --api-key-path ~/.p12/api-key.json --staple rustdesk-$VERSION.dmg
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.0
|
||||
Version: 1.2.1
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.0
|
||||
Version: 1.2.1
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.0
|
||||
Version: 1.2.1
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
117
src/core_main.rs
117
src/core_main.rs
@@ -6,6 +6,18 @@ use hbb_common::log;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::platform::register_breakdown_handler;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! my_println{
|
||||
($($arg:tt)*) => {
|
||||
#[cfg(not(windows))]
|
||||
println!("{}", format_args!($($arg)*));
|
||||
#[cfg(windows)]
|
||||
crate::platform::message_box(
|
||||
&format!("{}", format_args!($($arg)*))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// shared by flutter and sciter main function
|
||||
///
|
||||
/// [Note]
|
||||
@@ -19,15 +31,23 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
let mut _is_elevate = false;
|
||||
let mut _is_run_as_system = false;
|
||||
let mut _is_quick_support = false;
|
||||
let mut _is_flutter_connect = false;
|
||||
let mut _is_flutter_invoke_new_connection = false;
|
||||
let mut arg_exe = Default::default();
|
||||
for arg in std::env::args() {
|
||||
if i == 0 {
|
||||
arg_exe = arg;
|
||||
} else if i > 0 {
|
||||
#[cfg(feature = "flutter")]
|
||||
if arg == "--connect" || arg == "--play" {
|
||||
_is_flutter_connect = true;
|
||||
if [
|
||||
"--connect",
|
||||
"--play",
|
||||
"--file-transfer",
|
||||
"--port-forward",
|
||||
"--rdp",
|
||||
]
|
||||
.contains(&arg.as_str())
|
||||
{
|
||||
_is_flutter_invoke_new_connection = true;
|
||||
}
|
||||
if arg == "--elevate" {
|
||||
_is_elevate = true;
|
||||
@@ -43,9 +63,9 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
if args.is_empty() {
|
||||
#[cfg(target_os = "linux")]
|
||||
hbb_common::allow_err!(crate::platform::check_autostart_config());
|
||||
if crate::check_process("--server", false) && !crate::check_process("--tray", true) {
|
||||
#[cfg(target_os = "linux")]
|
||||
hbb_common::allow_err!(crate::platform::check_autostart_config());
|
||||
hbb_common::allow_err!(crate::run_me(vec!["--tray"]));
|
||||
}
|
||||
}
|
||||
@@ -63,7 +83,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "flutter")]
|
||||
if _is_flutter_connect {
|
||||
if _is_flutter_invoke_new_connection {
|
||||
return core_main_invoke_new_connection(std::env::args());
|
||||
}
|
||||
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe);
|
||||
@@ -75,7 +95,7 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
args.clear();
|
||||
}
|
||||
if args.len() > 0 && args[0] == "--version" {
|
||||
println!("{}", crate::VERSION);
|
||||
my_println!("{}", crate::VERSION);
|
||||
return None;
|
||||
}
|
||||
#[cfg(windows)]
|
||||
@@ -215,18 +235,23 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
return None;
|
||||
} else if args[0] == "--password" {
|
||||
if args.len() == 2 {
|
||||
if crate::platform::is_root() {
|
||||
if crate::platform::is_installed()
|
||||
&& crate::platform::check_super_user_permission().unwrap_or_default()
|
||||
{
|
||||
crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
|
||||
my_println!("Done!");
|
||||
} else {
|
||||
println!("Administrative privileges required!");
|
||||
my_println!("Installation and administrative privileges required!");
|
||||
}
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--get-id" {
|
||||
if crate::platform::is_root() {
|
||||
println!("{}", crate::ipc::get_id());
|
||||
if crate::platform::is_installed()
|
||||
&& crate::platform::check_super_user_permission().unwrap_or_default()
|
||||
{
|
||||
my_println!("{}", crate::ipc::get_id());
|
||||
} else {
|
||||
println!("Permission denied!");
|
||||
my_println!("Installation and administrative privileges required!");
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--check-hwcodec-config" {
|
||||
@@ -318,38 +343,48 @@ fn import_config(path: &str) {
|
||||
/// If it returns [`Some`], then the process will continue, and flutter gui will be started.
|
||||
#[cfg(feature = "flutter")]
|
||||
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
|
||||
args.position(|element| {
|
||||
return element == "--connect" || element == "--play";
|
||||
})?;
|
||||
let mut peer_id = args.next().unwrap_or("".to_string());
|
||||
if peer_id.is_empty() {
|
||||
eprintln!("please provide a valid peer id");
|
||||
return None;
|
||||
}
|
||||
let app_name = crate::get_app_name();
|
||||
let ext = format!(".{}", app_name.to_lowercase());
|
||||
if peer_id.ends_with(&ext) {
|
||||
peer_id = peer_id.replace(&ext, "");
|
||||
}
|
||||
let mut switch_uuid = None;
|
||||
while let Some(item) = args.next() {
|
||||
if item == "--switch_uuid" {
|
||||
switch_uuid = args.next();
|
||||
let mut authority = None;
|
||||
let mut id = None;
|
||||
let mut param_array = vec![];
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--connect" | "--play" | "--file-transfer" | "--port-forward" | "--rdp" => {
|
||||
authority = Some((&arg.to_string()[2..]).to_owned());
|
||||
id = args.next();
|
||||
}
|
||||
"--password" => {
|
||||
if let Some(password) = args.next() {
|
||||
param_array.push(format!("password={password}"));
|
||||
}
|
||||
}
|
||||
"--relay" => {
|
||||
param_array.push(format!("relay=true"));
|
||||
}
|
||||
// inner
|
||||
"--switch_uuid" => {
|
||||
if let Some(switch_uuid) = args.next() {
|
||||
param_array.push(format!("switch_uuid={switch_uuid}"));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut param_array = vec![];
|
||||
if switch_uuid.is_some() {
|
||||
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p));
|
||||
param_array.push(switch_uuid);
|
||||
let mut uni_links = Default::default();
|
||||
if let Some(authority) = authority {
|
||||
if let Some(mut id) = id {
|
||||
let app_name = crate::get_app_name();
|
||||
let ext = format!(".{}", app_name.to_lowercase());
|
||||
if id.ends_with(&ext) {
|
||||
id = id.replace(&ext, "");
|
||||
}
|
||||
let params = param_array.join("&");
|
||||
let params_flag = if params.is_empty() { "" } else { "?" };
|
||||
uni_links = format!("rustdesk://{}/{}{}{}", authority, id, params_flag, params);
|
||||
}
|
||||
}
|
||||
if uni_links.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let params = param_array.join("&");
|
||||
let params_flag = if params.is_empty() { "" } else { "?" };
|
||||
#[allow(unused)]
|
||||
let uni_links = format!(
|
||||
"rustdesk://connection/new/{}{}{}",
|
||||
peer_id, params_flag, params
|
||||
);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return try_send_by_dbus(uni_links);
|
||||
|
||||
@@ -910,7 +910,7 @@ pub mod connection_manager {
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn start_listen_ipc_thread() {
|
||||
fn start_listen_ipc_thread() {
|
||||
start_listen_ipc(true);
|
||||
}
|
||||
|
||||
@@ -931,6 +931,12 @@ pub mod connection_manager {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cm_init() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
start_listen_ipc_thread();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
|
||||
|
||||
@@ -1448,9 +1448,9 @@ pub fn main_use_texture_render() -> SyncReturn<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cm_start_listen_ipc_thread() {
|
||||
pub fn cm_init() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::flutter::connection_manager::start_listen_ipc_thread();
|
||||
crate::flutter::connection_manager::cm_init();
|
||||
}
|
||||
|
||||
/// Start an ipc server for receiving the url scheme.
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nom d'usuari oblidat"),
|
||||
("Password missed", "Contrasenya oblidada"),
|
||||
("Wrong credentials", "Credencials incorrectes"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar tag"),
|
||||
("Unremember Password", "Contrasenya oblidada"),
|
||||
("Favorites", "Preferits"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "用户名没有填写"),
|
||||
("Password missed", "密码没有填写"),
|
||||
("Wrong credentials", "提供的登录信息错误"),
|
||||
("The verification code is incorrect or has expired", "验证码错误或已超时"),
|
||||
("Edit Tag", "修改标签"),
|
||||
("Unremember Password", "忘记密码"),
|
||||
("Favorites", "收藏"),
|
||||
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "语音通话"),
|
||||
("Text chat", "文字聊天"),
|
||||
("Stop voice call", "停止语音通话"),
|
||||
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,或者在卡片选项里选择强制走中继连接。"),
|
||||
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r,如果最近访问里存在该卡片,也可以在卡片选项里选择强制走中继连接。"),
|
||||
("Reconnect", "重连"),
|
||||
("Codec", "编解码"),
|
||||
("Resolution", "分辨率"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", "接受并提权"),
|
||||
("accept_and_elevate_btn_tooltip", "接受连接并提升 UAC 权限"),
|
||||
("clipboard_wait_response_timeout_tip", "等待拷贝响应超时"),
|
||||
("Incoming connection", "收到的连接"),
|
||||
("Outgoing connection", "发起的连接"),
|
||||
("Exit", "退出"),
|
||||
("Open", "打开"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Chybí uživatelské jméno"),
|
||||
("Password missed", "Chybí heslo"),
|
||||
("Wrong credentials", "Nesprávné přihlašovací údaje"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Upravit štítek"),
|
||||
("Unremember Password", "Přestat si heslo pamatovat"),
|
||||
("Favorites", "Oblíbené"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Glemt brugernavn"),
|
||||
("Password missed", "Glemt kodeord"),
|
||||
("Wrong credentials", "Forkerte registreringsdata"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Rediger nøgleord"),
|
||||
("Unremember Password", "Glem adgangskoden"),
|
||||
("Favorites", "Favoritter"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Benutzername fehlt"),
|
||||
("Password missed", "Passwort fehlt"),
|
||||
("Wrong credentials", "Falsche Anmeldedaten"),
|
||||
("The verification code is incorrect or has expired", "Der Verifizierungscode ist falsch oder abgelaufen"),
|
||||
("Edit Tag", "Schlagwort bearbeiten"),
|
||||
("Unremember Password", "Gespeichertes Passwort löschen"),
|
||||
("Favorites", "Favoriten"),
|
||||
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Sprachanruf"),
|
||||
("Text chat", "Text-Chat"),
|
||||
("Stop voice call", "Sprachanruf beenden"),
|
||||
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" auf der Gegenstelle auswählen."),
|
||||
("relay_hint_tip", "Wenn eine direkte Verbindung nicht möglich ist, können Sie versuchen, eine Verbindung über einen Relay-Server herzustellen. \nWenn Sie eine Relay-Verbindung beim ersten Versuch herstellen möchten, können Sie das Suffix \"/r\" an die ID anhängen oder die Option \"Immer über Relay-Server verbinden\" in der Liste der letzten Sitzungen auswählen, sofern diese vorhanden ist."),
|
||||
("Reconnect", "Erneut verbinden"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Auflösung"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", "Akzeptieren und Rechte erhöhen"),
|
||||
("accept_and_elevate_btn_tooltip", "Akzeptieren Sie die Verbindung und erhöhen Sie die UAC-Berechtigungen."),
|
||||
("clipboard_wait_response_timeout_tip", "Zeitüberschreitung beim Warten auf die Antwort der Kopie."),
|
||||
("Incoming connection", "Eingehende Verbindung"),
|
||||
("Outgoing connection", "Ausgehende Verbindung"),
|
||||
("Exit", "Beenden"),
|
||||
("Open", "Öffnen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Δεν συμπληρώσατε το όνομα χρήστη"),
|
||||
("Password missed", "Δεν συμπληρώσατε τον κωδικό πρόσβασης"),
|
||||
("Wrong credentials", "Λάθος διαπιστευτήρια"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Επεξεργασία ετικέτας"),
|
||||
("Unremember Password", "Διαγραφή απομνημονευμένου κωδικού"),
|
||||
("Favorites", "Αγαπημένα"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."),
|
||||
("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."),
|
||||
("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."),
|
||||
("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions."),
|
||||
("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions if it exists."),
|
||||
("No transfers in progress", ""),
|
||||
("idd_driver_tip", "Install virtual display driver which is used when you have no physical displays."),
|
||||
("confirm_idd_driver_tip", "The option to install the virtual display driver is checked. Note that a test certificate will be installed to trust the virtual display driver. This test certificate will only be used to trust Rustdesk drivers."),
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Uzantnomo forgesita"),
|
||||
("Password missed", "Pasvorto forgesita"),
|
||||
("Wrong credentials", "Identigilo aŭ pasvorto erara"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Redakti etikedo"),
|
||||
("Unremember Password", "Forgesi pasvorton"),
|
||||
("Favorites", "Favorataj"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("x11 expected", "x11 necesario"),
|
||||
("Port", "Puerto"),
|
||||
("Settings", "Ajustes"),
|
||||
("Username", " Nombre de usuario"),
|
||||
("Username", "Nombre de usuario"),
|
||||
("Invalid port", "Puerto incorrecto"),
|
||||
("Closed manually by the peer", "Cerrado manualmente por el par"),
|
||||
("Enable remote configuration modification", "Habilitar modificación remota de configuración"),
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Olvidó su nombre de usuario"),
|
||||
("Password missed", "Olvidó su contraseña"),
|
||||
("Wrong credentials", "Credenciales incorrectas"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar tag"),
|
||||
("Unremember Password", "Olvidar contraseña"),
|
||||
("Favorites", "Favoritos"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", "Aceptar y Elevar"),
|
||||
("accept_and_elevate_btn_tooltip", "Aceptar la conexión y elevar permisos UAC."),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Connection in progress. Please wait.", "در حال اتصال. لطفا متظر بمانید"),
|
||||
("Please try 1 minute later", "لطفا بعد از 1 دقیقه مجددا تلاش کنید"),
|
||||
("Login Error", "ورود ناموفق بود"),
|
||||
("Successful", "ورود با موفقیت انجام شد"),
|
||||
("Successful", "با موفقیت انجام شد"),
|
||||
("Connected, waiting for image...", "...ارتباط برقرار شد. انتظار برای دریافت تصاویر"),
|
||||
("Name", "نام"),
|
||||
("Type", "نوع فایل"),
|
||||
@@ -144,7 +144,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed to connect via relay server", "انجام نشد Relay اتصال از طریق سرور"),
|
||||
("Failed to make direct connection to remote desktop", "اتصال مستقیم به دسکتاپ راه دور انجام نشد"),
|
||||
("Set Password", "تنظیم رمزعبور"),
|
||||
("OS Password", "رمز عیور سیستم عامل"),
|
||||
("OS Password", "رمز عبور سیستم عامل"),
|
||||
("install_tip", "لطفا برنامه را نصب کنید UAC و جلوگیری از خطای RustDesk برای راحتی در استفاده از نرم افزار"),
|
||||
("Click to upgrade", "برای ارتقا کلیک کنید"),
|
||||
("Click to download", "برای دانلود کلیک کنید"),
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "نام کاربری وجود ندارد"),
|
||||
("Password missed", "رمزعبور وجود ندارد"),
|
||||
("Wrong credentials", "اعتبارنامه نادرست است"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "ویرایش برچسب"),
|
||||
("Unremember Password", "رمز عبور ذخیره نشود"),
|
||||
("Favorites", "اتصالات دلخواه"),
|
||||
@@ -511,6 +512,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Collapse toolbar", "جمع کردن نوار ابزار"),
|
||||
("Accept and Elevate", "بپذیرید و افزایش دهید"),
|
||||
("accept_and_elevate_btn_tooltip", "را افزایش دهید UAC اتصال را بپذیرید و مجوزهای."),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("clipboard_wait_response_timeout_tip", "زمان انتظار برای مشخص شدن وضعیت کپی تمام شد."),
|
||||
("Incoming connection", "اتصال ورودی"),
|
||||
("Outgoing connection", "اتصال خروجی"),
|
||||
("Exit", "خروج"),
|
||||
("Open", "باز کردن"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nom d'utilisateur manquant"),
|
||||
("Password missed", "Mot de passe manquant"),
|
||||
("Wrong credentials", "Identifiant ou mot de passe erroné"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Modifier la balise"),
|
||||
("Unremember Password", "Oublier le Mot de passe"),
|
||||
("Favorites", "Favoris"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Üres felhasználónév"),
|
||||
("Password missed", "Üres jelszó"),
|
||||
("Wrong credentials", "Hibás felhasználónév vagy jelszó"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Címke szerkesztése"),
|
||||
("Unremember Password", "A jelszó megjegyzésének törlése"),
|
||||
("Favorites", "Kedvencek"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Username tidak sesuai"),
|
||||
("Password missed", "Kata sandi tidak sesuai"),
|
||||
("Wrong credentials", "Username atau password salah"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Ubah Tag"),
|
||||
("Unremember Password", "Lupa Kata Sandi"),
|
||||
("Favorites", "Favorit"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nome utente mancante"),
|
||||
("Password missed", "Password mancante"),
|
||||
("Wrong credentials", "Credenziali errate"),
|
||||
("The verification code is incorrect or has expired", "Il codice di verifica non è corretto o è scaduto"),
|
||||
("Edit Tag", "Modifica tag"),
|
||||
("Unremember Password", "Dimentica password"),
|
||||
("Favorites", "Preferiti"),
|
||||
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Voice call", "Chiamata vocale"),
|
||||
("Text chat", "Chat testuale"),
|
||||
("Stop voice call", "Interrompi chiamata vocale"),
|
||||
("relay_hint_tip", "Se non è possibile connettersi direttamente, puoi provare a farlo tramite relay.\nInoltre, se si vuoi usare il relay al primo tentativo, è possibile aggiungere all'ID il suffisso '/r\' o selezionare nella scheda peer l'opzione 'Collegati sempre tramite relay'."),
|
||||
("relay_hint_tip", "Se non è possibile connettersi direttamente, puoi provare a farlo tramite relay.\nInoltre, se si vuoi usare il relay al primo tentativo, è possibile aggiungere all'ID il suffisso '/r\' o selezionare nella scheda se esiste l'opzione 'Collegati sempre tramite relay'."),
|
||||
("Reconnect", "Riconnetti"),
|
||||
("Codec", "Codec"),
|
||||
("Resolution", "Risoluzione"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", "Accetta ed eleva"),
|
||||
("accept_and_elevate_btn_tooltip", "Accetta la connessione ed eleva le autorizzazioni UAC."),
|
||||
("clipboard_wait_response_timeout_tip", "Timeout attesa risposta della copia."),
|
||||
].iter().cloned().collect();
|
||||
("Incoming connection", "Connessioni in entrata"),
|
||||
("Outgoing connection", "Conenssioni in uscita"),
|
||||
("Exit", "Esci da RustDesk"),
|
||||
("Open", "Apri RustDesk"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "ユーザー名がありません"),
|
||||
("Password missed", "パスワードがありません"),
|
||||
("Wrong credentials", "資格情報が間違っています"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "タグを編集"),
|
||||
("Unremember Password", "パスワードの記憶を解除"),
|
||||
("Favorites", "お気に入り"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "사용자명 누락"),
|
||||
("Password missed", "비밀번호 누락"),
|
||||
("Wrong credentials", "틀린 인증 정보"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "태그 수정"),
|
||||
("Unremember Password", "패스워드 기억하지 않기"),
|
||||
("Favorites", "즐겨찾기"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Қолданушы аты бос"),
|
||||
("Password missed", "Құпия сөз бос"),
|
||||
("Wrong credentials", "Бұрыс тіркелгі деректер"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Тақты Өндеу"),
|
||||
("Unremember Password", "Құпия сөзді Ұмыту"),
|
||||
("Favorites", "Таңдаулылар"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Prarastas vartotojo vardas"),
|
||||
("Password missed", "Slaptažodis praleistas"),
|
||||
("Wrong credentials", "Klaidingi kredencialai"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Redaguoti žymą"),
|
||||
("Unremember Password", "Nebeprisiminti slaptažodžio"),
|
||||
("Favorites", "Parankiniai"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Gebruikersnaam gemist"),
|
||||
("Password missed", "Wachtwoord vergeten"),
|
||||
("Wrong credentials", "Verkeerde inloggegevens"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Label Bewerken"),
|
||||
("Unremember Password", "Wachtwoord vergeten"),
|
||||
("Favorites", "Favorieten"),
|
||||
@@ -511,6 +512,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Collapse toolbar", "Werkbalk samenvouwen"),
|
||||
("Accept and Elevate", "Accepteren en Verheffen"),
|
||||
("accept_and_elevate_btn_tooltip", "Accepteer de verbinding en verhoog de UAC-machtigingen."),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("clipboard_wait_response_timeout_tip", "Time-out in afwachting van kopieer-antwoord."),
|
||||
("Incoming connection", "Inkomende verbinding"),
|
||||
("Outgoing connection", "Uitgaande verbinding"),
|
||||
("Exit", "Verlaten"),
|
||||
("Open", "Open"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nieprawidłowe nazwa użytkownika"),
|
||||
("Password missed", "Nieprawidłowe hasło"),
|
||||
("Wrong credentials", "Błędne dane uwierzytelniające"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Edytuj tag"),
|
||||
("Unremember Password", "Zapomnij hasło"),
|
||||
("Favorites", "Ulubione"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", "Akceptuj i Podnieś uprawnienia"),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nome de utilizador em falta"),
|
||||
("Password missed", "Palavra-chave em falta"),
|
||||
("Wrong credentials", "Nome de utilizador ou palavra-chave incorrectos"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar Tag"),
|
||||
("Unremember Password", "Esquecer Palavra-chave"),
|
||||
("Favorites", "Favoritos"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Nome de usuário requerido"),
|
||||
("Password missed", "Senha requerida"),
|
||||
("Wrong credentials", "Nome de usuário ou senha incorretos"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Editar Tag"),
|
||||
("Unremember Password", "Esquecer Senha"),
|
||||
("Favorites", "Favoritos"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Username missed", "Lipsește numele de utilizator"),
|
||||
("Password missed", "Lipsește parola"),
|
||||
("Wrong credentials", "Nume sau parolă greșită"),
|
||||
("The verification code is incorrect or has expired", ""),
|
||||
("Edit Tag", "Modifică etichetă"),
|
||||
("Unremember Password", "Uită parola"),
|
||||
("Favorites", "Favorite"),
|
||||
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Accept and Elevate", ""),
|
||||
("accept_and_elevate_btn_tooltip", ""),
|
||||
("clipboard_wait_response_timeout_tip", ""),
|
||||
("Incoming connection", ""),
|
||||
("Outgoing connection", ""),
|
||||
("Exit", ""),
|
||||
("Open", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user