114 Commits
1.2.0 ... 1.2.1

121 changed files with 1263 additions and 461 deletions

View File

@@ -13,8 +13,6 @@ on:
push: push:
branches: branches:
- master - master
tags:
- '*'
paths-ignore: paths-ignore:
- ".github/**" - ".github/**"
- "docs/**" - "docs/**"

View File

@@ -6,6 +6,9 @@ on:
upload-artifact: upload-artifact:
type: boolean type: boolean
default: true default: true
upload-tag:
type: string
default: "nightly"
env: env:
CARGO_NDK_VERSION: "3.1.2" CARGO_NDK_VERSION: "3.1.2"
@@ -15,11 +18,11 @@ env:
# for arm64 linux # for arm64 linux
FLUTTER_ELINUX_VERSION: "3.10.5" FLUTTER_ELINUX_VERSION: "3.10.5"
FLUTTER_ELINUX_COMMIT_ID: "410b3ca42f2cd0c485edf517a1666652bab442d4" FLUTTER_ELINUX_COMMIT_ID: "410b3ca42f2cd0c485edf517a1666652bab442d4"
TAG_NAME: "nightly" TAG_NAME: "${{ inputs.upload-tag }}"
# vcpkg version: 2023.04.15 # vcpkg version: 2023.04.15
# for multiarch gcc compatibility # for multiarch gcc compatibility
VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1" VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1"
VERSION: "1.2.0" VERSION: "1.2.1"
NDK_VERSION: "r25c" NDK_VERSION: "r25c"
#signing keys env variable checks #signing keys env variable checks
ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}' ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}'
@@ -374,6 +377,7 @@ jobs:
uses: ./.github/workflows/bridge.yml uses: ./.github/workflows/bridge.yml
build-rustdesk-ios: build-rustdesk-ios:
if: ${{ inputs.upload-artifact == 'true' }}
needs: [generate-bridge-linux] needs: [generate-bridge-linux]
name: build rustdesk ios ipa ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] name: build rustdesk ios ipa ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@@ -779,6 +783,7 @@ jobs:
path: target/release/liblibrustdesk.so path: target/release/liblibrustdesk.so
build-rustdesk-lib-linux-arm: build-rustdesk-lib-linux-arm:
if: ${{ inputs.upload-artifact == 'true' }}
needs: [generate-bridge-linux, build-vcpkg-deps-linux] needs: [generate-bridge-linux, build-vcpkg-deps-linux]
name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] name: build-rust-lib ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@@ -941,6 +946,7 @@ jobs:
path: target/release/liblibrustdesk.so path: target/release/liblibrustdesk.so
build-rustdesk-sciter-arm: build-rustdesk-sciter-arm:
if: ${{ inputs.upload-artifact == 'true' }}
needs: [build-vcpkg-deps-linux] needs: [build-vcpkg-deps-linux]
name: build-rustdesk(sciter) ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] name: build-rustdesk(sciter) ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@@ -1099,6 +1105,7 @@ jobs:
path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}-sciter.deb
build-rustdesk-linux-arm: build-rustdesk-linux-arm:
if: ${{ inputs.upload-artifact == 'true' }}
needs: [build-rustdesk-lib-linux-arm] needs: [build-rustdesk-lib-linux-arm]
name: build-rustdesk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}] 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 runs-on: ubuntu-20.04 # 20.04 has more performance on arm build

View File

@@ -9,8 +9,6 @@ on:
push: push:
branches: branches:
- master - master
tags:
- '*'
paths-ignore: paths-ignore:
- ".github/**" - ".github/**"
- "docs/**" - "docs/**"
@@ -21,4 +19,4 @@ jobs:
uses: ./.github/workflows/flutter-build.yml uses: ./.github/workflows/flutter-build.yml
with: with:
upload-artifact: false upload-artifact: false

View File

@@ -12,3 +12,4 @@ jobs:
secrets: inherit secrets: inherit
with: with:
upload-artifact: true upload-artifact: true
upload-tag: "nightly"

18
.github/workflows/flutter-tag.yml vendored Normal file
View 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"

View File

@@ -10,7 +10,7 @@ env:
# vcpkg version: 2022.05.10 # vcpkg version: 2022.05.10
# for multiarch gcc compatibility # for multiarch gcc compatibility
VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44" VCPKG_COMMIT_ID: "14e7bb4ae24616ec54ff6b2f6ef4e8659434ea44"
VERSION: "1.2.0" VERSION: "1.2.1"
jobs: jobs:
build-for-windows-2022-12-05: build-for-windows-2022-12-05:

4
Cargo.lock generated
View File

@@ -1054,7 +1054,7 @@ dependencies = [
[[package]] [[package]]
name = "confy" name = "confy"
version = "0.4.0-2" version = "0.4.0-2"
source = "git+https://github.com/open-trade/confy#9f231b2039cf8a8f8cdf6b829c5ac0016e146077" source = "git+https://github.com/open-trade/confy#7855cd3c32b1a60b44e5076ee8f6b4131da10350"
dependencies = [ dependencies = [
"directories-next", "directories-next",
"serde 1.0.163", "serde 1.0.163",
@@ -5146,7 +5146,7 @@ dependencies = [
[[package]] [[package]]
name = "rustdesk" name = "rustdesk"
version = "1.2.0" version = "1.2.1"
dependencies = [ dependencies = [
"android_logger", "android_logger",
"arboard", "arboard",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rustdesk" name = "rustdesk"
version = "1.2.0" version = "1.2.1"
authors = ["rustdesk <info@rustdesk.com>"] authors = ["rustdesk <info@rustdesk.com>"]
edition = "2021" edition = "2021"
build= "build.rs" build= "build.rs"

View File

@@ -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. 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 | | 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 | [Hetzner](https://www.hetzner.com) | 2 vCPU / 4 GB RAM |
| Germany | [Codext](https://codext.de) | 4 vCPU / 8 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 | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |

View File

@@ -2,7 +2,7 @@
version: 1 version: 1
script: script:
- rm -rf ./AppDir || true - rm -rf ./AppDir || true
- bsdtar -zxvf ../rustdesk-1.2.0.deb - bsdtar -zxvf ../rustdesk-1.2.1.deb
- tar -xvf ./data.tar.xz - tar -xvf ./data.tar.xz
- mkdir ./AppDir - mkdir ./AppDir
- mv ./usr ./AppDir/usr - mv ./usr ./AppDir/usr
@@ -17,7 +17,7 @@ AppDir:
id: rustdesk id: rustdesk
name: rustdesk name: rustdesk
icon: rustdesk icon: rustdesk
version: 1.2.0 version: 1.2.1
exec: usr/lib/rustdesk/rustdesk exec: usr/lib/rustdesk/rustdesk
exec_args: $@ exec_args: $@
apt: apt:
@@ -50,6 +50,7 @@ AppDir:
- libva-x11-2 - libva-x11-2
- libvdpau1 - libvdpau1
- libgstreamer-plugins-base1.0-0 - libgstreamer-plugins-base1.0-0
- gstreamer1.0-pipewire
- libwayland-cursor0 - libwayland-cursor0
- libwayland-egl1 - libwayland-egl1
- libpulse0 - libpulse0

View File

@@ -2,7 +2,7 @@
version: 1 version: 1
script: script:
- rm -rf ./AppDir || true - rm -rf ./AppDir || true
- bsdtar -zxvf ../rustdesk-1.2.0.deb - bsdtar -zxvf ../rustdesk-1.2.1.deb
- tar -xvf ./data.tar.xz - tar -xvf ./data.tar.xz
- mkdir ./AppDir - mkdir ./AppDir
- mv ./usr ./AppDir/usr - mv ./usr ./AppDir/usr
@@ -17,7 +17,7 @@ AppDir:
id: rustdesk id: rustdesk
name: rustdesk name: rustdesk
icon: rustdesk icon: rustdesk
version: 1.2.0 version: 1.2.1
exec: usr/lib/rustdesk/rustdesk exec: usr/lib/rustdesk/rustdesk
exec_args: $@ exec_args: $@
apt: apt:
@@ -52,6 +52,7 @@ AppDir:
- libva-x11-2 - libva-x11-2
- libvdpau1 - libvdpau1
- libgstreamer-plugins-base1.0-0 - libgstreamer-plugins-base1.0-0
- gstreamer1.0-pipewire
- libwayland-cursor0 - libwayland-cursor0
- libwayland-egl1 - libwayland-egl1
- libpulse0 - libpulse0

View File

@@ -285,7 +285,7 @@ Version: %s
Architecture: %s Architecture: %s
Maintainer: rustdesk <info@rustdesk.com> Maintainer: rustdesk <info@rustdesk.com>
Homepage: https://rustdesk.com Homepage: https://rustdesk.com
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g 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. Description: A remote control software.
""" % (version, get_arch()) """ % (version, get_arch())

View File

@@ -32,9 +32,9 @@
فيما يلي الخوادم التي تستخدمها مجانًا، وقد تتغير طوال الوقت. إذا لم تكن قريبًا من أحد هؤلاء، فقد تكون شبكتك بطيئة. فيما يلي الخوادم التي تستخدمها مجانًا، وقد تتغير طوال الوقت. إذا لم تكن قريبًا من أحد هؤلاء، فقد تكون شبكتك بطيئة.
| الموقع | المورد | المواصفات | | الموقع | المورد | المواصفات |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |
## التبعيات ## التبعيات

View File

@@ -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é. 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 | | umístění | dodavatel | parametry |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB 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í ## Softwarové součásti, na kterých závisí

View File

@@ -25,9 +25,9 @@ Nedenfor er de servere, du bruger gratis, det kan ændre sig med tiden. Hvis du
| Beliggenhed | Udbyder | Specifikation | | Beliggenhed | Udbyder | Specifikation |
| ---------- | ------------- | ------------------ | | ---------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
## Afhængigheder ## Afhængigheder

View File

@@ -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. 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 | | 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 | [Hetzner](https://www.hetzner.com/de/) | 2 vCPU / 4 GB RAM |
| Deutschland | [Codext](https://codext.de/) | 4 vCPU / 8 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 | | Ukraine (Kiew) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4 GB RAM |

View File

@@ -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. 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 | | Situo | Vendanto | Detaloj |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -31,7 +31,6 @@ A continuación se muestran los servidores gratuitos, pueden cambiar a medida qu
| Ubicación | Compañía | Especificación | | Ubicación | Compañía | Especificación |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -30,7 +30,6 @@
شما مي‌توانید از سرورهای زیر به رایگان استفاده کنید. این لیست ممکن است به مرور زمان تغییر می‌کند. اگر به این سرورها نزدیک نیستید، ممکن است اتصال شما کند باشد. شما مي‌توانید از سرورهای زیر به رایگان استفاده کنید. این لیست ممکن است به مرور زمان تغییر می‌کند. اگر به این سرورها نزدیک نیستید، ممکن است اتصال شما کند باشد.
| موقعیت | سرویس دهنده | مشخصات | | موقعیت | سرویس دهنده | مشخصات |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| کره‌ی جنوبی، سئول | AWS lightsail | 1 vCPU / 0.5GB RAM |
| آلمان | Hetzner | 2 vCPU / 4GB RAM | | آلمان | Hetzner | 2 vCPU / 4GB RAM |
| آلمان | Codext | 4 vCPU / 8GB RAM | | آلمان | Codext | 4 vCPU / 8GB RAM |

View File

@@ -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. 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 | | Sijainti | Myyjä | Määrittely |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -25,7 +25,6 @@ Ci-dessous se trouvent les serveurs que vous utilisez gratuitement, cela peut ch
| Location | Vendor | Specification | | Location | Vendor | Specification |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -34,7 +34,6 @@
Παρακάτω είναι οι διακομιστές που χρησιμοποιούνται δωρεάν, ενδέχεται να αλλάξουν με την πάροδο του χρόνου. Εάν δεν είστε κοντά σε ένα από αυτούς, το δίκτυό σας ίσως να είναι αργό. Παρακάτω είναι οι διακομιστές που χρησιμοποιούνται δωρεάν, ενδέχεται να αλλάξουν με την πάροδο του χρόνου. Εάν δεν είστε κοντά σε ένα από αυτούς, το δίκτυό σας ίσως να είναι αργό.
| Περιοχή | Πάροχος | Προδιαγραφές | | Περιοχή | Πάροχος | Προδιαγραφές |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Σεούλ | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Γερμανία | Hetzner | 2 vCPU / 4GB RAM | | Γερμανία | Hetzner | 2 vCPU / 4GB RAM |
| Γερμανία | Codext | 4 vCPU / 8GB RAM | | Γερμανία | Codext | 4 vCPU / 8GB RAM |
| Ουκρανία (Κίεβο) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ουκρανία (Κίεβο) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -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. 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ó | | Hely | Host | Specifikáció |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -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. 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 | | Lokasi | Vendor | Spesifikasi |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -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. 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 | | Posizione | Vendor | Specifiche |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |
| Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -29,7 +29,6 @@ RustDeskは誰からの貢献も歓迎します。 貢献するには [`docs/CON
下記のサーバーは、無料で使用できますが、後々変更されることがあります。これらのサーバーから遠い場合、接続が遅い可能性があります。 下記のサーバーは、無料で使用できますが、後々変更されることがあります。これらのサーバーから遠い場合、接続が遅い可能性があります。
| Location | Vendor | Specification | | Location | Vendor | Specification |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -29,7 +29,6 @@ RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`docs/C
표에 있는 서버는 무료로 사용할 수 있지만 추후 변경될 수도 있습니다. 이 서버에서 멀다면, 네트워크가 느려질 가능성도 있습니다. 표에 있는 서버는 무료로 사용할 수 있지만 추후 변경될 수도 있습니다. 이 서버에서 멀다면, 네트워크가 느려질 가능성도 있습니다.
| Location | Vendor | Specification | | Location | Vendor | Specification |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -24,7 +24,6 @@
നിങ്ങൾ സൗജന്യമായി ഉപയോഗിക്കുന്ന സെർവറുകൾ ചുവടെയുണ്ട്, അത് സമയത്തിനനുസരിച്ച് മാറിയേക്കാം. നിങ്ങൾ ഇവയിലൊന്നിനോട് അടുത്തല്ലെങ്കിൽ, നിങ്ങളുടെ നെറ്റ്‌വർക്ക് സ്ലോ ആയേക്കാം. നിങ്ങൾ സൗജന്യമായി ഉപയോഗിക്കുന്ന സെർവറുകൾ ചുവടെയുണ്ട്, അത് സമയത്തിനനുസരിച്ച് മാറിയേക്കാം. നിങ്ങൾ ഇവയിലൊന്നിനോട് അടുത്തല്ലെങ്കിൽ, നിങ്ങളുടെ നെറ്റ്‌വർക്ക് സ്ലോ ആയേക്കാം.
| സ്ഥാനം | കച്ചവടക്കാരൻ | വിവരണം | | സ്ഥാനം | കച്ചവടക്കാരൻ | വിവരണം |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -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. 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 | | Locatie | Aanbieder | Specificaties |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Duitsland | Hetzner | 2 vCPU / 4GB RAM | | Duitsland | Hetzner | 2 vCPU / 4GB RAM |
| Duitsland | Codext | 4 vCPU / 8GB RAM | | Duitsland | Codext | 4 vCPU / 8GB RAM |
| Oekraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Oekraine (Kyiv) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -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. 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 | | Lokalizacja | Dostawca | Specyfikacja |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Korea Płd. (Seul) | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Niemcy | Hetzner | 2 vCPU / 4GB RAM | | Niemcy | Hetzner | 2 vCPU / 4GB RAM |
| Niemcy | Codext | 4 vCPU / 8GB RAM | | Niemcy | Codext | 4 vCPU / 8GB RAM |
| Ukraina (Kijów) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Ukraina (Kijów) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -25,7 +25,6 @@ Abaixo estão os servidores que você está utilizando de graça, ele pode mudar
| Localização | Fornecedor | Especificações | | Localização | Fornecedor | Especificações |
| ----------- | ------------- | ------------------ | | ----------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -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 | | Германия | Hetzner | 2 vCPU / 4GB RAM |
| Германия | Codext | 4 vCPU / 8GB RAM | | Германия | Codext | 4 vCPU / 8GB RAM |
| Россия (Москва) | [nt-vps](https://nt-vps.ru) | 8 vCPU / 8GB RAM |
## Зависимости ## Зависимости

View File

@@ -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 | | Німеччина | Hetzner | 2 VCPU / 4GB RAM |
| Німеччина | Codext | 4 vCPU / 8GB RAM | | Німеччина | Codext | 4 vCPU / 8GB RAM |
| Україна (Київ) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM | | Україна (Київ) | [dc.volia](https://dc.volia.com) | 2 vCPU / 4GB RAM |

View File

@@ -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 | | Đị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 | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -26,7 +26,6 @@ Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https:
| Location | Vendor | Specification | | Location | Vendor | Specification |
| --------- | ------------- | ------------------ | | --------- | ------------- | ------------------ |
| Seoul | AWS lightsail | 1 vCPU / 0.5GB RAM |
| Germany | Hetzner | 2 vCPU / 4GB RAM | | Germany | Hetzner | 2 vCPU / 4GB RAM |
| Germany | Codext | 4 vCPU / 8GB RAM | | Germany | Codext | 4 vCPU / 8GB RAM |

View File

@@ -12,7 +12,7 @@
"name": "rustdesk", "name": "rustdesk",
"buildsystem": "simple", "buildsystem": "simple",
"build-commands": [ "build-commands": [
"bsdtar -zxvf rustdesk-1.2.0.deb", "bsdtar -zxvf rustdesk-1.2.1.deb",
"tar -xvf ./data.tar.xz", "tar -xvf ./data.tar.xz",
"cp -r ./usr/* /app/", "cp -r ./usr/* /app/",
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk", "mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
@@ -25,7 +25,7 @@
"sources": [ "sources": [
{ {
"type": "file", "type": "file",
"path": "../rustdesk-1.2.0.deb" "path": "../rustdesk-1.2.1.deb"
}, },
{ {
"type": "file", "type": "file",

View File

@@ -176,6 +176,10 @@ class MyTheme {
static const Color dark = Colors.black87; static const Color dark = Colors.black87;
static const Color button = Color(0xFF2C8CFF); static const Color button = Color(0xFF2C8CFF);
static const Color hoverBorder = Color(0xFF999999); 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 // ListTile
static const ListTileThemeData listTileTheme = ListTileThemeData( 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) { if (!isDesktop) {
return; return;
} }
@@ -1538,7 +1542,7 @@ Future<bool> initUniLinks() async {
if (initialLink == null) { if (initialLink == null) {
return false; return false;
} }
return parseRustdeskUri(initialLink); return handleUriLink(uriString: initialLink);
} catch (err) { } catch (err) {
debugPrintStack(label: "$err"); debugPrintStack(label: "$err");
return false; return false;
@@ -1559,7 +1563,7 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
debugPrint("A uri was received: $uri."); debugPrint("A uri was received: $uri.");
if (uri != null) { if (uri != null) {
if (handleByFlutter) { if (handleByFlutter) {
callUniLinksUriHandler(uri); handleUriLink(uri: uri);
} else { } else {
bind.sendUrlScheme(url: uri.toString()); bind.sendUrlScheme(url: uri.toString());
} }
@@ -1572,90 +1576,147 @@ StreamSubscription? listenUniLinks({handleByFlutter = true}) {
return sub; return sub;
} }
/// Handle command line arguments enum UriLinkType {
/// remoteDesktop,
/// * Returns true if we successfully handle the startup arguments. fileTransfer,
bool checkArguments() { portForward,
if (kBootArgs.isNotEmpty) { rdp,
final ret = parseRustdeskUri(kBootArgs.first); }
if (ret) {
return true; // 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] if (args == null) return false;
// check connect args
var connectIndex = kBootArgs.indexOf("--connect"); UriLinkType? type;
if (connectIndex == -1) { String? id;
return false; String? password;
} String? switchUuid;
String? id = bool? forceRelay;
kBootArgs.length <= connectIndex + 1 ? null : kBootArgs[connectIndex + 1]; for (int i = 0; i < args.length; i++) {
String? password = switch (args[i]) {
kBootArgs.length <= connectIndex + 2 ? null : kBootArgs[connectIndex + 2]; case '--connect':
if (password != null && password.startsWith("--")) { case '--play':
password = null; type = UriLinkType.remoteDesktop;
} id = args[i + 1];
final switchUuidIndex = kBootArgs.indexOf("--switch_uuid"); i++;
String? switchUuid = kBootArgs.length <= switchUuidIndex + 1 break;
? null case '--file-transfer':
: kBootArgs[switchUuidIndex + 1]; type = UriLinkType.fileTransfer;
if (id != null) { id = args[i + 1];
if (id.startsWith(kUniLinksPrefix)) { i++;
return parseRustdeskUri(id); break;
} else { case '--port-forward':
// remove "--connect xxx" in the `bootArgs` array type = UriLinkType.portForward;
kBootArgs.removeAt(connectIndex); id = args[i + 1];
kBootArgs.removeAt(connectIndex); i++;
// fallback to peer id break;
Future.delayed(Duration.zero, () { case '--rdp':
rustDeskWinManager.newRemoteDesktop(id, type = UriLinkType.rdp;
password: password, switch_uuid: switchUuid); id = args[i + 1];
}); i++;
return true; 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; return false;
} }
/// Parse `rustdesk://` unilinks List<String>? urlLinkToCmdArgs(Uri uri) {
/// String? command;
/// Returns true if we successfully handle the uri provided. String? id;
/// [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;
if (uri.authority == "connection" && uri.path.startsWith("/new/")) { if (uri.authority == "connection" && uri.path.startsWith("/new/")) {
peerId = uri.path.substring("/new/".length); // For compatibility
} else if (uri.authority == "connect") { command = '--connect';
peerId = uri.path.substring(1); 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) { } else if (uri.authority.length > 2 && uri.path.length <= 1) {
// "/" or "" // rustdesk://<connect-id>
peerId = uri.authority; command = '--connect';
} else { id = uri.authority;
return false;
} }
var param = uri.queryParameters;
String? switch_uuid = param["switch_uuid"]; List<String> args = List.empty(growable: true);
String? password = param["password"]; if (command != null && id != null) {
Future.delayed(Duration.zero, () { args.add(command);
rustDeskWinManager.newRemoteDesktop(peerId, args.add(id);
password: password, switch_uuid: switch_uuid); var param = uri.queryParameters;
}); String? password = param["password"];
return true; 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, connectMainDesktop(String id,
@@ -2101,3 +2162,64 @@ Future<void> start_service(bool is_start) async {
bind.mainSetOption(key: "stop-service", value: is_start ? "" : "Y"); 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))
],
);
}

View File

@@ -285,6 +285,29 @@ class PeerStringOption {
Get.find<RxString>(tag: tag(id, opt)); 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) { initSharedStates(String id) {
PrivacyModeState.init(id); PrivacyModeState.init(id);
BlockInputState.init(id); BlockInputState.init(id);
@@ -294,6 +317,7 @@ initSharedStates(String id) {
RemoteCursorMovedState.init(id); RemoteCursorMovedState.init(id);
FingerprintState.init(id); FingerprintState.init(id);
PeerBoolOption.init(id, 'zoom-cursor', () => false); PeerBoolOption.init(id, 'zoom-cursor', () => false);
UnreadChatCountState.init(id);
} }
removeSharedStates(String id) { removeSharedStates(String id) {
@@ -305,4 +329,5 @@ removeSharedStates(String id) {
RemoteCursorMovedState.delete(id); RemoteCursorMovedState.delete(id);
FingerprintState.delete(id); FingerprintState.delete(id);
PeerBoolOption.delete(id, 'zoom-cursor'); PeerBoolOption.delete(id, 'zoom-cursor');
UnreadChatCountState.delete(id);
} }

View File

@@ -7,10 +7,16 @@ import 'package:provider/provider.dart';
import '../../mobile/pages/home_page.dart'; import '../../mobile/pages/home_page.dart';
enum ChatPageType {
mobileMain,
desktopCM,
}
class ChatPage extends StatelessWidget implements PageShape { class ChatPage extends StatelessWidget implements PageShape {
late final ChatModel chatModel; late final ChatModel chatModel;
final ChatPageType? type;
ChatPage({ChatModel? chatModel}) { ChatPage({ChatModel? chatModel, this.type}) {
this.chatModel = chatModel ?? gFFI.chatModel; this.chatModel = chatModel ?? gFFI.chatModel;
} }
@@ -18,27 +24,53 @@ class ChatPage extends StatelessWidget implements PageShape {
final title = translate("Chat"); final title = translate("Chat");
@override @override
final icon = Icon(Icons.chat); final icon = unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum);
@override @override
final appBarActions = [ final appBarActions = [
PopupMenuButton<int>( PopupMenuButton<MessageKey>(
tooltip: "", tooltip: "",
icon: Icon(Icons.group), icon: unreadTopRightBuilder(gFFI.chatModel.mobileUnreadSum,
icon: Icon(Icons.group)),
itemBuilder: (context) { itemBuilder: (context) {
// only mobile need [appBarActions], just bind gFFI.chatModel // only mobile need [appBarActions], just bind gFFI.chatModel
final chatModel = gFFI.chatModel; final chatModel = gFFI.chatModel;
return chatModel.messages.entries.map((entry) { return chatModel.messages.entries.map((entry) {
final id = entry.key; final key = entry.key;
final user = entry.value.chatUser; final user = entry.value.chatUser;
return PopupMenuItem<int>( final client = gFFI.serverModel.clients
child: Text("${user.firstName} ${user.id}"), .firstWhereOrNull((e) => e.id == key.connId);
value: id, 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(); }).toList();
}, },
onSelected: (id) { onSelected: (key) {
gFFI.chatModel.changeCurrentID(id); gFFI.chatModel.changeCurrentKey(key);
}) })
]; ];
@@ -50,16 +82,27 @@ class ChatPage extends StatelessWidget implements PageShape {
color: Theme.of(context).scaffoldBackgroundColor, color: Theme.of(context).scaffoldBackgroundColor,
child: Consumer<ChatModel>( child: Consumer<ChatModel>(
builder: (context, chatModel, child) { 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( return Stack(
children: [ children: [
LayoutBuilder(builder: (context, constraints) { LayoutBuilder(builder: (context, constraints) {
final chat = DashChat( final chat = DashChat(
onSend: chatModel.send, onSend: chatModel.send,
currentUser: chatModel.me, currentUser: chatModel.me,
messages: messages: chatModel
chatModel.messages[chatModel.currentID]?.chatMessages ?? .messages[chatModel.currentKey]?.chatMessages ??
[], [],
readOnly: readOnly,
inputOptions: InputOptions( inputOptions: InputOptions(
focusNode: chatModel.inputNode, focusNode: chatModel.inputNode,
textController: chatModel.textController, textController: chatModel.textController,
@@ -127,22 +170,6 @@ class ChatPage extends StatelessWidget implements PageShape {
); );
return SelectionArea(child: chat); 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); ).paddingOnly(bottom: 8);
}, },

View File

@@ -943,16 +943,20 @@ showSetOSPassword(
SessionID sessionId, SessionID sessionId,
bool login, bool login,
OverlayDialogManager dialogManager, OverlayDialogManager dialogManager,
String? osPassword,
Function()? closeCallback,
) async { ) async {
final controller = TextEditingController(); final controller = TextEditingController();
var password = osPassword ??= await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ?? '';
await bind.sessionGetOption(sessionId: sessionId, arg: 'os-password') ??
'';
var autoLogin = var autoLogin =
await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') != await bind.sessionGetOption(sessionId: sessionId, arg: 'auto-login') !=
''; '';
controller.text = password; controller.text = osPassword;
dialogManager.show((setState, close, context) { dialogManager.show((setState, close, context) {
closeWithCallback([dynamic]) {
close();
if (closeCallback != null) closeCallback();
}
submit() { submit() {
var text = controller.text.trim(); var text = controller.text.trim();
bind.sessionPeerOption( bind.sessionPeerOption(
@@ -964,7 +968,7 @@ showSetOSPassword(
if (text != '' && login) { if (text != '' && login) {
bind.sessionInputOsPassword(sessionId: sessionId, value: text); bind.sessionInputOsPassword(sessionId: sessionId, value: text);
} }
close(); closeWithCallback();
} }
return CustomAlertDialog( return CustomAlertDialog(
@@ -998,7 +1002,7 @@ showSetOSPassword(
dialogButton( dialogButton(
"Cancel", "Cancel",
icon: Icon(Icons.close_rounded), icon: Icon(Icons.close_rounded),
onPressed: close, onPressed: closeWithCallback,
isOutline: true, isOutline: true,
), ),
dialogButton( dialogButton(
@@ -1008,7 +1012,7 @@ showSetOSPassword(
), ),
], ],
onSubmit: submit, onSubmit: submit,
onCancel: close, onCancel: closeWithCallback,
); );
}); });
} }

View File

@@ -434,11 +434,16 @@ Future<bool?> loginDialog() async {
} }
break; break;
case HttpType.kAuthResTypeEmailCheck: case HttpType.kAuthResTypeEmailCheck:
setState(() => isInProgress = false); if (isMobile) {
final res = await verificationCodeDialog(resp.user);
if (res == true) {
close(true); close(true);
return; verificationCodeDialog(resp.user);
} else {
setState(() => isInProgress = false);
final res = await verificationCodeDialog(resp.user);
if (res == true) {
close(true);
return;
}
} }
break; break;
default: default:
@@ -512,7 +517,11 @@ Future<bool?> loginDialog() async {
size: 25, size: 25,
// No need to handle the branch of null. // No need to handle the branch of null.
// Because we can ensure the color is not null when debug. // 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, onTap: onDialogCancel,
hoverColor: Colors.red, hoverColor: Colors.red,

View File

@@ -32,7 +32,7 @@ class DraggableChatWindow extends StatelessWidget {
width: width, width: width,
height: height, height: height,
builder: (context, onPanUpdate) { builder: (context, onPanUpdate) {
return isIOS final child = isIOS
? ChatPage(chatModel: chatModel) ? ChatPage(chatModel: chatModel)
: Scaffold( : Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
@@ -44,6 +44,10 @@ class DraggableChatWindow extends StatelessWidget {
), ),
body: ChatPage(chatModel: chatModel), body: ChatPage(chatModel: chatModel),
); );
return Container(
decoration:
BoxDecoration(border: Border.all(color: MyTheme.border)),
child: child);
}); });
} }

View File

@@ -11,6 +11,8 @@ import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
bool isEditOsPassword = false;
class TTextMenu { class TTextMenu {
final Widget child; final Widget child;
final VoidCallback onPressed; final VoidCallback onPressed;
@@ -44,6 +46,28 @@ class TToggleMenu {
{required this.child, required this.value, required this.onChanged}); {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) { List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
final ffiModel = ffi.ffiModel; final ffiModel = ffi.ffiModel;
final pi = ffiModel.pi; final pi = ffiModel.pi;
@@ -63,17 +87,26 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
// osAccount / osPassword // osAccount / osPassword
v.add( v.add(
TTextMenu( TTextMenu(
child: Row(children: [ child: Row(children: [
Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')), Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')),
Offstage( Offstage(
offstage: isDesktop, offstage: isDesktop,
child: child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12),
Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12)) )
]), ]),
trailingIcon: Transform.scale(scale: 0.8, child: Icon(Icons.edit)), trailingIcon: Transform.scale(
onPressed: () => pi.is_headless scale: 0.8,
? showSetOSAccount(sessionId, ffi.dialogManager) child: InkWell(
: showSetOSPassword(sessionId, false, ffi.dialogManager)), 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 // paste
if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) { if (isMobile && perms['keyboard'] != false && perms['clipboard'] != false) {

View File

@@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart'; import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/models/user_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';

View File

@@ -1559,7 +1559,7 @@ class _AboutState extends State<_About> {
.marginSymmetric(vertical: 4.0)), .marginSymmetric(vertical: 4.0)),
InkWell( InkWell(
onTap: () { onTap: () {
launchUrlString('https://rustdesk.com/privacy'); launchUrlString('https://rustdesk.com/privacy.html');
}, },
child: Text( child: Text(
translate('Privacy Statement'), translate('Privacy Statement'),

View File

@@ -52,10 +52,12 @@ class FileManagerPage extends StatefulWidget {
const FileManagerPage( const FileManagerPage(
{Key? key, {Key? key,
required this.id, required this.id,
required this.password,
required this.tabController, required this.tabController,
this.forceRelay}) this.forceRelay})
: super(key: key); : super(key: key);
final String id; final String id;
final String? password;
final bool? forceRelay; final bool? forceRelay;
final DesktopTabController tabController; final DesktopTabController tabController;
@@ -79,7 +81,10 @@ class _FileManagerPageState extends State<FileManagerPage>
void initState() { void initState() {
super.initState(); super.initState();
_ffi = FFI(); _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((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_ffi.dialogManager _ffi.dialogManager
.showLoading(translate('Connecting...'), onCancel: closeConnection); .showLoading(translate('Connecting...'), onCancel: closeConnection);

View File

@@ -44,6 +44,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
page: FileManagerPage( page: FileManagerPage(
key: ValueKey(params['id']), key: ValueKey(params['id']),
id: params['id'], id: params['id'],
password: params['password'],
tabController: tabController, tabController: tabController,
forceRelay: params['forceRelay'], forceRelay: params['forceRelay'],
))); )));
@@ -72,6 +73,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
page: FileManagerPage( page: FileManagerPage(
key: ValueKey(id), key: ValueKey(id),
id: id, id: id,
password: args['password'],
tabController: tabController, tabController: tabController,
forceRelay: args['forceRelay'], forceRelay: args['forceRelay'],
))); )));

View File

@@ -183,9 +183,9 @@ class _InstallPageBodyState extends State<_InstallPageBody>
InkWell( InkWell(
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
onTap: () => onTap: () =>
launchUrlString('https://rustdesk.com/privacy'), launchUrlString('https://rustdesk.com/privacy.html'),
child: Tooltip( child: Tooltip(
message: 'https://rustdesk.com/privacy', message: 'https://rustdesk.com/privacy.html',
child: Row(children: [ child: Row(children: [
Icon(Icons.launch_outlined, size: 16) Icon(Icons.launch_outlined, size: 16)
.marginOnly(right: 5), .marginOnly(right: 5),

View File

@@ -1,5 +1,4 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart';
const double _kColumn1Width = 30; const double _kColumn1Width = 30;
const double _kColumn4Width = 100; const double _kColumn4Width = 100;
@@ -30,11 +28,13 @@ class PortForwardPage extends StatefulWidget {
const PortForwardPage( const PortForwardPage(
{Key? key, {Key? key,
required this.id, required this.id,
required this.password,
required this.tabController, required this.tabController,
required this.isRDP, required this.isRDP,
this.forceRelay}) this.forceRelay})
: super(key: key); : super(key: key);
final String id; final String id;
final String? password;
final DesktopTabController tabController; final DesktopTabController tabController;
final bool isRDP; final bool isRDP;
final bool? forceRelay; final bool? forceRelay;
@@ -57,12 +57,10 @@ class _PortForwardPageState extends State<PortForwardPage>
_ffi = FFI(); _ffi = FFI();
_ffi.start(widget.id, _ffi.start(widget.id,
isPortForward: true, isPortForward: true,
password: widget.password,
forceRelay: widget.forceRelay, forceRelay: widget.forceRelay,
isRdp: widget.isRDP); isRdp: widget.isRDP);
Get.put(_ffi, tag: 'pf_${widget.id}'); Get.put(_ffi, tag: 'pf_${widget.id}');
if (!Platform.isLinux) {
Wakelock.enable();
}
debugPrint("Port forward page init success with id ${widget.id}"); debugPrint("Port forward page init success with id ${widget.id}");
widget.tabController.onSelected?.call(widget.id); widget.tabController.onSelected?.call(widget.id);
} }
@@ -71,9 +69,6 @@ class _PortForwardPageState extends State<PortForwardPage>
void dispose() { void dispose() {
_ffi.close(); _ffi.close();
_ffi.dialogManager.dismissAll(); _ffi.dialogManager.dismissAll();
if (!Platform.isLinux) {
Wakelock.disable();
}
Get.delete<FFI>(tag: 'pf_${widget.id}'); Get.delete<FFI>(tag: 'pf_${widget.id}');
super.dispose(); super.dispose();
} }

View File

@@ -43,6 +43,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
page: PortForwardPage( page: PortForwardPage(
key: ValueKey(params['id']), key: ValueKey(params['id']),
id: params['id'], id: params['id'],
password: params['password'],
tabController: tabController, tabController: tabController,
isRDP: isRDP, isRDP: isRDP,
forceRelay: params['forceRelay'], forceRelay: params['forceRelay'],
@@ -77,6 +78,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
page: PortForwardPage( page: PortForwardPage(
key: ValueKey(args['id']), key: ValueKey(args['id']),
id: id, id: id,
password: args['password'],
isRDP: isRDP, isRDP: isRDP,
tabController: tabController, tabController: tabController,
forceRelay: args['forceRelay'], forceRelay: args['forceRelay'],

View File

@@ -56,15 +56,14 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
if (peerId != null) { if (peerId != null) {
ConnectionTypeState.init(peerId); ConnectionTypeState.init(peerId);
tabController.onSelected = (id) { tabController.onSelected = (id) {
final remotePage = tabController.state.value.tabs final remotePage = tabController.widget(id);
.firstWhereOrNull((tab) => tab.key == id)
?.page;
if (remotePage is RemotePage) { if (remotePage is RemotePage) {
final ffi = remotePage.ffi; final ffi = remotePage.ffi;
bind.setCurSessionId(sessionId: ffi.sessionId); bind.setCurSessionId(sessionId: ffi.sessionId);
} }
WindowController.fromWindowId(windowId()) WindowController.fromWindowId(windowId())
.setTitle(getWindowNameWithId(id)); .setTitle(getWindowNameWithId(id));
UnreadChatCountState.find(id).value = 0;
}; };
tabController.add(TabInfo( tabController.add(TabInfo(
key: peerId, key: peerId,
@@ -206,6 +205,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
).paddingOnly(right: 5), ).paddingOnly(right: 5),
), ),
label, label,
unreadMessageCountBuilder(UnreadChatCountState.find(key))
.marginOnly(left: 4),
], ],
); );

View File

@@ -100,10 +100,16 @@ class ConnectionManagerState extends State<ConnectionManager> {
gFFI.serverModel.tabController.onSelected = (client_id_str) { gFFI.serverModel.tabController.onSelected = (client_id_str) {
final client_id = int.tryParse(client_id_str); final client_id = int.tryParse(client_id_str);
if (client_id != null) { if (client_id != null) {
gFFI.chatModel.changeCurrentID(client_id);
final client = final client =
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == client_id); gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == client_id);
if (client != null) { 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)); windowManager.setTitle(getWindowNameWithId(client.peerId));
} }
} }
@@ -144,10 +150,11 @@ class ConnectionManagerState extends State<ConnectionManager> {
showClose: true, showClose: true,
onWindowCloseButton: handleWindowCloseButton, onWindowCloseButton: handleWindowCloseButton,
controller: serverModel.tabController, controller: serverModel.tabController,
selectedBorderColor: MyTheme.accent,
maxLabelWidth: 100, maxLabelWidth: 100,
tail: buildScrollJumper(), tail: buildScrollJumper(),
selectedTabBackgroundColor: selectedTabBackgroundColor:
Theme.of(context).hintColor.withOpacity(0.2), Theme.of(context).hintColor.withOpacity(0),
tabBuilder: (key, icon, label, themeConf) { tabBuilder: (key, icon, label, themeConf) {
final client = serverModel.clients final client = serverModel.clients
.firstWhereOrNull((client) => client.id.toString() == key); .firstWhereOrNull((client) => client.id.toString() == key);
@@ -158,10 +165,8 @@ class ConnectionManagerState extends State<ConnectionManager> {
message: key, message: key,
waitDuration: Duration(seconds: 1), waitDuration: Duration(seconds: 1),
child: label), child: label),
Obx(() => Offstage( unreadMessageCountBuilder(client?.unreadChatMessageCount)
offstage: .marginOnly(left: 4),
!(client?.hasUnreadChatMessage.value ?? false),
child: Icon(Icons.circle, color: Colors.red, size: 10)))
], ],
); );
}, },
@@ -170,7 +175,16 @@ class ConnectionManagerState extends State<ConnectionManager> {
Consumer<ChatModel>( Consumer<ChatModel>(
builder: (_, model, child) => model.isShowCMChatPage builder: (_, model, child) => model.isShowCMChatPage
? Expanded( ? 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 - flex: (kConnectionManagerWindowSizeOpenChat.width -
kConnectionManagerWindowSizeClosedChat kConnectionManagerWindowSizeClosedChat
.width) .width)
@@ -437,7 +451,8 @@ class _CmHeaderState extends State<_CmHeader>
child: IconButton( child: IconButton(
onPressed: () => checkClickTime( onPressed: () => checkClickTime(
client.id, client.id,
() => gFFI.chatModel.toggleCMChatPage(client.id), () => gFFI.chatModel
.toggleCMChatPage(MessageKey(client.peerId, client.id)),
), ),
icon: SvgPicture.asset('assets/chat2.svg'), icon: SvgPicture.asset('assets/chat2.svg'),
splashRadius: kDesktopIconButtonSplashRadius, splashRadius: kDesktopIconButtonSplashRadius,

View File

@@ -1,5 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -111,6 +113,36 @@ class _ToolbarTheme {
static const double buttonVMargin = 6; static const double buttonVMargin = 6;
static const double iconRadius = 8; static const double iconRadius = 8;
static const double elevation = 3; 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(); typedef DismissFunc = void Function();
@@ -475,9 +507,17 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
textStyle: MaterialStatePropertyAll( textStyle: MaterialStatePropertyAll(
TextStyle(fontWeight: FontWeight.normal), 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( menuBarTheme: MenuBarThemeData(
style: MenuStyle( style: MenuStyle(
padding: MaterialStatePropertyAll(EdgeInsets.zero), padding: MaterialStatePropertyAll(EdgeInsets.zero),
@@ -1413,7 +1453,8 @@ class _ChatMenuState extends State<_ChatMenu> {
initPos = Offset(pos.dx, pos.dy + _ToolbarTheme.dividerHeight); 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); widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
}); });
} }
@@ -1635,11 +1676,8 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
width: _ToolbarTheme.buttonSize, width: _ToolbarTheme.buttonSize,
height: _ToolbarTheme.buttonSize, height: _ToolbarTheme.buttonSize,
child: SubmenuButton( child: SubmenuButton(
menuStyle: widget.menuStyle, menuStyle: widget.menuStyle ?? _ToolbarTheme.defaultMenuStyle,
style: ButtonStyle( style: _ToolbarTheme.defaultMenuButtonStyle,
backgroundColor: MaterialStatePropertyAll(Colors.transparent),
padding: MaterialStatePropertyAll(EdgeInsets.zero),
overlayColor: MaterialStatePropertyAll(Colors.transparent)),
onHover: (value) => setState(() { onHover: (value) => setState(() {
hover = value; hover = value;
}), }),
@@ -1681,6 +1719,7 @@ class _SubmenuButton extends StatelessWidget {
child: child, child: child,
menuChildren: menuChildren:
menuChildren.map((e) => _buildPointerTrackWidget(e, ffi)).toList(), menuChildren.map((e) => _buildPointerTrackWidget(e, ffi)).toList(),
menuStyle: _ToolbarTheme.defaultMenuStyle,
); );
} }
} }

View File

@@ -187,6 +187,10 @@ class DesktopTabController {
state.value.tabs.clear(); state.value.tabs.clear();
state.refresh(); state.refresh();
} }
Widget? widget(String key) {
return state.value.tabs.firstWhereOrNull((tab) => tab.key == key)?.page;
}
} }
class TabThemeConf { class TabThemeConf {
@@ -221,6 +225,7 @@ class DesktopTab extends StatelessWidget {
final double? maxLabelWidth; final double? maxLabelWidth;
final Color? selectedTabBackgroundColor; final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor; final Color? unSelectedTabBackgroundColor;
final Color? selectedBorderColor;
final DesktopTabController controller; final DesktopTabController controller;
@@ -248,6 +253,7 @@ class DesktopTab extends StatelessWidget {
this.maxLabelWidth, this.maxLabelWidth,
this.selectedTabBackgroundColor, this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor, this.unSelectedTabBackgroundColor,
this.selectedBorderColor,
}) : super(key: key) { }) : super(key: key) {
tabType = controller.tabType; tabType = controller.tabType;
isMainWindow = tabType == DesktopTabType.main || isMainWindow = tabType == DesktopTabType.main ||
@@ -295,37 +301,16 @@ class DesktopTab extends StatelessWidget {
if (tabType != DesktopTabType.main) { if (tabType != DesktopTabType.main) {
return child; return child;
} }
var block = false.obs; return buildRemoteBlock(
return Obx(() => MouseRegion( child: child,
onEnter: (_) async { use: () async {
var access_mode = await bind.mainGetOption(key: 'access-mode'); var access_mode = await bind.mainGetOption(key: 'access-mode');
var option = option2bool( var option = option2bool(
'allow-remote-config-modification', 'allow-remote-config-modification',
await bind.mainGetOption( await bind.mainGetOption(
key: 'allow-remote-config-modification')); key: 'allow-remote-config-modification'));
if (access_mode == 'view' || (access_mode.isEmpty && !option)) { return 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),
)),
],
),
));
} }
List<Widget> _tabWidgets = []; List<Widget> _tabWidgets = [];
@@ -430,15 +415,17 @@ class DesktopTab extends StatelessWidget {
} }
}, },
child: _ListView( child: _ListView(
controller: controller, controller: controller,
tabBuilder: tabBuilder, tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder, tabMenuBuilder: tabMenuBuilder,
labelGetter: labelGetter, labelGetter: labelGetter,
maxLabelWidth: maxLabelWidth, maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor:
selectedTabBackgroundColor, selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor:
unSelectedTabBackgroundColor))), unSelectedTabBackgroundColor,
selectedBorderColor: selectedBorderColor,
))),
], ],
))), ))),
// hide simulated action buttons when we in compatible ui mode, because of reusing system title bar. // 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 LabelGetter? labelGetter;
final double? maxLabelWidth; final double? maxLabelWidth;
final Color? selectedTabBackgroundColor; final Color? selectedTabBackgroundColor;
final Color? selectedBorderColor;
final Color? unSelectedTabBackgroundColor; final Color? unSelectedTabBackgroundColor;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
@@ -753,6 +741,7 @@ class _ListView extends StatelessWidget {
this.maxLabelWidth, this.maxLabelWidth,
this.selectedTabBackgroundColor, this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor, this.unSelectedTabBackgroundColor,
this.selectedBorderColor,
}); });
/// Check whether to show ListView /// Check whether to show ListView
@@ -805,6 +794,7 @@ class _ListView extends StatelessWidget {
selectedTabBackgroundColor: selectedTabBackgroundColor ?? selectedTabBackgroundColor: selectedTabBackgroundColor ??
MyTheme.tabbar(context).selectedTabBackgroundColor, MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor, unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
selectedBorderColor: selectedBorderColor,
); );
}).toList())); }).toList()));
} }
@@ -825,6 +815,7 @@ class _Tab extends StatefulWidget {
final double? maxLabelWidth; final double? maxLabelWidth;
final Color? selectedTabBackgroundColor; final Color? selectedTabBackgroundColor;
final Color? unSelectedTabBackgroundColor; final Color? unSelectedTabBackgroundColor;
final Color? selectedBorderColor;
const _Tab({ const _Tab({
Key? key, Key? key,
@@ -842,6 +833,7 @@ class _Tab extends StatefulWidget {
this.maxLabelWidth, this.maxLabelWidth,
this.selectedTabBackgroundColor, this.selectedTabBackgroundColor,
this.unSelectedTabBackgroundColor, this.unSelectedTabBackgroundColor,
this.selectedBorderColor,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -932,35 +924,46 @@ class _TabState extends State<_Tab> with RestorationMixin {
}, },
onTap: () => widget.onTap(), onTap: () => widget.onTap(),
child: Container( child: Container(
color: isSelected decoration: isSelected && widget.selectedBorderColor != null
? widget.selectedTabBackgroundColor ? BoxDecoration(
: widget.unSelectedTabBackgroundColor, border: Border(
child: Row( bottom: BorderSide(
children: [ color: widget.selectedBorderColor!,
SizedBox( width: 1,
height: _kTabBarHeight, ),
child: Row( ),
crossAxisAlignment: CrossAxisAlignment.center, )
children: [ : null,
_buildTabContent(), child: Container(
Obx((() => _CloseButton( color: isSelected
visible: hover.value && widget.closable, ? widget.selectedTabBackgroundColor
tabSelected: isSelected, : widget.unSelectedTabBackgroundColor,
onClose: () => widget.onClose(), child: Row(
))) children: [
])).paddingOnly(left: 10, right: 5), SizedBox(
Offstage( height: _kTabBarHeight,
offstage: !showDivider, child: Row(
child: VerticalDivider( crossAxisAlignment: CrossAxisAlignment.center,
width: 1, children: [
indent: _kDividerIndent, _buildTabContent(),
endIndent: _kDividerIndent, Obx((() => _CloseButton(
color: MyTheme.tabbar(context).dividerColor, 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), selectedIconColor: Color.fromARGB(255, 26, 26, 26),
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96), unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
dividerColor: Color.fromARGB(255, 238, 238, 238), dividerColor: Color.fromARGB(255, 238, 238, 238),
hoverColor: Color.fromARGB(51, 158, 158, 158), hoverColor: Colors.white54,
closeHoverColor: Color.fromARGB(255, 224, 224, 224), closeHoverColor: Colors.white,
selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240)); selectedTabBackgroundColor: Colors.white54);
static const dark = TabbarTheme( static const dark = TabbarTheme(
selectedTabIconColor: MyTheme.accent, selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98), unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
selectedTextColor: Color.fromARGB(255, 255, 255, 255), selectedTextColor: Colors.white,
unSelectedTextColor: Color.fromARGB(255, 192, 192, 192), unSelectedTextColor: Color.fromARGB(255, 192, 192, 192),
selectedIconColor: Color.fromARGB(255, 192, 192, 192), selectedIconColor: Color.fromARGB(255, 192, 192, 192),
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255), unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),

View File

@@ -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. // Check the startup argument, if we successfully handle the argument, we keep the main window hidden.
final handledByUniLinks = await initUniLinks(); final handledByUniLinks = await initUniLinks();
debugPrint("handled by uni links: $handledByUniLinks"); debugPrint("handled by uni links: $handledByUniLinks");
if (handledByUniLinks || checkArguments()) { if (handledByUniLinks || handleUriLink(cmdArgs: kBootArgs)) {
windowManager.hide(); windowManager.hide();
} else { } else {
windowManager.show(); windowManager.show();
@@ -225,6 +225,7 @@ void runConnectionManagerScreen(bool hide) async {
} else { } else {
await showCmWindow(isStartup: true); await showCmWindow(isStartup: true);
} }
windowManager.setResizable(false);
// Start the uni links handler and redirect links to Native, not for Flutter. // Start the uni links handler and redirect links to Native, not for Flutter.
listenUniLinks(handleByFlutter: false); listenUniLinks(handleByFlutter: false);
} }

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:auto_size_text_field/auto_size_text_field.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -37,6 +38,7 @@ class ConnectionPage extends StatefulWidget implements PageShape {
class _ConnectionPageState extends State<ConnectionPage> { class _ConnectionPageState extends State<ConnectionPage> {
/// Controller for the id input bar. /// Controller for the id input bar.
final _idController = IDTextEditingController(); final _idController = IDTextEditingController();
final RxBool _idEmpty = true.obs;
/// Update url. If it's not null, means an update is available. /// Update url. If it's not null, means an update is available.
var _updateUrl = ''; var _updateUrl = '';
@@ -60,6 +62,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
if (_updateUrl.isNotEmpty) setState(() {}); if (_updateUrl.isNotEmpty) setState(() {});
}); });
} }
_idController.addListener(() {
_idEmpty.value = _idController.text.isEmpty;
});
} }
@override @override
@@ -126,7 +132,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
Expanded( Expanded(
child: Container( child: Container(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: TextField( child: AutoSizeTextField(
minFontSize: 18,
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
keyboardType: TextInputType.visiblePassword, 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( SizedBox(
width: 60, width: 60,
height: 60, height: 60,

View File

@@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; 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/server_page.dart';
import 'package:flutter_hbb/mobile/pages/settings_page.dart'; import 'package:flutter_hbb/mobile/pages/settings_page.dart';
import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/chat_page.dart'; import '../../common/widgets/chat_page.dart';
import 'connection_page.dart'; import 'connection_page.dart';
abstract class PageShape extends Widget { abstract class PageShape extends Widget {
final String title = ""; final String title = "";
final Icon icon = Icon(null); final Widget icon = Icon(null);
final List<Widget> appBarActions = []; final List<Widget> appBarActions = [];
} }
@@ -22,7 +24,9 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
var _selectedIndex = 0; var _selectedIndex = 0;
int get selectedIndex => _selectedIndex;
final List<PageShape> _pages = []; final List<PageShape> _pages = [];
final _blockableOverlayState = BlockableOverlayState();
void refreshPages() { void refreshPages() {
setState(() { setState(() {
@@ -34,13 +38,14 @@ class _HomePageState extends State<HomePage> {
void initState() { void initState() {
super.initState(); super.initState();
initPages(); initPages();
_blockableOverlayState.applyFfi(gFFI);
} }
void initPages() { void initPages() {
_pages.clear(); _pages.clear();
_pages.add(ConnectionPage()); _pages.add(ConnectionPage());
if (isAndroid) { if (isAndroid) {
_pages.addAll([ChatPage(), ServerPage()]); _pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
} }
_pages.add(SettingsPage()); _pages.add(SettingsPage());
} }
@@ -62,7 +67,7 @@ class _HomePageState extends State<HomePage> {
// backgroundColor: MyTheme.grayBg, // backgroundColor: MyTheme.grayBg,
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: Text("RustDesk"), title: appTitle(),
actions: _pages.elementAt(_selectedIndex).appBarActions, actions: _pages.elementAt(_selectedIndex).appBarActions,
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
@@ -80,6 +85,8 @@ class _HomePageState extends State<HomePage> {
if (index == 1 && _selectedIndex != index) { if (index == 1 && _selectedIndex != index) {
gFFI.chatModel.hideChatIconOverlay(); gFFI.chatModel.hideChatIconOverlay();
gFFI.chatModel.hideChatWindowOverlay(); gFFI.chatModel.hideChatWindowOverlay();
gFFI.chatModel
.mobileClearClientUnread(gFFI.chatModel.currentKey.connId);
} }
_selectedIndex = index; _selectedIndex = index;
}), }),
@@ -87,6 +94,53 @@ class _HomePageState extends State<HomePage> {
body: _pages.elementAt(_selectedIndex), 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 { class WebHomePage extends StatelessWidget {

View File

@@ -43,8 +43,6 @@ class _RemotePageState extends State<RemotePage> {
double _mouseScrollIntegral = 0; // mouse scroll speed controller double _mouseScrollIntegral = 0; // mouse scroll speed controller
Orientation? _currentOrientation; Orientation? _currentOrientation;
final _blockableOverlayState = BlockableOverlayState();
final keyboardVisibilityController = KeyboardVisibilityController(); final keyboardVisibilityController = KeyboardVisibilityController();
late final StreamSubscription<bool> keyboardSubscription; late final StreamSubscription<bool> keyboardSubscription;
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
@@ -70,8 +68,9 @@ class _RemotePageState extends State<RemotePage> {
gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId); gFFI.qualityMonitorModel.checkShowQualityMonitor(sessionId);
keyboardSubscription = keyboardSubscription =
keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged); keyboardVisibilityController.onChange.listen(onSoftKeyboardChanged);
_blockableOverlayState.applyFfi(gFFI);
initSharedStates(widget.id); initSharedStates(widget.id);
gFFI.chatModel
.changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID));
} }
@override @override
@@ -351,8 +350,8 @@ class _RemotePageState extends State<RemotePage> {
color: Colors.white, color: Colors.white,
icon: Icon(Icons.message), icon: Icon(Icons.message),
onPressed: () { onPressed: () {
gFFI.chatModel gFFI.chatModel.changeCurrentKey(MessageKey(
.changeCurrentID(ChatModel.clientModeID); widget.id, ChatModel.clientModeID));
gFFI.chatModel.toggleChatOverlay(); gFFI.chatModel.toggleChatOverlay();
}, },
) )

View File

@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart'; import 'package:flutter_hbb/mobile/widgets/dialog.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -419,14 +420,16 @@ class ConnectionManager extends StatelessWidget {
? const SizedBox.shrink() ? const SizedBox.shrink()
: IconButton( : IconButton(
onPressed: () { onPressed: () {
gFFI.chatModel.changeCurrentID(client.id); gFFI.chatModel.changeCurrentKey(
MessageKey(client.peerId, client.id));
final bar = navigationBarKey.currentWidget; final bar = navigationBarKey.currentWidget;
if (bar != null) { if (bar != null) {
bar as BottomNavigationBar; bar as BottomNavigationBar;
bar.onTap!(1); bar.onTap!(1);
} }
}, },
icon: const Icon(Icons.chat))) icon: unreadTopRightBuilder(
client.unreadChatMessageCount)))
], ],
), ),
client.authorized client.authorized

View File

@@ -1,12 +1,18 @@
import 'dart:async'; import 'dart:async';
import 'package:dash_chat_2/dash_chat_2.dart'; 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:draggable_float_widget/draggable_float_widget.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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/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_rx/src/rx_types/rx_types.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:uuid/uuid.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -16,6 +22,24 @@ import '../common/widgets/overlay.dart';
import '../main.dart'; import '../main.dart';
import 'model.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 { class MessageBody {
ChatUser chatUser; ChatUser chatUser;
List<ChatMessage> chatMessages; List<ChatMessage> chatMessages;
@@ -45,6 +69,8 @@ class ChatModel with ChangeNotifier {
Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus; Rx<VoiceCallStatus> get voiceCallStatus => _voiceCallStatus;
TextEditingController textController = TextEditingController(); TextEditingController textController = TextEditingController();
RxInt mobileUnreadSum = 0.obs;
MessageKey? latestReceivedKey;
@override @override
void dispose() { void dispose() {
@@ -53,19 +79,18 @@ class ChatModel with ChangeNotifier {
} }
final ChatUser me = ChatUser( final ChatUser me = ChatUser(
id: "", id: Uuid().v4().toString(),
firstName: translate("Me"), firstName: translate("Me"),
); );
late final Map<int, MessageBody> _messages = {}..[clientModeID] = late final Map<MessageKey, MessageBody> _messages = {};
MessageBody(me, []);
var _currentID = clientModeID; MessageKey _currentKey = MessageKey('', -2); // -2 is invalid value
late bool _isShowCMChatPage = false; 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; bool get isShowCMChatPage => _isShowCMChatPage;
@@ -115,15 +140,7 @@ class ChatModel with ChangeNotifier {
); );
} }
ChatUser get currentUser { ChatUser? get currentUser => _messages[_currentKey]?.chatUser;
final user = messages[currentID]?.chatUser;
if (user == null) {
_currentID = clientModeID;
return me;
} else {
return user;
}
}
showChatIconOverlay({Offset offset = const Offset(200, 50)}) { showChatIconOverlay({Offset offset = const Offset(200, 50)}) {
if (chatIconOverlayEntry != null) { if (chatIconOverlayEntry != null) {
@@ -178,6 +195,12 @@ class ChatModel with ChangeNotifier {
final overlayState = _blockableOverlayState?.state; final overlayState = _blockableOverlayState?.state;
if (overlayState == null) return; 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) { final overlay = OverlayEntry(builder: (context) {
return Listener( return Listener(
onPointerDown: (_) { onPointerDown: (_) {
@@ -229,21 +252,29 @@ class ChatModel with ChangeNotifier {
} }
} }
showChatPage(int id) async { showChatPage(MessageKey key) async {
if (isConnManager) { if (isDesktop) {
if (!_isShowCMChatPage) { if (isConnManager) {
await toggleCMChatPage(id); if (!_isShowCMChatPage) {
await toggleCMChatPage(key);
}
} else {
if (_isChatOverlayHide()) {
await toggleChatOverlay();
}
} }
} else { } else {
if (_isChatOverlayHide()) { if (key.connId == clientModeID) {
await toggleChatOverlay(); if (_isChatOverlayHide()) {
await toggleChatOverlay();
}
} }
} }
} }
toggleCMChatPage(int id) async { toggleCMChatPage(MessageKey key) async {
if (gFFI.chatModel.currentID != id) { if (gFFI.chatModel.currentKey != key) {
gFFI.chatModel.changeCurrentID(id); gFFI.chatModel.changeCurrentKey(key);
} }
if (_isShowCMChatPage) { if (_isShowCMChatPage) {
_isShowCMChatPage = !_isShowCMChatPage; _isShowCMChatPage = !_isShowCMChatPage;
@@ -261,25 +292,30 @@ class ChatModel with ChangeNotifier {
} }
} }
changeCurrentID(int id) { changeCurrentKey(MessageKey key) {
if (_messages.containsKey(id)) { updateConnIdOfKey(key);
_currentID = id; String? peerName;
notifyListeners(); if (key.connId == clientModeID) {
peerName = parent.target?.ffiModel.pi.username;
} else { } else {
final client = parent.target?.serverModel.clients peerName = parent.target?.serverModel.clients
.firstWhere((client) => client.id == id); .firstWhereOrNull((client) => client.peerId == key.peerId)
if (client == null) { ?.name;
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();
} }
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 { receive(int id, String text) async {
@@ -292,49 +328,90 @@ class ChatModel with ChangeNotifier {
if (desktopType == DesktopType.cm) { if (desktopType == DesktopType.cm) {
await showCmWindow(); 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 // mobile: first message show overlay icon
if (!isDesktop && chatIconOverlayEntry == null) { if (!isDesktop && chatIconOverlayEntry == null) {
showChatIconOverlay(); showChatIconOverlay();
} }
// show chat page // show chat page
await showChatPage(id); await showChatPage(messagekey);
int toId = currentID;
late final ChatUser chatUser; late final ChatUser chatUser;
if (id == clientModeID) { if (id == clientModeID) {
chatUser = ChatUser( chatUser = ChatUser(
firstName: session.ffiModel.pi.username, 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 { } else {
final client = final client = session.serverModel.clients
session.serverModel.clients.firstWhere((client) => client.id == id); .firstWhereOrNull((client) => client.id == id);
if (client == null) {
debugPrint("Failed to receive msg, client is null");
return;
}
if (isDesktop) { if (isDesktop) {
window_on_top(null); window_on_top(null);
// disable auto jumpTo other tab when hasFocus, and mark unread message // disable auto jumpTo other tab when hasFocus, and mark unread message
final currentSelectedTab = final currentSelectedTab =
session.serverModel.tabController.state.value.selectedTabInfo; session.serverModel.tabController.state.value.selectedTabInfo;
if (currentSelectedTab.key != id.toString() && inputNode.hasFocus) { if (currentSelectedTab.key != id.toString() && inputNode.hasFocus) {
client.hasUnreadChatMessage.value = true; client.unreadChatMessageCount.value += 1;
} else { } else {
parent.target?.serverModel.jumpTo(id); parent.target?.serverModel.jumpTo(id);
toId = id;
} }
} else { } else {
toId = id; if (HomePage.homeKey.currentState?.selectedIndex != 1 ||
_currentKey != messagekey) {
client.unreadChatMessageCount.value += 1;
mobileUpdateUnreadSum();
}
} }
chatUser = ChatUser(id: client.peerId, firstName: client.name); chatUser = ChatUser(id: client.peerId, firstName: client.name);
} }
insertMessage(messagekey,
if (!_messages.containsKey(id)) {
_messages[id] = MessageBody(chatUser, []);
}
_messages[id]!.insert(
ChatMessage(text: text, user: chatUser, createdAt: DateTime.now())); 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(); notifyListeners();
} }
@@ -344,17 +421,63 @@ class ChatModel with ChangeNotifier {
return; return;
} }
message.text = trimmedText; message.text = trimmedText;
_messages[_currentID]?.insert(message); insertMessage(_currentKey, message);
if (_currentID == clientModeID && parent.target != null) { if (_currentKey.connId == clientModeID && parent.target != null) {
bind.sessionSendChat(sessionId: sessionId, text: message.text); bind.sessionSendChat(sessionId: sessionId, text: message.text);
} else { } else {
bind.cmSendChat(connId: _currentID, msg: message.text); bind.cmSendChat(connId: _currentKey.connId, msg: message.text);
} }
notifyListeners(); notifyListeners();
inputNode.requestFocus(); 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() { close() {
hideChatIconOverlay(); hideChatIconOverlay();
hideChatWindowOverlay(); hideChatWindowOverlay();

View File

@@ -248,7 +248,7 @@ class FfiModel with ChangeNotifier {
onUrlSchemeReceived(Map<String, dynamic> evt) { onUrlSchemeReceived(Map<String, dynamic> evt) {
final url = evt['url'].toString().trim(); final url = evt['url'].toString().trim();
if (url.startsWith(kUniLinksPrefix) && parseRustdeskUri(url)) { if (url.startsWith(kUniLinksPrefix) && handleUriLink(uriString: url)) {
return; return;
} }
switch (url) { switch (url) {

View File

@@ -233,7 +233,7 @@ class PlatformFFI {
'_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir'); '_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir');
} }
if (desktopType == DesktopType.cm) { if (desktopType == DesktopType.cm) {
await _ffiBind.cmStartListenIpcThread(); await _ffiBind.cmInit();
} }
await _ffiBind.mainDeviceId(id: id); await _ffiBind.mainDeviceId(id: id);
await _ffiBind.mainDeviceName(name: name); await _ffiBind.mainDeviceName(name: name);

View File

@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.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:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@@ -462,13 +463,7 @@ class ServerModel with ChangeNotifier {
key: client.id.toString(), key: client.id.toString(),
label: client.name, label: client.name,
closable: false, closable: false,
onTap: () { onTap: () {},
if (client.hasUnreadChatMessage.value) {
client.hasUnreadChatMessage.value = false;
final chatModel = parent.target!.chatModel;
chatModel.showChatPage(client.id);
}
},
page: desktop.buildConnectionCard(client))); page: desktop.buildConnectionCard(client)));
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
if (!hideCm) window_on_top(null); if (!hideCm) window_on_top(null);
@@ -480,6 +475,8 @@ class ServerModel with ChangeNotifier {
cmHiddenTimer = null; cmHiddenTimer = null;
}); });
} }
parent.target?.chatModel
.updateConnIdOfKey(MessageKey(client.peerId, client.id));
} }
void showLoginDialog(Client client) { void showLoginDialog(Client client) {
@@ -643,7 +640,7 @@ class Client {
bool inVoiceCall = false; bool inVoiceCall = false;
bool incomingVoiceCall = false; bool incomingVoiceCall = false;
RxBool hasUnreadChatMessage = false.obs; RxInt unreadChatMessageCount = 0.obs;
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId, Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
this.keyboard, this.clipboard, this.audio); this.keyboard, this.clipboard, this.audio);

View File

@@ -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({ var msg = jsonEncode({
"type": WindowType.FileTransfer.index, "type": WindowType.FileTransfer.index,
"id": remoteId, "id": remoteId,
"password": password,
"forceRelay": forceRelay, "forceRelay": forceRelay,
}); });
@@ -117,11 +119,12 @@ class RustDeskMultiWindowManager {
} }
Future<dynamic> newPortForward(String remoteId, bool isRDP, Future<dynamic> newPortForward(String remoteId, bool isRDP,
{bool? forceRelay}) async { {String? password, bool? forceRelay}) async {
final msg = jsonEncode({ final msg = jsonEncode({
"type": WindowType.PortForward.index, "type": WindowType.PortForward.index,
"id": remoteId, "id": remoteId,
"isRDP": isRDP, "isRDP": isRDP,
"password": password,
"forceRelay": forceRelay, "forceRelay": forceRelay,
}); });

View File

@@ -64,7 +64,7 @@ static void my_application_activate(GApplication* application) {
int width = 800, height = 600; int width = 800, height = 600;
if (gIsConnectionManager) { if (gIsConnectionManager) {
width = 300; width = 300;
height = 400; height = 490;
} }
gtk_window_set_default_size(window, width, height); // <-- comment this line gtk_window_set_default_size(window, width, height); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window)); gtk_widget_show(GTK_WIDGET(window));

View File

@@ -65,6 +65,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" 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: back_button_interceptor:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -319,7 +327,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: "30518303e28702bf6b8110465293c05d21bc4cd2" resolved-ref: aee670819f5fe7e8b0f05e0239dafb5c62f7a84b
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window" url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
source: git source: git
version: "0.1.0" version: "0.1.0"

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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 # 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: environment:
sdk: ">=2.17.0" sdk: ">=2.17.0"
@@ -96,6 +96,7 @@ dependencies:
percent_indicator: ^4.2.2 percent_indicator: ^4.2.2
dropdown_button2: ^2.0.0 dropdown_button2: ^2.0.0
uuid: ^3.0.7 uuid: ^3.0.7
auto_size_text_field: ^2.2.1
dev_dependencies: dev_dependencies:
icons_launcher: ^2.0.4 icons_launcher: ^2.0.4

View File

@@ -84,7 +84,6 @@ const CHARS: &[char] = &[
pub const RENDEZVOUS_SERVERS: &[&str] = &[ pub const RENDEZVOUS_SERVERS: &[&str] = &[
"rs-ny.rustdesk.com", "rs-ny.rustdesk.com",
"rs-sg.rustdesk.com", "rs-sg.rustdesk.com",
"rs-cn.rustdesk.com",
]; ];
pub const RS_PUB_KEY: &str = match option_env!("RS_PUB_KEY") { 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); let c = PeerConfig::load(&id_decoded_string);
if c.info.platform.is_empty() {
fs::remove_file(p).ok();
}
(id_decoded_string, t, c) (id_decoded_string, t, c)
}) })
.filter(|p| !p.2.info.platform.is_empty())
.collect(); .collect();
peers.sort_unstable_by(|a, b| b.1.cmp(&a.1)); peers.sort_unstable_by(|a, b| b.1.cmp(&a.1));
return peers; return peers;

View File

@@ -1,5 +1,5 @@
pkgname=rustdesk pkgname=rustdesk
pkgver=1.2.0 pkgver=1.2.1
pkgrel=0 pkgrel=0
epoch= epoch=
pkgdesc="" pkgdesc=""

2
res/bump.sh Normal file
View 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
View 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

View File

@@ -1,5 +1,5 @@
Name: rustdesk Name: rustdesk
Version: 1.2.0 Version: 1.2.1
Release: 0 Release: 0
Summary: RPM package Summary: RPM package
License: GPL-3.0 License: GPL-3.0

View File

@@ -1,5 +1,5 @@
Name: rustdesk Name: rustdesk
Version: 1.2.0 Version: 1.2.1
Release: 0 Release: 0
Summary: RPM package Summary: RPM package
License: GPL-3.0 License: GPL-3.0

View File

@@ -1,5 +1,5 @@
Name: rustdesk Name: rustdesk
Version: 1.2.0 Version: 1.2.1
Release: 0 Release: 0
Summary: RPM package Summary: RPM package
License: GPL-3.0 License: GPL-3.0

View File

@@ -6,6 +6,18 @@ use hbb_common::log;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::platform::register_breakdown_handler; 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 /// shared by flutter and sciter main function
/// ///
/// [Note] /// [Note]
@@ -19,15 +31,23 @@ pub fn core_main() -> Option<Vec<String>> {
let mut _is_elevate = false; let mut _is_elevate = false;
let mut _is_run_as_system = false; let mut _is_run_as_system = false;
let mut _is_quick_support = 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(); let mut arg_exe = Default::default();
for arg in std::env::args() { for arg in std::env::args() {
if i == 0 { if i == 0 {
arg_exe = arg; arg_exe = arg;
} else if i > 0 { } else if i > 0 {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
if arg == "--connect" || arg == "--play" { if [
_is_flutter_connect = true; "--connect",
"--play",
"--file-transfer",
"--port-forward",
"--rdp",
]
.contains(&arg.as_str())
{
_is_flutter_invoke_new_connection = true;
} }
if arg == "--elevate" { if arg == "--elevate" {
_is_elevate = true; _is_elevate = true;
@@ -43,9 +63,9 @@ pub fn core_main() -> Option<Vec<String>> {
} }
#[cfg(any(target_os = "linux", target_os = "windows"))] #[cfg(any(target_os = "linux", target_os = "windows"))]
if args.is_empty() { 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) { 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"])); hbb_common::allow_err!(crate::run_me(vec!["--tray"]));
} }
} }
@@ -63,7 +83,7 @@ pub fn core_main() -> Option<Vec<String>> {
} }
} }
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
if _is_flutter_connect { if _is_flutter_invoke_new_connection {
return core_main_invoke_new_connection(std::env::args()); return core_main_invoke_new_connection(std::env::args());
} }
let click_setup = cfg!(windows) && args.is_empty() && crate::common::is_setup(&arg_exe); 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(); args.clear();
} }
if args.len() > 0 && args[0] == "--version" { if args.len() > 0 && args[0] == "--version" {
println!("{}", crate::VERSION); my_println!("{}", crate::VERSION);
return None; return None;
} }
#[cfg(windows)] #[cfg(windows)]
@@ -215,18 +235,23 @@ pub fn core_main() -> Option<Vec<String>> {
return None; return None;
} else if args[0] == "--password" { } else if args[0] == "--password" {
if args.len() == 2 { 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(); crate::ipc::set_permanent_password(args[1].to_owned()).unwrap();
my_println!("Done!");
} else { } else {
println!("Administrative privileges required!"); my_println!("Installation and administrative privileges required!");
} }
} }
return None; return None;
} else if args[0] == "--get-id" { } else if args[0] == "--get-id" {
if crate::platform::is_root() { if crate::platform::is_installed()
println!("{}", crate::ipc::get_id()); && crate::platform::check_super_user_permission().unwrap_or_default()
{
my_println!("{}", crate::ipc::get_id());
} else { } else {
println!("Permission denied!"); my_println!("Installation and administrative privileges required!");
} }
return None; return None;
} else if args[0] == "--check-hwcodec-config" { } 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. /// If it returns [`Some`], then the process will continue, and flutter gui will be started.
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> { fn core_main_invoke_new_connection(mut args: std::env::Args) -> Option<Vec<String>> {
args.position(|element| { let mut authority = None;
return element == "--connect" || element == "--play"; let mut id = None;
})?; let mut param_array = vec![];
let mut peer_id = args.next().unwrap_or("".to_string()); while let Some(arg) = args.next() {
if peer_id.is_empty() { match arg.as_str() {
eprintln!("please provide a valid peer id"); "--connect" | "--play" | "--file-transfer" | "--port-forward" | "--rdp" => {
return None; authority = Some((&arg.to_string()[2..]).to_owned());
} id = args.next();
let app_name = crate::get_app_name(); }
let ext = format!(".{}", app_name.to_lowercase()); "--password" => {
if peer_id.ends_with(&ext) { if let Some(password) = args.next() {
peer_id = peer_id.replace(&ext, ""); param_array.push(format!("password={password}"));
} }
let mut switch_uuid = None; }
while let Some(item) = args.next() { "--relay" => {
if item == "--switch_uuid" { param_array.push(format!("relay=true"));
switch_uuid = args.next(); }
// inner
"--switch_uuid" => {
if let Some(switch_uuid) = args.next() {
param_array.push(format!("switch_uuid={switch_uuid}"));
}
}
_ => {}
} }
} }
let mut param_array = vec![]; let mut uni_links = Default::default();
if switch_uuid.is_some() { if let Some(authority) = authority {
let switch_uuid = switch_uuid.map_or("".to_string(), |p| format!("switch_uuid={}", p)); if let Some(mut id) = id {
param_array.push(switch_uuid); 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")] #[cfg(target_os = "linux")]
return try_send_by_dbus(uni_links); return try_send_by_dbus(uni_links);

View File

@@ -910,7 +910,7 @@ pub mod connection_manager {
#[inline] #[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn start_listen_ipc_thread() { fn start_listen_ipc_thread() {
start_listen_ipc(true); 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")] #[cfg(target_os = "android")]
use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use hbb_common::tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};

View File

@@ -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")))] #[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. /// Start an ipc server for receiving the url scheme.

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nom d'usuari oblidat"), ("Username missed", "Nom d'usuari oblidat"),
("Password missed", "Contrasenya oblidada"), ("Password missed", "Contrasenya oblidada"),
("Wrong credentials", "Credencials incorrectes"), ("Wrong credentials", "Credencials incorrectes"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar tag"), ("Edit Tag", "Editar tag"),
("Unremember Password", "Contrasenya oblidada"), ("Unremember Password", "Contrasenya oblidada"),
("Favorites", "Preferits"), ("Favorites", "Preferits"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "用户名没有填写"), ("Username missed", "用户名没有填写"),
("Password missed", "密码没有填写"), ("Password missed", "密码没有填写"),
("Wrong credentials", "提供的登录信息错误"), ("Wrong credentials", "提供的登录信息错误"),
("The verification code is incorrect or has expired", "验证码错误或已超时"),
("Edit Tag", "修改标签"), ("Edit Tag", "修改标签"),
("Unremember Password", "忘记密码"), ("Unremember Password", "忘记密码"),
("Favorites", "收藏"), ("Favorites", "收藏"),
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "语音通话"), ("Voice call", "语音通话"),
("Text chat", "文字聊天"), ("Text chat", "文字聊天"),
("Stop voice call", "停止语音通话"), ("Stop voice call", "停止语音通话"),
("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r或者在卡片选项里选择强制走中继连接。"), ("relay_hint_tip", "可能无法直连,可以尝试中继连接。\n另外,如果想直接使用中继连接,可以在 ID 后面添加/r如果最近访问里存在该卡片,也可以在卡片选项里选择强制走中继连接。"),
("Reconnect", "重连"), ("Reconnect", "重连"),
("Codec", "编解码"), ("Codec", "编解码"),
("Resolution", "分辨率"), ("Resolution", "分辨率"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", "接受并提权"), ("Accept and Elevate", "接受并提权"),
("accept_and_elevate_btn_tooltip", "接受连接并提升 UAC 权限"), ("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(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Chybí uživatelské jméno"), ("Username missed", "Chybí uživatelské jméno"),
("Password missed", "Chybí heslo"), ("Password missed", "Chybí heslo"),
("Wrong credentials", "Nesprávné přihlašovací údaje"), ("Wrong credentials", "Nesprávné přihlašovací údaje"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Upravit štítek"), ("Edit Tag", "Upravit štítek"),
("Unremember Password", "Přestat si heslo pamatovat"), ("Unremember Password", "Přestat si heslo pamatovat"),
("Favorites", "Oblíbené"), ("Favorites", "Oblíbené"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Glemt brugernavn"), ("Username missed", "Glemt brugernavn"),
("Password missed", "Glemt kodeord"), ("Password missed", "Glemt kodeord"),
("Wrong credentials", "Forkerte registreringsdata"), ("Wrong credentials", "Forkerte registreringsdata"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Rediger nøgleord"), ("Edit Tag", "Rediger nøgleord"),
("Unremember Password", "Glem adgangskoden"), ("Unremember Password", "Glem adgangskoden"),
("Favorites", "Favoritter"), ("Favorites", "Favoritter"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Benutzername fehlt"), ("Username missed", "Benutzername fehlt"),
("Password missed", "Passwort fehlt"), ("Password missed", "Passwort fehlt"),
("Wrong credentials", "Falsche Anmeldedaten"), ("Wrong credentials", "Falsche Anmeldedaten"),
("The verification code is incorrect or has expired", "Der Verifizierungscode ist falsch oder abgelaufen"),
("Edit Tag", "Schlagwort bearbeiten"), ("Edit Tag", "Schlagwort bearbeiten"),
("Unremember Password", "Gespeichertes Passwort löschen"), ("Unremember Password", "Gespeichertes Passwort löschen"),
("Favorites", "Favoriten"), ("Favorites", "Favoriten"),
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Sprachanruf"), ("Voice call", "Sprachanruf"),
("Text chat", "Text-Chat"), ("Text chat", "Text-Chat"),
("Stop voice call", "Sprachanruf beenden"), ("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"), ("Reconnect", "Erneut verbinden"),
("Codec", "Codec"), ("Codec", "Codec"),
("Resolution", "Auflösung"), ("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", "Akzeptieren und Rechte erhöhen"),
("accept_and_elevate_btn_tooltip", "Akzeptieren Sie die Verbindung und erhöhen Sie die UAC-Berechtigungen."), ("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."), ("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(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Δεν συμπληρώσατε το όνομα χρήστη"), ("Username missed", "Δεν συμπληρώσατε το όνομα χρήστη"),
("Password missed", "Δεν συμπληρώσατε τον κωδικό πρόσβασης"), ("Password missed", "Δεν συμπληρώσατε τον κωδικό πρόσβασης"),
("Wrong credentials", "Λάθος διαπιστευτήρια"), ("Wrong credentials", "Λάθος διαπιστευτήρια"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Επεξεργασία ετικέτας"), ("Edit Tag", "Επεξεργασία ετικέτας"),
("Unremember Password", "Διαγραφή απομνημονευμένου κωδικού"), ("Unremember Password", "Διαγραφή απομνημονευμένου κωδικού"),
("Favorites", "Αγαπημένα"), ("Favorites", "Αγαπημένα"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -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."), ("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."), ("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."), ("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", ""), ("No transfers in progress", ""),
("idd_driver_tip", "Install virtual display driver which is used when you have no physical displays."), ("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."), ("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."),

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Uzantnomo forgesita"), ("Username missed", "Uzantnomo forgesita"),
("Password missed", "Pasvorto forgesita"), ("Password missed", "Pasvorto forgesita"),
("Wrong credentials", "Identigilo aŭ pasvorto erara"), ("Wrong credentials", "Identigilo aŭ pasvorto erara"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Redakti etikedo"), ("Edit Tag", "Redakti etikedo"),
("Unremember Password", "Forgesi pasvorton"), ("Unremember Password", "Forgesi pasvorton"),
("Favorites", "Favorataj"), ("Favorites", "Favorataj"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -208,7 +208,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("x11 expected", "x11 necesario"), ("x11 expected", "x11 necesario"),
("Port", "Puerto"), ("Port", "Puerto"),
("Settings", "Ajustes"), ("Settings", "Ajustes"),
("Username", " Nombre de usuario"), ("Username", "Nombre de usuario"),
("Invalid port", "Puerto incorrecto"), ("Invalid port", "Puerto incorrecto"),
("Closed manually by the peer", "Cerrado manualmente por el par"), ("Closed manually by the peer", "Cerrado manualmente por el par"),
("Enable remote configuration modification", "Habilitar modificación remota de configuración"), ("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"), ("Username missed", "Olvidó su nombre de usuario"),
("Password missed", "Olvidó su contraseña"), ("Password missed", "Olvidó su contraseña"),
("Wrong credentials", "Credenciales incorrectas"), ("Wrong credentials", "Credenciales incorrectas"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar tag"), ("Edit Tag", "Editar tag"),
("Unremember Password", "Olvidar contraseña"), ("Unremember Password", "Olvidar contraseña"),
("Favorites", "Favoritos"), ("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", "Aceptar y Elevar"),
("accept_and_elevate_btn_tooltip", "Aceptar la conexión y elevar permisos UAC."), ("accept_and_elevate_btn_tooltip", "Aceptar la conexión y elevar permisos UAC."),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -80,7 +80,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Connection in progress. Please wait.", "در حال اتصال. لطفا متظر بمانید"), ("Connection in progress. Please wait.", "در حال اتصال. لطفا متظر بمانید"),
("Please try 1 minute later", "لطفا بعد از 1 دقیقه مجددا تلاش کنید"), ("Please try 1 minute later", "لطفا بعد از 1 دقیقه مجددا تلاش کنید"),
("Login Error", "ورود ناموفق بود"), ("Login Error", "ورود ناموفق بود"),
("Successful", "ورود با موفقیت انجام شد"), ("Successful", "با موفقیت انجام شد"),
("Connected, waiting for image...", "...ارتباط برقرار شد. انتظار برای دریافت تصاویر"), ("Connected, waiting for image...", "...ارتباط برقرار شد. انتظار برای دریافت تصاویر"),
("Name", "نام"), ("Name", "نام"),
("Type", "نوع فایل"), ("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 connect via relay server", "انجام نشد Relay اتصال از طریق سرور"),
("Failed to make direct connection to remote desktop", "اتصال مستقیم به دسکتاپ راه دور انجام نشد"), ("Failed to make direct connection to remote desktop", "اتصال مستقیم به دسکتاپ راه دور انجام نشد"),
("Set Password", "تنظیم رمزعبور"), ("Set Password", "تنظیم رمزعبور"),
("OS Password", "رمز عیور سیستم عامل"), ("OS Password", "رمز عبور سیستم عامل"),
("install_tip", "لطفا برنامه را نصب کنید UAC و جلوگیری از خطای RustDesk برای راحتی در استفاده از نرم افزار"), ("install_tip", "لطفا برنامه را نصب کنید UAC و جلوگیری از خطای RustDesk برای راحتی در استفاده از نرم افزار"),
("Click to upgrade", "برای ارتقا کلیک کنید"), ("Click to upgrade", "برای ارتقا کلیک کنید"),
("Click to download", "برای دانلود کلیک کنید"), ("Click to download", "برای دانلود کلیک کنید"),
@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "نام کاربری وجود ندارد"), ("Username missed", "نام کاربری وجود ندارد"),
("Password missed", "رمزعبور وجود ندارد"), ("Password missed", "رمزعبور وجود ندارد"),
("Wrong credentials", "اعتبارنامه نادرست است"), ("Wrong credentials", "اعتبارنامه نادرست است"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "ویرایش برچسب"), ("Edit Tag", "ویرایش برچسب"),
("Unremember Password", "رمز عبور ذخیره نشود"), ("Unremember Password", "رمز عبور ذخیره نشود"),
("Favorites", "اتصالات دلخواه"), ("Favorites", "اتصالات دلخواه"),
@@ -511,6 +512,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "جمع کردن نوار ابزار"), ("Collapse toolbar", "جمع کردن نوار ابزار"),
("Accept and Elevate", "بپذیرید و افزایش دهید"), ("Accept and Elevate", "بپذیرید و افزایش دهید"),
("accept_and_elevate_btn_tooltip", "را افزایش دهید UAC اتصال را بپذیرید و مجوزهای."), ("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(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nom d'utilisateur manquant"), ("Username missed", "Nom d'utilisateur manquant"),
("Password missed", "Mot de passe manquant"), ("Password missed", "Mot de passe manquant"),
("Wrong credentials", "Identifiant ou mot de passe erroné"), ("Wrong credentials", "Identifiant ou mot de passe erroné"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Modifier la balise"), ("Edit Tag", "Modifier la balise"),
("Unremember Password", "Oublier le Mot de passe"), ("Unremember Password", "Oublier le Mot de passe"),
("Favorites", "Favoris"), ("Favorites", "Favoris"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Üres felhasználónév"), ("Username missed", "Üres felhasználónév"),
("Password missed", "Üres jelszó"), ("Password missed", "Üres jelszó"),
("Wrong credentials", "Hibás felhasználónév vagy 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"), ("Edit Tag", "Címke szerkesztése"),
("Unremember Password", "A jelszó megjegyzésének törlése"), ("Unremember Password", "A jelszó megjegyzésének törlése"),
("Favorites", "Kedvencek"), ("Favorites", "Kedvencek"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Username tidak sesuai"), ("Username missed", "Username tidak sesuai"),
("Password missed", "Kata sandi tidak sesuai"), ("Password missed", "Kata sandi tidak sesuai"),
("Wrong credentials", "Username atau password salah"), ("Wrong credentials", "Username atau password salah"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Ubah Tag"), ("Edit Tag", "Ubah Tag"),
("Unremember Password", "Lupa Kata Sandi"), ("Unremember Password", "Lupa Kata Sandi"),
("Favorites", "Favorit"), ("Favorites", "Favorit"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nome utente mancante"), ("Username missed", "Nome utente mancante"),
("Password missed", "Password mancante"), ("Password missed", "Password mancante"),
("Wrong credentials", "Credenziali errate"), ("Wrong credentials", "Credenziali errate"),
("The verification code is incorrect or has expired", "Il codice di verifica non è corretto o è scaduto"),
("Edit Tag", "Modifica tag"), ("Edit Tag", "Modifica tag"),
("Unremember Password", "Dimentica password"), ("Unremember Password", "Dimentica password"),
("Favorites", "Preferiti"), ("Favorites", "Preferiti"),
@@ -453,7 +454,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Voice call", "Chiamata vocale"), ("Voice call", "Chiamata vocale"),
("Text chat", "Chat testuale"), ("Text chat", "Chat testuale"),
("Stop voice call", "Interrompi chiamata vocale"), ("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"), ("Reconnect", "Riconnetti"),
("Codec", "Codec"), ("Codec", "Codec"),
("Resolution", "Risoluzione"), ("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", "Accetta ed eleva"),
("accept_and_elevate_btn_tooltip", "Accetta la connessione ed eleva le autorizzazioni UAC."), ("accept_and_elevate_btn_tooltip", "Accetta la connessione ed eleva le autorizzazioni UAC."),
("clipboard_wait_response_timeout_tip", "Timeout attesa risposta della copia."), ("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();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "ユーザー名がありません"), ("Username missed", "ユーザー名がありません"),
("Password missed", "パスワードがありません"), ("Password missed", "パスワードがありません"),
("Wrong credentials", "資格情報が間違っています"), ("Wrong credentials", "資格情報が間違っています"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "タグを編集"), ("Edit Tag", "タグを編集"),
("Unremember Password", "パスワードの記憶を解除"), ("Unremember Password", "パスワードの記憶を解除"),
("Favorites", "お気に入り"), ("Favorites", "お気に入り"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "사용자명 누락"), ("Username missed", "사용자명 누락"),
("Password missed", "비밀번호 누락"), ("Password missed", "비밀번호 누락"),
("Wrong credentials", "틀린 인증 정보"), ("Wrong credentials", "틀린 인증 정보"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "태그 수정"), ("Edit Tag", "태그 수정"),
("Unremember Password", "패스워드 기억하지 않기"), ("Unremember Password", "패스워드 기억하지 않기"),
("Favorites", "즐겨찾기"), ("Favorites", "즐겨찾기"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Қолданушы аты бос"), ("Username missed", "Қолданушы аты бос"),
("Password missed", "Құпия сөз бос"), ("Password missed", "Құпия сөз бос"),
("Wrong credentials", "Бұрыс тіркелгі деректер"), ("Wrong credentials", "Бұрыс тіркелгі деректер"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Тақты Өндеу"), ("Edit Tag", "Тақты Өндеу"),
("Unremember Password", "Құпия сөзді Ұмыту"), ("Unremember Password", "Құпия сөзді Ұмыту"),
("Favorites", "Таңдаулылар"), ("Favorites", "Таңдаулылар"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Prarastas vartotojo vardas"), ("Username missed", "Prarastas vartotojo vardas"),
("Password missed", "Slaptažodis praleistas"), ("Password missed", "Slaptažodis praleistas"),
("Wrong credentials", "Klaidingi kredencialai"), ("Wrong credentials", "Klaidingi kredencialai"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Redaguoti žymą"), ("Edit Tag", "Redaguoti žymą"),
("Unremember Password", "Nebeprisiminti slaptažodžio"), ("Unremember Password", "Nebeprisiminti slaptažodžio"),
("Favorites", "Parankiniai"), ("Favorites", "Parankiniai"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Gebruikersnaam gemist"), ("Username missed", "Gebruikersnaam gemist"),
("Password missed", "Wachtwoord vergeten"), ("Password missed", "Wachtwoord vergeten"),
("Wrong credentials", "Verkeerde inloggegevens"), ("Wrong credentials", "Verkeerde inloggegevens"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Label Bewerken"), ("Edit Tag", "Label Bewerken"),
("Unremember Password", "Wachtwoord vergeten"), ("Unremember Password", "Wachtwoord vergeten"),
("Favorites", "Favorieten"), ("Favorites", "Favorieten"),
@@ -511,6 +512,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Collapse toolbar", "Werkbalk samenvouwen"), ("Collapse toolbar", "Werkbalk samenvouwen"),
("Accept and Elevate", "Accepteren en Verheffen"), ("Accept and Elevate", "Accepteren en Verheffen"),
("accept_and_elevate_btn_tooltip", "Accepteer de verbinding en verhoog de UAC-machtigingen."), ("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(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nieprawidłowe nazwa użytkownika"), ("Username missed", "Nieprawidłowe nazwa użytkownika"),
("Password missed", "Nieprawidłowe hasło"), ("Password missed", "Nieprawidłowe hasło"),
("Wrong credentials", "Błędne dane uwierzytelniające"), ("Wrong credentials", "Błędne dane uwierzytelniające"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Edytuj tag"), ("Edit Tag", "Edytuj tag"),
("Unremember Password", "Zapomnij hasło"), ("Unremember Password", "Zapomnij hasło"),
("Favorites", "Ulubione"), ("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", "Akceptuj i Podnieś uprawnienia"),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nome de utilizador em falta"), ("Username missed", "Nome de utilizador em falta"),
("Password missed", "Palavra-chave em falta"), ("Password missed", "Palavra-chave em falta"),
("Wrong credentials", "Nome de utilizador ou palavra-chave incorrectos"), ("Wrong credentials", "Nome de utilizador ou palavra-chave incorrectos"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar Tag"), ("Edit Tag", "Editar Tag"),
("Unremember Password", "Esquecer Palavra-chave"), ("Unremember Password", "Esquecer Palavra-chave"),
("Favorites", "Favoritos"), ("Favorites", "Favoritos"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Nome de usuário requerido"), ("Username missed", "Nome de usuário requerido"),
("Password missed", "Senha requerida"), ("Password missed", "Senha requerida"),
("Wrong credentials", "Nome de usuário ou senha incorretos"), ("Wrong credentials", "Nome de usuário ou senha incorretos"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Editar Tag"), ("Edit Tag", "Editar Tag"),
("Unremember Password", "Esquecer Senha"), ("Unremember Password", "Esquecer Senha"),
("Favorites", "Favoritos"), ("Favorites", "Favoritos"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@@ -233,6 +233,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Username missed", "Lipsește numele de utilizator"), ("Username missed", "Lipsește numele de utilizator"),
("Password missed", "Lipsește parola"), ("Password missed", "Lipsește parola"),
("Wrong credentials", "Nume sau parolă greșită"), ("Wrong credentials", "Nume sau parolă greșită"),
("The verification code is incorrect or has expired", ""),
("Edit Tag", "Modifică etichetă"), ("Edit Tag", "Modifică etichetă"),
("Unremember Password", "Uită parola"), ("Unremember Password", "Uită parola"),
("Favorites", "Favorite"), ("Favorites", "Favorite"),
@@ -512,5 +513,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Elevate", ""), ("Accept and Elevate", ""),
("accept_and_elevate_btn_tooltip", ""), ("accept_and_elevate_btn_tooltip", ""),
("clipboard_wait_response_timeout_tip", ""), ("clipboard_wait_response_timeout_tip", ""),
("Incoming connection", ""),
("Outgoing connection", ""),
("Exit", ""),
("Open", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

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