Compare commits

..

21 Commits

Author SHA1 Message Date
alonginwind
d4410e78e2 feat(ui): show alias instead of peerId in terminal tab label (#13332) 2025-10-29 16:11:20 +08:00
fufesou
e3fcc6cce3 fix: file transfer, auto start on reconnect (#13329)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-10-29 15:15:05 +08:00
fufesou
265d08fc3b fix: mobile remove "Scale custom" (#13323)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-10-28 20:25:33 +08:00
21pages
7c8329c5c6 fix mac hwcodec check (#13320)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-10-28 16:29:42 +08:00
Kendall
d3947c9a19 Fix typo in Spanish translation for downloading message (#13289) 2025-10-27 17:09:42 +08:00
Luca-rickrolled-himself
cd99331668 Add Romanian Locale (#13270)
* Create CODE_OF_CONDUCT-RO.md

* Create CONTRIBUTING-RO.md

* Create SECURITY-RO.md

* Create README-RO.md

* Update README.md
2025-10-27 16:52:36 +08:00
Re*Index. (ot_inc)
472e18b10a Update Japanese (#13268) 2025-10-27 16:52:22 +08:00
Ivan Beà
d443f5de28 Update catalan translation ca.rs (#13267)
Update catalan translation
2025-10-27 16:52:10 +08:00
21pages
3242d132f6 opt ui of Windows session dialog (#13303)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-10-27 16:51:30 +08:00
Tomppaa
e66d2facd4 Create fi.rs (#13212)
Please add Finnish language rustdesk.
2025-10-26 21:28:56 +08:00
Tomppaa
f15b8dc0da Update lang.rs (#13259)
added finnish language.
2025-10-26 21:28:35 +08:00
Nguyễn Quý Hy
3275824aec Allow flipping sort order in mobile app's file transfer (#13273)
* Allow flipping sort order in mobile app's file transfer

Signed-off-by: Nguyen Quy Hy <nguyenquyhy@live.com.sg>

* Change ascending to be non-nullable

Signed-off-by: Nguyen Quy Hy <nguyenquyhy@live.com.sg>

* Revert file_model change

Signed-off-by: Nguyen Quy Hy <nguyenquyhy@live.com.sg>

---------

Signed-off-by: Nguyen Quy Hy <nguyenquyhy@live.com.sg>
2025-10-25 21:10:26 +08:00
21pages
965cb704ec add try catch on android setCodecInfo in case of unexpected crash (#13280)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-10-24 21:04:18 +08:00
fufesou
938e165470 fix: save frame, LateInitializationError (#13265)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-10-24 17:20:56 +08:00
esterTion
9058ef3344 ios: Enable file sharing and document browser support (#13226) 2025-10-23 15:58:50 +08:00
fufesou
ed39cc3038 fix: video service, wait timeout (#13208)
Use multiple frame fetched notifiers.

Signed-off-by: fufesou <linlong1266@gmail.com>
2025-10-22 13:19:08 +08:00
21pages
a77752c4cb fix tab lable translation (#13240)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-10-21 15:39:52 +08:00
21pages
c9940957f0 fix camera large error log (#13227)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2025-10-20 13:23:41 +08:00
Andrzej Rudnik
c90d72d720 Update pl.rs (#13210) 2025-10-19 14:19:53 +08:00
XLion
2c30bd9d24 Update tw.rs (#13203) 2025-10-18 16:39:08 +08:00
rustdesk
f2dc8e21a8 build 61 2025-10-18 08:50:07 +08:00
32 changed files with 1445 additions and 333 deletions

View File

@@ -4,7 +4,7 @@
<a href="#how-to-build-with-docker">Docker</a> •
<a href="#file-structure">Structure</a> •
<a href="#snapshot">Snapshot</a><br>
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>]<br>
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>] | [<a href="docs/README-RO.md">Română</a>]<br>
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
</p>

View File

@@ -0,0 +1,85 @@
# Codul de Conduită al Contributorilor
## Angajamentul Nostru
Noi, ca membri, contribuitori și lideri, ne angajăm să facem ca participarea în comunitatea noastră să fie o experiență fără hărțuire pentru toată lumea, indiferent de vârstă, dimensiunea corpului, dizabilități vizibile sau invizibile, etnie, caracteristici sexuale, identitate și exprimare de gen, nivel de experiență, educație, statut socio-economic, naționalitate, aspect personal, rasă, religie sau identitate și orientare sexuală.
Ne angajăm să acționăm și să interacționăm în moduri care contribuie la o comunitate deschisă, primitoare, diversă, incluzivă și sănătoasă.
## Standardele Noastre
Exemple de comportamente care contribuie la un mediu pozitiv pentru comunitatea noastră includ:
* Demonstrarea empatiei și a bunătății față de ceilalți
* Respectarea opiniilor, punctelor de vedere și experiențelor diferite
* Oferirea și acceptarea cu grație a feedback-ului constructiv
* Asumarea responsabilității și cererea de scuze celor afectați de greșelile noastre și învățarea din experiență
* Concentrarea pe ceea ce este cel mai bun nu doar pentru noi ca indivizi, ci pentru întreaga comunitate
Exemple de comportamente inacceptabile includ:
* Utilizarea limbajului sau imaginilor sexualizate, precum și atenția sau avansurile sexuale de orice fel
* Trollare, insulte sau comentarii denigratoare și atacuri personale sau politice
* Hărțuire publică sau privată
* Publicarea informațiilor private ale altora, cum ar fi adresa fizică sau de e-mail, fără permisiunea explicită
* Alte comportamente care ar putea fi considerate inadecvate într-un cadru profesional
## Responsabilități de Aplicare
Liderii comunității sunt responsabili pentru clarificarea și aplicarea standardelor noastre de comportament acceptabil și vor lua măsuri corective adecvate și echitabile ca răspuns la orice comportament pe care îl consideră inadecvat, amenințător, ofensator sau dăunător.
Liderii comunității au dreptul și responsabilitatea de a elimina, edita sau respinge comentarii, commit-uri, cod, editări wiki, probleme și alte contribuții care nu se aliniază acestui Cod de Conduită și vor comunica motivele pentru deciziile de moderare atunci când este cazul.
## Domeniu de Aplicare
Acest Cod de Conduită se aplică în toate spațiile comunității și se aplică și atunci când un individ reprezintă oficial comunitatea în spații publice.
Exemple de reprezentare a comunității includ utilizarea unei adrese de e-mail oficiale, postarea printr-un cont oficial de social media sau acționarea ca reprezentant desemnat la un eveniment online sau offline.
## Aplicare
Cazurile de comportament abuziv, hărțuitor sau altfel inacceptabil pot fi raportate liderilor comunității responsabili pentru aplicare la [info@rustdesk.com](mailto:info@rustdesk.com).
Toate plângerile vor fi revizuite și investigate prompt și corect.
Toți liderii comunității sunt obligați să respecte confidențialitatea și securitatea persoanei care raportează orice incident.
## Ghiduri de Aplicare
Liderii comunității vor urma aceste Ghiduri privind Impactul Comunității pentru a stabili consecințele pentru orice acțiune pe care o consideră o încălcare a acestui Cod de Conduită:
### 1. Corectare
**Impact asupra comunității**: Utilizarea limbajului neadecvat sau alte comportamente considerate neprofesionale sau nedorite în comunitate.
**Consecință**: O avertizare scrisă și privată din partea liderilor comunității, oferind claritate asupra naturii încălcării și o explicație despre motivul pentru care comportamentul a fost inadecvat. Poate fi cerută o scuză publică.
### 2. Avertisment
**Impact asupra comunității**: Încălcare printr-un incident singular sau o serie de acțiuni.
**Consecință**: Un avertisment cu consecințe pentru continuarea comportamentului. Nicio interacțiune cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, pentru o perioadă specificată. Aceasta include evitarea interacțiunilor în spațiile comunității, precum și pe canale externe, cum ar fi rețelele sociale. Încălcarea acestor termeni poate duce la o suspendare temporară sau permanentă.
### 3. Suspendare Temporară
**Impact asupra comunității**: O încălcare serioasă a standardelor comunității, inclusiv comportament neadecvat susținut.
**Consecință**: Suspendare temporară de la orice tip de interacțiune sau comunicare publică cu comunitatea pentru o perioadă specificată. Nicio interacțiune publică sau privată cu persoanele implicate, inclusiv interacțiuni nesolicitate cu cei care aplică Codul de Conduită, nu este permisă în această perioadă. Încălcarea acestor termeni poate duce la o interdicție permanentă.
### 4. Interdicție Permanentă
**Impact asupra comunității**: Demonstrând un tipar de încălcare a standardelor comunității, inclusiv comportament neadecvat susținut, hărțuire a unei persoane sau agresiune față de sau denigrare a unor grupuri de persoane.
**Consecință**: Interdicție permanentă de la orice tip de interacțiune publică în cadrul comunității.
## Atribuire
Acest Cod de Conduită este adaptat din [Contributor Covenant][homepage], versiunea 2.0, disponibil la [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Ghidurile privind Impactul Comunității au fost inspirate de [scara de aplicare a codului de conduită Mozilla][Mozilla CoC].
Pentru răspunsuri la întrebări frecvente despre acest cod de conduită, vezi FAQ la [https://www.contributor-covenant.org/faq][FAQ]. Traduceri sunt disponibile la [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

31
docs/CONTRIBUTING-RO.md Normal file
View File

@@ -0,0 +1,31 @@
# Contribuții la RustDesk
RustDesk primește cu plăcere contribuții din partea tuturor. Iată ghidurile dacă te gândești să ne ajuți:
## Contribuții
Contribuțiile la RustDesk sau la dependențele sale ar trebui făcute sub forma de pull request-uri pe GitHub. Fiecare pull request va fi revizuit de un contributor principal (cineva cu permisiunea de a aplica patch-uri) și fie va fi integrat în arborele principal, fie vor fi oferite sugestii pentru modificările necesare. Toate contribuțiile trebuie să urmeze acest format, chiar și cele ale contributorilor principali.
Dacă dorești să lucrezi la o problemă, te rugăm să o revendici mai întâi comentând pe GitHub issue-ul pe care vrei să lucrezi. Aceasta previne eforturi duplicate din partea contributorilor asupra aceleiași probleme.
## Lista de verificare pentru Pull Request
- Creează un branch din branch-ul `master` și, dacă este necesar, fă rebase la branch-ul `master` curent înainte de a trimite pull request-ul. Dacă nu se poate integra curat cu `master`, ți se poate cere să faci rebase la modificările tale.
- Commit-urile ar trebui să fie cât mai mici posibil, asigurând totodată că fiecare commit este corect independent (adică fiecare commit ar trebui să compileze și să treacă testele).
- Commit-urile trebuie să fie însoțite de un semnătura Developer Certificate of Origin (http://developercertificate.org), care indică faptul că tu (și angajatorul tău, dacă este cazul) ești de acord să respecți termenii [licenței proiectului](../LICENCE). În git, aceasta este opțiunea `-s` la `git commit`.
- Dacă patch-ul tău nu este revizuit sau ai nevoie ca o anumită persoană să-l revizuiască, poți @-reply unui reviewer cerând o revizuire în pull request sau într-un comentariu, sau poți solicita o revizuire prin [email](mailto:info@rustdesk.com).
- Adaugă teste relevante pentru bug-ul corectat sau pentru funcționalitatea nouă.
Pentru instrucțiuni specifice git, vezi [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
## Conduită
[Codul de Conduită RustDesk](https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md)
## Comunicare
Contributorii RustDesk frecventează [Discord](https://discord.gg/nDceKgxnkV).

181
docs/README-RO.md Normal file
View File

@@ -0,0 +1,181 @@
<p align="center">
<img src="../res/logo-header.svg" alt="RustDesk - desktopul tău la distanță"><br>
<a href="../README.md#raw-steps-to-build">Construire</a> •
<a href="../README.md#how-to-build-with-docker">Docker</a> •
<a href="../README.md#file-structure">Structură</a> •
<a href="../README.md#snapshot">Capturi</a><br>
[<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-DA.md">Dansk</a>] | [<a href="README-GR.md">Ελληνικά</a>] | [<a href="README-TR.md">Türkçe</a>] | [<a href="README-NO.md">Norsk</a>] | [<a href="README-RO.md">Română</a>]<br>
<b>Avem nevoie de ajutorul tău pentru a traduce acest README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> și <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> în limba ta maternă</b>
</p>
> [!Atenție]
> **Declinare de responsabilitate privind utilizarea abuzivă:** <br>
> Dezvoltatorii RustDesk nu susțin sau aprobă utilizarea neetică sau ilegală a acestui software. Utilizarea abuzivă, cum ar fi accesul neautorizat, controlul sau invadarea intimității, este strict împotriva regulilor noastre. Autorii nu sunt responsabili pentru utilizarea necorespunzătoare a aplicației.
Conversați cu noi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
[![RustDesk Server Pro](https://img.shields.io/badge/RustDesk%20Server%20Pro-Advanced%20Features-blue)](https://rustdesk.com/pricing.html)
Încă o soluție de desktop la distanță scrisă în Rust. Funcționează imediat, fără configurare necesară. Ai control total asupra datelor tale, fără probleme de securitate. Poți folosi serverul nostru de rendezvous/relay, [să-ți configurezi propriul server](https://rustdesk.com/server) sau [să scrii propriul server de rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
![imagine](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png)
RustDesk primește contribuții de la oricine. Vezi [CONTRIBUTING.md](../docs/CONTRIBUTING.md) pentru ajutor la început.
[**ÎNTREBĂRI FRECVENTE (FAQ)**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
[**DESCĂRCARE BINARE**](https://github.com/rustdesk/rustdesk/releases)
[**BUILD NIGHTLY**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
[<img src="https://flathub.org/api/badge?svg&locale=en"
alt="Get it on Flathub"
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
## Dependențe
Versiunile desktop folosesc Flutter sau Sciter (depreciat) pentru interfață; acest ghid este pentru Sciter doar, deoarece este mai ușor și mai prietenos pentru început. Vezi [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) pentru construire cu Flutter.
Te rugăm să descarci singur librăria dinamică Sciter.
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
## Pași pentru construire (Raw Steps to build)
- Pregătește mediul de dezvoltare Rust și mediul de construire C++
- Instalează [vcpkg](https://github.com/microsoft/vcpkg) și setează corect variabila de mediu `VCPKG_ROOT`
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
- Linux/macOS: vcpkg install libvpx libyuv opus aom
- rulează `cargo run`
## [Construire](https://rustdesk.com/docs/en/dev/build/)
## Cum se construiește pe Linux
### Ubuntu 18 (Debian 10)
```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
```
### openSUSE Tumbleweed
```sh
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
```
### Fedora 28 (CentOS 8)
```sh
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
```
### Arch (Manjaro)
```sh
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
```
### Instalează vcpkg
```sh
git clone https://github.com/microsoft/vcpkg
cd vcpkg
git checkout 2023.04.15
cd ..
vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=$HOME/vcpkg
vcpkg/vcpkg install libvpx libyuv opus aom
```
### Repară libvpx (Pentru Fedora)
```sh
cd vcpkg/buildtrees/libvpx/src
cd *
./configure
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
make
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
cd
```
### Build
```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
git clone --recurse-submodules https://github.com/rustdesk/rustdesk
cd rustdesk
mkdir -p target/debug
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
mv libsciter-gtk.so target/debug
VCPKG_ROOT=$HOME/vcpkg cargo run
```
## Cum să construiești cu Docker
Începe prin clonarea repository-ului și construirea imaginii Docker:
```sh
git clone https://github.com/rustdesk/rustdesk
cd rustdesk
git submodule update --init --recursive
docker build -t "rustdesk-builder" .
```
Apoi, de fiecare dată când trebuie să construiești aplicația, rulează comanda următoare:
```sh
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
```
Reține că prima construire poate dura mai mult până când dependențele sunt în cache; construirile ulterioare vor fi mai rapide. De asemenea, dacă trebuie să specifici argumente diferite comenzii de build, le poți adăuga la finalul comenzii în poziția `<OPTIONAL-ARGS>`. De exemplu, pentru a construi o versiune optimizată de release, adaugă `--release`. Executabilul rezultat va fi disponibil în folderul `target` pe sistemul tău, și poate fi rulat cu:
```sh
target/debug/rustdesk
```
Sau, dacă rulezi un executabil release:
```sh
target/release/rustdesk
```
Asigură-te că rulezi aceste comenzi din rădăcina repository-ului RustDesk, altfel aplicația poate să nu găsească resursele necesare. De asemenea, reține că alte subcomenzi cargo, cum ar fi `install` sau `run`, nu sunt acceptate în prezent prin această metodă, deoarece ar instala sau rula programul în interiorul containerului în loc de gazdă.
## Structura fișierelor
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: codec video, config, wrapper tcp/udp, protobuf, funcții fs pentru transfer de fișiere și alte funcții utilitare
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: capturare ecran
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: control tastatură/mouse specific platformei
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: implementare copy/paste pentru fișiere pentru Windows, Linux, macOS.
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: interfață Sciter învechită (depreciată)
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: servicii audio/clipboard/input/video și conexiuni de rețea
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: inițiază o conexiune peer
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: comunică cu [rustdesk-server](https://github.com/rustdesk/rustdesk-server), așteaptă conexiune directă remote (TCP hole punching) sau prin relay
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: cod specific platformei
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: cod Flutter pentru desktop și mobil
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: JavaScript pentru clientul Flutter web
## Capturi de ecran
![Connection Manager](https://github.com/rustdesk/rustdesk/assets/28412477/db82d4e7-c4bc-4823-8e6f-6af7eadf7651)
![Connected to a Windows PC](https://github.com/rustdesk/rustdesk/assets/28412477/9baa91e9-3362-4d06-aa1a-7518edcbd7ea)
![File Transfer](https://github.com/rustdesk/rustdesk/assets/28412477/39511ad3-aa9a-4f8c-8947-1cce286a46ad)
![TCP Tunneling](https://github.com/rustdesk/rustdesk/assets/28412477/78e8708f-e87e-4570-8373-1360033ea6c5)

9
docs/SECURITY-RO.md Normal file
View File

@@ -0,0 +1,9 @@
# Politica de Securitate
## Raportarea unei Vulnerabilități
Acordăm o mare importanță securității proiectului. Încurajăm toți utilizatorii să ne raporteze orice vulnerabilități pe care le descoperă.
Dacă găsești o vulnerabilitate de securitate în proiectul RustDesk, te rugăm să o raportezi responsabil trimițând un e-mail la info@rustdesk.com.
În acest moment, nu avem un program de recompense pentru descoperirea de bug-uri. Suntem o echipă mică care încearcă să rezolve o problemă mare.
Te rugăm să raportezi orice vulnerabilitate în mod responsabil, astfel încât să putem continua să construim o aplicație sigură pentru întreaga comunitate.

View File

@@ -62,7 +62,13 @@ class MainActivity : FlutterActivity() {
channelTag
)
initFlutterChannel(flutterMethodChannel!!)
thread { setCodecInfo() }
thread {
try {
setCodecInfo()
} catch (e: Exception) {
Log.e("MainActivity", "Failed to setCodecInfo: ${e.message}", e)
}
}
}
override fun onResume() {

View File

@@ -43,6 +43,8 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -60,6 +62,8 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -1736,22 +1736,29 @@ final Debouncer _saveWindowDebounce = Debouncer(delay: Duration(seconds: 1));
/// Save window position and size on exit
/// Note that windowId must be provided if it's subwindow
Future<void> saveWindowPosition(WindowType type, {int? windowId, bool? flush}) async {
Future<void> saveWindowPosition(WindowType type,
{int? windowId, bool? flush}) async {
if (type != WindowType.Main && windowId == null) {
debugPrint(
"Error: windowId cannot be null when saving positions for sub window");
}
late Offset position;
late Size sz;
Offset? position;
Size? sz;
late bool isMaximized;
bool isFullscreen = stateGlobal.fullscreen.isTrue;
setPreFrame() {
final pos = bind.getLocalFlutterOption(k: windowFramePrefix + type.name);
var lpos = LastWindowPosition.loadFromString(pos);
position = Offset(
lpos?.offsetWidth ?? position.dx, lpos?.offsetHeight ?? position.dy);
sz = Size(lpos?.width ?? sz.width, lpos?.height ?? sz.height);
if (lpos != null) {
if (lpos.offsetWidth != null && lpos.offsetHeight != null) {
position = Offset(lpos.offsetWidth!, lpos.offsetHeight!);
}
if (lpos.width != null && lpos.height != null) {
sz = Size(lpos.width!, lpos.height!);
}
}
}
switch (type) {
@@ -1791,20 +1798,20 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId, bool? flush}) a
}
break;
}
if (isWindows) {
if (isWindows && position != null) {
const kMinOffset = -10000;
const kMaxOffset = 10000;
if (position.dx < kMinOffset ||
position.dy < kMinOffset ||
position.dx > kMaxOffset ||
position.dy > kMaxOffset) {
if (position!.dx < kMinOffset ||
position!.dy < kMinOffset ||
position!.dx > kMaxOffset ||
position!.dy > kMaxOffset) {
debugPrint("Invalid position: $position, ignore saving position");
return;
}
}
final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized, isFullscreen);
final pos = LastWindowPosition(sz?.width, sz?.height, position?.dx,
position?.dy, isMaximized, isFullscreen);
final WindowKey key = (type: type, windowId: windowId);

View File

@@ -2121,15 +2121,20 @@ void showWindowsSessionsDialog(
return CustomAlertDialog(
title: null,
content: msgboxContent(type, title, text),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
msgboxContent(type, title, text).marginOnly(bottom: 12),
ComboBox(
keys: sids,
values: names,
initialKey: selectedUserValue,
onChanged: (value) {
selectedUserValue = value;
}),
],
),
actions: [
ComboBox(
keys: sids,
values: names,
initialKey: selectedUserValue,
onChanged: (value) {
selectedUserValue = value;
}),
dialogButton('Connect', onPressed: submit, isOutline: false),
],
);

View File

@@ -364,11 +364,12 @@ Future<List<TRadioMenu<String>>> toolbarViewStyle(
value: kRemoteViewStyleAdaptive,
groupValue: groupValue,
onChanged: onChanged),
TRadioMenu<String>(
child: Text(translate('Scale custom')),
value: kRemoteViewStyleCustom,
groupValue: groupValue,
onChanged: onChanged)
if (isDesktop || isWebDesktop)
TRadioMenu<String>(
child: Text(translate('Scale custom')),
value: kRemoteViewStyleCustom,
groupValue: groupValue,
onChanged: onChanged)
];
}

View File

@@ -61,9 +61,11 @@ class _TerminalTabPageState extends State<TerminalTabPage> {
String? connToken,
}) {
final tabKey = '${peerId}_$terminalId';
final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias');
final tabLabel = alias.isNotEmpty ? '$alias #$terminalId' : '$peerId #$terminalId';
return TabInfo(
key: tabKey,
label: '$peerId #$terminalId',
label: tabLabel,
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
onTabCloseButton: () async {

View File

@@ -153,135 +153,6 @@ class _ToolbarTheme {
typedef DismissFunc = void Function();
class RemoteMenuEntry {
static MenuEntryRadios<String> viewStyle(
String remoteId,
FFI ffi,
EdgeInsets padding, {
DismissFunc? dismissFunc,
DismissCallback? dismissCallback,
RxString? rxViewStyle,
}) {
return MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Scale original'),
value: kRemoteViewStyleOriginal,
dismissOnClicked: true,
dismissCallback: dismissCallback,
),
MenuEntryRadioOption(
text: translate('Scale adaptive'),
value: kRemoteViewStyleAdaptive,
dismissOnClicked: true,
dismissCallback: dismissCallback,
),
MenuEntryRadioOption(
text: translate('Scale custom'),
value: kRemoteViewStyleCustom,
dismissOnClicked: true,
dismissCallback: dismissCallback,
),
],
curOptionGetter: () async {
// null means peer id is not found, which there's no need to care about
final viewStyle =
await bind.sessionGetViewStyle(sessionId: ffi.sessionId) ?? '';
if (rxViewStyle != null) {
rxViewStyle.value = viewStyle;
}
return viewStyle;
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetViewStyle(
sessionId: ffi.sessionId, value: newValue);
if (rxViewStyle != null) {
rxViewStyle.value = newValue;
}
ffi.canvasModel.updateViewStyle();
if (dismissFunc != null) {
dismissFunc();
}
},
padding: padding,
dismissOnClicked: true,
dismissCallback: dismissCallback,
);
}
static MenuEntrySwitch2<String> showRemoteCursor(
String remoteId,
SessionID sessionId,
EdgeInsets padding, {
DismissFunc? dismissFunc,
DismissCallback? dismissCallback,
}) {
final state = ShowRemoteCursorState.find(remoteId);
final optKey = 'show-remote-cursor';
return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'),
getter: () {
return state;
},
setter: (bool v) async {
await bind.sessionToggleOption(sessionId: sessionId, value: optKey);
state.value =
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: optKey);
if (dismissFunc != null) {
dismissFunc();
}
},
padding: padding,
dismissOnClicked: true,
dismissCallback: dismissCallback,
);
}
static MenuEntrySwitch<String> disableClipboard(
SessionID sessionId,
EdgeInsets? padding, {
DismissFunc? dismissFunc,
DismissCallback? dismissCallback,
}) {
return createSwitchMenuEntry(
sessionId,
'Disable clipboard',
'disable-clipboard',
padding,
true,
dismissCallback: dismissCallback,
);
}
static MenuEntrySwitch<String> createSwitchMenuEntry(
SessionID sessionId,
String text,
String option,
EdgeInsets? padding,
bool dismissOnClicked, {
DismissFunc? dismissFunc,
DismissCallback? dismissCallback,
}) {
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate(text),
getter: () async {
return bind.sessionGetToggleOptionSync(
sessionId: sessionId, arg: option);
},
setter: (bool v) async {
await bind.sessionToggleOption(sessionId: sessionId, value: option);
if (dismissFunc != null) {
dismissFunc();
}
},
padding: padding,
dismissOnClicked: dismissOnClicked,
dismissCallback: dismissCallback,
);
}
static MenuEntryButton<String> insertLock(
SessionID sessionId,
EdgeInsets? padding, {

View File

@@ -405,10 +405,15 @@ class _DesktopTabState extends State<DesktopTab>
}
_saveFrame({bool? flush}) async {
if (tabType == DesktopTabType.main) {
await saveWindowPosition(WindowType.Main, flush: flush);
} else if (kWindowType != null && kWindowId != null) {
await saveWindowPosition(kWindowType!, windowId: kWindowId, flush: flush);
try {
if (tabType == DesktopTabType.main) {
await saveWindowPosition(WindowType.Main, flush: flush);
} else if (kWindowType != null && kWindowId != null) {
await saveWindowPosition(kWindowType!,
windowId: kWindowId, flush: flush);
}
} catch (e) {
debugPrint('Error saving window position: $e');
}
}
@@ -1080,11 +1085,12 @@ class _TabState extends State<_Tab> with RestorationMixin {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
child: Tooltip(
message: widget.tabType == DesktopTabType.main
? ''
: translate(widget.label.value),
message:
widget.tabType == DesktopTabType.main ? '' : widget.label.value,
child: Text(
translate(widget.label.value),
widget.tabType == DesktopTabType.main
? translate(widget.label.value)
: widget.label.value,
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected

View File

@@ -92,6 +92,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
gFFI.dialogManager.dismissAll();
WakelockPlus.disable();
});
model.jobController.clear();
super.dispose();
}
@@ -424,6 +425,7 @@ class FileManagerView extends StatefulWidget {
class _FileManagerViewState extends State<FileManagerView> {
final _listScrollController = ScrollController();
final _breadCrumbScroller = ScrollController();
late final ascending = Rx<bool>(controller.sortAscending);
bool get isLocal => widget.controller.isLocal;
FileController get controller => widget.controller;
@@ -589,57 +591,67 @@ class _FileManagerViewState extends State<FileManagerView> {
Widget headTools() => Container(
child: Row(
children: [
Expanded(child: Obx(() {
final home = controller.options.value.home;
final isWindows = controller.options.value.isWindows;
return BreadCrumb(
items: getPathBreadCrumbItems(controller.shortPath, isWindows,
() => controller.goToHomeDirectory(), (list) {
var path = "";
if (home.startsWith(list[0])) {
// absolute path
for (var item in list) {
path = PathUtil.join(path, item, isWindows);
}
} else {
path += home;
for (var item in list) {
path = PathUtil.join(path, item, isWindows);
}
}
controller.openDirectory(path);
}),
divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
);
})),
Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: controller.goBack,
),
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: controller.goToParentDirectory,
),
PopupMenuButton<SortBy>(
tooltip: "",
icon: Icon(Icons.sort),
itemBuilder: (context) {
return SortBy.values
.map((e) => PopupMenuItem(
child: Text(translate(e.toString())),
value: e,
))
.toList();
},
onSelected: controller.changeSortStyle),
Expanded(child: Obx(() {
final home = controller.options.value.home;
final isWindows = controller.options.value.isWindows;
return BreadCrumb(
items: getPathBreadCrumbItems(controller.shortPath, isWindows,
() => controller.goToHomeDirectory(), (list) {
var path = "";
if (home.startsWith(list[0])) {
// absolute path
for (var item in list) {
path = PathUtil.join(path, item, isWindows);
}
} else {
path += home;
for (var item in list) {
path = PathUtil.join(path, item, isWindows);
}
}
controller.openDirectory(path);
}),
divider: Icon(Icons.chevron_right),
overflow: ScrollableOverflow(controller: _breadCrumbScroller),
);
})),
Row(
children: [
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: controller.goBack,
),
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: controller.goToParentDirectory,
),
PopupMenuButton<SortBy>(
tooltip: "",
icon: Icon(Icons.sort),
itemBuilder: (context) {
return SortBy.values
.map((e) => PopupMenuItem(
child: Text(translate(e.toString())),
value: e,
))
.toList();
},
onSelected: (sortBy) {
// If selecting the same sort option, flip the order
// If selecting a different sort option, use ascending order
if (controller.sortBy.value == sortBy) {
ascending.value = !controller.sortAscending;
} else {
ascending.value = true;
}
controller.changeSortStyle(sortBy, ascending: ascending.value);
}
),
],
)
],
)
],
));
));
Widget listTail() => Obx(() => Container(
height: 100,

View File

@@ -1033,30 +1033,54 @@ class JobController {
await bind.sessionCancelJob(sessionId: sessionId, actId: id);
}
void loadLastJob(Map<String, dynamic> evt) {
Future<void> loadLastJob(Map<String, dynamic> evt) async {
debugPrint("load last job: $evt");
Map<String, dynamic> jobDetail = json.decode(evt['value']);
// int id = int.parse(jobDetail['id']);
String remote = jobDetail['remote'];
String to = jobDetail['to'];
bool showHidden = jobDetail['show_hidden'];
int fileNum = jobDetail['file_num'];
bool isRemote = jobDetail['is_remote'];
final currJobId = JobController.jobID.next();
String fileName = path.basename(isRemote ? remote : to);
var jobProgress = JobProgress()
..type = JobType.transfer
..fileName = fileName
..jobName = isRemote ? remote : to
..id = currJobId
..isRemoteToLocal = isRemote
..fileNum = fileNum
..remote = remote
..to = to
..showHidden = showHidden
..state = JobState.paused;
jobTable.add(jobProgress);
bind.sessionAddJob(
bool isAutoStart = jobDetail['auto_start'] == true;
int currJobId = -1;
if (isAutoStart) {
// Ensure jobDetail['id'] exists and is an int
if (jobDetail.containsKey('id') &&
jobDetail['id'] != null &&
jobDetail['id'] is int) {
currJobId = jobDetail['id'];
}
}
if (currJobId < 0) {
// If id is missing or invalid, disable auto-start and assign a new job id
isAutoStart = false;
currJobId = JobController.jobID.next();
}
if (!isAutoStart) {
if (!(isDesktop || isWebDesktop)) {
// Don't add to job table if not auto start on mobile.
// Because mobile does not support job list view now.
return;
}
// Add to job table if not auto start on desktop.
String fileName = path.basename(isRemote ? remote : to);
final jobProgress = JobProgress()
..type = JobType.transfer
..fileName = fileName
..jobName = isRemote ? remote : to
..id = currJobId
..isRemoteToLocal = isRemote
..fileNum = fileNum
..remote = remote
..to = to
..showHidden = showHidden
..state = JobState.paused;
jobTable.add(jobProgress);
}
await bind.sessionAddJob(
sessionId: sessionId,
isRemote: isRemote,
includeHidden: showHidden,
@@ -1065,6 +1089,11 @@ class JobController {
to: isRemote ? to : remote,
fileNum: fileNum,
);
if (isAutoStart) {
await bind.sessionResumeJob(
sessionId: sessionId, actId: currJobId, isRemote: isRemote);
}
}
void resumeJob(int jobId) {
@@ -1095,6 +1124,11 @@ class JobController {
}
debugPrint("update folder files: $info");
}
void clear() {
jobTable.clear();
jobResultListener.clear();
}
}
class JobResultListener<T> {

View File

@@ -475,7 +475,11 @@ class RustDeskMultiWindowManager {
final shouldSavePos = type != WindowType.Terminal || i == windows.length - 1;
if (shouldSavePos) {
debugPrint("closing multi window, type: ${type.toString()} id: $wId");
await saveWindowPosition(type, windowId: wId);
try {
await saveWindowPosition(type, windowId: wId);
} catch (e) {
debugPrint('Failed to save window position of $wId, $e');
}
}
try {
await WindowController.fromWindowId(wId).setPreventClose(false);

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
version: 1.4.3+60
version: 1.4.3+61
environment:
sdk: '^3.1.0'

View File

@@ -268,12 +268,12 @@ impl TraitCapturer for CameraCapturer {
#[cfg(windows)]
fn is_gdi(&self) -> bool {
false
true
}
#[cfg(windows)]
fn set_gdi(&mut self) -> bool {
false
true
}
#[cfg(feature = "vram")]

View File

@@ -687,7 +687,7 @@ pub fn check_available_hwcodec() -> String {
height: 720,
pixfmt: DEFAULT_PIXFMT,
align: HW_STRIDE_ALIGN as _,
kbs: 0,
kbs: 1000,
fps: DEFAULT_FPS,
gop: DEFAULT_GOP,
quality: DEFAULT_HW_QUALITY,

View File

@@ -23,7 +23,7 @@ use std::{
os::raw::{c_char, c_int, c_void},
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, RwLock,
},
};
@@ -756,7 +756,7 @@ impl InvokeUiSession for FlutterHandler {
// unused in flutter
fn clear_all_jobs(&self) {}
fn load_last_job(&self, _cnt: i32, job_json: &str) {
fn load_last_job(&self, _cnt: i32, job_json: &str, _auto_start: bool) {
self.push_event("load_last_job", &[("value", job_json)], &[]);
}
@@ -1328,6 +1328,7 @@ pub fn session_add(
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default()
};

View File

@@ -46,6 +46,7 @@ mod uk;
mod vi;
mod ta;
mod ge;
mod fi;
pub const LANGS: &[(&str, &str)] = &[
("en", "English"),
@@ -93,6 +94,7 @@ pub const LANGS: &[(&str, &str)] = &[
("sc", "Sardu"),
("ta", "தமிழ்"),
("ge", "ქართული"),
("fi", "Suomi"),
];
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -152,6 +154,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"kz" => kz::T.deref(),
"uk" => uk::T.deref(),
"fa" => fa::T.deref(),
"fi" => fi::T.deref(),
"ca" => ca::T.deref(),
"el" => el::T.deref(),
"sv" => sv::T.deref(),

View File

@@ -652,18 +652,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Untagged", "Sense etiquetar"),
("new-version-of-{}-tip", ""),
("Accessible devices", "Dispositius accessibles"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Veuillez mettre à niveau le client RustDesk vers la version {} ou plus récente du côté distant !"),
("upgrade_remote_rustdesk_client_to_{}_tip", ""),
("d3d_render_tip", ""),
("Use D3D rendering", ""),
("Printer", ""),
("Use D3D rendering", "Utilitza renderització D3D"),
("Printer", "Impressora"),
("printer-os-requirement-tip", ""),
("printer-requires-installed-{}-client-tip", ""),
("printer-{}-not-installed-tip", ""),
("printer-{}-ready-tip", ""),
("Install {} Printer", ""),
("Outgoing Print Jobs", ""),
("Incoming Print Jobs", ""),
("Incoming Print Job", ""),
("Install {} Printer", "Instal·la {} impressora"),
("Outgoing Print Jobs", "Treballs d'impressió sortints"),
("Incoming Print Jobs", "Treballs d'impressió entrants"),
("Incoming Print Job", "Treballs d'impressió entrant"),
("use-the-default-printer-tip", ""),
("use-the-selected-printer-tip", ""),
("auto-print-tip", ""),
@@ -672,54 +672,54 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("remote-printing-disallowed-text-tip", ""),
("save-settings-tip", ""),
("dont-show-again-tip", ""),
("Take screenshot", ""),
("Taking screenshot", ""),
("Take screenshot", "Fes una captura de pantalla"),
("Taking screenshot", "Fent la captura de pantalla"),
("screenshot-merged-screen-not-supported-tip", ""),
("screenshot-action-tip", ""),
("Save as", ""),
("Copy to clipboard", ""),
("Enable remote printer", ""),
("Downloading {}", ""),
("{} Update", ""),
("Save as", "Anomena i desa"),
("Copy to clipboard", "Copia al porta-retalls"),
("Enable remote printer", "Habilita l'impressora remota"),
("Downloading {}", "Descarregant {}"),
("{} Update", "{} Actualitza"),
("{}-to-update-tip", ""),
("download-new-version-failed-tip", ""),
("Auto update", ""),
("Auto update", "Actualització automàtica"),
("update-failed-check-msi-tip", ""),
("websocket_tip", ""),
("Use WebSocket", ""),
("Trackpad speed", ""),
("Default trackpad speed", ""),
("Numeric one-time password", ""),
("Enable IPv6 P2P connection", ""),
("Enable UDP hole punching", ""),
("Trackpad speed", "Velocitat del trackpad"),
("Default trackpad speed", "Velocitat per defecte del trackpad"),
("Numeric one-time password", "Contrasenya numèrica d'un sol ús"),
("Enable IPv6 P2P connection", "Habilita la connexió IPv6 P2P"),
("Enable UDP hole punching", "Activa la perforació UDP"),
("View camera", "Mostra la càmera"),
("Enable camera", ""),
("No cameras", ""),
("Enable camera", "Habilita la càmera"),
("No cameras", "No hi ha càmeres"),
("view_camera_unsupported_tip", ""),
("Terminal", ""),
("Enable terminal", ""),
("New tab", ""),
("Keep terminal sessions on disconnect", ""),
("Terminal (Run as administrator)", ""),
("Terminal", "Terminal"),
("Enable terminal", "Habilita el terminal"),
("New tab", "Nova finestra"),
("Keep terminal sessions on disconnect", "Mantingues les sessions de terminal desconnectades"),
("Terminal (Run as administrator)", "Terminal (executa com a administrador"),
("terminal-admin-login-tip", ""),
("Failed to get user token.", ""),
("Incorrect username or password.", ""),
("The user is not an administrator.", ""),
("Failed to check if the user is an administrator.", ""),
("Supported only in the installed version.", ""),
("Failed to get user token.", "No s'ha pogut obtenir el token d'usuari."),
("Incorrect username or password.", "Nom d'usuari o contrasenya incorrecte"),
("The user is not an administrator.", "Aquest usuari no és administrador"),
("Failed to check if the user is an administrator.", "No s'ha pogut comprovar si l'usuari és administrador."),
("Supported only in the installed version.", "Només compatible amb la versió instal·lada."),
("elevation_username_tip", ""),
("Preparing for installation ...", ""),
("Show my cursor", ""),
("Preparing for installation ...", "Preparant per a l'instal·lació..."),
("Show my cursor", "Mostra el meu punter"),
("Scale custom", "Escala personalitzada"),
("Custom scale slider", "Control lliscant d'escala personalitzada"),
("Decrease", "Disminueix"),
("Increase", "Augmenta"),
("Show virtual mouse", ""),
("Virtual mouse size", ""),
("Small", ""),
("Large", ""),
("Show virtual joystick", ""),
("Edit note", ""),
("Alias", ""),
("Show virtual mouse", "Mostra el ratolí virtual"),
("Virtual mouse size", "Mida del ratolí virtual"),
("Small", "Petita"),
("Large", "Gran"),
("Show virtual joystick", "Mostra el joystick virtual"),
("Edit note", "Edita la nota"),
("Alias", "Alias"),
].iter().cloned().collect();
}

View File

@@ -679,7 +679,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Save as", "Guardar como"),
("Copy to clipboard", "Copiar al portapapeles"),
("Enable remote printer", "Habilitar impresora remota"),
("Downloading {}", "Descarngando {}"),
("Downloading {}", "Descargando {}"),
("{} Update", "{} Actualizar"),
("{}-to-update-tip", "{} Se cerrará ahora e instalará la nueva versión."),
("download-new-version-failed-tip", "Descarga fallida. Puedes volver a intentarlo o hacer clic en el botón \"Download\" para descargar desde la página de lanzamientos y actualizar manualmente."),

725
src/lang/fi.rs Normal file
View File

@@ -0,0 +1,725 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "Tila"),
("Your Desktop", "Oma työpöytä"),
("desk_tip", "Työpöytääsi voidaan käyttää tällä tunnuksella ja salasanalla."),
("Password", "Salasana"),
("Ready", "Valmis"),
("Established", "Yhdistetty"),
("connecting_status", "Yhdistetään RustDesk verkkoon..."),
("Enable service", "Ota palvelu käyttöön"),
("Start service", "Käynnistä palvelu"),
("Service is running", "Palvelu on käynnissä"),
("Service is not running", "Palvelu ei ole käynnissä"),
("not_ready_status", "Ei valmis tarkista yhteys."),
("Control Remote Desktop", "Hallitse etätyöpöytää"),
("Transfer file", "Siirrä tiedosto"),
("Connect", "Yhdistä"),
("Recent sessions", "Viimeisimmät istunnot"),
("Address book", "Osoitekirja"),
("Confirmation", "Vahvistus"),
("TCP tunneling", "TCP tunnelointi"),
("Remove", "Poista"),
("Refresh random password", "Päivitä satunnainen salasana"),
("Set your own password", "Aseta oma salasana"),
("Enable keyboard/mouse", "Salli näppäimistö ja hiiri"),
("Enable clipboard", "Salli leikepöytä"),
("Enable file transfer", "Salli tiedostonsiirto"),
("Enable TCP tunneling", "Salli TCP tunnelointi"),
("IP Whitelisting", "IP osoitteiden sallintalista"),
("ID/Relay Server", "ID/Välityspalvelin"),
("Import server config", "Tuo palvelimen asetukset"),
("Export Server Config", "Vie palvelimen asetukset"),
("Import server configuration successfully", "Palvelimen asetukset tuotu onnistuneesti"),
("Export server configuration successfully", "Palvelimen asetukset viety onnistuneesti"),
("Invalid server configuration", "Virheellinen palvelimen määritys"),
("Clipboard is empty", "Leikepöytä on tyhjä"),
("Stop service", "Pysäytä palvelu"),
("Change ID", "Vaihda ID"),
("Your new ID", "Uusi ID"),
("length %min% to %max%", "pituus %min%%max%"),
("starts with a letter", "alkaa kirjaimella"),
("allowed characters", "sallitut merkit"),
("id_change_tip", "Sallitut merkit: az, AZ, 09, - ja _. Ensimmäisen merkin on oltava kirjain. Pituus 616 merkkiä."),
("Website", "Verkkosivusto"),
("About", "Tietoa"),
("Slogan_tip", "Tehty sydämellä tässä kaoottisessa maailmassa!"),
("Privacy Statement", "Tietosuojaseloste"),
("Mute", "Mykistä"),
("Build Date", "Koontipäivä"),
("Version", "Versio"),
("Home", "Etusivu"),
("Audio Input", "Äänitulo"),
("Enhancements", "Parannukset"),
("Hardware Codec", "Laitteistokoodekki"),
("Adaptive bitrate", "Mukautuva bittinopeus"),
("ID Server", "ID palvelin"),
("Relay Server", "Välityspalvelin"),
("API Server", "API palvelin"),
("invalid_http", "Osoitteen on alettava http:// tai https://"),
("Invalid IP", "Virheellinen IP osoite"),
("Invalid format", "Virheellinen muoto"),
("server_not_support", "Palvelin ei tue tätä ominaisuutta"),
("Not available", "Ei saatavilla"),
("Too frequent", "Liian tiheä pyyntö"),
("Cancel", "Peruuta"),
("Skip", "Ohita"),
("Close", "Sulje"),
("Retry", "Yritä uudelleen"),
("OK", "OK"),
("Password Required", "Salasana vaaditaan"),
("Please enter your password", "Syötä salasanasi"),
("Remember password", "Muista salasana"),
("Wrong Password", "Väärä salasana"),
("Do you want to enter again?", "Haluatko yrittää uudelleen?"),
("Connection Error", "Yhteysvirhe"),
("Error", "Virhe"),
("Reset by the peer", "Yhteys katkaistu vastapuolen toimesta"),
("Connecting...", "Yhdistetään..."),
("Connection in progress. Please wait.", "Yhdistetään odota hetki."),
("Please try 1 minute later", "Yritä uudelleen minuutin kuluttua"),
("Login Error", "Kirjautumisvirhe"),
("Successful", "Onnistui"),
("Connected, waiting for image...", "Yhdistetty, odotetaan kuvaa..."),
("Name", "Nimi"),
("Type", "Tyyppi"),
("Modified", "Muokattu"),
("Size", "Koko"),
("Show Hidden Files", "Näytä piilotetut tiedostot"),
("Receive", "Vastaanota"),
("Send", "Lähetä"),
("Refresh File", "Päivitä tiedosto"),
("Local", "Paikallinen"),
("Remote", "Etä"),
("Remote Computer", "Etätietokone"),
("Local Computer", "Paikallinen tietokone"),
("Confirm Delete", "Vahvista poisto"),
("Delete", "Poista"),
("Properties", "Ominaisuudet"),
("Multi Select", "Monivalinta"),
("Select All", "Valitse kaikki"),
("Unselect All", "Poista kaikki valinnat"),
("Empty Directory", "Tyhjä kansio"),
("Not an empty directory", "Hakemisto ei ole tyhjä"),
("Are you sure you want to delete this file?", "Haluatko varmasti poistaa tämän tiedoston?"),
("Are you sure you want to delete this empty directory?", "Haluatko varmasti poistaa tämän tyhjän hakemiston?"),
("Are you sure you want to delete the file of this directory?", "Haluatko varmasti poistaa tämän hakemiston tiedoston?"),
("Do this for all conflicts", "Tee sama kaikille ristiriidoille"),
("This is irreversible!", "Tätä toimintoa ei voi perua!"),
("Deleting", "Poistetaan"),
("files", "tiedostoa"),
("Waiting", "Odotetaan"),
("Finished", "Valmis"),
("Speed", "Nopeus"),
("Custom Image Quality", "Mukautettu kuvanlaatu"),
("Privacy mode", "Yksityisyystila"),
("Block user input", "Estä käyttäjän toiminta"),
("Unblock user input", "Salli käyttäjän toiminta"),
("Adjust Window", "Sovita ikkuna"),
("Original", "Alkuperäinen"),
("Shrink", "Pienennä"),
("Stretch", "Venytä"),
("Scrollbar", "Vierityspalkki"),
("ScrollAuto", "Automaattinen vieritys"),
("Good image quality", "Hyvä kuvanlaatu"),
("Balanced", "Tasapainotettu"),
("Optimize reaction time", "Optimoi vasteaika"),
("Custom", "Mukautettu"),
("Show remote cursor", "Näytä etäkursori"),
("Show quality monitor", "Näytä laadunvalvonta"),
("Disable clipboard", "Poista leikepöytä käytöstä"),
("Lock after session end", "Lukitse istunnon päätyttyä"),
("Insert Ctrl + Alt + Del", "Lähetä Ctrl + Alt + Del"),
("Insert Lock", "Aseta lukitse"),
("Refresh", "Päivitä"),
("ID does not exist", "Tunnusta ei ole olemassa"),
("Failed to connect to rendezvous server", "Yhteys tapaamispalvelimeen epäonnistui"),
("Please try later", "Yritä myöhemmin uudelleen"),
("Remote desktop is offline", "Etätyöpöytä ei ole online tilassa"),
("Key mismatch", "Avaimet eivät täsmää"),
("Timeout", "Aikakatkaisu"),
("Failed to connect to relay server", "Yhteys välityspalvelimeen epäonnistui"),
("Failed to connect via rendezvous server", "Yhteys tapaamispalvelimen kautta epäonnistui"),
("Failed to connect via relay server", "Yhteys välityspalvelimen kautta epäonnistui"),
("Failed to make direct connection to remote desktop", "Suora yhteys etätyöpöytään epäonnistui"),
("Set Password", "Aseta salasana"),
("OS Password", "Käyttöjärjestelmän salasana"),
("install_tip", "Joissain tapauksissa RustDesk ei toimi oikein etäpuolella UAC:n vuoksi. Välttääksesi tämän, napsauta alla olevaa painiketta asentaaksesi RustDeskin järjestelmään."),
("Click to upgrade", "Päivitä napsauttamalla"),
("Configure", "Määritä"),
("config_acc", "Etätyöpöydän hallintaa varten sinun on annettava RustDeskille ”Esteettömyys”-oikeudet."),
("config_screen", "Etätyöpöydän käyttöä varten sinun on annettava RustDeskille ”Näytön tallennus” oikeudet."),
("Installing ...", "Asennetaan ..."),
("Install", "Asenna"),
("Installation", "Asennus"),
("Installation Path", "Asennuspolku"),
("Create start menu shortcuts", "Luo pikakuvakkeet Käynnistä valikkoon"),
("Create desktop icon", "Luo kuvake työpöydälle"),
("agreement_tip", "Aloittamalla asennuksen hyväksyt käyttöoikeussopimuksen."),
("Accept and Install", "Hyväksy ja asenna"),
("End-user license agreement", "Käyttöoikeussopimus"),
("Generating ...", "Luodaan ..."),
("Your installation is lower version.", "Asennettu versio on vanhempi."),
("not_close_tcp_tip", "Älä sulje tätä ikkunaa tunnelin ollessa käytössä"),
("Listening ...", "Kuunnellaan ..."),
("Remote Host", "Etätietokone"),
("Remote Port", "Etäportti"),
("Action", "Toiminto"),
("Add", "Lisää"),
("Local Port", "Paikallinen portti"),
("Local Address", "Paikallinen osoite"),
("Change Local Port", "Vaihda paikallinen porttia"),
("setup_server_tip", "Nopeampaa yhteyttä varten voit asettaa oman palvelimen"),
("Too short, at least 6 characters.", "Liian lyhyt, vähintään 6 merkkiä."),
("The confirmation is not identical.", "Vahvistus ei täsmää."),
("Permissions", "Oikeudet"),
("Accept", "Hyväksy"),
("Dismiss", "Hylkää"),
("Disconnect", "Katkaise yhteys"),
("Enable file copy and paste", "Salli tiedostojen kopiointi ja liittäminen"),
("Connected", "Yhdistetty"),
("Direct and encrypted connection", "Suora ja salattu yhteys"),
("Relayed and encrypted connection", "Välitetty ja salattu yhteys"),
("Direct and unencrypted connection", "Suora ja salaamaton yhteys"),
("Relayed and unencrypted connection", "Välitetty ja salaamaton yhteys"),
("Enter Remote ID", "Anna ID"),
("Enter your password", "Syötä salasanasi"),
("Logging in...", "Kirjaudutaan sisään..."),
("Enable RDP session sharing", "Salli RDP istunnon jakaminen"),
("Auto Login", "Automaattinen kirjautuminen"),
("Enable direct IP access", "Salli suora IP yhteys"),
("Rename", "Nimeä uudelleen"),
("Space", "Välilyönti"),
("Create desktop shortcut", "Luo työpöydän pikakuvake"),
("Change Path", "Vaihda polku"),
("Create Folder", "Luo kansio"),
("Please enter the folder name", "Anna kansion nimi"),
("Fix it", "Korjaa"),
("Warning", "Varoitus"),
("Login screen using Wayland is not supported", "Kirjautumisnäyttö Waylandilla ei ole tuettu"),
("Reboot required", "Uudelleenkäynnistys vaaditaan"),
("Unsupported display server", "Näyttöpalvelin ei ole tuettu"),
("x11 expected", "X11 odotettu"),
("Port", "Portti"),
("Settings", "Asetukset"),
("Username", "Käyttäjänimi"),
("Invalid port", "Virheellinen portti"),
("Closed manually by the peer", "Suljettu vastapuolen toimesta"),
("Enable remote configuration modification", "Salli etäasetusten muokkaus"),
("Run without install", "Suorita ilman asennusta"),
("Connect via relay", "Yhdistä välityspalvelimen kautta"),
("Always connect via relay", "Yhdistä aina välityspalvelimen kautta"),
("whitelist_tip", "Vain sallitut IP osoitteet voivat muodostaa yhteyden"),
("Login", "Kirjaudu sisään"),
("Verify", "Vahvista"),
("Remember me", "Muista minut"),
("Trust this device", "Luota tähän laitteeseen"),
("Verification code", "Vahvistuskoodi"),
("verification_tip", "Vahvistuskoodi on lähetetty rekisteröityyn sähköpostiosoitteeseen. Syötä koodi jatkaaksesi kirjautumista."),
("Logout", "Kirjaudu ulos"),
("Tags", "Tunnisteet"),
("Search ID", "Hae ID"),
("whitelist_sep", "Valkoisen listan erotin"),
("Add ID", "Lisää ID"),
("Add Tag", "Lisää tunniste"),
("Unselect all tags", "Poista kaikki tunnistevalinnat"),
("Network error", "Verkkovirhe"),
("Username missed", "Käyttäjänimi puuttuu"),
("Password missed", "Salasana puuttuu"),
("Wrong credentials", "Virheelliset kirjautumistiedot"),
("The verification code is incorrect or has expired", "Vahvistuskoodi on virheellinen tai vanhentunut"),
("Edit Tag", "Muokkaa tunnistetta"),
("Forget Password", "Unohditko salasanasi"),
("Favorites", "Suosikit"),
("Add to Favorites", "Lisää suosikkeihin"),
("Remove from Favorites", "Poista suosikeista"),
("Empty", "Tyhjä"),
("Invalid folder name", "Virheellinen kansion nimi"),
("Socks5 Proxy", "Socks5 välityspalvelin"),
("Socks5/Http(s) Proxy", "Socks5/HTTP(s)-välityspalvelin"),
("Discovered", "Löydetty"),
("install_daemon_tip", "Palvelun automaattista käynnistystä varten RustDesk daemon on asennettava järjestelmään."),
("Remote ID", "Etätunnus"),
("Paste", "Liitä"),
("Paste here?", "Liitä tähän?"),
("Are you sure to close the connection?", "Haluatko varmasti katkaista yhteyden?"),
("Download new version", "Lataa uusi versio"),
("Touch mode", "Kosketustila"),
("Mouse mode", "Hiiritila"),
("One-Finger Tap", "Yksi sormipainallus"),
("Left Mouse", "Vasen hiiren painike"),
("One-Long Tap", "Pitkä painallus yhdellä sormella"),
("Two-Finger Tap", "Kahden sormen napautus"),
("Right Mouse", "Oikea hiiren painike"),
("One-Finger Move", "Yhden sormen liike"),
("Double Tap & Move", "Kaksoisnapautus ja liike"),
("Mouse Drag", "Vedä hiirellä"),
("Three-Finger vertically", "Kolmen sormen pystysuora liike"),
("Mouse Wheel", "Hiiren rulla"),
("Two-Finger Move", "Kahden sormen liike"),
("Canvas Move", "Siirrä näkymää"),
("Pinch to Zoom", "Lähennä tai loitonna"),
("Canvas Zoom", "Suurennus"),
("Reset canvas", "Palauta näkymä"),
("No permission of file transfer", "Ei oikeutta tiedostonsiirtoon"),
("Note", "Huomautus"),
("Connection", "Yhteys"),
("Share screen", "Jaa näyttö"),
("Chat", "Keskustelu"),
("Total", "Yhteensä"),
("items", "kohdetta"),
("Selected", "Valittu"),
("Screen Capture", "Näytön kaappaus"),
("Input Control", "Tulon hallinta"),
("Audio Capture", "Äänen tallennus"),
("Do you accept?", "Hyväksytkö?"),
("Open System Setting", "Avaa järjestelmäasetukset"),
("How to get Android input permission?", "Kuinka myöntää Androidin oikeudet?"),
("android_input_permission_tip1", "Siirry Androidin asetuksiin ja ota RustDeskille käyttöön 'Syötteen ohjaus' oikeus."),
("android_input_permission_tip2", "Jos et löydä asetusta, etsi 'Esteettömyys' ja salli RustDesk ohjelman käyttö."),
("android_new_connection_tip", "Uusi yhteyspyyntö vastaanotettu."),
("android_service_will_start_tip", "RustDesk palvelu käynnistyy taustalla."),
("android_stop_service_tip", "Pysäytä taustapalvelu tarvittaessa RustDeskin asetuksista."),
("android_version_audio_tip", "Äänensiirto vaatii Android 10:n tai uudemman."),
("android_start_service_tip", "RustDesk palvelu käynnistetään..."),
("android_permission_may_not_change_tip", "Oikeudet eivät ehkä päivity heti. Käynnistä sovellus uudelleen, jos muutokset eivät tule voimaan."),
("Account", "Tili"),
("Overwrite", "Korvaa"),
("This file exists, skip or overwrite this file?", "Tämä tiedosto on jo olemassa, ohitetaanko vai korvataanko se?"),
("Quit", "Poistu"),
("Help", "Ohje"),
("Failed", "Epäonnistui"),
("Succeeded", "Onnistui"),
("Someone turns on privacy mode, exit", "Yksityisyystila otettu käyttöön, poistutaan"),
("Unsupported", "Ei tuettu"),
("Peer denied", "Vastapuoli hylkäsi pyynnön"),
("Please install plugins", "Asenna tarvittavat lisäosat"),
("Peer exit", "Vastapuoli sulki yhteyden"),
("Failed to turn off", "Sammutus epäonnistui"),
("Turned off", "Sammutettu"),
("Language", "Kieli"),
("Keep RustDesk background service", "Pidä RustDeskin taustapalvelu käynnissä"),
("Ignore Battery Optimizations", "Ohita akun optimoinnit"),
("android_open_battery_optimizations_tip", "Poista RustDeskin akkuoptimointi, jotta yhteys pysyy vakaana taustalla."),
("Start on boot", "Käynnistä automaattisesti laitteen käynnistyessä"),
("Start the screen sharing service on boot, requires special permissions", "Käynnistä näytönjakopalvelu laitteen käynnistyessä (vaatii erityisoikeudet)"),
("Connection not allowed", "Yhteyttä ei sallita"),
("Legacy mode", "Perinteinen tila"),
("Map mode", "Karttatila"),
("Translate mode", "Käännöstila"),
("Use permanent password", "Käytä pysyvää salasanaa"),
("Use both passwords", "Käytä molempia salasanoja"),
("Set permanent password", "Aseta pysyvä salasana"),
("Enable remote restart", "Salli etäuudelleenkäynnistys"),
("Restart remote device", "Käynnistä etälaite uudelleen"),
("Are you sure you want to restart", "Haluatko varmasti käynnistää laitteen uudelleen?"),
("Restarting remote device", "Etälaitetta käynnistetään uudelleen"),
("remote_restarting_tip", "Odota, kunnes etälaite käynnistyy uudelleen ja muodostaa yhteyden."),
("Copied", "Kopioitu"),
("Exit Fullscreen", "Poistu koko näytöstä"),
("Fullscreen", "Koko näyttö"),
("Mobile Actions", "Puhelin toiminnot"),
("Select Monitor", "Valitse näyttö"),
("Control Actions", "Ohjaustoiminnot"),
("Display Settings", "Näyttöasetukset"),
("Ratio", "Suhde"),
("Image Quality", "Kuvanlaatu"),
("Scroll Style", "Vieritystyyli"),
("Show Toolbar", "Näytä työkalupalkki"),
("Hide Toolbar", "Piilota työkalupalkki"),
("Direct Connection", "Suora yhteys"),
("Relay Connection", "Välitetty yhteys"),
("Secure Connection", "Suojattu yhteys"),
("Insecure Connection", "Suojaamaton yhteys"),
("Scale original", "Skaalaa alkuperäinen"),
("Scale adaptive", "Mukautuva skaalaus"),
("General", "Yleiset"),
("Security", "Turvallisuus"),
("Theme", "Teema"),
("Dark Theme", "Tumma teema"),
("Light Theme", "Vaalea teema"),
("Dark", "Tumma"),
("Light", "Vaalea"),
("Follow System", "Seuraa järjestelmän teemaa"),
("Enable hardware codec", "Käytä laitteistokoodausta"),
("Unlock Security Settings", "Avaa suojausasetukset"),
("Enable audio", "Ota ääni käyttöön"),
("Unlock Network Settings", "Avaa verkkoasetukset"),
("Server", "Palvelin"),
("Direct IP Access", "Suora IP yhteys"),
("Proxy", "Välityspalvelin"),
("Apply", "Käytä"),
("Disconnect all devices?", "Katkaistaanko yhteys kaikkiin laitteisiin?"),
("Clear", "Tyhjennä"),
("Audio Input Device", "Äänitulolaite"),
("Use IP Whitelisting", "Käytä IP sallitut listaa"),
("Network", "Verkko"),
("Pin Toolbar", "Kiinnitä työkalupalkki"),
("Unpin Toolbar", "Irrota työkalupalkki"),
("Recording", "Tallennus"),
("Directory", "Hakemisto"),
("Automatically record incoming sessions", "Tallenna saapuvat istunnot automaattisesti"),
("Automatically record outgoing sessions", "Tallenna lähtevät istunnot automaattisesti"),
("Change", "Vaihda"),
("Start session recording", "Aloita istunnon tallennus"),
("Stop session recording", "Lopeta istunnon tallennus"),
("Enable recording session", "Ota istunnon tallennus käyttöön"),
("Enable LAN discovery", "Ota LAN havaitseminen käyttöön"),
("Deny LAN discovery", "Estä LAN havaitseminen"),
("Write a message", "Kirjoita viesti"),
("Prompt", "Kehote"),
("Please wait for confirmation of UAC...", "Odota UAC hyväksyntää..."),
("elevated_foreground_window_tip", "Käyttäjävalvontaikkuna on etualalla, hyväksy pyyntö etäkäytön jatkamiseksi."),
("Disconnected", "Yhteys katkaistu"),
("Other", "Muu"),
("Confirm before closing multiple tabs", "Vahvista ennen useiden välilehtien sulkemista"),
("Keyboard Settings", "Näppäimistöasetukset"),
("Full Access", "Täysi käyttöoikeus"),
("Screen Share", "Näytönjako"),
("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vaatii Ubuntu 21.04:n tai uudemman version."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vaatii uudemman Linux jakelun version. Kokeile X11 työpöytää tai vaihda käyttöjärjestelmää."),
("JumpLink", "Pikalinkki"),
("Please Select the screen to be shared(Operate on the peer side).", "Valitse jaettava näyttö (toiminto etäpäässä)."),
("Show RustDesk", "Näytä RustDesk"),
("This PC", "Tämä tietokone"),
("or", "tai"),
("Continue with", "Jatka käyttäen"),
("Elevate", "Korota oikeudet"),
("Zoom cursor", "Suurennusosoitin"),
("Accept sessions via password", "Hyväksy istunnot salasanalla"),
("Accept sessions via click", "Hyväksy istunnot napsauttamalla"),
("Accept sessions via both", "Hyväksy istunnot kummallakin tavalla"),
("Please wait for the remote side to accept your session request...", "Odota, että etäpää hyväksyy istuntopyyntösi..."),
("One-time Password", "Kertakäyttösalasana"),
("Use one-time password", "Käytä kertakäyttösalasanaa"),
("One-time password length", "Kertakäyttösalasanan pituus"),
("Request access to your device", "Pyydä pääsyä laitteeseesi"),
("Hide connection management window", "Piilota yhteydenhallintaikkuna"),
("hide_cm_tip", "Yhteydenhallintaikkuna voidaan piilottaa, jotta etäistunto ei keskeydy."),
("wayland_experiment_tip", "Wayland tuki on kokeellinen ja saattaa aiheuttaa yhteysongelmia."),
("Right click to select tabs", "Valitse välilehti hiiren oikealla painikkeella"),
("Skipped", "Ohitettu"),
("Add to address book", "Lisää osoitekirjaan"),
("Group", "Ryhmä"),
("Search", "Haku"),
("Closed manually by web console", "Suljettu manuaalisesti verkkokonsolista"),
("Local keyboard type", "Paikallinen näppäimistötyyppi"),
("Select local keyboard type", "Valitse paikallinen näppäimistötyyppi"),
("software_render_tip", "Jos laitteistokiihdytys ei toimi oikein, voit käyttää ohjelmistopohjaista renderöintiä."),
("Always use software rendering", "Käytä aina ohjelmistopohjaista renderöintiä"),
("config_input", "Syöteasetukset"),
("config_microphone", "Mikrofoni"),
("request_elevation_tip", "Etätoiminto vaatii järjestelmänvalvojan oikeudet."),
("Wait", "Odota"),
("Elevation Error", "Oikeuksien korotus epäonnistui"),
("Ask the remote user for authentication", "Pyydä etäkäyttäjää vahvistamaan oikeudet"),
("Choose this if the remote account is administrator", "Valitse tämä, jos etätili on järjestelmänvalvoja"),
("Transmit the username and password of administrator", "Lähetä järjestelmänvalvojan käyttäjätunnus ja salasana"),
("still_click_uac_tip", "Etäkäyttäjän on edelleen hyväksyttävä UAC kehote omalla koneellaan."),
("Request Elevation", "Pyydä oikeuksien korotusta"),
("wait_accept_uac_tip", "Odota, että etäkäyttäjä hyväksyy UAC pyynnön..."),
("Elevate successfully", "Oikeuksien korotus onnistui"),
("uppercase", "iso kirjain"),
("lowercase", "pieni kirjain"),
("digit", "numero"),
("special character", "erikoismerkki"),
("length>=8", "vähintään 8 merkkiä"),
("Weak", "Heikko"),
("Medium", "Keskitaso"),
("Strong", "Vahva"),
("Switch Sides", "Vaihda puolia"),
("Please confirm if you want to share your desktop?", "Haluatko varmasti jakaa työpöytäsi?"),
("Display", "Näyttö"),
("Default View Style", "Oletusnäkymän tyyli"),
("Default Scroll Style", "Oletusvieritys tyyli"),
("Default Image Quality", "Oletuskuvanlaatu"),
("Default Codec", "Oletuskoodekki"),
("Bitrate", "Bittinopeus"),
("FPS", "Kuvataajuus (FPS)"),
("Auto", "Automaattinen"),
("Other Default Options", "Muut oletusasetukset"),
("Voice call", "Äänipuhelu"),
("Text chat", "Tekstikeskustelu"),
("Stop voice call", "Lopeta äänipuhelu"),
("relay_hint_tip", "Jos suora yhteys ei toimi, käytetään automaattisesti välityspalvelinta."),
("Reconnect", "Yhdistä uudelleen"),
("Codec", "Koodekki"),
("Resolution", "Resoluutio"),
("No transfers in progress", "Ei käynnissä olevia siirtoja"),
("Set one-time password length", "Aseta kertakäyttösalasanan pituus"),
("RDP Settings", "RDP asetukset"),
("Sort by", "Järjestä"),
("New Connection", "Uusi yhteys"),
("Restore", "Palauta"),
("Minimize", "Pienennä"),
("Maximize", "Suurenna"),
("Your Device", "Sinun laitteesi"),
("empty_recent_tip", "Ei äskettäisiä istuntoja"),
("empty_favorite_tip", "Ei suosikkeja"),
("empty_lan_tip", "LAN laitteita ei löytynyt"),
("empty_address_book_tip", "Osoitekirja on tyhjä"),
("Empty Username", "Tyhjä käyttäjänimi"),
("Empty Password", "Tyhjä salasana"),
("Me", "Minä"),
("identical_file_tip", "Saman niminen tiedosto on jo olemassa"),
("show_monitors_tip", "Näytä kaikki käytettävissä olevat näytöt"),
("View Mode", "Näkymätila"),
("login_linux_tip", "Kirjaudu sisään Linux käyttäjätunnuksellasi"),
("verify_rustdesk_password_tip", "Vahvista RustDesk salasanasi kirjautumista varten"),
("remember_account_tip", "Muista tilini kirjautumista varten"),
("os_account_desk_tip", "Käytä käyttöjärjestelmän käyttäjätiliä kirjautumiseen"),
("OS Account", "Käyttöjärjestelmän tili"),
("another_user_login_title_tip", "Toinen käyttäjä on kirjautunut sisään"),
("another_user_login_text_tip", "Etäistunto keskeytetään, koska toinen käyttäjä on ottanut hallinnan."),
("xorg_not_found_title_tip", "Xorg ei löydy"),
("xorg_not_found_text_tip", "X11 palvelinta ei löydetty. Vaihda Xorg ympäristöön jatkaaksesi."),
("no_desktop_title_tip", "Työpöytää ei havaittu"),
("no_desktop_text_tip", "Työpöytäympäristöä ei löydy. Asenna esimerkiksi GNOME tai XFCE."),
("No need to elevate", "Oikeuksien korotusta ei tarvita"),
("System Sound", "Järjestelmän ääni"),
("Default", "Oletus"),
("New RDP", "Uusi RDP yhteys"),
("Fingerprint", "Sormenjälki"),
("Copy Fingerprint", "Kopioi sormenjälki"),
("no fingerprints", "Ei sormenjälkiä"),
("Select a peer", "Valitse vastapää"),
("Select peers", "Valitse useita vastapään laitteita"),
("Plugins", "Laajennukset"),
("Uninstall", "Poista asennus"),
("Update", "Päivitä"),
("Enable", "Ota käyttöön"),
("Disable", "Poista käytöstä"),
("Options", "Asetukset"),
("resolution_original_tip", "Näytä alkuperäisessä resoluutiossa ilman skaalausta"),
("resolution_fit_local_tip", "Sovita etänäyttö paikalliseen näkymään"),
("resolution_custom_tip", "Käytä mukautettua resoluutiota"),
("Collapse toolbar", "Tiivistä työkalupalkki"),
("Accept and Elevate", "Hyväksy ja korota oikeudet"),
("accept_and_elevate_btn_tooltip", "Hyväksy ja korota oikeudet järjestelmänvalvojaksi"),
("clipboard_wait_response_timeout_tip", "Leikepöydän pyyntö aikakatkaistiin ei vastausta etäpäästä."),
("Incoming connection", "Saapuva yhteys"),
("Outgoing connection", "Lähtevä yhteys"),
("Exit", "Poistu"),
("Open", "Avaa"),
("logout_tip", "Haluatko varmasti kirjautua ulos?"),
("Service", "Palvelu"),
("Start", "Käynnistä"),
("Stop", "Pysäytä"),
("exceed_max_devices", "Olet saavuttanut hallittavien laitteiden enimmäismäärän."),
("Sync with recent sessions", "Synkronoi viimeisimpiin istuntoihin"),
("Sort tags", "Järjestä tunnisteet"),
("Open connection in new tab", "Avaa yhteys uuteen välilehteen"),
("Move tab to new window", "Siirrä välilehti uuteen ikkunaan"),
("Can not be empty", "Ei voi olla tyhjä"),
("Already exists", "On jo olemassa"),
("Change Password", "Vaihda salasana"),
("Refresh Password", "Päivitä salasana"),
("ID", "Tunnus"),
("Grid View", "Ruudukkonäkymä"),
("List View", "Luettelonäkymä"),
("Select", "Valitse"),
("Toggle Tags", "Näytä/piilota tunnisteet"),
("pull_ab_failed_tip", "Osoitekirjan lataus epäonnistui palvelimelta."),
("push_ab_failed_tip", "Osoitekirjan lähetys palvelimelle epäonnistui."),
("synced_peer_readded_tip", "Synkronoitu laite lisättiin uudelleen."),
("Change Color", "Vaihda väri"),
("Primary Color", "Pääväri"),
("HSV Color", "HSV väriarvot"),
("Installation Successful!", "Asennus onnistui!"),
("Installation failed!", "Asennus epäonnistui!"),
("Reverse mouse wheel", "Käänteinen hiiren rullaussuunta"),
("{} sessions", "{} istuntoa"),
("scam_title", "Huijausvaroitus"),
("scam_text1", "Älä anna tuntemattomille henkilöille pääsyä tietokoneeseesi."),
("scam_text2", "RustDesk ei koskaan pyydä maksua tai etäkäyttöä ilman lupaasi."),
("Don't show again", "Älä näytä uudelleen"),
("I Agree", "Hyväksyn"),
("Decline", "Hylkää"),
("Timeout in minutes", "Aikakatkaisu minuuteissa"),
("auto_disconnect_option_tip", "Katkaise yhteys automaattisesti, jos ei aktiivisuutta määräaikaan mennessä."),
("Connection failed due to inactivity", "Yhteys epäonnistui toimettomuuden vuoksi"),
("Check for software update on startup", "Tarkista ohjelmistopäivitykset käynnistyksen yhteydessä"),
("upgrade_rustdesk_server_pro_to_{}_tip", "Päivitä RustDesk Server Pro versioon {} jatkaaksesi."),
("pull_group_failed_tip", "Ryhmäasetusten nouto epäonnistui."),
("Filter by intersection", "Suodata leikkausten perusteella"),
("Remove wallpaper during incoming sessions", "Poista taustakuva saapuvien istuntojen ajaksi"),
("Test", "Testaa"),
("display_is_plugged_out_msg", "Näyttö on irrotettu"),
("No displays", "Ei näyttöjä"),
("Open in new window", "Avaa uudessa ikkunassa"),
("Show displays as individual windows", "Näytä näytöt erillisinä ikkunoina"),
("Use all my displays for the remote session", "Käytä kaikkia näyttöjä etäistunnossa"),
("selinux_tip", "SELinux saattaa estää etäyhteyden toiminnan. Tarkista asetukset."),
("Change view", "Vaihda näkymä"),
("Big tiles", "Suuret ruudut"),
("Small tiles", "Pienet ruudut"),
("List", "Lista"),
("Virtual display", "Virtuaalinäyttö"),
("Plug out all", "Irrota kaikki"),
("True color (4:4:4)", "Tarkka väri (4:4:4)"),
("Enable blocking user input", "Estä käyttäjän syöte etäpäässä"),
("id_input_tip", "Anna etätunnus muodossa tunnus@palvelin"),
("privacy_mode_impl_mag_tip", "Yksityisyystila käyttää suurennustekniikkaa piilottaakseen sisällön."),
("privacy_mode_impl_virtual_display_tip", "Yksityisyystila käyttää virtuaalinäyttöä tietosuojan takaamiseksi."),
("Enter privacy mode", "Siirry yksityisyystilaan"),
("Exit privacy mode", "Poistu yksityisyystilasta"),
("idd_not_support_under_win10_2004_tip", "Virtuaalinäyttöä ei tueta Windows 10 2004 versiota vanhemmissa järjestelmissä."),
("input_source_1_tip", "Valitse syöte 1: fyysinen näppäimistö tai hiiri"),
("input_source_2_tip", "Valitse syöte 2: virtuaalinen syöte"),
("Swap control-command key", "Vaihda Ctrl ja Command näppäinten paikkaa"),
("swap-left-right-mouse", "Vaihda hiiren vasen ja oikea painike"),
("2FA code", "2FA koodi"),
("More", "Lisää"),
("enable-2fa-title", "Ota kaksivaiheinen todennus käyttöön"),
("enable-2fa-desc", "Lisää turvallisuutta vahvistamalla kirjautumisesi 2FA koodilla."),
("wrong-2fa-code", "Väärä 2FA koodi"),
("enter-2fa-title", "Syötä 2FA koodi"),
("Email verification code must be 6 characters.", "Sähköpostivarmennuskoodin on oltava 6 merkkiä pitkä."),
("2FA code must be 6 digits.", "2FA koodin on oltava 6 numeroa."),
("Multiple Windows sessions found", "Useita Windows istuntoja havaittu"),
("Please select the session you want to connect to", "Valitse istunto, johon haluat muodostaa yhteyden"),
("powered_by_me", "Ylpeästi kehitetty omavaraisesti"),
("outgoing_only_desk_tip", "Tämä asennus tukee vain lähteviä yhteyksiä."),
("preset_password_warning", "Esiasetettu salasana voi olla turvaton — vaihda se suojataksesi yhteytesi."),
("Security Alert", "Turvailmoitus"),
("My address book", "Oma osoitekirja"),
("Personal", "Henkilökohtainen"),
("Owner", "Omistaja"),
("Set shared password", "Aseta jaettu salasana"),
("Exist in", "Sisältyy kohteeseen"),
("Read-only", "Vain luku"),
("Read/Write", "Luku ja kirjoitus"),
("Full Control", "Täysi hallinta"),
("share_warning_tip", "Jakaminen antaa muille pääsyn laitteeseesi. Varmista, että luotat käyttäjään."),
("Everyone", "Kaikki"),
("ab_web_console_tip", "Osoitekirjaa voidaan hallita myös verkkokonsolin kautta."),
("allow-only-conn-window-open-tip", "Salli vain yksi yhteyshallintaikkuna kerrallaan."),
("no_need_privacy_mode_no_physical_displays_tip", "Yksityisyystilaa ei tarvita, koska fyysisiä näyttöjä ei ole."),
("Follow remote cursor", "Seuraa etäosoitinta"),
("Follow remote window focus", "Seuraa etäikkunan kohdistusta"),
("default_proxy_tip", "Käytetään oletusarvoista välityspalvelinta, ellei muuta määritetty."),
("no_audio_input_device_tip", "Äänitulolaitetta ei löydy."),
("Incoming", "Saapuva"),
("Outgoing", "Lähtevä"),
("Clear Wayland screen selection", "Tyhjennä Wayland näyttövalinta"),
("clear_Wayland_screen_selection_tip", "Tyhjentää nykyisen Wayland näytön valinnan."),
("confirm_clear_Wayland_screen_selection_tip", "Haluatko varmasti tyhjentää Wayland näyttövalinnan?"),
("android_new_voice_call_tip", "Uusi äänipuhelu aloitettu"),
("texture_render_tip", "Käytä tekstuuripohjaista renderöintiä paremman suorituskyvyn saavuttamiseksi."),
("Use texture rendering", "Käytä tekstuurirenderöintiä"),
("Floating window", "Kelluva ikkuna"),
("floating_window_tip", "Kelluva ikkuna pysyy muiden sovellusten päällä etäistunnon aikana."),
("Keep screen on", "Pidä näyttö päällä"),
("Never", "Ei koskaan"),
("During controlled", "Kun etäohjattuna"),
("During service is on", "Kun palvelu on käynnissä"),
("Capture screen using DirectX", "Kaappaa näyttö käyttämällä DirectX"),
("Back", "Takaisin"),
("Apps", "Sovellukset"),
("Volume up", "Lisää äänenvoimakkuutta"),
("Volume down", "Vähennä äänenvoimakkuutta"),
("Power", "Virta"),
("Telegram bot", "Telegram-botti"),
("enable-bot-tip", "Ota Telegram botti käyttöön etähallintaa varten."),
("enable-bot-desc", "Mahdollistaa ilmoitukset ja etätoiminnot Telegramin kautta."),
("cancel-2fa-confirm-tip", "Haluatko varmasti poistaa kaksivaiheisen todennuksen käytöstä?"),
("cancel-bot-confirm-tip", "Haluatko varmasti poistaa Telegram-botin käytöstä?"),
("About RustDesk", "Tietoa RustDeskistä"),
("Send clipboard keystrokes", "Lähetä leikepöydän näppäinsyötteet"),
("network_error_tip", "Verkkovirhe tarkista yhteys ja yritä uudelleen."),
("Unlock with PIN", "Avaa PIN-koodilla"),
("Requires at least {} characters", "Vaatii vähintään {} merkkiä"),
("Wrong PIN", "Väärä PIN-koodi"),
("Set PIN", "Aseta PIN-koodi"),
("Enable trusted devices", "Ota luotetut laitteet käyttöön"),
("Manage trusted devices", "Hallitse luotettuja laitteita"),
("Platform", "Alusta"),
("Days remaining", "Päiviä jäljellä"),
("enable-trusted-devices-tip", "Vain luotetut laitteet voivat muodostaa yhteyden ilman lisävahvistusta."),
("Parent directory", "Ylähakemisto"),
("Resume", "Jatka"),
("Invalid file name", "Virheellinen tiedostonimi"),
("one-way-file-transfer-tip", "Tiedostonsiirto on yksisuuntainen vain lähetys tai vastaanotto."),
("Authentication Required", "Tunnistautuminen vaaditaan"),
("Authenticate", "Tunnistaudu"),
("web_id_input_tip", "Anna etätunnus verkkoliittymässä muodossa tunnus@palvelin"),
("Download", "Lataa"),
("Upload folder", "Lataa kansio"),
("Upload files", "Lataa tiedostoja"),
("Clipboard is synchronized", "Leikepöytä on synkronoitu"),
("Update client clipboard", "Päivitä asiakkaan leikepöytä"),
("Untagged", "Tunnisteeton"),
("new-version-of-{}-tip", "Uusi versio sovelluksesta {} on saatavilla"),
("Accessible devices", "Käytettävissä olevat laitteet"),
("upgrade_remote_rustdesk_client_to_{}_tip", "Päivitä etä-RustDesk-asiakasversioon {} yhteensopivuuden takaamiseksi"),
("d3d_render_tip", "Käytä Direct3D-renderöintiä paremman suorituskyvyn saavuttamiseksi"),
("Use D3D rendering", "Käytä D3D-renderöintiä"),
("Printer", "Tulostin"),
("printer-os-requirement-tip", "Tulostustoiminto vaatii yhteensopivan käyttöjärjestelmän"),
("printer-requires-installed-{}-client-tip", "Tulostus vaatii, että {} asiakas on asennettu"),
("printer-{}-not-installed-tip", "{} tulostinta ei ole asennettu"),
("printer-{}-ready-tip", "{}-tulostin on valmis"),
("Install {} Printer", "Asenna {} tulostin"),
("Outgoing Print Jobs", "Lähtevät tulostustyöt"),
("Incoming Print Jobs", "Saapuvat tulostustyöt"),
("Incoming Print Job", "Saapuva tulostustyö"),
("use-the-default-printer-tip", "Käytä oletustulostinta"),
("use-the-selected-printer-tip", "Käytä valittua tulostinta"),
("auto-print-tip", "Tulosta saapuvat työt automaattisesti"),
("print-incoming-job-confirm-tip", "Hyväksytäänkö saapuvan tulostustyön tulostus?"),
("remote-printing-disallowed-tile-tip", "Etätulostus estetty"),
("remote-printing-disallowed-text-tip", "Etätulostus ei ole sallittu tässä laitteessa tai yhteydessä."),
("save-settings-tip", "Tallenna asetukset"),
("dont-show-again-tip", "Älä näytä uudelleen"),
("Take screenshot", "Ota kuvakaappaus"),
("Taking screenshot", "Otetaan kuvakaappausta"),
("screenshot-merged-screen-not-supported-tip", "Yhdistetyn näytön kuvakaappaus ei ole tuettu"),
("screenshot-action-tip", "Valitse, mitä haluat tehdä kuvakaappaukselle"),
("Save as", "Tallenna nimellä"),
("Copy to clipboard", "Kopioi leikepöydälle"),
("Enable remote printer", "Ota etätulostin käyttöön"),
("Downloading {}", "Ladataan {}"),
("{} Update", "{} päivitys"),
("{}-to-update-tip", "Päivitä sovellus {} jatkaaksesi"),
("download-new-version-failed-tip", "Uuden version lataus epäonnistui"),
("Auto update", "Automaattinen päivitys"),
("update-failed-check-msi-tip", "Päivitys epäonnistui tarkista MSI asennuspaketti"),
("websocket_tip", "Käytä WebSocket protokollaa yhteyden muodostamiseen"),
("Use WebSocket", "Käytä WebSocketia"),
("Trackpad speed", "Kosketuslevyn nopeus"),
("Default trackpad speed", "Oletusnopeus kosketuslevylle"),
("Numeric one-time password", "Numeerinen kertakäyttösalasana"),
("Enable IPv6 P2P connection", "Ota IPv6 P2P yhteys käyttöön"),
("Enable UDP hole punching", "Ota käyttöön UDP hole punching tekniikka"),
("View camera", "Näytä kamera"),
("Enable camera", "Ota kamera käyttöön"),
("No cameras", "Ei kameroita"),
("view_camera_unsupported_tip", "Kameranäkymä ei ole tuettu tällä alustalla"),
("Terminal", "Pääte"),
("Enable terminal", "Ota pääte käyttöön"),
("New tab", "Uusi välilehti"),
("Keep terminal sessions on disconnect", "Säilytä pääteistunnot yhteyden katketessa"),
("Terminal (Run as administrator)", "Pääte (Suorita järjestelmänvalvojana)"),
("terminal-admin-login-tip", "Kirjaudu järjestelmänvalvojana käyttääksesi tätä päätettä"),
("Failed to get user token.", "Käyttäjätunnuksen hakeminen epäonnistui."),
("Incorrect username or password.", "Virheellinen käyttäjätunnus tai salasana."),
("The user is not an administrator.", "Käyttäjä ei ole järjestelmänvalvoja."),
("Failed to check if the user is an administrator.", "Järjestelmänvalvojan tarkistus epäonnistui."),
("Supported only in the installed version.", "Tuettu vain asennetussa versiossa."),
("elevation_username_tip", "Anna järjestelmänvalvojan käyttäjätunnus oikeuksien korotusta varten"),
("Preparing for installation ...", "Valmistellaan asennusta..."),
("Show my cursor", "Näytä osoittimeni"),
("Scale custom", "Mukautettu skaalaus"),
("Custom scale slider", "Mukautetun skaalauksen liukusäädin"),
("Decrease", "Pienennä"),
("Increase", "Suurenna"),
("Show virtual mouse", "Näytä virtuaalinen hiiri"),
("Virtual mouse size", "Virtuaalihiiren koko"),
("Small", "Pieni"),
("Large", "Suuri"),
("Show virtual joystick", "Näytä virtuaalinen ohjain"),
("Edit note", "Muokkaa muistiinpanoa"),
("Alias", "Alias"),
].iter().cloned().collect();
}

View File

@@ -128,7 +128,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom", "カスタム"),
("Show remote cursor", "リモートコンピューターのカーソルを表示する"),
("Show quality monitor", "ディスプレイの品質を表示する"),
("Disable clipboard", "クリップボードを無効化"),
("Disable clipboard", "クリップボードを無効化する"),
("Lock after session end", "セッション終了後にロックする"),
("Insert Ctrl + Alt + Del", "Ctrl + Alt + Del を送信"),
("Insert Lock", "ロック命令を送信"),
@@ -463,7 +463,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Password", "空のパスワード"),
("Me", "あなた"),
("identical_file_tip", "このファイルはリモートコンピューターと同一です。"),
("show_monitors_tip", "ツールバーにディスプレイを表示します。"),
("show_monitors_tip", "ツールバーにディスプレイを表示する"),
("View Mode", "表示モード"),
("login_linux_tip", "X デスクトップのセッションにログインするには、リモートコンピューターのLinuxアカウントにログインする必要があります。"),
("verify_rustdesk_password_tip", "RustDesk のパスワードを確認する"),
@@ -548,7 +548,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("display_is_plugged_out_msg", "ディスプレイが接続されていません。最初のディスプレイを選択してください。"),
("No displays", "ディスプレイがありません"),
("Open in new window", "新しいウィンドウで開く"),
("Show displays as individual windows", "ディスプレイを別のウィンドウとして表示する"),
("Show displays as individual windows", "ディスプレイを別のウィンドウとして表示する"),
("Use all my displays for the remote session", "すべてのディスプレイをセッションで使用する"),
("selinux_tip", "SELinuxが有効になっているため、RustDesk が正常に動作しない可能性があります。"),
("Change view", "表示を変更"),
@@ -557,7 +557,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("List", "リスト"),
("Virtual display", "仮想ディスプレイ"),
("Plug out all", "すべて切断"),
("True color (4:4:4)", "True color (4:4:4)"),
("True color (4:4:4)", "True Color (4:4:4)"),
("Enable blocking user input", "ユーザー入力のブロックを有効化する"),
("id_input_tip", "ID、IPアドレス、またはドメインとポート番号(<ドメイン>:<ポート>)を使用できます。\n他のサーバーのデバイスにアクセスしたい場合は、サーバーアドレス(<id>@<サーバーアドレス>?key=<キーの値>)を追加してください。 \n(例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=)\nパブリックサーバーのデバイスに接続したい場合は、「<id>@public」のように入力してください。パブリックサーバーの場合、キーは不要です。\n\n初回接続で中継接続を行いたい場合は、「9123456234/r」のように末尾に「/r」を付けてください。"),
("privacy_mode_impl_mag_tip", "モード 1"),
@@ -719,7 +719,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Small", ""),
("Large", ""),
("Show virtual joystick", "仮想ジョイスティックを表示する"),
("Edit note", ""),
("Alias", ""),
("Edit note", "メモを編集"),
("Alias", "エイリアス"),
].iter().cloned().collect();
}

View File

@@ -709,17 +709,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Supported only in the installed version.", "Wspierane tylko dla zainstalowanej aplikacji."),
("elevation_username_tip", "Podaj nazwę użytkownika lub domena\\użytkownik"),
("Preparing for installation ...", "Przygotowywanie do instalacji ..."),
("Show my cursor", ""),
("Scale custom", ""),
("Custom scale slider", ""),
("Decrease", ""),
("Increase", ""),
("Show virtual mouse", ""),
("Virtual mouse size", ""),
("Small", ""),
("Large", ""),
("Show virtual joystick", ""),
("Edit note", ""),
("Alias", ""),
("Show my cursor", "Pokaż mój kursor"),
("Scale custom", "Skala użytkownika"),
("Custom scale slider", "Suwak skali użytkownika"),
("Decrease", "Zmniejsz"),
("Increase", "Zwiększ"),
("Show virtual mouse", "Pokaż wirtualną mysz"),
("Virtual mouse size", "Wielkość wirtualnego kursora myszy"),
("Small", "Mały"),
("Large", "Duży"),
("Show virtual joystick", "Pokaz wirtualny joystick"),
("Edit note", "Edytuj notatkę"),
("Alias", "Alias"),
].iter().cloned().collect();
}

View File

@@ -714,12 +714,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Custom scale slider", "自訂縮放滑桿"),
("Decrease", "縮小"),
("Increase", "放大"),
("Show virtual mouse", ""),
("Virtual mouse size", ""),
("Small", ""),
("Large", ""),
("Show virtual joystick", ""),
("Edit note", ""),
("Alias", ""),
("Show virtual mouse", "顯示虛擬滑鼠"),
("Virtual mouse size", "虛擬滑鼠大小"),
("Small", ""),
("Large", ""),
("Show virtual joystick", "顯示虛擬搖桿"),
("Edit note", "編輯備註"),
("Alias", "別名"),
].iter().cloned().collect();
}

View File

@@ -776,7 +776,9 @@ impl Connection {
}
Some((instant, value)) = rx_video.recv() => {
if !conn.video_ack_required {
video_service::notify_video_frame_fetched(id, Some(instant.into()));
if let Some(message::Union::VideoFrame(vf)) = &value.union {
video_service::notify_video_frame_fetched(vf.display as usize, id, Some(instant.into()));
}
}
if let Err(err) = conn.stream.send(&value as &Message).await {
conn.on_close(&err.to_string(), false).await;
@@ -924,7 +926,7 @@ impl Connection {
crate::plugin::EVENT_ON_CONN_CLOSE_SERVER.to_owned(),
conn.lr.my_id.clone(),
);
video_service::notify_video_frame_fetched(id, None);
video_service::notify_video_frame_fetched_by_conn_id(id, None);
if conn.authorized {
password::update_temporary_password();
}
@@ -2909,7 +2911,7 @@ impl Connection {
self.update_auto_disconnect_timer();
}
Some(misc::Union::VideoReceived(_)) => {
video_service::notify_video_frame_fetched(
video_service::notify_video_frame_fetched_by_conn_id(
self.inner.id,
Some(Instant::now().into()),
);

View File

@@ -62,11 +62,18 @@ use std::{
pub const OPTION_REFRESH: &'static str = "refresh";
type FrameFetchedNotifierSender = UnboundedSender<(i32, Option<Instant>)>;
type FrameFetchedNotifierReceiver = Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>;
lazy_static::lazy_static! {
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
let (tx, rx) = unbounded_channel();
(tx, Arc::new(TokioMutex::new(rx)))
};
static ref FRAME_FETCHED_NOTIFIERS: Mutex<HashMap<usize, (FrameFetchedNotifierSender, FrameFetchedNotifierReceiver)>> = Mutex::new(HashMap::default());
// display_idx -> set of conn id.
// Used to record which connections need to be notified when
// 1. A new frame is received from a web client.
// Because web client does not send the display index in message `VideoReceived`.
// 2. The client is closing.
static ref DISPLAY_CONN_IDS: Arc<Mutex<HashMap<usize, HashSet<i32>>>> = Default::default();
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
@@ -80,18 +87,45 @@ struct Screenshot {
}
#[inline]
pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) {
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok();
pub fn notify_video_frame_fetched(display_idx: usize, conn_id: i32, frame_tm: Option<Instant>) {
if let Some(notifier) = FRAME_FETCHED_NOTIFIERS.lock().unwrap().get(&display_idx) {
notifier.0.send((conn_id, frame_tm)).ok();
}
}
#[inline]
pub fn notify_video_frame_fetched_by_conn_id(conn_id: i32, frame_tm: Option<Instant>) {
let vec_display_idx: Vec<usize> = {
let display_conn_ids = DISPLAY_CONN_IDS.lock().unwrap();
display_conn_ids
.iter()
.filter_map(|(display_idx, conn_ids)| {
if conn_ids.contains(&conn_id) {
Some(*display_idx)
} else {
None
}
})
.collect()
};
let notifiers = FRAME_FETCHED_NOTIFIERS.lock().unwrap();
for display_idx in vec_display_idx {
if let Some(notifier) = notifiers.get(&display_idx) {
notifier.0.send((conn_id, frame_tm)).ok();
}
}
}
struct VideoFrameController {
display_idx: usize,
cur: Instant,
send_conn_ids: HashSet<i32>,
}
impl VideoFrameController {
fn new() -> Self {
fn new(display_idx: usize) -> Self {
Self {
display_idx,
cur: Instant::now(),
send_conn_ids: HashSet::new(),
}
@@ -105,6 +139,10 @@ impl VideoFrameController {
if !conn_ids.is_empty() {
self.cur = tm;
self.send_conn_ids = conn_ids;
DISPLAY_CONN_IDS
.lock()
.unwrap()
.insert(self.display_idx, self.send_conn_ids.clone());
}
}
@@ -115,8 +153,20 @@ impl VideoFrameController {
}
let timeout_dur = Duration::from_millis(timeout_millis as u64);
match tokio::time::timeout(timeout_dur, FRAME_FETCHED_NOTIFIER.1.lock().await.recv()).await
{
let receiver = {
match FRAME_FETCHED_NOTIFIERS
.lock()
.unwrap()
.get(&self.display_idx)
{
Some(notifier) => notifier.1.clone(),
None => {
return;
}
}
};
let mut receiver_guard = receiver.lock().await;
match tokio::time::timeout(timeout_dur, receiver_guard.recv()).await {
Err(_) => {
// break if timeout
// log::error!("blocking wait frame receiving timeout {}", timeout_millis);
@@ -131,6 +181,14 @@ impl VideoFrameController {
// this branch would never be reached
}
}
while !receiver_guard.is_empty() {
if let Some((id, instant)) = receiver_guard.recv().await {
if let Some(tm) = instant {
log::trace!("Channel recv latency: {}", tm.elapsed().as_secs_f32());
}
fetched_conn_ids.insert(id);
}
}
}
}
@@ -183,6 +241,14 @@ pub fn get_service_name(source: VideoSource, idx: usize) -> String {
}
pub fn new(source: VideoSource, idx: usize) -> GenericService {
let _ = FRAME_FETCHED_NOTIFIERS
.lock()
.unwrap()
.entry(idx)
.or_insert_with(|| {
let (tx, rx) = unbounded_channel();
(tx, Arc::new(TokioMutex::new(rx)))
});
let vs = VideoService {
sp: GenericService::new(get_service_name(source, idx), true),
idx,
@@ -464,7 +530,7 @@ fn get_capturer(
}
fn run(vs: VideoService) -> ResultType<()> {
let mut _raii = Raii::new(vs.sp.name());
let mut _raii = Raii::new(vs.idx, vs.sp.name());
// Wayland only support one video capturer for now. It is ok to call ensure_inited() here.
//
// ensure_inited() is needed because clear() may be called.
@@ -476,7 +542,7 @@ fn run(vs: VideoService) -> ResultType<()> {
let _wayland_call_on_ret = {
// Increment active display count when starting
let _display_count = super::wayland::increment_active_display_count();
SimpleCallOnReturn {
b: true,
f: Box::new(|| {
@@ -563,7 +629,7 @@ fn run(vs: VideoService) -> ResultType<()> {
sp.set_option_bool(OPTION_REFRESH, false);
}
let mut frame_controller = VideoFrameController::new();
let mut frame_controller = VideoFrameController::new(display_idx);
let start = time::Instant::now();
let mut last_check_displays = time::Instant::now();
@@ -811,6 +877,7 @@ fn run(vs: VideoService) -> ResultType<()> {
break;
}
}
DISPLAY_CONN_IDS.lock().unwrap().remove(&display_idx);
let elapsed = now.elapsed();
// may need to enable frame(timeout)
@@ -824,15 +891,17 @@ fn run(vs: VideoService) -> ResultType<()> {
}
struct Raii {
display_idx: usize,
name: String,
try_vram: bool,
}
impl Raii {
fn new(name: String) -> Self {
fn new(display_idx: usize, name: String) -> Self {
log::info!("new video service: {}", name);
VIDEO_QOS.lock().unwrap().new_display(name.clone());
Raii {
display_idx,
name,
try_vram: true,
}
@@ -849,6 +918,7 @@ impl Drop for Raii {
#[cfg(feature = "vram")]
Encoder::update(scrap::codec::EncodingUpdate::Check);
VIDEO_QOS.lock().unwrap().remove_display(&self.name);
DISPLAY_CONN_IDS.lock().unwrap().remove(&self.display_idx);
}
}

View File

@@ -137,7 +137,7 @@ class JobTable: Reactor.Component {
self.timer(30ms, function() { self.update(); });
}
function addJob(id, path, to, file_num, show_hidden, is_remote) {
function addJob(id, path, to, file_num, show_hidden, is_remote, auto_start) {
var job = { type: "transfer",
id: id, path: path, to: to,
include_hidden: show_hidden,
@@ -146,6 +146,10 @@ class JobTable: Reactor.Component {
this.job_map[id] = this.jobs[this.jobs.length - 1];
handler.update_next_job_id(id + 1);
handler.add_job(id, 0, path, to, file_num, show_hidden, is_remote);
if (auto_start) {
this.continueJob(id);
this.update();
}
stdout.println(JSON.stringify(job));
}
@@ -279,7 +283,8 @@ class JobTable: Reactor.Component {
if (!err) {
handler.remove_dir(job.id, job.path, job.is_remote);
refreshDir(job.is_remote);
if (is_remote) file_transfer.remote_folder_view.table.resetCurrent();
// Use the job's is_remote; local variable `is_remote` is undefined in this scope.
if (job.is_remote) file_transfer.remote_folder_view.table.resetCurrent();
else file_transfer.local_folder_view.table.resetCurrent();
}
} else if (!job.no_confirm) {
@@ -697,9 +702,9 @@ handler.clearAllJobs = function() {
file_transfer.job_table.clearAllJobs();
}
handler.addJob = function (id, path, to, file_num, show_hidden, is_remote) { // load last job
handler.addJob = function (id, path, to, file_num, show_hidden, is_remote, auto_start) { // load last job
// stdout.println("restore job: " + is_remote);
file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote);
file_transfer.job_table.addJob(id,path,to,file_num,show_hidden,is_remote,auto_start);
}
handler.updateTransferList = function () {

View File

@@ -1,7 +1,7 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{Arc, Mutex, RwLock},
sync::{atomic::AtomicUsize, Arc, Mutex, RwLock},
};
use sciter::{
@@ -199,7 +199,7 @@ impl InvokeUiSession for SciterHandler {
self.call("clearAllJobs", &make_args!());
}
fn load_last_job(&self, cnt: i32, job_json: &str) {
fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool) {
let job: Result<TransferJobMeta, serde_json::Error> = serde_json::from_str(job_json);
if let Ok(job) = job {
let path;
@@ -213,7 +213,15 @@ impl InvokeUiSession for SciterHandler {
}
self.call(
"addJob",
&make_args!(cnt, path, to, job.file_num, job.show_hidden, job.is_remote),
&make_args!(
cnt,
path,
to,
job.file_num,
job.show_hidden,
job.is_remote,
auto_start
),
);
}
}
@@ -570,6 +578,7 @@ impl SciterSession {
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
reconnect_count: Arc::new(AtomicUsize::new(0)),
..Default::default()
};

View File

@@ -29,7 +29,10 @@ use std::{
collections::HashMap,
ops::{Deref, DerefMut},
str::FromStr,
sync::{Arc, Mutex, RwLock},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
time::SystemTime,
};
use uuid::Uuid;
@@ -61,6 +64,9 @@ pub struct Session<T: InvokeUiSession> {
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
pub connection_round_state: Arc<Mutex<ConnectionRoundState>>,
pub printer_names: Arc<RwLock<HashMap<i32, String>>>,
// Indicate whether the session is reconnected.
// Used to auto start file transfer after reconnection.
pub reconnect_count: Arc<AtomicUsize>,
}
#[derive(Clone)]
@@ -1272,6 +1278,7 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.write().unwrap().force_relay = true;
}
self.lc.write().unwrap().peer_info = None;
self.reconnect_count.fetch_add(1, Ordering::SeqCst);
let mut lock = self.thread.lock().unwrap();
// No need to join the previous thread, because it will exit automatically.
// And the previous thread will not change important states.
@@ -1372,6 +1379,24 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Close);
}
fn try_auto_start_job_str(is_reconnected: bool, job_str: &str) -> Option<String> {
if is_reconnected {
let job_str = job_str.trim();
if let Some(stripped) = job_str.strip_suffix('}') {
format!(r#"{},"auto_start": true}}"#, stripped).into()
} else {
// unreachable in normal cases
log::warn!(
"The last character is not '}}': {}, auto start is ignored on flutter",
job_str
);
Some(job_str.to_owned())
}
} else {
None
}
}
pub fn load_last_jobs(&self) {
self.clear_all_jobs();
let pc = self.load_config();
@@ -1379,18 +1404,32 @@ impl<T: InvokeUiSession> Session<T> {
// no last jobs
return;
}
let reconnect_count_thr = if cfg!(feature = "flutter") { 0 } else { 1 };
let is_reconnected = self.reconnect_count.load(Ordering::SeqCst) > reconnect_count_thr;
// TODO: can add a confirm dialog
let mut cnt = 1;
for job_str in pc.transfer.read_jobs.iter() {
if !job_str.is_empty() {
self.load_last_job(cnt, job_str);
self.load_last_job(
cnt,
Self::try_auto_start_job_str(is_reconnected, job_str)
.as_deref()
.unwrap_or(job_str),
is_reconnected,
);
cnt += 1;
log::info!("restore read_job: {:?}", job_str);
}
}
for job_str in pc.transfer.write_jobs.iter() {
if !job_str.is_empty() {
self.load_last_job(cnt, job_str);
self.load_last_job(
cnt,
Self::try_auto_start_job_str(is_reconnected, job_str)
.as_deref()
.unwrap_or(job_str),
is_reconnected,
);
cnt += 1;
log::info!("restore write_job: {:?}", job_str);
}
@@ -1623,7 +1662,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn clear_all_jobs(&self);
fn new_message(&self, msg: String);
fn update_transfer_list(&self);
fn load_last_job(&self, cnt: i32, job_json: &str);
fn load_last_job(&self, cnt: i32, job_json: &str, auto_start: bool);
fn update_folder_files(
&self,
id: i32,