diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9e02f555b..0d21f017d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature Request about: Suggest an idea for this project ((English only, Please). title: '' -labels: feature-request +labels: enhancement assignees: '' --- diff --git a/.github/workflows/snap-ci.yml b/.github/workflows/snap-ci.yml deleted file mode 100644 index a80b02f39..000000000 --- a/.github/workflows/snap-ci.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Snap CI - -on: - push: - branches: - - master - paths-ignore: - - README.md - tags: - - '*' - -jobs: - build-snap-master-package: - runs-on: ubuntu-18.04 - - steps: - - name: Check out Git repository - uses: actions/checkout@v2 - - name: Use Snapcraft - uses: snapcore/action-build@v1 - id: build - - uses: actions/upload-artifact@v2 - with: - name: rustdesk.snap - path: ${{ steps.build.outputs.snap }} - # - uses: snapcore/action-publish@v1 - # with: - # store_login: ${{ secrets.SNAP_TOKEN }} - # snap: ${{ steps.build.outputs.snap }} - # release: edge \ No newline at end of file diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 000000000..82243264c --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,12 @@ +name: Publish to WinGet +on: + release: + types: [released] +jobs: + publish: + runs-on: windows-latest # action can only be run on windows + steps: + - uses: vedantmgoyal2009/winget-releaser@latest + with: + identifier: RustDesk.RustDesk + token: ${{ secrets.WINGET_TOKEN }} diff --git a/.gitignore b/.gitignore index d9d64935f..5b26711c5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,16 @@ src/version.rs *exe *tgz cert.pfx -flutter_hbb *.bak *png *svg *jpg -web_hbb sciter.dll **pdb src/bridge_generated.rs *deb rustdesk +# appimage +appimage/AppDir +appimage/*.AppImage +appimage/appimage-build \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0be7bb111..3cbfd47aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk" -version = "1.2.0" +version = "1.1.10" authors = ["rustdesk "] edition = "2021" build= "build.rs" @@ -19,6 +19,8 @@ path = "src/naming.rs" inline = [] hbbs = [] cli = [] +with_rc = ["simple_rc"] +appimage = [] use_samplerate = ["samplerate"] use_rubato = ["rubato"] use_dasp = ["dasp"] @@ -81,6 +83,7 @@ winit = "0.25" winapi = { version = "0.3", features = ["winuser"] } winreg = "0.10" windows-service = "0.4" +virtual_display = { path = "libs/virtual_display" } [target.'cfg(target_os = "macos")'.dependencies] objc = "0.2" @@ -105,7 +108,7 @@ jni = "0.19.0" flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge" } [workspace] -members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display"] +members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/simple_rc"] [package.metadata.winres] LegalCopyright = "Copyright © 2022 Purslane, Inc." @@ -119,6 +122,7 @@ winapi = { version = "0.3", features = [ "winnt" ] } [build-dependencies] cc = "1.0" hbb_common = { path = "libs/hbb_common" } +simple_rc = { path = "libs/simple_rc", optional = true } flutter_rust_bridge_codegen = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge" } [dev-dependencies] @@ -133,10 +137,9 @@ osx_minimum_system_version = "10.14" resources = ["mac-tray.png"] #https://github.com/johnthagen/min-sized-rust -#!!! rembember call "strip target/release/rustdesk" -# which reduce binary size a lot [profile.release] lto = true codegen-units = 1 panic = 'abort' +strip = true #opt-level = 'z' # only have smaller size after strip diff --git a/LICENSE b/LICENCE similarity index 86% rename from LICENSE rename to LICENCE index f288702d2..0ad25db4b 100644 --- a/LICENSE +++ b/LICENCE @@ -1,5 +1,5 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,17 +7,15 @@ Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see +For more information on this, and how to apply and follow the GNU AGPL, see . - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README-AR.md b/README-AR.md new file mode 100644 index 000000000..055a654d2 --- /dev/null +++ b/README-AR.md @@ -0,0 +1,190 @@ +

+ RustDesk - Your remote desktop
+ Servers • + Build • + Docker • + Structure • + Snapshot
+ [česky] | [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
+ لغتك الأم, Doc و RustDesk UI, README نحن بحاجة إلى مساعدتك لترجمة هذا +

+ +[Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) :تواصل معنا عبر + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +.Rustبرنامج آخر لسطح المكتب عن بعد، مكتوب بـ +يعمل خارج الصندوق، لا حاجة إلى إعدادات. لديك سيطرة كاملة على بياناتك، دون مخاوف بشأن الأمن. يمكنك استخدام خادم + الخاص بنا rendezvous/relay +[جهز لنفسك واحدا](https://rustdesk.com/server), أو +[خاص بك rendezvous/relay أكتب خادم](https://github.com/rustdesk/rustdesk-server-demo). + +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) + +لمساعدتك على ذلك [`CONTRIBUTING.md`](CONTRIBUTING.md) يرحب بمساهمة الجميع. اطلع على RustDesk. + +[**؟ RustDesk كيفية يعمل**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + +[**BINARY تنزيل**](https://github.com/rustdesk/rustdesk/releases) + +## خوادم مفتوحة ومجانية + +فيما يلي الخوادم التي تستخدمها مجانًا، وقد تتغير طوال الوقت. إذا لم تكن قريبًا من أحد هؤلاء، فقد تكون شبكتك بطيئة. +| الموقع | المورد | المواصفات | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## التبعيات + + لواجهة المستخدم الرسومية [sciter](https://sciter.com/) نسخة سطح المكتب تستخدم + بنفسك sciter dynamic library عليك تحميل + +[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) + + Sciter إلى Flutter سنقوم بترحيل نسخة سطح المكتب من .Flutter تستخدم إصدارات الهاتف المحمول. + +## خطوات البناء + +- C++ build env و Rust development env قم بإعداد + +- بطريقة صحيحة `VCPKG_ROOT` env variable وأعد [vcpkg](https://github.com/microsoft/vcpkg) ثبت + + - Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static` + - Linux/MacOS: `vcpkg install libvpx libyuv opus` + +- run `cargo run` + +## [البناء](https://rustdesk.com/docs/en/dev/build/) + +## Linux + +### Ubuntu 18 (Debian 10) + +```sh +sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake +``` + +### 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 +``` + +### 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 pulseaudio +``` + +### pynput package تثبيت + +```sh +pip3 install pynput +``` + +### vcpkg تثبيت + +```sh +git clone https://github.com/microsoft/vcpkg +cd vcpkg +git checkout 2021.12.01 +cd .. +vcpkg/bootstrap-vcpkg.sh +export VCPKG_ROOT=$HOME/vcpkg +vcpkg/vcpkg install libvpx libyuv opus +``` + +### Fix libvpx (For Fedora) + +```sh +cd vcpkg/buildtrees/libvpx/src +cd * +./configure +sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile +sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile +make +cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ +cd +``` + +### البناء + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +mkdir -p target/debug +wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so +mv libsciter-gtk.so target/debug +VCPKG_ROOT=$HOME/vcpkg cargo run +``` + +### X11 (Xorg) إلى Wayland تغيير + +افتراضية GNOME session ك Xorg إتبع [هذه](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) الخطوات لإعداد Wayland لا تدعم RustDesk + +## Docker طريقة البناء باستخدام + +ابدأ باستنساخ المستودع وبناء الكونتاينر: + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +ثم، في كل مرة تحتاج إلى بناء التطبيق، قم بتشغيل الأمر التالي: + +```sh +docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder +``` + +لاحظ أن البناء الأول قد يستغرق وقتًا أطول قبل تخزين التبعيات، وسيكون البناء اللاحق أسرع. بالإضافة إلى ذلك، إذا كنت بحاجة إلى تحديد وسائط مختلفة لأمر البناء، فيمكنك القيام بذلك في نهاية الأمر بوضع +`` +على سبيل المثال، إذا كنت ترغب في بناء إصدار محسن، فستقوم بتشغيل الأمر أعلاه متبوعًا بـ +`--release` +:سيكون الملف القابل للتنفيذ الناتج متاحًا في مجلد تارغت، ويمكن تشغيله باستخدام + +```sh +target/debug/rustdesk +``` + +:أو في حال قمت ببناء إصدار محسن + +```sh +target/release/rustdesk +``` + +RustDesk يرجى التأكد من أنك تنفذ هذه الأوامر من جذر مستودع +وإلا فقد لا يتمكن التطبيق من العثور على الموارد المطلوبة. لاحظ أيضًا أن الأوامر الفرعية الأخرى مثل +`install` أو `run` +لا يتم دعمها حاليًا عبر هذه الطريقة لأنها ستقوم بتثبيت أو تشغيل البرنامج داخل الكونتاينر بدلاً من الهوست. + +## هيكل الملف + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: وظائف لنقل الملفات، وبعض وظائف المرافق الأخرى tcp/udp، protobuf ترميز الفيديو، إعدادات + +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: التقاط الشاشة +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: التحكم في لوحة المفاتيح/الماوس الخاصة بكل منصة +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: واجهة المستخدم الرسومية +- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: خدمات الصوت/الحافظة/المدخلات/الفيديو، ووصلات الشبكة +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: بدء اتصال متقارن +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: أو المنقول عن بُعد (TCP hole punching) انتظر الاتصال المباشر [rustdesk-server](https://github.com/rustdesk/rustdesk-server) الإتصال ب +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: رمز خاص بكل منصة +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: رمز الهاتف المحمول +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**:Flutter لعميل الويب الخاص ب Javascript + +## لقطات + +![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) + +![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) + +![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) + +![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) diff --git a/README-CS.md b/README-CS.md new file mode 100644 index 000000000..1dd5463a1 --- /dev/null +++ b/README-CS.md @@ -0,0 +1,176 @@ +

+ RustDesk – vaše vzdálená plocha
+ Servery • + Sestavení ze zdrojových kódů • + Docker • + Struktura • + Ukázky
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
+ Potřebujeme Vaši pomoc s překláním textů tohoto ČTIMNE, uživatelského rozhraní aplikace RustDesk a dokumentace k ní do vašeho jazyka +

+ +Dopisujte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) + + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +Zase další software pro přístup k ploše na dálku, naprogramovaný v jazyce Rust. Funguje hned tak, jak je – není třeba žádného nastavování. Svá data máte ve svých rukách, bez obav o zabezpečení. Je možné používat námi poskytovaný propojovací/předávací (relay) server, [vytvořit si svůj vlastní](https://rustdesk.com/server), nebo [si dokonce svůj vlastní naprogramovat](https://github.com/rustdesk/rustdesk-server-demo), budete-li chtít. + +Projekt RustDesk vítá přiložení ruky k dílu od každého. Jak začít se dozvíte z [`CONTRIBUTING.md`](CONTRIBUTING.md). + +[**Jak RustDesk funguje?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + +[**STAHOVÁNÍ ZKOMPILOVANÝCH APLIKACÍ**](https://github.com/rustdesk/rustdesk/releases) + +## Veřejné, zdarma službu nabízející servery + +Níže jsou uvedeny servery zdarma k vašemu použití (údaje se mohou v čase měnit). Pokud se nenacházíte v oblastech světa poblíž nich, spojení může být pomalé. +| umístění | dodavatel | parametry | +| --------- | ------------- | ------------------ | +| Soul | AWS lightsail | 1 VCPU / 0,5GB RAM | +| Singapur | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## Softwarové součásti, na kterých závisí + +Varianta pro počítač používá pro grafické uživatelské rozhraní [sciter](https://sciter.com/) – stáhněte si potřebnou knihovnu. + +[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) + +Varianta pro mobilní platformy používá aplikační rámec (framework) Flutter. Na tu také v budoucnu předěláme i variantu pro počítač. + +## Stručně kroky pro sestavení ze zdrojových kódů + +- Připravte si vývojové prostředí pro jazyky Rust a C++ + +- Nainstalujte [vcpkg](https://github.com/microsoft/vcpkg), a nastavte správně proměnnou prostsředí `VCPKG_ROOT` + + - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static + - Linux/MacOS: vcpkg install libvpx libyuv opus + +- spusťte `cargo run` + +## [Sestavení ze zdrojových kódů](https://rustdesk.com/docs/en/dev/build/) + +## Jak zkompilovat na Linuxu + +### Ubuntu 18 (Debian 10) + +```sh +sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake +``` + +### 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 +``` + +### 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 pulseaudio +``` + +### Instalace balíčku pynput + +```sh +pip3 install pynput +``` + +### Instalace vcpkg + +```sh +git clone https://github.com/microsoft/vcpkg +cd vcpkg +git checkout 2021.12.01 +cd .. +vcpkg/bootstrap-vcpkg.sh +export VCPKG_ROOT=$HOME/vcpkg +vcpkg/vcpkg install libvpx libyuv opus +``` + +### Oprava libvpx (pro Fedoru) + +```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 +``` + +### Sestavení + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +mkdir -p target/debug +wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so +mv libsciter-gtk.so target/debug +VCPKG_ROOT=$HOME/vcpkg cargo run +``` + +### Změna z Wayland na X11 (Xorg) + +RustDesk (zatím) nepodporuje zobrazovací server Wayland. Jak nastavit Xorg jako výchozí pro relace v prostředí GNOME naleznete [zde](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/). + +## Jak sestavit prostřednictvím Docker kontejnerizace + +Začněte tím, že si naklonujete tento repozitář a sestavíte docker kontejner: + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +Poté pokaždé, když bude třeba aplikaci sestavit, spusťte následující příkaz: + +```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 +``` + +Všimněte si, že prvotní sestavení může trvat déle (než se do mezipaměti uloží veškeré softwarové součásti, které jsou potřeba) – následná opakování už budou rychlejší. Dále, pokud potřebujete příkazu pro sestavení zadat nějaké argumenty, je možné je zapsat na konec příkazu na pozici ``. Například, pokud byste chtěli sestavit optimalizovaně pro vydání, spustili byste výše uvedený příkaz následovaný `--release`. Výsledný spustitelný soubor se objeví v cílové složce na vašem systému a bude ho možné spustit pomocí: + +```sh +target/debug/rustdesk +``` + +Nebo, pokud spouštíte variantu pro vydání: + +```sh +target/release/rustdesk +``` + +Zajistětě, abyste tyto příkazy spouštěli z kořene repozitáře s RustDesk, jinak aplikace nemusí být schopná nalézt potřebné prostředky (resources). Také si všimněte, že ostatní dílčí príkazy nástroje cargo, jako třeba `install` nebo `run` zatím nejsou prostřednictvím této metody podporovány, protože by vedly k instalaci či spuštění program uvnitř kontejneru namísto přímo v systému. + +## Struktura souborů + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: kodek videa, nastavení, obalovaní tcp/udp, vyrovnávací paměť protokolu, funkce souborového systému pro přenos souborů a pár dalších podpůrných funkcí +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: zachytávání obsahu obrazovky +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: ovládání klávesnice/myši pro jednotlivé platformy +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: grafické uživatelské rozhraní +- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: služby pro zvuk/schránku/zadávání/video a síťová spojení +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: spouští připojení k protějšku +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: komunikace s [rustdesk-server](https://github.com/rustdesk/rustdesk-server), očekávání vzdálených příméhých („proděrováváním“ TCP) nebo předávaných (relay) spojení +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: zdrojové kódy, specifické pro jednotlivé platformy +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: zdrojové kódy pro použití s aplikačním rámcem (framework) Flutter pro mobilní platformy +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript pro Flutter webový klient + +## Ukázky + +![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) + +![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) + +![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) + +![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) diff --git a/README-DE.md b/README-DE.md index 5a9939605..3f770d226 100644 --- a/README-DE.md +++ b/README-DE.md @@ -5,7 +5,7 @@ DockerDateistrukturScreenshots
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Wir brauchen deine Hilfe um diese README Datei zu verbessern und aktualisieren

diff --git a/README-EO.md b/README-EO.md index 6cfc1e1ac..21a4f9521 100644 --- a/README-EO.md +++ b/README-EO.md @@ -5,7 +5,7 @@ DockerStrukturoEkrankopio
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Ni bezonas helpon traduki tiun README kaj la interfacon al via denaska lingvo

@@ -125,7 +125,7 @@ Tiam, ĉiuj fojoj, kiuj vi bezonas kompili la programon, plenumu tiun komandon: 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 ``` -Notu, ke la unua kompilado povas daŭri longe, antaŭ ke la dependantaĵoj estu kaŝitaj, sekvaj kompiladoj estos pli rapidaj. Aldone, se vi bezonas specifi diferentajn argumentojn por la kompilkomando, vi povas fari ĝin en la fine de la komando, en la posicio ``. Ekzemple, se vi volas kompili version de eldono optimigita, vi plenumus la komandon supre, kun `---release`. La plenumebla dosiero disponeblos en la cela dosierujo sur via sistemo, kaj povos esti plenumita kun: +Notu, ke la unua kompilado povas daŭri longe, antaŭ ke la dependantaĵoj estu kaŝitaj, sekvaj kompiladoj estos pli rapidaj. Aldone, se vi bezonas specifi diferentajn argumentojn por la kompilkomando, vi povas fari ĝin en la fine de la komando, en la posicio ``. Ekzemple, se vi volas kompili version de eldono optimigita, vi plenumus la komandon supre, kun `--release`. La plenumebla dosiero disponeblos en la cela dosierujo sur via sistemo, kaj povos esti plenumita kun: ```sh target/debug/rustdesk diff --git a/README-ES.md b/README-ES.md index 84bfcd88c..ce8601fa0 100644 --- a/README-ES.md +++ b/README-ES.md @@ -5,11 +5,11 @@ DockerEstructuraCaptura de pantalla
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [česky] | [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Necesitamos tu ayuda para traducir este README a tu idioma

-Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) +Chatea con nosotros: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) @@ -23,9 +23,11 @@ RustDesk agradece la contribución de todo el mundo. Ve [`CONTRIBUTING.md`](CONT A continuación se muestran los servidores que está utilizando de forma gratuita, puede cambiar en algún momento. Si no estás cerca de uno de ellos, tu red puede ser lenta. -- Seoul, AWS lightsail, 1 VCPU/0.5G RAM -- Singapore, Vultr, 1 VCPU/1G RAM -- Dallas, Vultr, 1 VCPU/1G RAM +| Ubicación | Vendedor | Especificación | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | ## Dependencies @@ -37,7 +39,7 @@ La versión Desktop usa [sciter](https://sciter.com/) para GUI, por favor bajate ## Pasos para compilar desde el inicio -- Prepara el entono de desarrollode Rust y el entorno de compilación de C++ y Rust. +- Prepara el entono de desarrollo de Rust y el entorno de compilación de C++ y Rust. - Instala [vcpkg](https://github.com/microsoft/vcpkg), y configura la variable de entono `VCPKG_ROOT` correctamente. @@ -78,7 +80,7 @@ export VCPKG_ROOT=$HOME/vcpkg vcpkg/vcpkg install libvpx libyuv opus ``` -### Soluciona libvpx (For Fedora) +### Soluciona libvpx (Para Fedora) ```sh cd vcpkg/buildtrees/libvpx/src @@ -124,7 +126,7 @@ Entonces, cada vez que necesites compilar una modificación, ejecuta el siguient 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 ``` -Ten en cuenta que la primera compilación puede tardar más tiempo antes de que las dependencias se almacenen en la caché, las siguientes compilaciones serán más rápidas. Además, si necesitas especificar diferentes argumentos a la orden de compilación, puede hacerlo al final de la linea de comandos en el apartado``. Por ejemplo, si desea compilar una versión optimizada para publicación, deberá ejecutar el comando anterior seguido de `---release`. El ejecutable resultante estará disponible en la carpeta de destino en su sistema, y puede ser ejecutado con: +Ten en cuenta que la primera compilación puede tardar más tiempo antes de que las dependencias se almacenen en la caché, las siguientes compilaciones serán más rápidas. Además, si necesitas especificar diferentes argumentos a la orden de compilación, puede hacerlo al final de la linea de comandos en el apartado ``. Por ejemplo, si desea compilar una versión optimizada para publicación, deberá ejecutar el comando anterior seguido de `--release`. El ejecutable resultante estará disponible en la carpeta de destino en su sistema, y puede ser ejecutado con: ```sh target/debug/rustdesk @@ -148,6 +150,8 @@ Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del - **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: iniciar una conexión "peer to peer" - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicación con [rustdesk-server](https://github.com/rustdesk/rustdesk-server), esperar la conexión remota directa ("TCP hole punching") o conexión indirecta ("relayed") - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico de cada plataforma +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para cliente web Flutter ## Captura de pantalla diff --git a/README-FA.md b/README-FA.md new file mode 100644 index 000000000..0f7ca1a95 --- /dev/null +++ b/README-FA.md @@ -0,0 +1,178 @@ +

+ RustDesk - Your remote desktop
+ اسنپ شات • + ساختار • + داکر • + ساخت • + سرور
+ [česky] | [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
+ ‫برای ترجمه این RustDesk UI ،README و Doc به زبان مادری شما به کمکتون نیاز داریم +

+ +با ما گپ بزنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) + + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) + +یک نرم افزار دیگر کنترل دسکتاپ از راه دور، که با Rust نوشته شده است. راه اندازی سریع وبدون نیاز به تنظیمات. شما کنترل کاملی بر داده های خود دارید، بدون هیچ گونه نگرانی امنیتی. +می‌توانید از سرور rendezvous/relay ما استفاده کنید، [سرور خودتان را راه‌اندازی کنید](https://rustdesk.com/server) یا +[ سرورrendezvous/relay خود را بنویسید](https://github.com/rustdesk/rustdesk). + +‫راست دسک (RustDesk) از مشارکت همه استقبال می کند. برای راهنمایی جهت مشارکت به [`CONTRIBUTING.md`](CONTRIBUTING.md) مراجعه کنید. + +[راست دسک چطور کار می کند؟](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + +[دانلود باینری](https://github.com/rustdesk/rustdesk/releases) + +## سرورهای عمومی رایگان + +سرورهایی زیر را به صورت رایگان میتوانید استفاده می کنید. این لیست ممکن است در طول زمان تغییر کند. اگر به این سرورها نزدیک نیستید، ممکن است سرویس شما کند شود. +| موقعیت | سرویس دهنده | مشخصات | +| --------- | ------------- | ------------------ | +| Seoul | AWS lightsail | 1 VCPU / 0.5GB RAM | +| Singapore | Vultr | 1 VCPU / 1GB RAM | +| Dallas | Vultr | 1 VCPU / 1GB RAM | | + +## وابستگی ها + +نسخه‌های دسکتاپ از [sciter](https://sciter.com/) برای رابط کاربری گرافیکی استفاده می‌کنند، لطفا کتابخانه پویا sciter را خودتان دانلود کنید. + +[ویندوز](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) | +[لینوکس](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) | +[مک](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib) + +نسخه های موبایل از Flutter استفاده می کنند. بعداً نسخه دسکتاپ را از Sciter به Flutter منتقل خواهیم کرد. + +## مراحل بنیادین برای ساخت + +‫- محیط توسعه نرم افزار Rust و محیط ساخت ++C خود را آماده کنید + +‫- نرم افزار [vcpkg](https://github.com/microsoft/vcpkg) را نصب کنید و متغیر `VCPKG_ROOT` را به درستی تنظیم کنید: + + - Windows: `vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static` + - Linux/MacOS: `vcpkg install libvpx libyuv opus` + +- run `cargo run` + +## [ساخت](https://rustdesk.com/docs/en/dev/build/) + +## نحوه ساخت بر روی لینوکس + +### ساخت بر روی (Ubuntu 18 (Debian 10 + +```sh +sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake +``` + +### ساخت بر روی (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 +``` + +### ساخت بر روی (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 pulseaudio +``` + +### بسته pynput را نصب کنید + +```sh +pip3 install pynput +``` + +### نرم افزار vcpkg را نصب کنید + +```sh +git clone https://github.com/microsoft/vcpkg +cd vcpkg +git checkout 2021.12.01 +cd .. +vcpkg/bootstrap-vcpkg.sh +export VCPKG_ROOT=$HOME/vcpkg +vcpkg/vcpkg install libvpx libyuv opus +``` + +### رفع ایراد libvpx (برای فدورا) + +```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 +``` + +### ساخت + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +mkdir -p target/debug +wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so +mv libsciter-gtk.so target/debug +VCPKG_ROOT=$HOME/vcpkg cargo run +``` + +### تغییر Wayland به (X11 (Xorg + +راست دسک از Wayland پشتیبانی نمی کند. برای جایگزنی Xorg به عنوان پیش‌فرض GNOM، [اینجا](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) را کلیک کنید. + +## نحوه ساخت با داکر + +این مخزن گیت را کلون کنید و کانتینر را به روش زیر بسازید + +```sh +git clone https://github.com/rustdesk/rustdesk +cd rustdesk +docker build -t "rustdesk-builder" . +``` + +سپس، هر بار که نیاز به ساخت اپلیکیشن داشتید، دستور زیر را اجرا کنید: + +```sh +docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder +``` + +توجه داشته باشید که ساخت اول ممکن است قبل از کش شدن وابستگی ها بیشتر طول بکشد، دفعات بعدی سریعتر خواهند بود. علاوه بر این، اگر نیاز به تعیین آرگومان های مختلف برای دستور ساخت دارید، می توانید این کار را در انتهای دستور ساخت و از طریق `` انجام دهید. به عنوان مثال، اگر می خواهید یک نسخه نهایی بهینه سازی شده ایجاد کنید، دستور بالا را تایپ کنید و در انتها `release--` را اضافه کنید. فایل اجرایی به دست آمده در پوشه مقصد در سیستم شما در دسترس خواهد بود و می تواند با دستور: + +```sh +target/debug/rustdesk +``` + +یا برای نسخه بهینه سازی شده دستور زیر را اجرا کنید: + +```sh +target/release/rustdesk +``` + +لطفاً اطمینان حاصل کنید که این دستورات را از پوشه مخزن RustDesk اجرا می کنید، در غیر این صورت ممکن است برنامه نتواند منابع مورد نیاز را پیدا کند. همچنین توجه داشته باشید که سایر دستورات فرعی Cargo مانند `install` یا `run` در حال حاضر از طریق این روش پشتیبانی نمی شوند زیرا برنامه به جای سیستم عامل میزبان, در داخل کانتینر نصب و اجرا میشود. + +## ساختار پوشه ها + +- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video codec, config, tcp/udp wrapper, protobuf, fs functions for file transfer, and some other utility functions +- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: screen capture +- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform specific keyboard/mouse control +- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI +- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: audio/clipboard/input/video services, and network connections +- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start a peer connection +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Communicate with [rustdesk-server](https://github.com/rustdesk/rustdesk-server), wait for remote direct (TCP hole punching) or relayed connection +- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform specific code +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client + +## اسکرین شات ها + +![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) + +![image](https://user-images.githubusercontent.com/71636191/113112619-f705a480-923b-11eb-911d-97e984ef52b6.png) + +![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) + +![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) diff --git a/README-FI.md b/README-FI.md index fc208b383..a2d7534e0 100644 --- a/README-FI.md +++ b/README-FI.md @@ -5,7 +5,7 @@ DockerRakenneTilannevedos
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Tarvitsemme apua tämän README-tiedoston kääntämiseksi äidinkielellesi

@@ -125,7 +125,7 @@ Sitten, joka kerta kun sinun on rakennettava sovellus, aja seuraava komento: 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 ``` -Huomaa, että ensimmäinen rakentaminen saattaa kestää pitempään ennen kuin riippuvuudet on siirretty välimuistiin, seuraavat rakentamiset ovat nopeampia. Lisäksi, jos sinun on määritettävä eri argumentteja rakentamiskomennolle, saatat tehdä sen niin, että komennon lopussa `-kohdassa. Esimerkiksi, jos haluat rakentaa optimoidun julkaisuversion, sinun on ajettava komento yllä siten, että sitä seuraa argumentti`---release`. Suoritettava tiedosto on saatavilla järjestelmäsi kohdehakemistossa, ja se voidaan suorittaa seuraavan kera: +Huomaa, että ensimmäinen rakentaminen saattaa kestää pitempään ennen kuin riippuvuudet on siirretty välimuistiin, seuraavat rakentamiset ovat nopeampia. Lisäksi, jos sinun on määritettävä eri argumentteja rakentamiskomennolle, saatat tehdä sen niin, että komennon lopussa `-kohdassa. Esimerkiksi, jos haluat rakentaa optimoidun julkaisuversion, sinun on ajettava komento yllä siten, että sitä seuraa argumentti`--release`. Suoritettava tiedosto on saatavilla järjestelmäsi kohdehakemistossa, ja se voidaan suorittaa seuraavan kera: ```sh target/debug/rustdesk diff --git a/README-FR.md b/README-FR.md index bc7ebfd8d..b1f8e3670 100644 --- a/README-FR.md +++ b/README-FR.md @@ -5,7 +5,7 @@ Docker - Structure - Images
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Nous avons besoin de votre aide pour traduire ce README dans votre langue maternelle.

@@ -124,7 +124,7 @@ Ensuite, chaque fois que vous devez compiler le logiciel, exécutez la commande 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 ``` -Notez que la première compilation peut prendre plus de temps avant que les dépendances ne soient mises en cache, les compilations suivantes seront plus rapides. De plus, si vous devez spécifier différents arguments à la commande de compilation, vous pouvez le faire à la fin de la commande à la position ``. Par exemple, si vous voulez compiler une version de release optimisée, vous devez exécuter la commande ci-dessus suivie de `---release`. L'exécutable résultant sera disponible dans le dossier cible sur votre système, et peut être lancé avec : +Notez que la première compilation peut prendre plus de temps avant que les dépendances ne soient mises en cache, les compilations suivantes seront plus rapides. De plus, si vous devez spécifier différents arguments à la commande de compilation, vous pouvez le faire à la fin de la commande à la position ``. Par exemple, si vous voulez compiler une version de release optimisée, vous devez exécuter la commande ci-dessus suivie de `--release`. L'exécutable résultant sera disponible dans le dossier cible sur votre système, et peut être lancé avec : ```sh target/debug/rustdesk diff --git a/README-ID.md b/README-ID.md index c1ccfb345..624336f45 100644 --- a/README-ID.md +++ b/README-ID.md @@ -5,7 +5,7 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Kami membutuhkan bantuan Anda untuk menerjemahkan README ini dan RustDesk UI ke bahasa asli anda

@@ -125,7 +125,7 @@ Kemudian, setiap kali Anda perlu build aplikasi, jalankan perintah berikut: 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 ``` -Perhatikan bahwa build pertama mungkin memerlukan waktu lebih lama sebelum dependensi di-cache, build berikutnya akan lebih cepat. Selain itu, jika Anda perlu menentukan argumen yang berbeda untuk perintah build, Anda dapat melakukannya di akhir perintah di posisi ``. Misalnya, jika Anda ingin membangun versi rilis yang dioptimalkan, Anda akan menjalankan perintah di atas diikuti oleh `---release`. Hasil eksekusi akan tersedia pada target folder di sistem anda, dan dapat dijalankan dengan: +Perhatikan bahwa build pertama mungkin memerlukan waktu lebih lama sebelum dependensi di-cache, build berikutnya akan lebih cepat. Selain itu, jika Anda perlu menentukan argumen yang berbeda untuk perintah build, Anda dapat melakukannya di akhir perintah di posisi ``. Misalnya, jika Anda ingin membangun versi rilis yang dioptimalkan, Anda akan menjalankan perintah di atas diikuti oleh `--release`. Hasil eksekusi akan tersedia pada target folder di sistem anda, dan dapat dijalankan dengan: ```sh target/debug/rustdesk diff --git a/README-IT.md b/README-IT.md index cf74924e6..7eba7860a 100644 --- a/README-IT.md +++ b/README-IT.md @@ -5,7 +5,7 @@ DockerStrutturaScreenshots
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Abbiamo bisogno del tuo aiuto per tradurre questo README e la RustDesk UI nella tua lingua nativa

@@ -125,7 +125,7 @@ Quindi, ogni volta che devi compilare l'applicazione, esegui il comando seguente 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 ``` -Tieni presente che la prima build potrebbe richiedere più tempo prima che le dipendenze vengano memorizzate nella cache, le build successive saranno più veloci. Inoltre, se hai bisogno di specificare argomenti diversi per il comando build, puoi farlo alla fine del comando nella posizione ``. Ad esempio, se si desidera creare una versione di rilascio ottimizzata, eseguire il comando sopra seguito da `---release`. L'eseguibile generato sarà creato nella cartella di destinazione del proprio sistema e può essere eseguito con: +Tieni presente che la prima build potrebbe richiedere più tempo prima che le dipendenze vengano memorizzate nella cache, le build successive saranno più veloci. Inoltre, se hai bisogno di specificare argomenti diversi per il comando build, puoi farlo alla fine del comando nella posizione ``. Ad esempio, se si desidera creare una versione di rilascio ottimizzata, eseguire il comando sopra seguito da `--release`. L'eseguibile generato sarà creato nella cartella di destinazione del proprio sistema e può essere eseguito con: ```sh target/debug/rustdesk diff --git a/README-JP.md b/README-JP.md index 3c2b6c686..60816a5d5 100644 --- a/README-JP.md +++ b/README-JP.md @@ -5,18 +5,23 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
このREADMEをあなたの母国語に翻訳するために、あなたの助けが必要です。

-Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) +Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) + [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分で設定する](https://rustdesk.com/server) ことも、 [自分でランデブー/リレーサーバを書くこともできます。](https://github.com/rustdesk/rustdesk-server-demo). +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) + RustDeskは誰からの貢献も歓迎します。 貢献するには [`CONTRIBUTING.md`](CONTRIBUTING.md) を参照してください。 +[**RustDeskはどの様に動くのか?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) ## 無料のパブリックサーバー @@ -36,6 +41,8 @@ RustDeskは誰からの貢献も歓迎します。 貢献するには [`CONTRIBU [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) +モバイル版はFlutterを利用します。デスクトップ版もSciterからFlutterへマイグレーション予定です。 + ## ビルド手順 - Rust開発環境とC ++ビルド環境を準備します @@ -47,6 +54,10 @@ RustDeskは誰からの貢献も歓迎します。 貢献するには [`CONTRIBU - run `cargo run` + + +## [Build](https://rustdesk.com/docs/en/dev/build/) + ## Linuxでのビルド手順 ### Ubuntu 18 (Debian 10) @@ -67,6 +78,12 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` +### Install pynput package + +```sh +pip3 install pynput +``` + ### Install vcpkg ```sh @@ -127,7 +144,7 @@ docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user ``` なお、最初のビルドでは、依存関係がキャッシュされるまで時間がかかることがありますが、その後のビルドではより速くなります。さらに、ビルドコマンドに別の引数を指定する必要がある場合は、コマンドの最後にある `` の位置で指定することができます。例えば、最適化されたリリースバージョンをビルドしたい場合は、上記のコマンドの後に -`---release` を実行します。できあがった実行ファイルは、システムのターゲット・フォルダに格納され、次のコマンドで実行できます。 +`--release` を実行します。できあがった実行ファイルは、システムのターゲット・フォルダに格納され、次のコマンドで実行できます。 ```sh target/debug/rustdesk diff --git a/README-KR.md b/README-KR.md index fb90b9d99..750cf91bd 100644 --- a/README-KR.md +++ b/README-KR.md @@ -5,19 +5,23 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.

-Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) +Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Rust로 작성되었고, 설정없이 바로 사용할 수 있는 리모트 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [스스로 설정](https://rustdesk.com/server)하는 것도, [스스로 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo). +Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [스스로 설정](https://rustdesk.com/server)하는 것도, [스스로 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo). + +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`CONTRIBUTING.md`](CONTRIBUTING.md)를 참조해주세요. +[**RustDesk는 어떻게 작동하는가?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) ## 무료 퍼블릭 서버 @@ -72,6 +76,12 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` +### Install pynput package + +```sh +pip3 install pynput +``` + ### Install vcpkg ```sh @@ -130,7 +140,7 @@ docker build -t "rustdesk-builder" . docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder ``` -첫 빌드에서는 의존관계가 캐시될 때까지 시간이 거릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 커맨드에 다른 인수를 지정할 필요가 있다면, 커맨드 끝에 있는 `` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 커맨드 뒤에 `---release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 커맨드로 실행할 수 있습니다. +첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 커맨드에 다른 인수를 지정할 필요가 있다면, 커맨드 끝에 있는 `` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 커맨드 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 커맨드로 실행할 수 있습니다. ```sh target/debug/rustdesk @@ -154,6 +164,8 @@ target/release/rustdesk - **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 피어 접속 시작 - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트(TCP hole punching) 혹은 relayed 접속 - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 플랫폼 고유의 코드 +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client ## Snapshot @@ -164,3 +176,4 @@ target/release/rustdesk ![image](https://user-images.githubusercontent.com/71636191/113112857-3fbd5d80-923c-11eb-9836-768325faf906.png) ![image](https://user-images.githubusercontent.com/71636191/135385039-38fdbd72-379a-422d-b97f-33df71fb1cec.png) + diff --git a/README-ML.md b/README-ML.md index 6dae55486..c479d0496 100644 --- a/README-ML.md +++ b/README-ML.md @@ -5,7 +5,7 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
ഈ README നിങ്ങളുടെ മാതൃഭാഷയിലേക്ക് വിവർത്തനം ചെയ്യാൻ ഞങ്ങൾക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്

@@ -125,7 +125,7 @@ docker build -t "rustdesk-builder" . docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder ``` -ഡിപൻഡൻസികൾ കാഷെ ചെയ്യുന്നതിനുമുമ്പ് ആദ്യ ബിൽഡ് കൂടുതൽ സമയമെടുത്തേക്കാം, തുടർന്നുള്ള ബിൽഡുകൾ വേഗത്തിലാകും. കൂടാതെ, നിങ്ങൾക്ക് ബിൽഡ് കമാൻഡിലേക്ക് വ്യത്യസ്ത ആർഗ്യുമെന്റുകൾ വ്യക്തമാക്കണമെങ്കിൽ, കമാൻഡിന്റെ അവസാനം `` സ്ഥാനത്ത് നിങ്ങൾക്ക് അങ്ങനെ ചെയ്യാം. ഉദാഹരണത്തിന്, നിങ്ങൾ ഒരു ഒപ്റ്റിമൈസ് ചെയ്ത റിലീസ് പതിപ്പ് നിർമ്മിക്കാൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ, മുകളിലുള്ള കമാൻഡ് തുടർന്ന് `---release` നിങ്ങൾ പ്രവർത്തിപ്പിക്കും. തത്ഫലമായുണ്ടാകുന്ന എക്സിക്യൂട്ടബിൾ നിങ്ങളുടെ സിസ്റ്റത്തിലെ ടാർഗെറ്റ് ഫോൾഡറിൽ ലഭ്യമാകും, കൂടാതെ ഇത് ഉപയോഗിച്ച് പ്രവർത്തിപ്പിക്കാം: +ഡിപൻഡൻസികൾ കാഷെ ചെയ്യുന്നതിനുമുമ്പ് ആദ്യ ബിൽഡ് കൂടുതൽ സമയമെടുത്തേക്കാം, തുടർന്നുള്ള ബിൽഡുകൾ വേഗത്തിലാകും. കൂടാതെ, നിങ്ങൾക്ക് ബിൽഡ് കമാൻഡിലേക്ക് വ്യത്യസ്ത ആർഗ്യുമെന്റുകൾ വ്യക്തമാക്കണമെങ്കിൽ, കമാൻഡിന്റെ അവസാനം `` സ്ഥാനത്ത് നിങ്ങൾക്ക് അങ്ങനെ ചെയ്യാം. ഉദാഹരണത്തിന്, നിങ്ങൾ ഒരു ഒപ്റ്റിമൈസ് ചെയ്ത റിലീസ് പതിപ്പ് നിർമ്മിക്കാൻ ആഗ്രഹിക്കുന്നുവെങ്കിൽ, മുകളിലുള്ള കമാൻഡ് തുടർന്ന് `--release` നിങ്ങൾ പ്രവർത്തിപ്പിക്കും. തത്ഫലമായുണ്ടാകുന്ന എക്സിക്യൂട്ടബിൾ നിങ്ങളുടെ സിസ്റ്റത്തിലെ ടാർഗെറ്റ് ഫോൾഡറിൽ ലഭ്യമാകും, കൂടാതെ ഇത് ഉപയോഗിച്ച് പ്രവർത്തിപ്പിക്കാം: ```sh target/debug/rustdesk diff --git a/README-NL.md b/README-NL.md index 2fdf77ca0..2d87504db 100644 --- a/README-NL.md +++ b/README-NL.md @@ -5,7 +5,7 @@ DockerStructuurSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
We hebben je hulp nodig om deze README te vertalen naar jouw moedertaal

@@ -125,7 +125,7 @@ Voer vervolgens de volgende commando's uit iedere keer dat je de applicatie opni 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 ``` -Let op dat de eerste build langer kan duren omdat de dependencies nog niet zijn gecached; latere builds zullen sneller zijn. Als je extra command line arguments wilt toevoegen aan het build-commando, dan kun je dat doen aan het einde van de opdrachtregel in plaats van ``. Bijvoorbeeld: als je een geoptimaliseerde releaseversie wilt bouwen, draai dan het bovenstaande commando gevolgd door `---release`. +Let op dat de eerste build langer kan duren omdat de dependencies nog niet zijn gecached; latere builds zullen sneller zijn. Als je extra command line arguments wilt toevoegen aan het build-commando, dan kun je dat doen aan het einde van de opdrachtregel in plaats van ``. Bijvoorbeeld: als je een geoptimaliseerde releaseversie wilt bouwen, draai dan het bovenstaande commando gevolgd door `--release`. Het uitvoerbare bestand, in debug-modus, zal verschijnen in de target-map, en kan als volgt worden uitgevoerd: diff --git a/README-PL.md b/README-PL.md index a43dfc165..162ca7648 100644 --- a/README-PL.md +++ b/README-PL.md @@ -5,7 +5,7 @@ DockerStrukturaSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Potrzebujemy twojej pomocy w tłumaczeniu README na twój ojczysty język

@@ -125,7 +125,7 @@ Następnie, za każdym razem, gdy potrzebujesz skompilować aplikację, uruchom 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 ``` -Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zostaną zbuforowane, kolejne będą szybsze. Dodatkowo, jeśli potrzebujesz określić inne argumenty dla polecenia budowania, możesz to zrobić na końcu komendy w miejscu ``. Na przykład, jeśli chciałbyś zbudować zoptymalizowaną wersję wydania, uruchomiłbyś powyższą komendę a następnie `---release`. Powstały plik wykonywalny będzie dostępny w folderze docelowym w twoim systemie, i może być uruchomiony z: +Zauważ, że pierwsza kompilacja może potrwać dłużej zanim zależności zostaną zbuforowane, kolejne będą szybsze. Dodatkowo, jeśli potrzebujesz określić inne argumenty dla polecenia budowania, możesz to zrobić na końcu komendy w miejscu ``. Na przykład, jeśli chciałbyś zbudować zoptymalizowaną wersję wydania, uruchomiłbyś powyższą komendę a następnie `--release`. Powstały plik wykonywalny będzie dostępny w folderze docelowym w twoim systemie, i może być uruchomiony z: ```sh target/debug/rustdesk diff --git a/README-PTBR.md b/README-PTBR.md index f31f2b18c..76b360283 100644 --- a/README-PTBR.md +++ b/README-PTBR.md @@ -5,7 +5,7 @@ DockerEstruturaScreenshots
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Precisamos de sua ajuda para traduzir este README e a UI do RustDesk para sua língua nativa

diff --git a/README-RU.md b/README-RU.md index b1a6accfe..54c161cf0 100644 --- a/README-RU.md +++ b/README-RU.md @@ -5,7 +5,7 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
Нам нужна ваша помощь для перевода этого README и RustDesk UI на ваш родной язык

@@ -127,7 +127,7 @@ docker build -t "rustdesk-builder" . docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder ``` -Обратите внимание, что первая сборка может занять больше времени, прежде чем зависимости будут кэшированы, но последующие сборки будут выполняться быстрее. Кроме того, если вам нужно указать другие аргументы для команды сборки, вы можете сделать это в конце команды в переменной ``. Например, если вы хотите создать оптимизированную версию, вы должны запустить приведенную выше команду и в конце строки добавить `---release`. Полученный исполняемый файл будет доступен в целевой папке вашей системы и может быть запущен с помощью: +Обратите внимание, что первая сборка может занять больше времени, прежде чем зависимости будут кэшированы, но последующие сборки будут выполняться быстрее. Кроме того, если вам нужно указать другие аргументы для команды сборки, вы можете сделать это в конце команды в переменной ``. Например, если вы хотите создать оптимизированную версию, вы должны запустить приведенную выше команду и в конце строки добавить `--release`. Полученный исполняемый файл будет доступен в целевой папке вашей системы и может быть запущен с помощью: ```sh target/debug/rustdesk diff --git a/README-ZH.md b/README-ZH.md index 696b537c4..8d4203b16 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -5,7 +5,7 @@ Docker结构截图
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]

Chat with us: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) @@ -180,7 +180,7 @@ groupmod: cannot lock /etc/group; try again later. 可以尝试把`-e PUID="$(id -u)" -e PGID="$(id -g)"`参数去掉。(出现这一问题的原因是容器中的 entrypoint 脚本中判定 uid 和 gid 与给定的环境变量不一致时会修改 user 的 uid 和 gid 重新运行,但是重新运行时取不到环境变量中的 uid 和 gid 了,会再次进入 uid 与 gid 与给定值不一致的逻辑分支) 请注意,第一次构建可能需要比较长的时间,因为需要缓存依赖项(国内网络经常出现拉取失败,可多尝试几次),后续构建会更快。此外,如果您需要为构建命令指定不同的参数, -您可以在命令末尾的 `` 位置执行此操作。例如,如果你想构建一个优化的发布版本,你可以在命令后跟 `---release`。 +您可以在命令末尾的 `` 位置执行此操作。例如,如果你想构建一个优化的发布版本,你可以在命令后跟 `--release`。 将在 target 下产生可执行程序,请通过以下方式运行调试版本: ```sh diff --git a/README.md b/README.md index 43e23bd60..2166073a7 100644 --- a/README.md +++ b/README.md @@ -5,21 +5,28 @@ DockerStructureSnapshot
- [中文] | [Español] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어]
+ [česky] | [中文] | [Español] | [فارسی] | [Français] | [Deutsch] | [Polski] | [Indonesian] | [Suomi] | [മലയാളം] | [日本語] | [Nederlands] | [Italiano] | [Русский] | [Português (Brasil)] | [Esperanto] | [한국어] | [العربي]
We need your help to translate this README, RustDesk UI and Doc to your native language

Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) - [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) Yet another remote desktop software, written in Rust. Works out of the box, no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/server), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo). +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) + RustDesk welcomes contribution from everyone. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for help getting started. +[**How does RustDesk work?**](https://github.com/rustdesk/rustdesk/wiki/How-does-RustDesk-work%3F) + [**BINARY DOWNLOAD**](https://github.com/rustdesk/rustdesk/releases) +[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) + ## Free Public Servers Below are the servers you are using for free, it may change along the time. If you are not close to one of these, your network may be slow. @@ -72,6 +79,12 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio ``` +### Install pynput package + +```sh +pip3 install pynput +``` + ### Install vcpkg ```sh @@ -130,7 +143,7 @@ Then, each time you need to build the application, run the following command: 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 ``` -Note that the first build may take longer before dependencies are cached, subsequent builds will be faster. Additionally, if you need to specify different arguments to the build command, you may do so at the end of the command in the `` position. For instance, if you wanted to build an optimized release version, you would run the command above followed by `---release`. The resulting executable will be available in the target folder on your system, and can be run with: +Note that the first build may take longer before dependencies are cached, subsequent builds will be faster. Additionally, if you need to specify different arguments to the build command, you may do so at the end of the command in the `` position. For instance, if you wanted to build an optimized release version, you would run the command above followed by `--release`. The resulting executable will be available in the target folder on your system, and can be run with: ```sh target/debug/rustdesk diff --git a/appimage/AppImageBuilder.yml b/appimage/AppImageBuilder.yml new file mode 100644 index 000000000..0ca62e97c --- /dev/null +++ b/appimage/AppImageBuilder.yml @@ -0,0 +1,98 @@ +# appimage-builder recipe see https://appimage-builder.readthedocs.io for details +# Please build this AppImage on Ubuntu 18.04 +version: 1 +script: + # Remove any previous build + - rm -rf AppDir | true + # Install application dependencies + - pip3 install --upgrade pip && pip3 install --ignore-installed --prefix=/usr --root=AppDir -r ./requirements.txt + # Download sciter.so + - mkdir -p AppDir/usr/lib/rustdesk/ + - pushd AppDir/usr/lib/rustdesk && wget https://github.com/c-smile/sciter-sdk/raw/29a598b6d20220b93848b5e8abab704619296857/bin.lnx/x64/libsciter-gtk.so && popd + # pynput_service.py + - cp ../pynput_service.py ./AppDir/usr/lib/rustdesk + # Build rustdesk + - pushd .. && python3 inline-sciter.py && cargo build --features inline,appimage --release && popd + - mkdir -p AppDir/usr/bin + - cp ../target/release/rustdesk AppDir/usr/bin/rustdesk + # Make usr and icons dirs + - mkdir -p AppDir/usr/share/icons/hicolor/128x128 && cp ../128x128.png AppDir/usr/share/icons/hicolor/128x128/rustdesk.png + - mkdir -p AppDir/usr/share/icons/hicolor/32x32 && cp ../32x32.png AppDir/usr/share/icons/hicolor/32x32/rustdesk.png + - cp rustdesk.desktop AppDir/ + +AppDir: + path: ./AppDir + app_info: + id: rustdesk + name: RustDesk + icon: rustdesk + version: 1.1.10 + exec: usr/bin/rustdesk + exec_args: $@ + apt: + arch: + - amd64 + allow_unauthenticated: true + sources: + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic main restricted + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates main restricted + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-updates multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-backports main restricted + universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main restricted + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security universe + - sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security multiverse + include: + - libgcc1:amd64 + - libgcrypt20:amd64 + - libgtk-3-0:amd64 + - liblz4-1:amd64 + - liblzma5:amd64 + - libpcre3:amd64 + - libpulse0:amd64 + - libsystemd0:amd64 + - libxau6:amd64 + - libxcb-randr0:amd64 + - libxdmcp6:amd64 + - libxdo3:amd64 + - libxext6:amd64 + - libxfixes3:amd64 + - libxinerama1:amd64 + - libxrender1:amd64 + - libxtst6:amd64 + - python3:amd64 + - python3-pkg-resources:amd64 + files: + include: [] + exclude: + - usr/share/man + - usr/share/doc/*/README.* + - usr/share/doc/*/changelog.* + - usr/share/doc/*/NEWS.* + - usr/share/doc/*/TODO.* + runtime: + env: + PYTHONHOME: '${APPDIR}/usr' + PYTHONPATH: '${APPDIR}/usr/lib/python3.6/site-packages' + test: + fedora-30: + image: appimagecrafters/tests-env:fedora-30 + command: ./AppRun + debian-stable: + image: appimagecrafters/tests-env:debian-stable + command: ./AppRun + archlinux-latest: + image: appimagecrafters/tests-env:archlinux-latest + command: ./AppRun + centos-7: + image: appimagecrafters/tests-env:centos-7 + command: ./AppRun + ubuntu-xenial: + image: appimagecrafters/tests-env:ubuntu-xenial + command: ./AppRun +AppImage: + arch: x86_64 + update-information: guess \ No newline at end of file diff --git a/appimage/README.md b/appimage/README.md new file mode 100644 index 000000000..1dcfa0b35 --- /dev/null +++ b/appimage/README.md @@ -0,0 +1,20 @@ +# How to build and run RustDesk in AppImage + +Begin by installing `appimage-builder` and predependencies mentioned in official website. + +Assume that `appimage-builder` is setup correctly, run commands below, `bash` or `zsh` is recommended: + +```bash +cd /path/to/rustdesk_root +./build_appimage.py +``` + +After a success package, you can see the message in console like: + +```shell +INFO:root:AppImage created successfully +``` + +The AppImage package is shown in `./appimage/RustDesk-VERSION-TARGET_PLATFORM.AppImage`. + +Note: AppImage version of rustdesk is an early version which requires more test. If you find problems, please open an issue. \ No newline at end of file diff --git a/appimage/requirements.txt b/appimage/requirements.txt new file mode 100644 index 000000000..d632797e5 --- /dev/null +++ b/appimage/requirements.txt @@ -0,0 +1 @@ +pynput \ No newline at end of file diff --git a/appimage/rustdesk.desktop b/appimage/rustdesk.desktop new file mode 100644 index 000000000..a0227f256 --- /dev/null +++ b/appimage/rustdesk.desktop @@ -0,0 +1,19 @@ +[Desktop Entry] +Version=1.1.10 +Name=RustDesk +GenericName=Remote Desktop +Comment=Remote Desktop +Exec=rustdesk +Icon=rustdesk +Terminal=false +Type=Application +StartupNotify=true +Categories=Other; +Keywords=internet; +Actions=new-window; + +X-Desktop-File-Install-Version=0.23 + +[Desktop Action new-window] +Name=Open a New Window + diff --git a/build.py b/build.py index ff547651f..45488ad63 100644 --- a/build.py +++ b/build.py @@ -2,9 +2,11 @@ import os import platform -import zlib -from shutil import copy2 +import zipfile +import urllib.request +import shutil import hashlib +import argparse windows = platform.platform().startswith('Windows') osx = platform.platform().startswith('Darwin') or platform.platform().startswith("macOS") @@ -20,7 +22,93 @@ def get_version(): return '' +def get_features(feature): + available_features = { + 'IddDriver': { + 'zip_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1/RustDeskIddDriver_x64.zip', + 'checksum_url': 'https://github.com/fufesou/RustDeskIddDriver/releases/download/v0.1' + '/RustDeskIddDriver_x64.zip.checksum_md5', + }, + 'PrivacyMode': { + 'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1' + '/TempTopMostWindow_x64.zip', + 'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.1' + '/TempTopMostWindow_x64.zip.checksum_md5', + } + } + apply_features = {} + if not feature: + return apply_features + elif isinstance(feature, str) and feature.upper() == 'ALL': + return available_features + elif isinstance(feature, list): + for feat in feature: + if isinstance(feat, str) and feat.upper() == 'ALL': + return available_features + if feat in available_features: + apply_features[feat] = available_features[feat] + else: + print(f'Unrecognized feature {feat}') + return apply_features + else: + raise Exception(f'Unsupported features param {feature}') + + +def make_parser(): + parser = argparse.ArgumentParser(description='Build script.') + parser.add_argument( + '-f', + '--feature', + dest='feature', + metavar='N', + type=str, + nargs='+', + default='', + help='Integrate features, windows only.' + 'Available: IddDriver, PrivacyMode. Special value is "ALL" and empty "". Default is empty.') + return parser + + +def download_extract_features(features, res_dir): + for (feat, feat_info) in features.items(): + print(f'{feat} download begin') + checksum_md5_response = urllib.request.urlopen(feat_info['checksum_url']) + checksum_md5 = checksum_md5_response.read().decode('utf-8').split()[0] + download_filename = feat_info['zip_url'].split('/')[-1] + filename, _headers = urllib.request.urlretrieve(feat_info['zip_url'], download_filename) + md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest() + if checksum_md5 != md5: + raise Exception(f'{feat} download failed') + print(f'{feat} download end. extract bein') + zip_file = zipfile.ZipFile(filename) + zip_list = zip_file.namelist() + for f in zip_list: + zip_file.extract(f, res_dir) + zip_file.close() + os.remove(download_filename) + print(f'{feat} extract end') + + +def build_windows(feature): + features = get_features(feature) + if not features: + os.system('cargo build --release --features inline') + else: + print(f'Build with features {list(features.keys())}') + res_dir = 'resources' + if os.path.isdir(res_dir) and not os.path.islink(res_dir): + shutil.rmtree(res_dir) + elif os.path.exists(res_dir): + raise Exception(f'Find file {res_dir}, not a directory') + os.makedirs(res_dir, exist_ok=True) + download_extract_features(features, res_dir) + os.system('cargo build --release --features inline,with_rc') + + def main(): + parser = make_parser() + args = parser.parse_args() + os.system("cp Cargo.toml Cargo.toml.bk") os.system("cp src/main.rs src/main.rs.bk") if windows: @@ -35,39 +123,40 @@ def main(): os.system('git checkout src/ui/common.tis') version = get_version() if windows: - os.system('cargo build --release --features inline') + build_windows(args.feature) # os.system('upx.exe target/release/rustdesk.exe') os.system('mv target/release/rustdesk.exe target/release/RustDesk.exe') pa = os.environ.get('P') if pa: - os.system('signtool sign /a /v /p %s /debug /f .\\cert.pfx /t http://timestamp.digicert.com target\\release\\rustdesk.exe'%pa) + os.system(f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com ' + 'target\\release\\rustdesk.exe') else: - print('Not signed') - os.system('cp -rf target/release/RustDesk.exe rustdesk-%s-putes.exe'%version) + print('Not signed') + os.system(f'cp -rf target/release/RustDesk.exe rustdesk-{version}-setdown.exe') elif os.path.isfile('/usr/bin/pacman'): os.system('cargo build --release --features inline') os.system('git checkout src/ui/common.tis') os.system('strip target/release/rustdesk') - os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD"%version) + os.system("sed -i 's/pkgver=.*/pkgver=%s/g' PKGBUILD" % version) # pacman -S -needed base-devel os.system('HBB=`pwd` makepkg -f') - os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst'%(version, version)) + os.system('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (version, version)) # pacman -U ./rustdesk.pkg.tar.zst elif os.path.isfile('/usr/bin/yum'): os.system('cargo build --release --features inline') os.system('strip target/release/rustdesk') - os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec"%version) + os.system("sed -i 's/Version: .*/Version: %s/g' rpm.spec" % version) os.system('HBB=`pwd` rpmbuild -ba rpm.spec') - os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm'%(version, version)) + os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm' % ( + version, version)) # yum localinstall rustdesk.rpm elif os.path.isfile('/usr/bin/zypper'): os.system('cargo build --release --features inline') os.system('strip target/release/rustdesk') - os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec"%version) + os.system("sed -i 's/Version: .*/Version: %s/g' rpm-suse.spec" % version) os.system('HBB=`pwd` rpmbuild -ba rpm-suse.spec') - os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm'%(version, version)) + os.system('mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (version, version)) # yum localinstall rustdesk.rpm - else: os.system('cargo bundle --release --features inline') if osx: @@ -81,12 +170,12 @@ def main(): txt = open(plist).read() with open(plist, "wt") as fh: fh.write(txt.replace("", """ - LSUIElement - 1 + LSUIElement + 1 """)) pa = os.environ.get('P') if pa: - os.system(''' + os.system(''' # buggy: rcodesign sign ... path/*, have to sign one by one #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/libsciter.dylib @@ -96,9 +185,9 @@ codesign -s "Developer ID Application: {0}" --force --options runtime ./target/ codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app '''.format(pa)) os.system('create-dmg target/release/bundle/osx/RustDesk.app') - os.rename('RustDesk %s.dmg'%version, 'rustdesk-%s.dmg'%version) + os.rename('RustDesk %s.dmg' % version, 'rustdesk-%s.dmg' % version) if pa: - os.system(''' + os.system(''' #rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./rustdesk-{1}.dmg codesign -s "Developer ID Application: {0}" --force --options runtime ./rustdesk-{1}.dmg # https://pyoxidizer.readthedocs.io/en/latest/apple_codesign_rcodesign.html @@ -106,7 +195,7 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9 # verify: spctl -a -t exec -v /Applications/RustDesk.app '''.format(pa, version)) else: - print('Not signed') + print('Not signed') else: os.system('mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb') os.system('dpkg-deb -R rustdesk.deb tmpdeb') @@ -122,7 +211,7 @@ rcodesign notarize --api-issuer 69a6de7d-2907-47e3-e053-5b8c7c11a4d1 --api-key 9 md5_file('usr/share/rustdesk/files/pynput_service.py') md5_file('usr/lib/rustdesk/libsciter-gtk.so') os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/') - os.rename('rustdesk.deb', 'rustdesk-%s.deb'%version) + os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version) os.system("mv Cargo.toml.bk Cargo.toml") os.system("mv src/main.rs.bk src/main.rs") diff --git a/build.rs b/build.rs index 4d51cd297..7d6aac441 100644 --- a/build.rs +++ b/build.rs @@ -27,6 +27,20 @@ fn build_manifest() { } } +#[cfg(all(windows, feature = "with_rc"))] +fn build_rc_source() { + use simple_rc::{generate_with_conf, Config, ConfigItem}; + generate_with_conf(&Config { + outfile: "src/rc.rs".to_owned(), + confs: vec![ConfigItem { + inc: "resources".to_owned(), + exc: vec![], + suppressed_front: "resources".to_owned(), + }], + }) + .unwrap(); +} + fn install_oboe() { let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); if target_os != "android" { @@ -89,6 +103,8 @@ fn main() { gen_flutter_rust_bridge(); // return; // } + #[cfg(all(windows, feature = "with_rc"))] + build_rc_source(); #[cfg(all(windows, feature = "inline"))] build_manifest(); #[cfg(windows)] diff --git a/build_appimage.py b/build_appimage.py new file mode 100755 index 000000000..1c7ae2443 --- /dev/null +++ b/build_appimage.py @@ -0,0 +1,23 @@ +#!/usr/bin/python3 +import os + +def get_version(): + with open("Cargo.toml") as fh: + for line in fh: + if line.startswith("version"): + return line.replace("version", "").replace("=", "").replace('"', '').strip() + return '' + +if __name__ == '__main__': + # check version + version = get_version() + os.chdir("appimage") + os.system("sed -i 's/^Version=.*/Version=%s/g' rustdesk.desktop" % version) + os.system("sed -i 's/^ version: .*/ version: %s/g' AppImageBuilder.yml" % version) + # build appimage + ret = os.system("appimage-builder --recipe AppImageBuilder.yml --skip-test") + if ret == 0: + print("RustDesk AppImage build success :)") + print("Check AppImage in '/path/to/rustdesk/appimage/RustDesk-VERSION-TARGET_PLATFORM.AppImage'") + else: + print("RustDesk AppImage build failed :(") diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 000000000..966ad3df8 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,11 @@ +An open-source remote desktop application, the open source TeamViewer alternative. +Source code: https://github.com/rustdesk/rustdesk +Doc: https://rustdesk.com/docs/en/manual/mobile/ + +In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the "Accessibility" service, RustDesk uses AccessibilityService API to implement Addroid remote control. + +In addtion to remote control, you can also transfer files between Android devices and PCs easily with RustDesk. + +You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, or self-hosting, or write your own rendezvous/relay server. Self-hosting server is free and open source: https://github.com/rustdesk/rustdesk-server + +Please download and install desktop version from: https://rustdesk.com, then you can access and control your desktop from your mobile, or control your mobile from desktop. diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 000000000..543fe8346 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png new file mode 100644 index 000000000..32e7b3554 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png new file mode 100644 index 000000000..0f9368545 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png new file mode 100644 index 000000000..b59279552 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png new file mode 100644 index 000000000..a4048ae69 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png new file mode 100644 index 000000000..5d726ab36 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png new file mode 100644 index 000000000..2c3fad113 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/6.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png new file mode 100644 index 000000000..5873757f9 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/7.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png new file mode 100644 index 000000000..faea2eb16 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/8.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 000000000..357fb37ab --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +An open-source remote desktop application, the open source TeamViewer alternative. diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt new file mode 100644 index 000000000..f1f44057e --- /dev/null +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -0,0 +1,12 @@ +开源远程桌面应用,开源 TeamViewer 替代方案。 +源代码:https://github.com/rustdesk/rustdesk +文档:https://rustdesk.com/docs/en/manual/mobile/ + +为了让远程设备通过鼠标或触摸控制您的 Android 设备,您需要允许 RustDesk 使用“Accessibility”服务,RustDesk 使用 AccessibilityService API 来实现 Addroid 远程控制。 + +除了远程控制,您还可以使用 RustDesk 在 Android 设备和 PC 之间轻松传输文件。 + +您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,或者自建,亦或者开发您的版本。 +自托管服务器是免费和开源的:https://github.com/rustdesk/rustdesk-server + +请从:https://rustdesk.com 下载并安装桌面版,然后您可以通过手机访问和控制您的桌面,或从桌面控制您的手机。 diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt new file mode 100644 index 000000000..69a4a5b52 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/short_description.txt @@ -0,0 +1 @@ +开源远程桌面应用,开源 TeamViewer 替代方案 diff --git a/flutter/.gitignore b/flutter/.gitignore index c8ff34feb..ede37092d 100644 --- a/flutter/.gitignore +++ b/flutter/.gitignore @@ -54,4 +54,7 @@ linux/flutter/generated_plugins.cmake macos/Flutter/GeneratedPluginRegistrant.swift windows/flutter/generated_plugin_registrant.cc windows/flutter/generated_plugin_registrant.h -windows/flutter/generated_plugins.cmake \ No newline at end of file +windows/flutter/generated_plugins.cmake +flutter_export_environment.sh +Flutter-Generated.xcconfig +key.jks diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml index c552efae5..ad496edb0 100644 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ b/flutter/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + () private var isWheelActionsPolling = false - @Keep @RequiresApi(Build.VERSION_CODES.N) - fun rustMouseInput(mask: Int, _x: Int, _y: Int) { + fun onMouseInput(mask: Int, _x: Int, _y: Int) { val x = if (_x < 0) { 0 } else { @@ -207,12 +206,15 @@ class InputService : AccessibilityService() { } } - @RequiresApi(Build.VERSION_CODES.O) override fun onServiceConnected() { super.onServiceConnected() ctx = this Log.d(logTag, "onServiceConnected!") - init(this) + } + + override fun onDestroy() { + ctx = null + super.onDestroy() } override fun onAccessibilityEvent(event: AccessibilityEvent?) {} diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index bb4e85c89..3cc105bfa 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -1,5 +1,12 @@ package com.carriez.flutter_hbb +/** + * Handle events from flutter + * Request MediaProjection permission + * + * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG + */ + import android.app.Activity import android.content.ComponentName import android.content.Context diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt index 4a1b8c06f..b9f9ff872 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt @@ -1,8 +1,11 @@ +package com.carriez.flutter_hbb + /** * Capture screen,get video and audio,send to rust. - * Handle notification + * Dispatch notifications + * + * Inspired by [droidVNC-NG] https://github.com/bk138/droidVNC-NG */ -package com.carriez.flutter_hbb import android.Manifest import android.annotation.SuppressLint @@ -69,10 +72,32 @@ class MainService : Service() { System.loadLibrary("rustdesk") } + @Keep + @RequiresApi(Build.VERSION_CODES.N) + fun rustMouseInput(mask: Int, x: Int, y: Int) { + // turn on screen with LIFT_DOWN when screen off + if (!powerManager.isInteractive && mask == LIFT_DOWN) { + if (wakeLock.isHeld) { + Log.d(logTag,"Turn on Screen, WakeLock release") + wakeLock.release() + } + Log.d(logTag,"Turn on Screen") + wakeLock.acquire(5000) + } else { + InputService.ctx?.onMouseInput(mask,x,y) + } + } + @Keep fun rustGetByName(name: String): String { return when (name) { - "screen_size" -> "${SCREEN_INFO.width}:${SCREEN_INFO.height}" + "screen_size" -> { + JSONObject().apply { + put("width",SCREEN_INFO.width) + put("height",SCREEN_INFO.height) + put("scale",SCREEN_INFO.scale) + }.toString() + } else -> "" } } @@ -130,6 +155,9 @@ class MainService : Service() { private var serviceLooper: Looper? = null private var serviceHandler: Handler? = null + private val powerManager: PowerManager by lazy { applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager } + private val wakeLock: PowerManager.WakeLock by lazy { powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rustdesk:wakelock")} + // jvm call rust private external fun init(ctx: Context) private external fun startServer() @@ -191,10 +219,6 @@ class MainService : Service() { } override fun onDestroy() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - InputService.ctx?.disableSelf() - } - InputService.ctx = null checkMediaPermission() super.onDestroy() } @@ -383,10 +407,6 @@ class MainService : Service() { mediaProjection = null checkMediaPermission() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - InputService.ctx?.disableSelf() - } - InputService.ctx = null stopForeground(true) stopSelf() } diff --git a/flutter/build_android.sh b/flutter/build_android.sh index 4f4038593..01ff23488 100755 --- a/flutter/build_android.sh +++ b/flutter/build_android.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash $ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/* -flutter build apk --target-platform android-arm64 --release --obfuscate --split-debug-info ./split-debug-info -flutter build appbundle --target-platform android-arm64 --release --obfuscate --split-debug-info ./split-debug-info +flutter build apk --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info +flutter build apk ---split-per-abi --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info +flutter build appbundle --target-platform android-arm64,android-arm --release --obfuscate --split-debug-info ./split-debug-info # build in linux # $ANDROID_NDK/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip android/app/src/main/jniLibs/arm64-v8a/* diff --git a/flutter/build_android_deps.sh b/flutter/build_android_deps.sh new file mode 100755 index 000000000..f120346cf --- /dev/null +++ b/flutter/build_android_deps.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +# Build libyuv / opus / libvpx / oboe for Android +# Required: +# 1. set VCPKG_ROOT / ANDROID_NDK path environment variables +# 2. vcpkg initialized +# 3. ndk, version: 22 (if ndk < 22 you need to change LD as `export LD=$TOOLCHAIN/bin/$NDK_LLVM_TARGET-ld`) + +if [ -z "$ANDROID_NDK" ]; then + echo "Failed! Please set ANDROID_NDK" + exit 1 +fi + +if [ -z "$VCPKG_ROOT" ]; then + echo "Failed! Please set VCPKG_ROOT" + exit 1 +fi + +API_LEVEL="21" + +# NDK llvm toolchain +HOST_TAG="linux-x86_64" # current platform, set as `ls $ANDROID_NDK/toolchains/llvm/prebuilt/` +TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG + +function build { + ANDROID_ABI=$1 + VCPKG_TARGET=$2 + NDK_LLVM_TARGET=$3 + LIBVPX_TARGET=$4 + + PREFIX=$VCPKG_ROOT/installed/$VCPKG_TARGET/ + + # 1 + echo "*** [$ANDROID_ABI][Start] Build opus / libyuv from vcpkg" + export ANDROID_NDK_HOME=$ANDROID_NDK + pushd $VCPKG_ROOT + $VCPKG_ROOT/vcpkg install opus --triplet $VCPKG_TARGET + $VCPKG_ROOT/vcpkg install libyuv --triplet $VCPKG_TARGET + popd + echo "*** [$ANDROID_ABI][Finished] Build opus / libyuv from vcpkg" + + # 2 + echo "*** [$ANDROID_ABI][Start] Build libvpx" + pushd build/libvpx + export AR=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ar + export AS=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-as + export LD=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ld.gold # if ndk < 22, use aarch64-linux-android-ld + export RANLIB=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-ranlib + export STRIP=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}-strip + + if [ $NDK_LLVM_TARGET == "arm-linux-androideabi" ] + then + export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang + export CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi${API_LEVEL}-clang++ + else + export CC=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang + export CXX=$TOOLCHAIN/bin/${NDK_LLVM_TARGET}${API_LEVEL}-clang++ + fi + make clean + ./configure --target=$LIBVPX_TARGET \ + --enable-pic --disable-vp8 \ + --disable-webm-io \ + --disable-unit-tests \ + --disable-examples \ + --disable-libyuv \ + --disable-postproc \ + --disable-vp8 \ + --disable-tools \ + --disable-docs \ + --prefix=$PREFIX + make -j5 + make install + + popd + echo "*** [$ANDROID_ABI][Finished] Build libvpx" + + # 3 + echo "*** [$ANDROID_ABI][Start] Build oboe" + pushd build/oboe + make clean + cmake -DBUILD_SHARED_LIBS=true \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DANDROID_TOOLCHAIN=clang \ + -DANDROID_STL=c++_shared \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DANDROID_ABI=$ANDROID_ABI \ + -DANDROID_PLATFORM=android-$API_LEVEL + make -j5 + make install + mv $PREFIX/lib/$ANDROID_ABI/liboboe.a $PREFIX/lib/ + popd + echo "*** [$ANDROID_ABI][Finished] Build oboe" + + echo "*** [$ANDROID_ABI][All Finished]" +} + +git clone -b v1.11.0 --depth=1 https://github.com/webmproject/libvpx.git build/libvpx +git clone -b 1.6.1 --depth=1 https://github.com/google/oboe build/oboe +patch -N -d build/oboe -p1 < ../src/oboe.patch + +# VCPKG_TARGET ANDROID_ABI +# arm64-android arm64-v8a +# arm-android armeabi-v7a +# x64-android x86_64 +# x86-android x86 + +# NDK_LLVM_TARGET +# aarch64-linux-android +# arm-linux-androideabi +# x86_64-linux-android +# i686-linux-android + +# LIBVPX_TARGET : +# arm64-android-gcc +# armv7-android-gcc +# x86_64-android-gcc +# x86-android-gcc + +# args: ANDROID_ABI VCPKG_TARGET NDK_LLVM_TARGET LIBVPX_TARGET +build arm64-v8a arm64-android aarch64-linux-android arm64-android-gcc +build armeabi-v7a arm-android arm-linux-androideabi armv7-android-gcc + +# rm -rf build/libvpx +# rm -rf build/oboe \ No newline at end of file diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 71d9ed9ad..a987f54df 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -236,7 +236,7 @@ class AccessibilityListener extends StatelessWidget { } }, onPointerUp: (evt) { - if (evt.size == 1 && GestureBinding.instance != null) { + if (evt.size == 1) { GestureBinding.instance.handlePointerEvent(PointerUpEvent( pointer: evt.pointer + offset, size: 0.1, @@ -246,7 +246,7 @@ class AccessibilityListener extends StatelessWidget { } }, onPointerMove: (evt) { - if (evt.size == 1 && GestureBinding.instance != null) { + if (evt.size == 1) { GestureBinding.instance.handlePointerEvent(PointerMoveEvent( pointer: evt.pointer + offset, size: 0.1, diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index 68841de89..9ae0f3766 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -113,8 +113,8 @@ class _ConnectionPageState extends State { : InkWell( onTap: () async { final url = _updateUrl + '.apk'; - if (await canLaunch(url)) { - await launch(url); + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } }, child: Container( @@ -275,7 +275,7 @@ class _ConnectionPageState extends State { ? [] : [ PopupMenuItem( - child: Text(translate('File transfer')), value: 'file') + child: Text(translate('Transfer File')), value: 'file') ]), elevation: 8, ); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index fcc5fcde8..25497dfbc 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -35,6 +35,7 @@ class _RemotePageState extends State { String _value = ''; double _scale = 1; double _mouseScrollIntegral = 0; // mouse scroll speed controller + Orientation? _currentOrientation; var _more = true; var _fn = false; @@ -127,7 +128,7 @@ class _RemotePageState extends State { common < oldValue.length && common < newValue.length && newValue[common] == oldValue[common]; - ++common); + ++common) {} for (i = 0; i < oldValue.length - common; ++i) { gFFI.inputKey('VK_BACK'); } @@ -260,12 +261,22 @@ class _RemotePageState extends State { color: Colors.black, child: isWebDesktop ? getBodyForDesktopWithListener(keyboard) - : SafeArea( - child: Container( + : SafeArea(child: + OrientationBuilder(builder: (ctx, orientation) { + if (_currentOrientation != orientation) { + debugPrint("on orientation changed"); + Timer(Duration(milliseconds: 200), () { + resetMobileActionsOverlay(); + _currentOrientation = orientation; + FFI.canvasModel.updateViewStyle(); + }); + } + return Container( color: MyTheme.canvasColor, child: _isPhysicalMouse ? getBodyForMobile() - : getBodyForMobileWithGesture()))); + : getBodyForMobileWithGesture()); + }))); }) ], ))), diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 3b0332fa7..9adddf280 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -204,7 +204,7 @@ class _PermissionCheckerState extends State { serverModel.toggleService), PermissionRow(translate("Input Control"), serverModel.inputOk, serverModel.toggleInput), - PermissionRow(translate("File Transfer"), serverModel.fileOk, + PermissionRow(translate("Transfer File"), serverModel.fileOk, serverModel.toggleFile), hasAudioPermission ? PermissionRow(translate("Audio Capture"), serverModel.audioOk, diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index a3965c199..868c599bd 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -70,8 +70,8 @@ class _SettingsState extends State { tiles: [ SettingsTile.navigation( onPressed: (context) async { - if (await canLaunch(url)) { - await launch(url); + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } }, title: Text(translate("Version: ") + version), @@ -107,8 +107,8 @@ void showAbout() { InkWell( onTap: () async { const url = 'https://rustdesk.com/'; - if (await canLaunch(url)) { - await launch(url); + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } }, child: Padding( @@ -151,7 +151,7 @@ fetch('http://localhost:21114/api/login', { 'uuid': gFFI.getByName('uuid') }; try { - final response = await http.post(Uri.parse('${url}/api/login'), + final response = await http.post(Uri.parse('$url/api/login'), headers: {"Content-Type": "application/json"}, body: json.encode(body)); return parseResp(response.body); } catch (e) { @@ -189,7 +189,7 @@ void refreshCurrentUser() async { 'uuid': gFFI.getByName('uuid') }; try { - final response = await http.post(Uri.parse('${url}/api/currentUser'), + final response = await http.post(Uri.parse('$url/api/currentUser'), headers: { "Content-Type": "application/json", "Authorization": "Bearer $token" @@ -215,7 +215,7 @@ void logout() async { 'uuid': gFFI.getByName('uuid') }; try { - await http.post(Uri.parse('${url}/api/logout'), + await http.post(Uri.parse('$url/api/logout'), headers: { "Content-Type": "application/json", "Authorization": "Bearer $token" @@ -245,7 +245,7 @@ String getUrl() { url = 'http://${tmp[0]}:$port'; } } else { - url = 'http://${url}:21114'; + url = 'http://$url:21114'; } } } diff --git a/flutter/lib/mobile/widgets/gestures.dart b/flutter/lib/mobile/widgets/gestures.dart index d70fe05e6..960439678 100644 --- a/flutter/lib/mobile/widgets/gestures.dart +++ b/flutter/lib/mobile/widgets/gestures.dart @@ -594,10 +594,7 @@ class _TapTracker { required this.entry, required Duration doubleTapMinTime, required this.gestureSettings, - }) : assert(doubleTapMinTime != null), - assert(event != null), - assert(event.buttons != null), - pointer = event.pointer, + }) : pointer = event.pointer, _initialGlobalPosition = event.position, initialButtons = event.buttons, _doubleTapMinTimeCountdown = @@ -643,7 +640,7 @@ class _TapTracker { /// CountdownZoned tracks whether the specified duration has elapsed since /// creation, honoring [Zone]. class _CountdownZoned { - _CountdownZoned({required Duration duration}) : assert(duration != null) { + _CountdownZoned({required Duration duration}) { Timer(duration, _onTimeout); } diff --git a/flutter/lib/mobile/widgets/overlay.dart b/flutter/lib/mobile/widgets/overlay.dart index d2a1bdb57..5b44d445b 100644 --- a/flutter/lib/mobile/widgets/overlay.dart +++ b/flutter/lib/mobile/widgets/overlay.dart @@ -228,6 +228,12 @@ class DraggableMobileActions extends StatelessWidget { } } +resetMobileActionsOverlay() { + if (mobileActionsOverlayEntry == null) return; + hideMobileActionsOverlay(); + showMobileActionsOverlay(); +} + showMobileActionsOverlay() { if (mobileActionsOverlayEntry != null) return; if (globalKey.currentContext == null || diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 9ed8b54d7..2c42d3b02 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -621,15 +621,7 @@ class FileFetcher { tryCompleteTask(String? msg, String? isLocalStr) { if (msg == null || isLocalStr == null) return; - late final isLocal; late final tasks; - if (isLocalStr == "true") { - isLocal = true; - } else if (isLocalStr == "false") { - isLocal = false; - } else { - return; - } try { final fd = FileDirectory.fromJson(jsonDecode(msg)); if (fd.id > 0) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index dbb0ce23e..a39940e9d 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -229,6 +229,7 @@ class FfiModel with ChangeNotifier { } void handleSwitchDisplay(Map evt) { + final oldOrientation = _display.width > _display.height; var old = _pi.currentDisplay; _pi.currentDisplay = int.parse(evt['display']); _display.x = double.parse(evt['x']); @@ -237,6 +238,11 @@ class FfiModel with ChangeNotifier { _display.height = int.parse(evt['height']); if (old != _pi.currentDisplay) parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y); + + // remote is mobile, and orientation changed + if ((_display.width > _display.height) != oldOrientation) { + gFFI.canvasModel.updateViewStyle(); + } notifyListeners(); } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 311fef334..c161b52db 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -56,7 +56,6 @@ class ServerModel with ChangeNotifier { * 2. check config * audio true by default (if permission on) (false default < Android 10) * file true by default (if permission on) - * input false by default (it need turning on manually everytime) */ await Future.delayed(Duration(seconds: 1)); diff --git a/flutter/lib/models/web_model.dart b/flutter/lib/models/web_model.dart index 13b62998f..59a0e610e 100644 --- a/flutter/lib/models/web_model.dart +++ b/flutter/lib/models/web_model.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_web_libraries_in_flutter + import 'dart:convert'; import 'dart:typed_data'; import 'dart:js'; diff --git a/flutter/ndk_arm.sh b/flutter/ndk_arm.sh new file mode 100755 index 000000000..fe9c81016 --- /dev/null +++ b/flutter/ndk_arm.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +cargo ndk --platform 21 --target armv7-linux-androideabi build --release diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 59fcbffca..3b6a51a90 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -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.1.10+27 +version: 1.1.10-1+28 environment: sdk: ">=2.16.1" diff --git a/icon.ico b/icon.ico new file mode 100644 index 000000000..41c02bb4e Binary files /dev/null and b/icon.ico differ diff --git a/libs/enigo/examples/key.rs b/libs/enigo/examples/key.rs index 5887634a0..9a4da34d5 100644 --- a/libs/enigo/examples/key.rs +++ b/libs/enigo/examples/key.rs @@ -10,4 +10,8 @@ fn main() { enigo.key_down(Key::Layout('.')).ok(); enigo.key_up(Key::Layout('.')); enigo.key_up(Key::Shift); + enigo.key_down(Key::Shift).ok(); + enigo.key_down(Key::Layout('-')).ok(); + enigo.key_up(Key::Layout('-')); + enigo.key_up(Key::Shift); } diff --git a/libs/enigo/src/lib.rs b/libs/enigo/src/lib.rs index 893f5918c..10cde9cbe 100644 --- a/libs/enigo/src/lib.rs +++ b/libs/enigo/src/lib.rs @@ -63,6 +63,8 @@ extern crate objc; mod win; #[cfg(target_os = "windows")] pub use win::Enigo; +#[cfg(target_os = "windows")] +pub use win::ENIGO_INPUT_EXTRA_VALUE; #[cfg(target_os = "macos")] mod macos; diff --git a/libs/enigo/src/linux.rs b/libs/enigo/src/linux.rs index 8881d26d5..adfe9507c 100644 --- a/libs/enigo/src/linux.rs +++ b/libs/enigo/src/linux.rs @@ -491,7 +491,11 @@ fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) { if !std::path::Path::new(&py).exists() { py = "/usr/lib/rustdesk/pynput_service.py".to_owned(); if !std::path::Path::new(&py).exists() { - log::error!("{} not exits", py); + // enigo libs, not rustdesk root project, so skip using appimage features + py = std::env::var("APPDIR").unwrap_or("".to_string()) + "/usr/lib/rustdesk/pynput_service.py"; + if !std::path::Path::new(&py).exists() { + log::error!("{} not exists", py); + } } } } diff --git a/libs/enigo/src/macos/macos_impl.rs b/libs/enigo/src/macos/macos_impl.rs index 53ffa1803..28c9362ed 100644 --- a/libs/enigo/src/macos/macos_impl.rs +++ b/libs/enigo/src/macos/macos_impl.rs @@ -338,7 +338,7 @@ impl KeyboardControllable for Enigo { fn key_click(&mut self, key: Key) { let keycode = self.key_to_keycode(key); - if keycode == 0 { + if keycode == u16::MAX { return; } @@ -355,7 +355,7 @@ impl KeyboardControllable for Enigo { fn key_down(&mut self, key: Key) -> crate::ResultType { let code = self.key_to_keycode(key); - if code == 0 { + if code == u16::MAX { return Err("".into()); } if let Some(src) = self.event_source.as_ref() { @@ -489,13 +489,18 @@ impl Enigo { Key::Layout(c) => self.map_key_board(c), Key::Super | Key::Command | Key::Windows | Key::Meta => kVK_Command, - _ => 0, + _ => u16::MAX, } } #[inline] fn map_key_board(&mut self, ch: char) -> CGKeyCode { - let mut code = 0; + // no idea why below char not working with shift, https://github.com/rustdesk/rustdesk/issues/406#issuecomment-1145157327 + // seems related to numpad char + if ch == '-' || ch == '=' || ch == '.' || ch == '/' || (ch >= '0' && ch <= '9') { + return self.map_key_board_en(ch); + } + let mut code = u16::MAX; unsafe { let (keyboard, layout) = get_layout(); if !keyboard.is_null() && !layout.is_null() { @@ -504,10 +509,10 @@ impl Enigo { let name = get_string(name_ref as _); if let Some(name) = name { if let Some(m) = self.char_to_vkey_map.get(&name) { - code = *m.get(&ch).unwrap_or(&0); + code = *m.get(&ch).unwrap_or(&u16::MAX); } else { let m = get_map(&name, layout); - code = *m.get(&ch).unwrap_or(&0); + code = *m.get(&ch).unwrap_or(&u16::MAX); self.char_to_vkey_map.insert(name.clone(), m); } } @@ -517,9 +522,14 @@ impl Enigo { CFRelease(keyboard); } } - if code > 0 { + if code != u16::MAX { return code; } + self.map_key_board_en(ch) + } + + #[inline] + fn map_key_board_en(&mut self, ch: char) -> CGKeyCode { match ch { 'a' => kVK_ANSI_A, 'b' => kVK_ANSI_B, @@ -568,7 +578,7 @@ impl Enigo { '.' => kVK_ANSI_Period, '/' => kVK_ANSI_Slash, '`' => kVK_ANSI_Grave, - _ => 0, + _ => u16::MAX, } } } diff --git a/libs/enigo/src/win/keycodes.rs b/libs/enigo/src/win/keycodes.rs index 351e82a87..ea35685c5 100644 --- a/libs/enigo/src/win/keycodes.rs +++ b/libs/enigo/src/win/keycodes.rs @@ -79,3 +79,4 @@ pub const EVK_MULTIPLY: u16 = 0x6A; pub const EVK_SUBTRACT: u16 = 0x6D; pub const EVK_DECIMAL: u16 = 0x6E; pub const EVK_DIVIDE: u16 = 0x6F; +pub const EVK_PERIOD: u16 = 0xBE; diff --git a/libs/enigo/src/win/mod.rs b/libs/enigo/src/win/mod.rs index 024d7a3fd..62cdbd6e6 100644 --- a/libs/enigo/src/win/mod.rs +++ b/libs/enigo/src/win/mod.rs @@ -2,3 +2,4 @@ mod win_impl; pub mod keycodes; pub use self::win_impl::Enigo; +pub use self::win_impl::ENIGO_INPUT_EXTRA_VALUE; diff --git a/libs/enigo/src/win/win_impl.rs b/libs/enigo/src/win/win_impl.rs index c1fc32131..ea1543faa 100644 --- a/libs/enigo/src/win/win_impl.rs +++ b/libs/enigo/src/win/win_impl.rs @@ -1,7 +1,7 @@ use winapi; use self::winapi::ctypes::c_int; -use self::winapi::shared::{minwindef::*, windef::*}; +use self::winapi::shared::{basetsd::ULONG_PTR, minwindef::*, windef::*}; use self::winapi::um::winbase::*; use self::winapi::um::winuser::*; @@ -18,6 +18,9 @@ extern "system" { pub struct Enigo; static mut LAYOUT: HKL = std::ptr::null_mut(); +/// The dwExtraInfo value in keyboard and mouse structure that used in SendInput() +pub const ENIGO_INPUT_EXTRA_VALUE: ULONG_PTR = 100; + fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD { let mut input = INPUT { type_: INPUT_MOUSE, @@ -28,7 +31,7 @@ fn mouse_event(flags: u32, data: u32, dx: i32, dy: i32) -> DWORD { mouseData: data, dwFlags: flags, time: 0, - dwExtraInfo: 0, + dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE, }) }, }; @@ -56,7 +59,7 @@ fn keybd_event(flags: u32, vk: u16, scan: u16) -> DWORD { wScan: scan, dwFlags: flags, time: 0, - dwExtraInfo: 0, + dwExtraInfo: ENIGO_INPUT_EXTRA_VALUE, }) }, }; @@ -376,7 +379,7 @@ impl Enigo { let keycode_and_shiftstate = unsafe { VkKeyScanExW(chr as _, LAYOUT) }; if keycode_and_shiftstate == (EVK_DECIMAL as i16) && chr == '.' { // a workaround of italian keyboard shift + '.' issue - unsafe { VkKeyScanW(chr as _) as _ } + EVK_PERIOD as _ } else { keycode_and_shiftstate as _ } diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index c296e6a7f..048301d7e 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -65,6 +65,10 @@ message LoginRequest { message ChatMessage { string text = 1; } +message Features { + bool privacy_mode = 1; +} + message PeerInfo { string username = 1; string hostname = 2; @@ -74,6 +78,7 @@ message PeerInfo { bool sas_enabled = 6; string version = 7; int32 conn_id = 8; + Features features = 9; } message LoginResponse { @@ -442,11 +447,6 @@ message OptionMessage { BoolOption enable_file_transfer = 9; } -message OptionResponse { - OptionMessage opt = 1; - string error = 2; -} - message TestDelay { int64 time = 1; bool from_client = 2; @@ -469,6 +469,44 @@ message AudioFrame { int64 timestamp = 2; } +message BackNotification { + // no need to consider block input by someone else + enum BlockInputState { + StateUnknown = 1; + OnSucceeded = 2; + OnFailed = 3; + OffSucceeded = 4; + OffFailed = 5; + } + enum PrivacyModeState { + StateUnknown = 1; + // Privacy mode on by someone else + OnByOther = 2; + // Privacy mode is not supported on the remote side + NotSupported = 3; + // Privacy mode on by self + OnSucceeded = 4; + // Privacy mode on by self, but denied + OnFailedDenied = 5; + // Some plugins are not found + OnFailedPlugin = 6; + // Privacy mode on by self, but failed + OnFailed = 7; + // Privacy mode off by self + OffSucceeded = 8; + // Ctrl + P + OffByPeer = 9; + // Privacy mode off by self, but failed + OffFailed = 10; + OffUnknown = 11; + } + + oneof union { + PrivacyModeState privacy_mode_state = 1; + BlockInputState block_input_state = 2; + } +} + message Misc { oneof union { ChatMessage chat_message = 4; @@ -478,8 +516,8 @@ message Misc { AudioFormat audio_format = 8; string close_reason = 9; bool refresh_video = 10; - OptionResponse option_response = 11; bool video_received = 12; + BackNotification back_notification = 13; } } diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index e6ca46ee3..91faf833b 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -16,7 +16,7 @@ pub const RENDEZVOUS_TIMEOUT: u64 = 12_000; pub const CONNECT_TIMEOUT: u64 = 18_000; pub const REG_INTERVAL: i64 = 12_000; pub const COMPRESS_LEVEL: i32 = 3; -const SERIAL: i32 = 1; +const SERIAL: i32 = 3; // 128x128 #[cfg(target_os = "macos")] // 128x128 on 160x160 canvas, then shrink to 128, mac looks better with padding pub const ICON: &str = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAyVBMVEUAAAAAcf8Acf8Acf8Acv8Acf8Acf8Acf8Acf8AcP8Acf8Ab/8AcP8Acf////8AaP/z+f/o8v/k7v/5/v/T5f8AYP/u9v/X6f+hx/+Kuv95pP8Aef/B1/+TwP9xoP8BdP/g6P+Irv9ZmP8Bgf/E3f98q/9sn/+01f+Es/9nm/9Jif8hhv8off/M4P+syP+avP86iP/c7f+xy/9yqf9Om/9hk/9Rjv+60P99tv9fpf88lv8yjf8Tgf8deP+kvP8BiP8NeP8hkP80gP8oj2VLAAAADXRSTlMA7o7qLvnaxZ1FOxYPjH9HWgAABHJJREFUeNrtm+tW4jAQgBfwuu7MtIUWsOUiCCioIIgLiqvr+z/UHq/LJKVkmwTcc/r9E2nzlU4mSTP9lpGRkZGR8VX5cZjfL+yCEXYL+/nDH//U/Pd8DgyTy39Xbv7oIAcWyB0cqbW/sweW2NtRaj8H1sgpGOwUIAH7Bkd7YJW9dXFwAJY5WNP/cmCZQnJvzIN18on5LwfWySXlxEPYAIcad8D6PdiHDbCfIFCADVBIENiFDbCbIACKPPXrZ+cP8E6/0znvP4EymgIEravIRcTxu8HxNSJ60a8W0AYECKrlAN+YwAthCd9wm1Ug6wKzIn5SgRduXfwkqDasCjx0XFzi9PV6zwNcIuhcWBOg+ikySq8C9UD4dEKWBCoOcspvAuLHTo9sCDQiFPHotRM48j8G5gVur1FdAN2uaYEuiz7xFsgEJ2RUoMUakXuBTHHoGxQYOBhHjeUBAefEnMAowFhaLBOKuOemBBbxLRQrH2PBCgMvNCPQGMeevTb9zLrPxz2Mo+QbEaijzPUcOOHMQZkKGRAIPem39+bypREMPTkQW/oCfk866zAkiIFG4yIKRE/aAnfiSd0WrORY6pFdXQEqi9mvAQm0RIOSnoCcZ8vJoz3diCnjRk+g8VP4/fuQDJ2Lxr6WwG0gXs9aTpDzW0vgDBlVUpixR8gYk44AD8FrUKHr8JQJGgIDnoDqoALxmWPQSi9AVVzm8gKUuEPGr/QCvptwJkbSYT/TC4S8C96DGjTj86aHtAI0x2WaBIq0eSYYpRa4EsdWVVwWu9O0Aj6f6dyBMnwEraeOgSYu0wZlauzA47QCbT7DgAQSE+hZWoEBF/BBmWOewNMK3BsSqKUW4MGcWqCSVmDkbvkXGKQOwg6PAUO9oL3xXhA20yaiCjuwYygRVQlUOTWTCf2SuNJTxeFjgaHByGuAIvd8ItdPLTDhS7IuqEE1YSKVOgbayLhSFQhMzYh8hwfBs1r7c505YVIQYEdNoKwxK06MJiyrpUFHiF0NAfCQUVHoiRclIXJIR6C2fqG37pBHvcWpgwzvAtYwkR5UGV2e42UISdBJETl3mg8ouo54Rcnti1/vaT+iuUQBt500Cgo4U10BeHSkk57FB0JjWkKRMWgLUA0lLodtImAQdaMiiri3+gIAPZQoutHNsgKF1aaDMhMyIdBf8Th+Bh8MTjGWCpl5Wv43tDmnF+IUVMrcZgRoiAxhtrloYizNkZaAnF5leglbNhj0wYCAbCDvGb0mP4nib7O7ZlcYQ2m1gPtIZgVgGNNMeaVAaWR+57TrqgtUnm3sHQ+kYeE6fufUubG1ez50FXbPnWgBlgSABmN3TTcsRl2yWkHRrwbiunvk/W2+Mg1hPZplPDeXRbZzStFH15s1QIVd3UImP5z/bHpeeQLvRJ7XLFUffQIlCvqlXETQbgN9/rlYABGosv+Vi9m2Xs639YLGrZd0br+odetlvdsvbN56abfd4vbCzv9Q3v/ygoOV21A4OPpfXvH4Ai+5ZGRkZGRkbJA/t/I0QMzoMiEAAAAASUVORK5CYII= @@ -271,7 +271,7 @@ impl Config { fn file_(suffix: &str) -> PathBuf { let name = format!("{}{}", *APP_NAME.read().unwrap(), suffix); - Self::path(name).with_extension("toml") + Config::with_extension(Self::path(name)) } pub fn get_home() -> PathBuf { @@ -687,6 +687,16 @@ impl Config { lock.store(); true } + + fn with_extension(path: PathBuf) -> PathBuf { + let ext = path.extension(); + if let Some(ext) = ext { + let ext = format!("{}.toml", ext.to_string_lossy()); + path.with_extension(&ext) + } else { + path.with_extension("toml") + } + } } const PEERS: &str = "peers"; @@ -716,7 +726,7 @@ impl PeerConfig { fn path(id: &str) -> PathBuf { let path: PathBuf = [PEERS, id].iter().collect(); - Config::path(path).with_extension("toml") + Config::with_extension(Config::path(path)) } pub fn peers() -> Vec<(String, SystemTime, PeerConfig)> { diff --git a/libs/hbb_common/src/fs.rs b/libs/hbb_common/src/fs.rs index da56cf6b6..4512ce940 100644 --- a/libs/hbb_common/src/fs.rs +++ b/libs/hbb_common/src/fs.rs @@ -195,7 +195,7 @@ pub fn is_file_exists(file_path: &str) -> bool { #[inline] pub fn can_enable_overwrite_detection(version: i64) -> bool { - version >= get_version_number("1.2.0") + version >= get_version_number("1.1.10") } #[derive(Default)] diff --git a/libs/hbb_common/src/socket_client.rs b/libs/hbb_common/src/socket_client.rs index 0375b713e..72ab73f16 100644 --- a/libs/hbb_common/src/socket_client.rs +++ b/libs/hbb_common/src/socket_client.rs @@ -11,7 +11,10 @@ use tokio_socks::{IntoTargetAddr, TargetAddr}; fn to_socket_addr(host: &str) -> ResultType { use std::net::ToSocketAddrs; - host.to_socket_addrs()?.next().context("Failed to solve") + host.to_socket_addrs()? + .filter(|x| x.is_ipv4()) + .next() + .context("Failed to solve") } pub fn get_target_addr(host: &str) -> ResultType> { @@ -60,8 +63,9 @@ pub async fn connect_tcp<'t, T: IntoTargetAddr<'t>>( .await } else { let addr = std::net::ToSocketAddrs::to_socket_addrs(&target_addr)? + .filter(|x| x.is_ipv4()) .next() - .context("Invalid target addr")?; + .context("Invalid target addr, no valid ipv4 address can be resolved.")?; Ok(FramedStream::new(addr, local, ms_timeout).await?) } } diff --git a/libs/hbb_common/src/udp.rs b/libs/hbb_common/src/udp.rs index 033861800..4213392a5 100644 --- a/libs/hbb_common/src/udp.rs +++ b/libs/hbb_common/src/udp.rs @@ -47,7 +47,7 @@ impl FramedSocket { #[allow(clippy::never_loop)] pub async fn new_reuse(addr: T) -> ResultType { - for addr in addr.to_socket_addrs()? { + for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) { let socket = new_socket(addr, true, 0)?.into_udp_socket(); return Ok(Self::Direct(UdpFramed::new( UdpSocket::from_std(socket)?, @@ -61,7 +61,7 @@ impl FramedSocket { addr: T, buf_size: usize, ) -> ResultType { - for addr in addr.to_socket_addrs()? { + for addr in addr.to_socket_addrs()?.filter(|x| x.is_ipv4()) { return Ok(Self::Direct(UdpFramed::new( UdpSocket::from_std(new_socket(addr, false, buf_size)?.into_udp_socket())?, BytesCodec::new(), diff --git a/libs/scrap/Cargo.toml b/libs/scrap/Cargo.toml index 00c4509ab..c99ec45da 100644 --- a/libs/scrap/Cargo.toml +++ b/libs/scrap/Cargo.toml @@ -17,17 +17,19 @@ block = "0.1" cfg-if = "1.0" libc = "0.2" num_cpus = "1.13" +lazy_static = "1.4" [dependencies.winapi] version = "0.3" default-features = true -features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser"] +features = ["dxgi", "dxgi1_2", "dxgi1_5", "d3d11", "winuser", "winerror", "errhandlingapi", "libloaderapi"] [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.10" jni = "0.19" lazy_static = "1.4" log = "0.4" +serde_json = "1.0" [target.'cfg(not(target_os = "android"))'.dev-dependencies] repng = "0.2" diff --git a/libs/scrap/examples/capture_mag.rs b/libs/scrap/examples/capture_mag.rs new file mode 100644 index 000000000..3e15b4e69 --- /dev/null +++ b/libs/scrap/examples/capture_mag.rs @@ -0,0 +1,105 @@ +extern crate repng; +extern crate scrap; + +use std::fs::File; + +#[cfg(windows)] +use scrap::CapturerMag; +use scrap::{i420_to_rgb, Display}; + +fn main() { + let n = Display::all().unwrap().len(); + for i in 0..n { + #[cfg(windows)] + record(i); + } +} + +fn get_display(i: usize) -> Display { + Display::all().unwrap().remove(i) +} + +#[cfg(windows)] +fn record(i: usize) { + for d in Display::all().unwrap() { + println!("{:?} {} {}", d.origin(), d.width(), d.height()); + } + + let display = get_display(i); + let (w, h) = (display.width(), display.height()); + + { + let mut capture_mag = + CapturerMag::new(display.origin(), display.width(), display.height(), false) + .expect("Couldn't begin capture."); + let wnd_cls = ""; + let wnd_name = "RustDeskPrivacyWindow"; + if false == capture_mag.exclude(wnd_cls, wnd_name).unwrap() { + println!("No window found for cls {} name {}", wnd_cls, wnd_name); + } else { + println!("Filter window for cls {} name {}", wnd_cls, wnd_name); + } + + let frame = capture_mag.frame(0).unwrap(); + println!("Capture data len: {}, Saving...", frame.len()); + + let mut bitflipped = Vec::with_capacity(w * h * 4); + let stride = frame.len() / h; + + for y in 0..h { + for x in 0..w { + let i = stride * y + 4 * x; + bitflipped.extend_from_slice(&[frame[i + 2], frame[i + 1], frame[i], 255]); + } + } + // Save the image. + let name = format!("capture_mag_{}_1.png", i); + repng::encode( + File::create(name.clone()).unwrap(), + w as u32, + h as u32, + &bitflipped, + ) + .unwrap(); + println!("Image saved to `{}`.", name); + } + + { + let mut capture_mag = + CapturerMag::new(display.origin(), display.width(), display.height(), true) + .expect("Couldn't begin capture."); + let wnd_cls = ""; + let wnd_title = "RustDeskPrivacyWindow"; + if false == capture_mag.exclude(wnd_cls, wnd_title).unwrap() { + println!("No window found for cls {} title {}", wnd_cls, wnd_title); + } else { + println!("Filter window for cls {} title {}", wnd_cls, wnd_title); + } + + let buffer = capture_mag.frame(0).unwrap(); + println!("Capture data len: {}, Saving...", buffer.len()); + + let mut frame = Default::default(); + i420_to_rgb(w, h, &buffer, &mut frame); + + let mut bitflipped = Vec::with_capacity(w * h * 4); + let stride = frame.len() / h; + + for y in 0..h { + for x in 0..w { + let i = stride * y + 3 * x; + bitflipped.extend_from_slice(&[frame[i], frame[i + 1], frame[i + 2], 255]); + } + } + let name = format!("capture_mag_{}_2.png", i); + repng::encode( + File::create(name.clone()).unwrap(), + w as u32, + h as u32, + &bitflipped, + ) + .unwrap(); + + println!("Image saved to `{}`.", name); + } +} diff --git a/libs/scrap/examples/screenshot.rs b/libs/scrap/examples/screenshot.rs index e2da3b3d8..b52ea11f7 100644 --- a/libs/scrap/examples/screenshot.rs +++ b/libs/scrap/examples/screenshot.rs @@ -46,8 +46,7 @@ fn record(i: usize) { } } }; - - println!("Captured! Saving..."); + println!("Captured data len: {}, Saving...", buffer.len()); // Flip the BGRA image into a RGBA image. @@ -96,8 +95,7 @@ fn record(i: usize) { } } }; - - println!("Captured! Saving..."); + println!("Captured data len: {}, Saving...", buffer.len()); let mut frame = Default::default(); i420_to_rgb(w, h, &buffer, &mut frame); diff --git a/libs/scrap/src/android/ffi.rs b/libs/scrap/src/android/ffi.rs index 7723d3ace..3515bab9f 100644 --- a/libs/scrap/src/android/ffi.rs +++ b/libs/scrap/src/android/ffi.rs @@ -17,7 +17,6 @@ use std::time::{Duration, Instant}; lazy_static! { static ref JVM: RwLock> = RwLock::new(None); static ref MAIN_SERVICE_CTX: RwLock> = RwLock::new(None); // MainService -> video service / audio service / info - static ref INPUT_CTX: RwLock> = RwLock::new(None); static ref VIDEO_RAW: Mutex = Mutex::new(FrameRaw::new("video", MAX_VIDEO_FRAME_TIMEOUT)); static ref AUDIO_RAW: Mutex = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT)); } @@ -148,25 +147,10 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init( *MAIN_SERVICE_CTX.write().unwrap() = Some(context); } -#[no_mangle] -pub extern "system" fn Java_com_carriez_flutter_1hbb_InputService_init( - env: JNIEnv, - _class: JClass, - ctx: JObject, -) { - log::debug!("InputService init from java"); - let jvm = env.get_java_vm().unwrap(); - - *JVM.write().unwrap() = Some(jvm); - - let context = env.new_global_ref(ctx).unwrap(); - *INPUT_CTX.write().unwrap() = Some(context); -} - -pub fn call_input_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> { +pub fn call_main_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> { if let (Some(jvm), Some(ctx)) = ( JVM.read().unwrap().as_ref(), - INPUT_CTX.read().unwrap().as_ref(), + MAIN_SERVICE_CTX.read().unwrap().as_ref(), ) { let env = jvm.attach_current_thread_as_daemon()?; env.call_method( diff --git a/libs/scrap/src/common/android.rs b/libs/scrap/src/common/android.rs index 555e6cd7b..1975a6505 100644 --- a/libs/scrap/src/common/android.rs +++ b/libs/scrap/src/common/android.rs @@ -1,11 +1,13 @@ use crate::android::ffi::*; use crate::rgba_to_i420; use lazy_static::lazy_static; +use serde_json::Value; +use std::collections::HashMap; use std::io; use std::sync::Mutex; lazy_static! { - static ref SCREEN_SIZE: Mutex<(u16, u16)> = Mutex::new((0, 0)); + static ref SCREEN_SIZE: Mutex<(u16, u16, u16)> = Mutex::new((0, 0, 0)); // (width, height, scale) } pub struct Capturer { @@ -65,9 +67,7 @@ impl Display { pub fn primary() -> io::Result { let mut size = SCREEN_SIZE.lock().unwrap(); if size.0 == 0 || size.1 == 0 { - let (w, h) = get_size().unwrap_or((0, 0)); - size.0 = w; - size.1 = h; + *size = get_size().unwrap_or_default(); } Ok(Display { default: true, @@ -111,19 +111,33 @@ impl Display { pub fn refresh_size() { let mut size = SCREEN_SIZE.lock().unwrap(); - let (w, h) = get_size().unwrap_or((0, 0)); - size.0 = w; - size.1 = h; + *size = get_size().unwrap_or_default(); + } + + // Big android screen size will be shrinked, to improve performance when screen-capturing and encoding + // e.g 2280x1080 size will be set to 1140x540, and `scale` is 2 + // need to multiply by `4` (2*2) when compute the bitrate + pub fn fix_quality() -> u16 { + let scale = SCREEN_SIZE.lock().unwrap().2; + if scale <= 0 { + 1 + } else { + scale * scale + } } } -fn get_size() -> Option<(u16, u16)> { +fn get_size() -> Option<(u16, u16, u16)> { let res = call_main_service_get_by_name("screen_size").ok()?; - if res.len() > 0 { - let mut sp = res.split(":"); - let w = sp.next()?.parse::().ok()?; - let h = sp.next()?.parse::().ok()?; - return Some((w, h)); + if let Ok(json) = serde_json::from_str::>(&res) { + if let (Some(Value::Number(w)), Some(Value::Number(h)), Some(Value::Number(scale))) = + (json.get("width"), json.get("height"), json.get("scale")) + { + let w = w.as_i64()? as _; + let h = h.as_i64()? as _; + let scale = scale.as_i64()? as _; + return Some((w, h, scale)); + } } None } diff --git a/libs/scrap/src/common/dxgi.rs b/libs/scrap/src/common/dxgi.rs index 4683ad5f2..c0b4130bb 100644 --- a/libs/scrap/src/common/dxgi.rs +++ b/libs/scrap/src/common/dxgi.rs @@ -111,3 +111,32 @@ impl Display { self.origin() == (0, 0) } } + +pub struct CapturerMag { + inner: dxgi::mag::CapturerMag, + data: Vec, +} + +impl CapturerMag { + pub fn is_supported() -> bool { + dxgi::mag::CapturerMag::is_supported() + } + + pub fn new(origin: (i32, i32), width: usize, height: usize, use_yuv: bool) -> io::Result { + Ok(CapturerMag { + inner: dxgi::mag::CapturerMag::new(origin, width, height, use_yuv)?, + data: Vec::new(), + }) + } + pub fn exclude(&mut self, cls: &str, name: &str) -> io::Result { + self.inner.exclude(cls, name) + } + // ((x, y), w, h) + pub fn get_rect(&self) -> ((i32, i32), usize, usize) { + self.inner.get_rect() + } + pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result> { + self.inner.frame(&mut self.data)?; + Ok(Frame(&self.data)) + } +} diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index dd2b4295a..108a4ae31 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -19,7 +19,7 @@ cfg_if! { } else if #[cfg(dxgi)] { mod dxgi; pub use self::dxgi::*; - } else if #[cfg(android)] { + } else if #[cfg(target_os = "android")] { mod android; pub use self::android::*; }else { @@ -36,13 +36,11 @@ mod vpx; #[inline] pub fn would_block_if_equal(old: &mut Vec, b: &[u8]) -> std::io::Result<()> { - let b = unsafe { - std::slice::from_raw_parts::(b.as_ptr() as _, b.len() / 16) - }; + let b = unsafe { std::slice::from_raw_parts::(b.as_ptr() as _, b.len() / 16) }; if b == &old[..] { return Err(std::io::ErrorKind::WouldBlock.into()); } old.resize(b.len(), 0); old.copy_from_slice(b); Ok(()) -} \ No newline at end of file +} diff --git a/libs/scrap/src/dxgi/mag.rs b/libs/scrap/src/dxgi/mag.rs new file mode 100644 index 000000000..0d63088b7 --- /dev/null +++ b/libs/scrap/src/dxgi/mag.rs @@ -0,0 +1,662 @@ +// logic from webrtc -- https://github.com/shiguredo/libwebrtc/blob/main/modules/desktop_capture/win/screen_capturer_win_magnifier.cc +use lazy_static; +use std::{ + ffi::CString, + io::{Error, ErrorKind, Result}, + mem::size_of, + sync::Mutex, +}; +use winapi::{ + shared::{ + basetsd::SIZE_T, + guiddef::{IsEqualGUID, GUID}, + minwindef::{BOOL, DWORD, FALSE, FARPROC, HINSTANCE, HMODULE, HRGN, TRUE, UINT}, + ntdef::{LONG, NULL}, + windef::{HWND, RECT}, + winerror::ERROR_CLASS_ALREADY_EXISTS, + }, + um::{ + errhandlingapi::GetLastError, + libloaderapi::{FreeLibrary, GetModuleHandleExA, GetProcAddress, LoadLibraryExA}, + winuser::*, + }, +}; + +pub const MW_FILTERMODE_EXCLUDE: u32 = 0; +pub const MW_FILTERMODE_INCLUDE: u32 = 1; +pub const GET_MODULE_HANDLE_EX_FLAG_PIN: u32 = 1; +pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2; +pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4; +pub const LOAD_LIBRARY_AS_DATAFILE: u32 = 2; +pub const LOAD_WITH_ALTERED_SEARCH_PATH: u32 = 8; +pub const LOAD_IGNORE_CODE_AUTHZ_LEVEL: u32 = 16; +pub const LOAD_LIBRARY_AS_IMAGE_RESOURCE: u32 = 32; +pub const LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE: u32 = 64; +pub const LOAD_LIBRARY_REQUIRE_SIGNED_TARGET: u32 = 128; +pub const LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR: u32 = 256; +pub const LOAD_LIBRARY_SEARCH_APPLICATION_DIR: u32 = 512; +pub const LOAD_LIBRARY_SEARCH_USER_DIRS: u32 = 1024; +pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 2048; +pub const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS: u32 = 4096; +pub const LOAD_LIBRARY_SAFE_CURRENT_DIRS: u32 = 8192; +pub const LOAD_LIBRARY_SEARCH_SYSTEM32_NO_FORWARDER: u32 = 16384; +pub const LOAD_LIBRARY_OS_INTEGRITY_CONTINUITY: u32 = 32768; + +extern "C" { + pub static GUID_WICPixelFormat32bppRGBA: GUID; +} + +lazy_static::lazy_static! { + static ref MAG_BUFFER: Mutex<(bool, Vec)> = Default::default(); +} + +pub type REFWICPixelFormatGUID = *const GUID; +pub type WICPixelFormatGUID = GUID; + +#[allow(non_snake_case)] +#[repr(C)] +#[derive(Copy, Clone)] +pub struct tagMAGIMAGEHEADER { + pub width: UINT, + pub height: UINT, + pub format: WICPixelFormatGUID, + pub stride: UINT, + pub offset: UINT, + pub cbSize: SIZE_T, +} +pub type MAGIMAGEHEADER = tagMAGIMAGEHEADER; +pub type PMAGIMAGEHEADER = *mut tagMAGIMAGEHEADER; + +// Function types +pub type MagImageScalingCallback = ::std::option::Option< + unsafe extern "C" fn( + hwnd: HWND, + srcdata: *mut ::std::os::raw::c_void, + srcheader: MAGIMAGEHEADER, + destdata: *mut ::std::os::raw::c_void, + destheader: MAGIMAGEHEADER, + unclipped: RECT, + clipped: RECT, + dirty: HRGN, + ) -> BOOL, +>; + +extern "C" { + pub fn MagShowSystemCursor(fShowCursor: BOOL) -> BOOL; +} +pub type MagInitializeFunc = ::std::option::Option BOOL>; +pub type MagUninitializeFunc = ::std::option::Option BOOL>; +pub type MagSetWindowSourceFunc = + ::std::option::Option BOOL>; +pub type MagSetWindowFilterListFunc = ::std::option::Option< + unsafe extern "C" fn( + hwnd: HWND, + dwFilterMode: DWORD, + count: ::std::os::raw::c_int, + pHWND: *mut HWND, + ) -> BOOL, +>; +pub type MagSetImageScalingCallbackFunc = ::std::option::Option< + unsafe extern "C" fn(hwnd: HWND, callback: MagImageScalingCallback) -> BOOL, +>; + +#[repr(C)] +#[derive(Debug, Clone)] +struct MagInterface { + init_succeeded: bool, + lib_handle: HINSTANCE, + pub mag_initialize_func: MagInitializeFunc, + pub mag_uninitialize_func: MagUninitializeFunc, + pub set_window_source_func: MagSetWindowSourceFunc, + pub set_window_filter_list_func: MagSetWindowFilterListFunc, + pub set_image_scaling_callback_func: MagSetImageScalingCallbackFunc, +} + +// NOTE: MagInitialize and MagUninitialize should not be called in global init and uninit. +// If so, strange errors occur. +impl MagInterface { + fn new() -> Result { + let mut s = MagInterface { + init_succeeded: false, + lib_handle: NULL as _, + mag_initialize_func: None, + mag_uninitialize_func: None, + set_window_source_func: None, + set_window_filter_list_func: None, + set_image_scaling_callback_func: None, + }; + s.init_succeeded = false; + unsafe { + if GetSystemMetrics(SM_CMONITORS) != 1 { + // Do not try to use the magnifier in multi-screen setup (where the API + // crashes sometimes). + return Err(Error::new( + ErrorKind::Other, + "Magnifier capturer cannot work on multi-screen system.", + )); + } + + // load lib + let lib_file_name = "Magnification.dll"; + let lib_file_name_c = CString::new(lib_file_name).unwrap(); + s.lib_handle = LoadLibraryExA( + lib_file_name_c.as_ptr() as _, + NULL, + LOAD_WITH_ALTERED_SEARCH_PATH, + ); + if s.lib_handle.is_null() { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed to LoadLibraryExA {}, error: {}", + lib_file_name, + GetLastError() + ), + )); + }; + + // load functions + s.mag_initialize_func = Some(std::mem::transmute(Self::load_func( + s.lib_handle, + "MagInitialize", + )?)); + s.mag_uninitialize_func = Some(std::mem::transmute(Self::load_func( + s.lib_handle, + "MagUninitialize", + )?)); + s.set_window_source_func = Some(std::mem::transmute(Self::load_func( + s.lib_handle, + "MagSetWindowSource", + )?)); + s.set_window_filter_list_func = Some(std::mem::transmute(Self::load_func( + s.lib_handle, + "MagSetWindowFilterList", + )?)); + s.set_image_scaling_callback_func = Some(std::mem::transmute(Self::load_func( + s.lib_handle, + "MagSetImageScalingCallback", + )?)); + + // MagInitialize + if let Some(init_func) = s.mag_initialize_func { + if FALSE == init_func() { + return Err(Error::new( + ErrorKind::Other, + format!("Failed to MagInitialize, error: {}", GetLastError()), + )); + } else { + s.init_succeeded = true; + } + } else { + return Err(Error::new( + ErrorKind::Other, + "Unreachable, mag_initialize_func should not be none", + )); + } + } + Ok(s) + } + + unsafe fn load_func(lib_module: HMODULE, func_name: &str) -> Result { + let func_name_c = CString::new(func_name).unwrap(); + let func = GetProcAddress(lib_module, func_name_c.as_ptr() as _); + if func.is_null() { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed to GetProcAddress {}, error: {}", + func_name, + GetLastError() + ), + )); + } + Ok(func) + } + + pub(super) fn uninit(&mut self) { + if self.init_succeeded { + if let Some(uninit_func) = self.mag_uninitialize_func { + unsafe { + if FALSE == uninit_func() { + println!("Failed MagUninitialize {}", GetLastError()) + } + } + } + if !self.lib_handle.is_null() { + unsafe { + if FALSE == FreeLibrary(self.lib_handle) { + println!("Failed FreeLibrary {}", GetLastError()) + } + } + self.lib_handle = NULL as _; + } + } + self.init_succeeded = false; + } +} + +impl Drop for MagInterface { + fn drop(&mut self) { + self.uninit(); + } +} + +pub struct CapturerMag { + mag_interface: MagInterface, + host_window: HWND, + magnifier_window: HWND, + + magnifier_host_class: CString, + host_window_name: CString, + magnifier_window_class: CString, + magnifier_window_name: CString, + + rect: RECT, + width: usize, + height: usize, + + use_yuv: bool, + data: Vec, +} + +impl Drop for CapturerMag { + fn drop(&mut self) { + self.destroy_windows(); + self.mag_interface.uninit(); + } +} + +impl CapturerMag { + pub(crate) fn is_supported() -> bool { + MagInterface::new().is_ok() + } + + pub(crate) fn new( + origin: (i32, i32), + width: usize, + height: usize, + use_yuv: bool, + ) -> Result { + unsafe { + let x = GetSystemMetrics(SM_XVIRTUALSCREEN); + let y = GetSystemMetrics(SM_YVIRTUALSCREEN); + let w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + let h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + if !(origin.0 == x as _ && origin.1 == y as _ && width == w as _ && height == h as _) { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed Check screen rect ({}, {}, {} , {}) to ({}, {}, {}, {})", + origin.0, + origin.1, + origin.0 + width as i32, + origin.1 + height as i32, + x, + y, + x + w, + y + h + ), + )); + } + } + + let mut s = Self { + mag_interface: MagInterface::new()?, + host_window: 0 as _, + magnifier_window: 0 as _, + magnifier_host_class: CString::new("ScreenCapturerWinMagnifierHost")?, + host_window_name: CString::new("MagnifierHost")?, + magnifier_window_class: CString::new("Magnifier")?, + magnifier_window_name: CString::new("MagnifierWindow")?, + rect: RECT { + left: origin.0 as _, + top: origin.1 as _, + right: origin.0 + width as LONG, + bottom: origin.1 + height as LONG, + }, + width, + height, + use_yuv, + data: Vec::new(), + }; + + unsafe { + let mut instance = 0 as HMODULE; + if 0 == GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + DefWindowProcA as _, + &mut instance as _, + ) { + return Err(Error::new( + ErrorKind::Other, + format!("Failed to GetModuleHandleExA, error: {}", GetLastError()), + )); + } + + // Register the host window class. See the MSDN documentation of the + // Magnification API for more infomation. + let wcex = WNDCLASSEXA { + cbSize: size_of::() as _, + style: 0, + lpfnWndProc: Some(DefWindowProcA), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: instance, + hIcon: 0 as _, + hCursor: LoadCursorA(NULL as _, IDC_ARROW as _), + hbrBackground: 0 as _, + lpszClassName: s.magnifier_host_class.as_ptr() as _, + lpszMenuName: 0 as _, + hIconSm: 0 as _, + }; + + // Ignore the error which may happen when the class is already registered. + if 0 == RegisterClassExA(&wcex) { + let code = GetLastError(); + if code != ERROR_CLASS_ALREADY_EXISTS { + return Err(Error::new( + ErrorKind::Other, + format!("Failed to RegisterClassExA, error: {}", code), + )); + } + } + + // Create the host window. + s.host_window = CreateWindowExA( + WS_EX_LAYERED, + s.magnifier_host_class.as_ptr(), + s.host_window_name.as_ptr(), + WS_POPUP, + 0, + 0, + 0, + 0, + NULL as _, + NULL as _, + instance, + NULL, + ); + if s.host_window.is_null() { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed to CreateWindowExA host_window, error: {}", + GetLastError() + ), + )); + } + + // Create the magnifier control. + s.magnifier_window = CreateWindowExA( + 0, + s.magnifier_window_class.as_ptr(), + s.magnifier_window_name.as_ptr(), + WS_CHILD | WS_VISIBLE, + 0, + 0, + 0, + 0, + s.host_window, + NULL as _, + instance, + NULL, + ); + if s.magnifier_window.is_null() { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed CreateWindowA magnifier_window, error: {}", + GetLastError() + ), + )); + } + + // Hide the host window. + let _ = ShowWindow(s.host_window, SW_HIDE); + + // Set the scaling callback to receive captured image. + if let Some(set_callback_func) = s.mag_interface.set_image_scaling_callback_func { + if FALSE + == set_callback_func( + s.magnifier_window, + Some(Self::on_gag_image_scaling_callback), + ) + { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed to MagSetImageScalingCallback, error: {}", + GetLastError() + ), + )); + } + } else { + return Err(Error::new( + ErrorKind::Other, + "Unreachable, set_image_scaling_callback_func should not be none", + )); + } + } + + Ok(s) + } + + pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result { + let name_c = CString::new(name).unwrap(); + unsafe { + let mut hwnd = if cls.len() == 0 { + FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr()) + } else { + let cls_c = CString::new(cls).unwrap(); + FindWindowExA(NULL as _, NULL as _, cls_c.as_ptr(), name_c.as_ptr()) + }; + + if hwnd.is_null() { + return Ok(false); + } + + if let Some(set_window_filter_list_func) = + self.mag_interface.set_window_filter_list_func + { + if FALSE + == set_window_filter_list_func( + self.magnifier_window, + MW_FILTERMODE_EXCLUDE, + 1, + &mut hwnd, + ) + { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed MagSetWindowFilterList for cls {} name {}, err: {}", + cls, + name, + GetLastError() + ), + )); + } + } else { + return Err(Error::new( + ErrorKind::Other, + "Unreachable, MagSetWindowFilterList should not be none", + )); + } + } + + Ok(true) + } + + pub(crate) fn get_rect(&self) -> ((i32, i32), usize, usize) { + ( + (self.rect.left as _, self.rect.top as _), + self.width as _, + self.height as _, + ) + } + + fn clear_data() { + let mut lock = MAG_BUFFER.lock().unwrap(); + lock.0 = false; + lock.1.clear(); + } + + pub(crate) fn frame(&mut self, data: &mut Vec) -> Result<()> { + Self::clear_data(); + + unsafe { + let x = GetSystemMetrics(SM_XVIRTUALSCREEN); + let y = GetSystemMetrics(SM_YVIRTUALSCREEN); + let w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + let h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + if !(self.rect.left == x as _ + && self.rect.top == y as _ + && self.rect.right == (x + w) as _ + && self.rect.bottom == (y + h) as _) + { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed Check screen rect ({}, {}, {} , {}) to ({}, {}, {}, {})", + self.rect.left, + self.rect.top, + self.rect.right, + self.rect.bottom, + x, + y, + x + w, + y + h + ), + )); + } + + if FALSE + == SetWindowPos( + self.magnifier_window, + HWND_TOP, + self.rect.left, + self.rect.top, + self.rect.right, + self.rect.bottom, + 0, + ) + { + return Err(Error::new( + ErrorKind::Other, + format!( + "Failed SetWindowPos (x, y, w , h) - ({}, {}, {}, {}), error {}", + self.rect.left, + self.rect.top, + self.rect.right, + self.rect.bottom, + GetLastError() + ), + )); + } + + // on_gag_image_scaling_callback will be called and fill in the + // frame before set_window_source_func_ returns. + if let Some(set_window_source_func) = self.mag_interface.set_window_source_func { + if FALSE == set_window_source_func(self.magnifier_window, self.rect) { + return Err(Error::new( + ErrorKind::Other, + format!("Failed to MagSetWindowSource, error: {}", GetLastError()), + )); + } + } else { + return Err(Error::new( + ErrorKind::Other, + "Unreachable, set_window_source_func should not be none", + )); + } + } + + let mut lock = MAG_BUFFER.lock().unwrap(); + if !lock.0 { + return Err(Error::new( + ErrorKind::Other, + "No data captured by magnifier", + )); + } + + if self.use_yuv { + self.data.resize(lock.1.len(), 0); + unsafe { + std::ptr::copy_nonoverlapping(&mut lock.1[0], &mut self.data[0], self.data.len()); + } + crate::common::bgra_to_i420( + self.width as usize, + self.height as usize, + &self.data, + data, + ); + } else { + data.resize(lock.1.len(), 0); + unsafe { + std::ptr::copy_nonoverlapping(&mut lock.1[0], &mut data[0], data.len()); + } + } + + Ok(()) + } + + fn destroy_windows(&mut self) { + if !self.magnifier_window.is_null() { + unsafe { + if FALSE == DestroyWindow(self.magnifier_window) { + // + println!("Failed DestroyWindow magnifier window {}", GetLastError()) + } + } + } + self.magnifier_window = NULL as _; + + if !self.host_window.is_null() { + unsafe { + if FALSE == DestroyWindow(self.host_window) { + // + println!("Failed DestroyWindow host window {}", GetLastError()) + } + } + } + self.host_window = NULL as _; + } + + unsafe extern "C" fn on_gag_image_scaling_callback( + _hwnd: HWND, + srcdata: *mut ::std::os::raw::c_void, + srcheader: MAGIMAGEHEADER, + _destdata: *mut ::std::os::raw::c_void, + _destheader: MAGIMAGEHEADER, + _unclipped: RECT, + _clipped: RECT, + _dirty: HRGN, + ) -> BOOL { + Self::clear_data(); + + if !IsEqualGUID(&srcheader.format, &GUID_WICPixelFormat32bppRGBA) { + // log warning? + return FALSE; + } + let mut lock = MAG_BUFFER.lock().unwrap(); + lock.1.resize(srcheader.cbSize, 0); + std::ptr::copy_nonoverlapping(srcdata as _, &mut lock.1[0], srcheader.cbSize); + lock.0 = true; + TRUE + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test() { + let mut capture_mag = CapturerMag::new((0, 0), 1920, 1080, false).unwrap(); + capture_mag.exclude("", "RustDeskPrivacyWindow").unwrap(); + std::thread::sleep(std::time::Duration::from_millis(1000 * 10)); + let mut data = Vec::new(); + capture_mag.frame(&mut data).unwrap(); + println!("capture data len: {}", data.len()); + } +} diff --git a/libs/scrap/src/dxgi/mod.rs b/libs/scrap/src/dxgi/mod.rs index 0d343962d..46692535d 100644 --- a/libs/scrap/src/dxgi/mod.rs +++ b/libs/scrap/src/dxgi/mod.rs @@ -1,6 +1,7 @@ use std::{io, mem, ptr, slice}; pub mod gdi; pub use gdi::CapturerGDI; +pub mod mag; use winapi::{ shared::{ diff --git a/libs/scrap/src/lib.rs b/libs/scrap/src/lib.rs index 4d481ed26..504f0a4b3 100644 --- a/libs/scrap/src/lib.rs +++ b/libs/scrap/src/lib.rs @@ -14,13 +14,13 @@ pub mod quartz; #[cfg(x11)] pub mod x11; -#[cfg(all(x11, feature="wayland"))] +#[cfg(all(x11, feature = "wayland"))] pub mod wayland; #[cfg(dxgi)] pub mod dxgi; -#[cfg(android)] +#[cfg(target_os = "android")] pub mod android; mod common; diff --git a/libs/simple_rc/Cargo.toml b/libs/simple_rc/Cargo.toml new file mode 100644 index 000000000..89304524d --- /dev/null +++ b/libs/simple_rc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "simple_rc" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde_derive = "1.0" +serde = "1.0" +walkdir = "2" +confy = { git = "https://github.com/open-trade/confy" } +hbb_common = { path = "../hbb_common" } diff --git a/libs/simple_rc/examples/generate.rs b/libs/simple_rc/examples/generate.rs new file mode 100644 index 000000000..2de39961a --- /dev/null +++ b/libs/simple_rc/examples/generate.rs @@ -0,0 +1,23 @@ +extern crate simple_rc; + +use simple_rc::*; + +fn main() { + { + const CONF_FILE: &str = "simple_rc.toml"; + generate(CONF_FILE).unwrap(); + } + + { + generate_with_conf(&Config { + outfile: "src/rc.rs".to_owned(), + confs: vec![ConfigItem { + inc: "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx".to_owned(), + // exc: vec!["*.dll".to_owned(), "*.exe".to_owned()], + exc: vec![], + suppressed_front: "D:/projects/windows".to_owned(), + }], + }) + .unwrap(); + } +} diff --git a/libs/simple_rc/simple_rc.toml b/libs/simple_rc/simple_rc.toml new file mode 100644 index 000000000..bef976967 --- /dev/null +++ b/libs/simple_rc/simple_rc.toml @@ -0,0 +1,12 @@ +# The output source file +outfile = "src/rc.rs" + +# The resource config list. +[[confs]] +# The file or director to integrate. +inc = "D:/projects/windows/RustDeskTempTopMostWindow/x64/Release/xxx" +# The exclusions. +exc = ["*.dll", "*.exe"] +# The front path that will ignore for extracting. +# The following config will make base output path to be "RustDeskTempTopMostWindow/x64/Release/xxx". +suppressed_front = "D:/projects/windows" diff --git a/libs/simple_rc/src/lib.rs b/libs/simple_rc/src/lib.rs new file mode 100644 index 000000000..e59e0493f --- /dev/null +++ b/libs/simple_rc/src/lib.rs @@ -0,0 +1,208 @@ +use hbb_common::{bail, ResultType}; +use serde_derive::{Deserialize, Serialize}; +use std::{collections::HashMap, fs::File, io::prelude::*, path::Path}; +use walkdir::WalkDir; + +//mod rc; + +#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)] +pub struct ConfigItem { + // include directory or file + pub inc: String, + // exclude files + pub exc: Vec, + // out_path = origin_path - suppressed_front + pub suppressed_front: String, +} + +#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)] +pub struct Config { + // output source file + pub outfile: String, + // config items + pub confs: Vec, +} + +pub fn get_outin_files<'a>(item: &'a ConfigItem) -> ResultType> { + let mut outin_filemap = HashMap::new(); + + for entry in WalkDir::new(&item.inc).follow_links(true) { + let path = entry?.into_path(); + if path.is_file() { + let mut exclude = false; + for excfile in item.exc.iter() { + if excfile.starts_with("*.") { + if let Some(ext) = path.extension().and_then(|x| x.to_str()) { + if excfile.ends_with(&format!(".{}", ext)) { + exclude = true; + break; + } + } + } else { + if path.ends_with(Path::new(excfile)) { + exclude = true; + break; + } + } + } + if exclude { + continue; + } + + let mut suppressed_front = item.suppressed_front.clone(); + if !suppressed_front.is_empty() && suppressed_front.ends_with('/') { + suppressed_front.push('/'); + } + let outpath = path.strip_prefix(Path::new(&suppressed_front))?; + let outfile = if outpath.is_absolute() { + match outpath + .file_name() + .and_then(|f| f.to_str()) + .map(|f| f.to_string()) + { + None => { + bail!("Failed to get filename of {}", outpath.display()); + } + Some(s) => s, + } + } else { + match outpath.to_str() { + None => { + bail!("Failed to convert {} to string", outpath.display()); + } + // Simple replace \ to / here. + // A better way is to use lib [path-slash](https://github.com/rhysd/path-slash) + Some(s) => s.to_string().replace("\\", "/"), + } + }; + let infile = match path.canonicalize()?.to_str() { + None => { + bail!("Failed to get file path of {}", path.display()); + } + Some(s) => s.to_string(), + }; + if let Some(_) = outin_filemap.insert(outfile.clone(), infile) { + bail!("outfile {} is set before", outfile); + } + } + } + + Ok(outin_filemap) +} + +pub fn generate(conf_file: &str) -> ResultType<()> { + let conf = confy::load_path(conf_file)?; + generate_with_conf(&conf)?; + Ok(()) +} + +pub fn generate_with_conf<'a>(conf: &'a Config) -> ResultType<()> { + let mut outfile = File::create(&conf.outfile)?; + + outfile.write( + br##"use hbb_common::{bail, ResultType}; +use std::{ + fs::{self, File}, + io::prelude::*, + path::Path, +}; + +"##, + )?; + + outfile.write(b"#[allow(dead_code)]\n")?; + outfile.write(b"pub fn extract_resources(root_path: &str) -> ResultType<()> {\n")?; + outfile.write(b" let mut resources: Vec<(&str, &[u8])> = Vec::new();\n")?; + + let mut outin_files = HashMap::new(); + for item in conf.confs.iter() { + for (o, i) in get_outin_files(item)?.into_iter() { + if let Some(_) = outin_files.insert(o.clone(), i) { + bail!("outfile {} is set before", o); + } + } + } + + let mut count = 1; + for (o, i) in outin_files.iter() { + let mut infile = File::open(&i)?; + let mut buffer = Vec::::new(); + infile.read_to_end(&mut buffer)?; + + let var_outfile = format!("outfile_{}", count); + let var_outdata = format!("outdata_{}", count); + + write!(outfile, " let {} = \"{}\";\n", var_outfile, o)?; + write!(outfile, " let {}: &[u8] = &[\n ", var_outdata)?; + + let mut line_num = 20; + for v in buffer { + if line_num == 0 { + write!(outfile, "\n ")?; + line_num = 20; + } + write!(outfile, "{:#04x}, ", v)?; + line_num -= 1; + } + write!(outfile, "\n ];\n")?; + + write!( + outfile, + " resources.push(({}, &{}));\n", + var_outfile, var_outdata + )?; + + count += 1; + } + + outfile.write(b" do_extract(root_path, resources)?;\n")?; + outfile.write(b" Ok(())\n")?; + outfile.write(b"}\n")?; + + outfile.write( + br##" +#[allow(dead_code)] +fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> { + let mut root_path = root_path.replace("\\", "/"); + if !root_path.ends_with('/') { + root_path.push('/'); + } + let root_path = Path::new(&root_path); + for (outfile, data) in resources { + let outfile_path = root_path.join(outfile); + match outfile_path.parent().and_then(|p| p.to_str()) { + None => { + bail!("Failed to get parent of {}", outfile_path.display()); + } + Some(p) => { + fs::create_dir_all(p)?; + let mut of = File::create(outfile_path)?; + of.write_all(data)?; + of.flush()?; + } + } + } + Ok(()) +} +"##, + )?; + + outfile.flush()?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } + + // #[test] + // fn test_extract() { + // use super::*; + // rc::extract_resources("D:").unwrap(); + // } +} diff --git a/libs/virtual_display/examples/idd_controller.rs b/libs/virtual_display/examples/idd_controller.rs index 98e2c2b44..7d5677724 100644 --- a/libs/virtual_display/examples/idd_controller.rs +++ b/libs/virtual_display/examples/idd_controller.rs @@ -1,12 +1,14 @@ #[cfg(windows)] use virtual_display::win10::{idd, DRIVER_INSTALL_PATH}; +#[cfg(windows)] use std::{ - ffi::{CStr, CString}, + ffi::CStr, io::{self, Read}, path::Path, }; +#[cfg(windows)] fn prompt_input() -> u8 { println!("Press key execute:"); println!(" 1. 'x' 1. exit"); @@ -24,6 +26,7 @@ fn prompt_input() -> u8 { .unwrap_or(0) } +#[cfg(windows)] unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) { println!("Plug in monitor begin"); if idd::FALSE == idd::MonitorPlugIn(index, edid, 25) { @@ -48,6 +51,7 @@ unsafe fn plug_in(index: idd::UINT, edid: idd::UINT) { } } +#[cfg(windows)] unsafe fn plug_out(index: idd::UINT) { println!("Plug out monitor begin"); if idd::FALSE == idd::MonitorPlugOut(index) { @@ -58,81 +62,91 @@ unsafe fn plug_out(index: idd::UINT) { } fn main() { - let abs_path = Path::new(DRIVER_INSTALL_PATH).canonicalize().unwrap(); - let full_inf_path = abs_path.to_str().unwrap(); + #[cfg(windows)] + { + let abs_path = Path::new(DRIVER_INSTALL_PATH).canonicalize().unwrap(); - unsafe { - let invalid_device = 0 as idd::HSWDEVICE; - let mut h_sw_device = invalid_device; - let full_inf_path = CString::new(full_inf_path).unwrap().into_raw(); - loop { - match prompt_input() as char { - 'x' => break, - 'i' => { - println!("Install or update driver begin"); - let mut reboot_required = idd::FALSE; - if idd::InstallUpdate(full_inf_path, &mut reboot_required) == idd::FALSE { - println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); - } else { - println!( - "Install or update driver done, reboot is {} required", - if reboot_required == idd::FALSE { - "not" - } else { - "" - } - ); + unsafe { + let invalid_device = 0 as idd::HSWDEVICE; + let mut h_sw_device = invalid_device; + + let full_inf_path: Vec = abs_path + .to_string_lossy() + .as_ref() + .encode_utf16() + .chain(Some(0).into_iter()) + .collect(); + + loop { + match prompt_input() as char { + 'x' => break, + 'i' => { + println!("Install or update driver begin, {}", abs_path.display()); + let mut reboot_required = idd::FALSE; + if idd::InstallUpdate(full_inf_path.as_ptr() as _, &mut reboot_required) + == idd::FALSE + { + println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); + } else { + println!( + "Install or update driver done, reboot is {} required", + if reboot_required == idd::FALSE { + "not" + } else { + "" + } + ); + } } - } - 'u' => { - println!("Uninstall driver begin"); - let mut reboot_required = idd::FALSE; - if idd::Uninstall(full_inf_path, &mut reboot_required) == idd::FALSE { - println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); - } else { - println!( - "Uninstall driver done, reboot is {} required", - if reboot_required == idd::FALSE { - "not" - } else { - "" - } - ); + 'u' => { + println!("Uninstall driver begin {}", abs_path.display()); + let mut reboot_required = idd::FALSE; + if idd::Uninstall(full_inf_path.as_ptr() as _, &mut reboot_required) + == idd::FALSE + { + println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); + } else { + println!( + "Uninstall driver done, reboot is {} required", + if reboot_required == idd::FALSE { + "not" + } else { + "" + } + ); + } } - } - 'c' => { - println!("Create device begin"); - if h_sw_device != invalid_device { - println!("Device created before"); - continue; + 'c' => { + println!("Create device begin"); + if h_sw_device != invalid_device { + println!("Device created before"); + continue; + } + if idd::FALSE == idd::DeviceCreate(&mut h_sw_device) { + println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); + idd::DeviceClose(h_sw_device); + h_sw_device = invalid_device; + } else { + println!("Create device done"); + } } - if idd::FALSE == idd::DeviceCreate(&mut h_sw_device) { - println!("{}", CStr::from_ptr(idd::GetLastMsg()).to_str().unwrap()); + 'd' => { + println!("Close device begin"); idd::DeviceClose(h_sw_device); h_sw_device = invalid_device; - } else { - println!("Create device done"); + println!("Close device done"); } + '1' => plug_in(0, 0), + '2' => plug_in(1, 0), + '3' => plug_in(2, 0), + '4' => plug_out(0), + '5' => plug_out(1), + '6' => plug_out(2), + _ => {} } - 'd' => { - println!("Close device begin"); - idd::DeviceClose(h_sw_device); - h_sw_device = invalid_device; - println!("Close device done"); - } - '1' => plug_in(0, 0), - '2' => plug_in(1, 0), - '3' => plug_in(2, 0), - '4' => plug_out(0), - '5' => plug_out(1), - '6' => plug_out(2), - _ => {} } - } - if !full_inf_path.is_null() { - let _ = CString::from_raw(full_inf_path); - } - idd::DeviceClose(h_sw_device); + idd::DeviceClose(h_sw_device); + } } } diff --git a/libs/virtual_display/src/lib.rs b/libs/virtual_display/src/lib.rs index 73b4bdbd4..9f71fd6da 100644 --- a/libs/virtual_display/src/lib.rs +++ b/libs/virtual_display/src/lib.rs @@ -2,7 +2,7 @@ pub mod win10; use hbb_common::{bail, lazy_static, ResultType}; -use std::{ffi::CString, path::Path, sync::Mutex}; +use std::{path::Path, sync::Mutex}; lazy_static::lazy_static! { // If device is uninstalled though "Device Manager" Window. @@ -33,16 +33,24 @@ pub fn install_update_driver(_reboot_required: &mut bool) -> ResultType<()> { bail!("{} not exists", install_path) } - let _full_install_path = match abs_path.to_str() { - Some(p) => CString::new(p)?.into_raw(), - None => bail!("{} not exists", install_path), - }; - #[cfg(windows)] unsafe { { + // Device must be created before install driver. + // https://github.com/fufesou/RustDeskIddDriver/issues/1 + if let Err(e) = create_device() { + bail!("{}", e); + } + + let full_install_path: Vec = abs_path + .to_string_lossy() + .as_ref() + .encode_utf16() + .chain(Some(0).into_iter()) + .collect(); + let mut reboot_required_tmp = win10::idd::FALSE; - if win10::idd::InstallUpdate(_full_install_path, &mut reboot_required_tmp) + if win10::idd::InstallUpdate(full_install_path.as_ptr() as _, &mut reboot_required_tmp) == win10::idd::FALSE { bail!("{}", win10::get_last_msg()?); @@ -65,16 +73,18 @@ pub fn uninstall_driver(_reboot_required: &mut bool) -> ResultType<()> { bail!("{} not exists", install_path) } - let _full_install_path = match abs_path.to_str() { - Some(p) => CString::new(p)?.into_raw(), - None => bail!("{} not exists", install_path), - }; - #[cfg(windows)] unsafe { { + let full_install_path: Vec = abs_path + .to_string_lossy() + .as_ref() + .encode_utf16() + .chain(Some(0).into_iter()) + .collect(); + let mut reboot_required_tmp = win10::idd::FALSE; - if win10::idd::Uninstall(_full_install_path, &mut reboot_required_tmp) + if win10::idd::Uninstall(full_install_path.as_ptr() as _, &mut reboot_required_tmp) == win10::idd::FALSE { bail!("{}", win10::get_last_msg()?); diff --git a/libs/virtual_display/src/win10/IddController.c b/libs/virtual_display/src/win10/IddController.c index dd18a622d..a30fa9d0a 100644 --- a/libs/virtual_display/src/win10/IddController.c +++ b/libs/virtual_display/src/win10/IddController.c @@ -64,14 +64,14 @@ const char* GetLastMsg() return g_lastMsg; } -BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired) +BOOL InstallUpdate(LPCWSTR fullInfPath, PBOOL rebootRequired) { SetLastMsg("Sucess"); - // UpdateDriverForPlugAndPlayDevices may return FALSE while driver was successfully installed... - if (FALSE == UpdateDriverForPlugAndPlayDevices( + // UpdateDriverForPlugAndPlayDevicesW may return FALSE while driver was successfully installed... + if (FALSE == UpdateDriverForPlugAndPlayDevicesW( NULL, - _T("RustDeskIddDriver"), // match hardware id in the inf file + L"RustDeskIddDriver", // match hardware id in the inf file fullInfPath, INSTALLFLAG_FORCE // | INSTALLFLAG_NONINTERACTIVE // INSTALLFLAG_NONINTERACTIVE may cause error 0xe0000247 @@ -82,7 +82,7 @@ BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired) DWORD error = GetLastError(); if (error != 0) { - SetLastMsg("UpdateDriverForPlugAndPlayDevices failed, last error 0x%x\n", error); + SetLastMsg("UpdateDriverForPlugAndPlayDevicesW failed, last error 0x%x\n", error); if (g_printMsg) { printf(g_lastMsg); @@ -94,11 +94,11 @@ BOOL InstallUpdate(LPCTSTR fullInfPath, PBOOL rebootRequired) return TRUE; } -BOOL Uninstall(LPCTSTR fullInfPath, PBOOL rebootRequired) +BOOL Uninstall(LPCWSTR fullInfPath, PBOOL rebootRequired) { SetLastMsg("Sucess"); - if (FALSE == DiUninstallDriver( + if (FALSE == DiUninstallDriverW( NULL, fullInfPath, 0, @@ -108,7 +108,7 @@ BOOL Uninstall(LPCTSTR fullInfPath, PBOOL rebootRequired) DWORD error = GetLastError(); if (error != 0) { - SetLastMsg("DiUninstallDriver failed, last error 0x%x\n", error); + SetLastMsg("DiUninstallDriverW failed, last error 0x%x\n", error); if (g_printMsg) { printf(g_lastMsg); diff --git a/pynput_service.py b/pynput_service.py index d8df09e96..90b8741ce 100644 --- a/pynput_service.py +++ b/pynput_service.py @@ -1,65 +1,32 @@ from pynput.keyboard import Key, Controller from pynput.keyboard._xorg import KeyCode from pynput._util.xorg import display_manager -import Xlib import os import sys import socket +from Xlib.ext.xtest import fake_input +from Xlib import X +import Xlib KeyCode._from_symbol("\0") # test + class MyController(Controller): def _handle(self, key, is_press): """Resolves a key identifier and sends a keyboard event. :param event: The *X* keyboard event. :param int keysym: The keysym to handle. """ - event = Xlib.display.event.KeyPress if is_press \ - else Xlib.display.event.KeyRelease keysym = self._keysym(key) + keycode = self._display.keysym_to_keycode(keysym) - # Make sure to verify that the key was resolved - if keysym is None: - raise self.InvalidKeyException(key) + with display_manager(self._display) as dm: + Xlib.ext.xtest.fake_input( + dm, + Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, + keycode) - # If the key has a virtual key code, use that immediately with - # fake_input; fake input,being an X server extension, has access to - # more internal state that we do - if key.vk is not None: - with display_manager(self._display) as dm: - Xlib.ext.xtest.fake_input( - dm, - Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, - dm.keysym_to_keycode(key.vk)) - # Otherwise use XSendEvent; we need to use this in the general case to - # work around problems with keyboard layouts - else: - try: - keycode, shift_state = self.keyboard_mapping[keysym] - with self.modifiers as modifiers: - alt_gr = Key.alt_gr in modifiers - if alt_gr: - self._send_key(event, keycode, shift_state) - else: - with display_manager(self._display) as dm: - Xlib.ext.xtest.fake_input( - dm, - Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, - keycode) - - except KeyError: - with self._borrow_lock: - keycode, index, count = self._borrows[keysym] - self._send_key( - event, - keycode, - index_to_shift(self._display, index)) - count += 1 if is_press else -1 - self._borrows[keysym] = (keycode, index, count) - - # Notify any running listeners - self._emit('_on_fake_event', key, is_press) keyboard = MyController() @@ -77,7 +44,7 @@ server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server.bind(server_address) server.listen(1) clientsocket, address = server.accept() -os.system('chmod a+rw %s'%server_address) +os.system('chmod a+rw %s' % server_address) print("Got pynput connection") @@ -121,4 +88,3 @@ def loop(): loop() clientsocket.close() server.close() - diff --git a/rustdesk.desktop b/rustdesk.desktop index 8bdbe06e0..11c7daad0 100644 --- a/rustdesk.desktop +++ b/rustdesk.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=1.0 +Version=1.2.0 Name=RustDesk GenericName=Remote Desktop Comment=Remote Desktop diff --git a/src/cli.rs b/src/cli.rs index 5aebea432..59c356a5a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,7 +21,7 @@ impl Session { pub fn new(id: &str, sender: mpsc::UnboundedSender) -> Self { let mut password = "".to_owned(); if PeerConfig::load(id).password.is_empty() { - password = rpassword::read_password_from_tty(Some("Enter password: ")).unwrap(); + password = rpassword::prompt_password("Enter password: ").unwrap(); } let session = Self { id: id.to_owned(), @@ -47,7 +47,7 @@ impl Interface for Session { .ok(); } else if msgtype == "re-input-password" { log::error!("{}: {}", title, text); - let pass = rpassword::read_password_from_tty(Some("Enter password: ")).unwrap(); + let pass = rpassword::prompt_password("Enter password: ").unwrap(); self.sender.send(Data::Login((pass, true))).ok(); } else if msgtype.contains("error") { log::error!("{}: {}: {}", msgtype, title, text); @@ -76,6 +76,10 @@ impl Interface for Session { async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) { handle_test_delay(t, peer).await; } + + fn send(&self, data: Data) { + self.sender.send(data).ok(); + } } #[tokio::main(flavor = "current_thread")] @@ -85,6 +89,7 @@ pub async fn start_one_port_forward( remote_host: String, remote_port: i32, key: String, + token: String, ) { crate::common::test_rendezvous_server(); crate::common::test_nat_type(); @@ -92,7 +97,7 @@ pub async fn start_one_port_forward( let handler = Session::new(&id, sender); handler.lc.write().unwrap().port_forward = (remote_host, remote_port); if let Err(err) = - crate::port_forward::listen(handler.id.clone(), port, handler.clone(), receiver, &key).await + crate::port_forward::listen(handler.id.clone(), port, handler.clone(), receiver, &key, &token).await { log::error!("Failed to listen on {}: {}", port, err); } diff --git a/src/client.rs b/src/client.rs index 236eb331d..247e2702c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -795,6 +795,7 @@ pub struct LoginConfigHandler { pub port_forward: (String, i32), pub version: i64, pub conn_id: i32, + features: Option, } impl Deref for LoginConfigHandler { @@ -924,11 +925,11 @@ impl LoginConfigHandler { }) .into(); } else if name == "privacy-mode" { - config.privacy_mode = !config.privacy_mode; + // try toggle privacy mode option.privacy_mode = (if config.privacy_mode { - BoolOption::Yes - } else { BoolOption::No + } else { + BoolOption::Yes }) .into(); } else if name == "enable-file-transfer" { @@ -1068,6 +1069,14 @@ impl LoginConfigHandler { } } + pub fn is_privacy_mode_supported(&self) -> bool { + if let Some(features) = &self.features { + features.privacy_mode + } else { + false + } + } + /// Create a [`Message`] for refreshing video. pub fn refresh() -> Message { let mut misc = Misc::new(); @@ -1166,6 +1175,7 @@ impl LoginConfigHandler { if !pi.version.is_empty() { self.version = hbb_common::get_version_number(&pi.version); } + self.features = pi.features.into_option(); let serde = PeerInfoSerde { username, hostname: pi.hostname.clone(), @@ -1378,9 +1388,9 @@ pub fn send_mouse( } /// Avtivate OS by sending mouse movement. -/// +/// /// # Arguments -/// +/// /// * `interface` - The interface for sending data. fn activate_os(interface: &impl Interface) { send_mouse(0, 0, 0, false, false, false, false, interface); @@ -1401,9 +1411,9 @@ fn activate_os(interface: &impl Interface) { } /// Input the OS's password. -/// +/// /// # Arguments -/// +/// /// * `p` - The password. /// * `avtivate` - Whether to activate OS. /// * `interface` - The interface for sending data. @@ -1414,9 +1424,9 @@ pub fn input_os_password(p: String, activate: bool, interface: impl Interface) { } /// Input the OS's password. -/// +/// /// # Arguments -/// +/// /// * `p` - The password. /// * `avtivate` - Whether to activate OS. /// * `interface` - The interface for sending data. @@ -1673,9 +1683,9 @@ lazy_static::lazy_static! { } /// Check if the given message is an error and can be retried. -/// +/// /// # Arguments -/// +/// /// * `msgtype` - The message type. /// * `title` - The title of the message. /// * `text` - The text of the message. diff --git a/src/client/file_trait.rs b/src/client/file_trait.rs index 5dbc614e6..45f0473ba 100644 --- a/src/client/file_trait.rs +++ b/src/client/file_trait.rs @@ -6,7 +6,7 @@ pub trait FileManager: Interface { fs::get_home_as_string() } - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))] fn read_dir(&self, path: String, include_hidden: bool) -> sciter::Value { match fs::read_dir(&fs::get_path(&path), include_hidden) { Err(_) => sciter::Value::null(), @@ -19,7 +19,7 @@ pub trait FileManager: Interface { } } - #[cfg(any(target_os = "android", target_os = "ios"))] + #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))] fn read_dir(&self, path: &str, include_hidden: bool) -> String { use crate::flutter::make_fd_to_json; match fs::read_dir(&fs::get_path(path), include_hidden) { diff --git a/src/common.rs b/src/common.rs index b3141a7fe..92ccb901e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -376,8 +376,18 @@ pub fn get_time() -> i64 { } pub fn run_me>(args: Vec) -> std::io::Result { - let cmd = std::env::current_exe()?; - return std::process::Command::new(cmd).args(&args).spawn(); + #[cfg(not(feature = "appimage"))] + { + let cmd = std::env::current_exe()?; + return std::process::Command::new(cmd).args(&args).spawn(); + } + #[cfg(feature = "appimage")] + { + let appdir = std::env::var("APPDIR").unwrap(); + let appimage_cmd = std::path::Path::new(&appdir).join("AppRun"); + log::info!("path: {:?}", appimage_cmd); + return std::process::Command::new(appimage_cmd).args(&args).spawn(); + } } pub fn username() -> String { @@ -482,7 +492,7 @@ pub fn is_ip(id: &str) -> bool { } pub fn is_setup(name: &str) -> bool { - name.to_lowercase().ends_with("putes.exe") || name.to_lowercase().ends_with("安装.exe") + name.to_lowercase().ends_with("setdown.exe") || name.to_lowercase().ends_with("安装.exe") } pub fn get_uuid() -> Vec { @@ -593,3 +603,33 @@ pub async fn post_request(url: String, body: String, header: &str) -> ResultType pub async fn post_request_sync(url: String, body: String, header: &str) -> ResultType { post_request(url, body, header).await } + +#[inline] +pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Message { + let mut misc = Misc::new(); + let mut back_notification = BackNotification::new(); + back_notification.set_privacy_mode_state(state); + misc.set_back_notification(back_notification); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + msg_out +} + +pub fn make_fd_to_json(fd: FileDirectory) -> String { + use serde_json::json; + let mut fd_json = serde_json::Map::new(); + fd_json.insert("id".into(), json!(fd.id)); + fd_json.insert("path".into(), json!(fd.path)); + + let mut entries = vec![]; + for entry in fd.entries { + let mut entry_map = serde_json::Map::new(); + entry_map.insert("entry_type".into(), json!(entry.entry_type.value())); + entry_map.insert("name".into(), json!(entry.name)); + entry_map.insert("size".into(), json!(entry.size)); + entry_map.insert("modified_time".into(), json!(entry.modified_time)); + entries.push(entry_map); + } + fd_json.insert("entries".into(), json!(entries)); + serde_json::to_string(&fd_json).unwrap_or("".into()) +} diff --git a/src/flutter.rs b/src/flutter.rs index 7a0f378f9..36df3972a 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -1,4 +1,5 @@ use crate::{client::*, flutter_ffi::EventToUI}; +use crate::common::{make_fd_to_json}; use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; use hbb_common::{ allow_err, @@ -499,7 +500,12 @@ impl Interface for Session { let mut displays = Vec::new(); let mut current = pi.current_display as usize; - if !lc.is_file_transfer { + if lc.is_file_transfer { + if pi.username.is_empty() { + self.msgbox("error", "Error", "No active console user logged on, please connect and logon first."); + return; + } + } else { if pi.displays.is_empty() { self.msgbox("error", "Remote Error", "No Display"); } diff --git a/src/ipc.rs b/src/ipc.rs index 2388a7d9c..5eabbab66 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -20,6 +20,16 @@ use std::{collections::HashMap, sync::atomic::Ordering}; #[cfg(not(windows))] use std::{fs::File, io::prelude::*}; +// State with timestamp, because std::time::Instant cannot be serialized +#[derive(Debug, Serialize, Deserialize, Copy, Clone)] +#[serde(tag = "t", content = "c")] +pub enum PrivacyModeState { + OffSucceeded, + OffFailed, + OffByPeer, + OffUnknown, +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(tag = "t", content = "c")] pub enum FS { @@ -116,6 +126,7 @@ pub enum Data { #[cfg(not(any(target_os = "android", target_os = "ios")))] ClipbaordFile(ClipbaordFile), ClipboardFileEnabled(bool), + PrivacyModeState((i32, PrivacyModeState)), } #[tokio::main(flavor = "current_thread")] diff --git a/src/lang.rs b/src/lang.rs index d79cfd32f..4b4998fc1 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,16 +1,20 @@ use std::ops::Deref; mod cn; -mod en; -mod fr; -mod it; -mod tw; +mod cs; +mod da; +mod sk; mod de; -mod ru; +mod en; +mod es; mod eo; -mod ptbr; +mod fr; mod id; +mod it; +mod ptbr; +mod ru; mod tr; +mod tw; #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn translate(name: String) -> String { @@ -22,15 +26,15 @@ pub fn translate_locale(name: String, locale: &str) -> String { let mut lang = hbb_common::config::LocalConfig::get_option("lang").to_lowercase(); if lang.is_empty() { // zh_CN on Linux, zh-Hans-CN on mac, zh_CN_#Hans on Android - if locale.starts_with("zh") && (locale.ends_with("CN") || locale.ends_with("SG") || locale.ends_with("Hans")) { - lang = "cn".to_owned(); + if locale.starts_with("zh") { + lang = (if locale.contains("TW") { "tw" } else { "cn" }).to_owned(); } } if lang.is_empty() { lang = locale .split("-") - .last() - .map(|x| x.split("_").last().unwrap_or_default()) + .next() + .map(|x| x.split("_").next().unwrap_or_default()) .unwrap_or_default() .to_owned(); } @@ -41,6 +45,7 @@ pub fn translate_locale(name: String, locale: &str) -> String { "it" => it::T.deref(), "tw" => tw::T.deref(), "de" => de::T.deref(), + "es" => es::T.deref(), "ru" => ru::T.deref(), "eo" => eo::T.deref(), "id" => id::T.deref(), @@ -48,6 +53,9 @@ pub fn translate_locale(name: String, locale: &str) -> String { "br" => ptbr::T.deref(), "pt" => ptbr::T.deref(), "tr" => tr::T.deref(), + "cs" => cs::T.deref(), + "da" => da::T.deref(), + "sk" => sk::T.deref(), _ => en::T.deref(), }; if let Some(v) = m.get(&name as &str) { diff --git a/src/lang/cn.rs b/src/lang/cn.rs index b2a3dc9a4..a09ab8d39 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "允许使用键盘鼠标"), ("Allow using clipboard", "允许使用剪贴板"), ("Allow hearing sound", "允许听到声音"), - ("Allow file transfer", "允许文件传输"), - ("File transfer", "文件传输"), + ("Allow file copy and paste", "允许复制粘贴文件"), ("Connected", "已经连接"), ("Direct and encrypted connection", "加密直连"), ("Relayed and encrypted connection", "加密中继连接"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "已选择"), ("Screen Capture", "屏幕录制"), ("Input Control", "输入控制"), - ("File Transfer", "文件传输"), ("Audio Capture", "音频录制"), ("File Connection", "文件连接"), ("Screen Connection", "屏幕连接"), @@ -265,10 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "当前安卓版本不支持音频录制,请升级至安卓10或更高。"), ("android_start_service_tip", "点击 [启动服务] 或打开 [屏幕录制] 权限开启手机屏幕共享服务。"), ("Account", "账号"), - ("Quit", "退出"), ("Overwrite", "覆盖"), ("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"), + ("Quit", "退出"), ("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac/#启用权限"), ("Help", "帮助"), + ("Failed", "失败"), + ("Succeeded", "成功"), + ("Someone turns on privacy mode, exit", "其他用户使用隐私模式,退出"), + ("Unsupported", "不支持"), + ("Peer denied", "被控端拒绝"), + ("Please install plugins", "请安装插件"), + ("Peer exit", "被控端退出"), + ("Failed to turn off", "退出失败"), + ("Turned off", "退出"), + ("In privacy mode", "进入隐私模式"), + ("Out privacy mode", "退出隐私模式"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs new file mode 100644 index 000000000..80e1568eb --- /dev/null +++ b/src/lang/cs.rs @@ -0,0 +1,283 @@ +lazy_static::lazy_static! { +pub static ref T: std::collections::HashMap<&'static str, &'static str> = + [ + ("Status", "Stav"), + ("Your Desktop", "Vaše plocha"), + ("desk_tip", "Pomocí tohoto identifikátoru a hesla můžete přistupovat ke své ploše."), + ("Password", "Heslo"), + ("Ready", "Připraveno"), + ("Established", "Navázáno"), + ("connecting_status", "Připojování se k Rusdesk síti…"), + ("Enable Service", "Povolit službu"), + ("Start Service", "Spustit službu"), + ("Service is running", "Služba je spuštěná"), + ("Service is not running", "Služba není spuštěná"), + ("not_ready_status", "Nepřipraveno. Zkontrolujte své připojení."), + ("Control Remote Desktop", "Ovládat vzdálenou plochu"), + ("Transfer File", "Přenést soubor"), + ("Connect", "Připojit"), + ("Recent Sessions", "Nedávné relace"), + ("Address Book", "Adresář kontaktů"), + ("Confirmation", "Potvrzení"), + ("TCP Tunneling", "TCP tunelování"), + ("Remove", "Odebrat"), + ("Refresh random password", "Vytvořit nové náhodné heslo"), + ("Set your own password", "Nastavte si své vlastní heslo"), + ("Enable Keyboard/Mouse", "Povolit klávesnici/myš"), + ("Enable Clipboard", "Povolit schránku"), + ("Enable File Transfer", "Povolit přenos souborů"), + ("Enable TCP Tunneling", "Povolit TCP tunelování"), + ("IP Whitelisting", "Povolování pouze z daných IP adres)"), + ("ID/Relay Server", "Identifikátor / předávací (relay) server"), + ("Stop service", "Zastavit službu"), + ("Change ID", "Změnit identifikátor"), + ("Website", "Webové stránky"), + ("About", "O aplikaci"), + ("Mute", "Ztlumit"), + ("Audio Input", "Vstup zvuku"), + ("ID Server", "Server pro identif."), + ("Relay Server", "Předávací (relay) server"), + ("API Server", "Server s API rozhraním"), + ("invalid_http", "Je třeba, aby začínalo na http:// nebo https://"), + ("Invalid IP", "Neplatná IP adresa"), + ("id_change_tip", "Použít je mozné pouze znaky a-z, A-Z, 0-9 a _ (podtržítko). Dále je třeba aby začínalo na písmeno a-z, A-Z. Délka mezi 6 a 16 znaky."), + ("Invalid format", "Neplatný formát"), + ("server_not_support", "Server zatím nepodporuje"), + ("Not available", "Není k dispozici"), + ("Too frequent", "Příliš časté"), + ("Cancel", "Storno"), + ("Skip", "Přeskočit"), + ("Close", "Zavřít"), + ("Retry", "Zkusit znovu"), + ("OK", "OK"), + ("Password Required", "Vyžadováno heslo"), + ("Please enter your password", "Zadejte své heslo"), + ("Remember password", "Zapamatovat heslo"), + ("Wrong Password", "Nesprávné heslo"), + ("Do you want to enter again?", "Chcete se znovu připojit?"), + ("Connection Error", "Chyba spojení"), + ("Error", "Chyba"), + ("Reset by the peer", "Resetováno protějškem"), + ("Connecting...", "Připojování…"), + ("Connection in progress. Please wait.", "Probíhá připojování – vyčkejte."), + ("Please try 1 minute later", "Zkuste to až za minutu či déle"), + ("Login Error", "Chyba přihlášení se"), + ("Successful", "Úspěšné"), + ("Connected, waiting for image...", "Připojeno, čeká se na obraz…"), + ("Name", "Název"), + ("Type", "Typ"), + ("Modified", "Změněno"), + ("Size", "Velikost"), + ("Show Hidden Files", "Zobrazit skryté soubory"), + ("Receive", "Přijmout"), + ("Send", "Odeslat"), + ("Refresh File", "Znovu načíst soubor"), + ("Local", "Místní"), + ("Remote", "Vzdálené"), + ("Remote Computer", "Vzdálený počítač"), + ("Local Computer", "Místní počítač"), + ("Confirm Delete", "Potvrdit smazání"), + ("Delete", "Smazat"), + ("Properties", "Vlastnosti"), + ("Multi Select", "Vícenásobný výběr"), + ("Empty Directory", "Prázdná složka"), + ("Not an empty directory", "Neprázdná složka"), + ("Are you sure you want to delete this file?", "Opravdu chcete tento soubor vymazat?"), + ("Are you sure you want to delete this empty directory?", "Opravdu chcete tuto prázdnou složku smazat?"), + ("Are you sure you want to delete the file of this directory?", "Opravdu chcete vymazat soubor, pocházející z této složky?"), + ("Do this for all conflicts", "Naložit takto se všemi konflikty"), + ("This is irreversible!", "Toto nelze vzít zpět"), + ("Deleting", "Mazání"), + ("files", "soubory"), + ("Waiting", "Čeká se"), + ("Finished", "Dokončeno"), + ("Speed", "Rychlost"), + ("Custom Image Quality", "Uživatelsky určená kvalita obrazu"), + ("Privacy mode", "Režim soukromí"), + ("Block user input", "Blokovat vstupní zařízení uživatele"), + ("Unblock user input", "Odblokovat vstupní zařízení uživatele"), + ("Adjust Window", "Přizpůsobit velikost okna"), + ("Original", "Původní"), + ("Shrink", "Oříznout"), + ("Stretch", "Roztáhnout"), + ("Good image quality", "Dobrá kvalita obrazu"), + ("Balanced", "Vyvážené"), + ("Optimize reaction time", "Optimalizovat pro co nejnižší prodlevu odezvy"), + ("Custom", "Uživatelsky určené"), + ("Show remote cursor", "Zobrazovat ukazatel myši z protějšku"), + ("Disable clipboard", "Vypnout schránku"), + ("Lock after session end", "Po ukončení relace zamknout plochu"), + ("Insert", "Vložit"), + ("Insert Lock", "Vložit zámek"), + ("Refresh", "Načíst znovu"), + ("ID does not exist", "Takový identifikátor neexistuje"), + ("Failed to connect to rendezvous server", "Nepodařil se připojit ke zprostředkovávajícímu serveru"), + ("Please try later", "Zkuste to později"), + ("Remote desktop is offline", "Vzdálená plocha není připojená ke službě"), + ("Key mismatch", "Neshoda klíčů"), + ("Timeout", "Překročen časový limit pro navázání spojení"), + ("Failed to connect to relay server", "Nepodařilo se připojit k předávacímu (relay) serveru"), + ("Failed to connect via rendezvous server", "Nepodařilo se připojit prostřednictvím zprostředkovávajícího serveru"), + ("Failed to connect via relay server", "Nepodařilo se připojit prostřednictvím předávacímu (relay) serveru"), + ("Failed to make direct connection to remote desktop", "Nepodařilo s navázat přímé připojení ke vzdálené ploše"), + ("Set Password", "Nastavit heslo"), + ("OS Password", "Heslo do operačního systému"), + ("install_tip", "Kvůli řízení oprávnění v systému (UAC), RustDesk v některých případech na protějšku nefunguje správně. Abyste se UAC vyhnuli, klikněte na níže uvedené tlačítko a nainstalujte tak RustDesk do systému."), + ("Click to upgrade", "Aktualizaci nainstalujete kliknutím"), + ("Click to download", "Stáhnete si kliknutím"), + ("Click to update", "Znovu načtete kliknutím"), + ("Configure", "Nastavit"), + ("config_acc", "Aby bylo možné na dálku ovládat vaši plochu, je třeba aplikaci RustDesk udělit oprávnění pro „Zpřístupnění pro hendikepované“."), + ("config_screen", "Aby bylo možné přistupovat k vaší ploše na dálku, je třeba aplikaci RustDesk udělit oprávněí pro „Nahrávání obsahu obrazovky“."), + ("Installing ...", "Instaluje se…"), + ("Install", "Nainstalovat"), + ("Installation", "Instalace"), + ("Installation Path", "Popis umístění instalace"), + ("Create start menu shortcuts", "Vytvořit zástupce v nabídce Start"), + ("Create desktop icon", "Vytvořit ikonu na ploše"), + ("agreement_tip", "Spuštěním instalace přijímáte licenční ujednání."), + ("Accept and Install", "Přijmout a nainstalovat"), + ("End-user license agreement", "Licencenční ujednání s koncovým uživatelem"), + ("Generating ...", "Vytváření…"), + ("Your installation is lower version.", "Máte nainstalovanou starší verzi"), + ("not_close_tcp_tip", "Po dobu, po kterou tunel potřebujete, nezavírejte toto okno"), + ("Listening ...", "Očekávní spojení…"), + ("Remote Host", "Vzdálený stroj"), + ("Remote Port", "Port na protějšku"), + ("Action", "Akce"), + ("Add", "Přidat"), + ("Local Port", "Místní port"), + ("setup_server_tip", "Rychlejší připojení získáte vytvořením si svého vlastního serveru"), + ("Too short, at least 6 characters.", "Příliš krátké – alespoň 6 znaků."), + ("The confirmation is not identical.", "Kontrolní zadání se neshoduje."), + ("Permissions", "Oprávnění"), + ("Accept", "Přijmout"), + ("Dismiss", "Zahodit"), + ("Disconnect", "Odpojit"), + ("Allow using keyboard and mouse", "Umožnit ovládání mé klávesnice a myši"), + ("Allow using clipboard", "Umožnit používání schránky"), + ("Allow hearing sound", "Umožnit slyšet můj zvuk"), + ("Allow file copy and paste", "Povolit kopírování a vkládání souborů"), + ("Connected", "Připojeno"), + ("Direct and encrypted connection", "Přímé a šifrované spojení"), + ("Relayed and encrypted connection", "Předávané (relay) a šifrované spojení"), + ("Direct and unencrypted connection", "Přímé a nešifrované spojení"), + ("Relayed and unencrypted connection", "Předávané (relay) a nešifrované spojení"), + ("Enter Remote ID", "Zadejte identifikátor protějšku"), + ("Enter your password", "Zadejte své heslo"), + ("Logging in...", "Přihlašování se…"), + ("Enable RDP session sharing", "Zapnout sdílení relace RDP protokolu"), + ("Auto Login", "Automatické přihlášení"), + ("Enable Direct IP Access", "Zapnout přímý přístup na IP adresu"), + ("Rename", "Přejmenovat"), + ("Space", "Mezera"), + ("Create Desktop Shortcut", "Vytvořit zástupce na ploše"), + ("Change Path", "Změnit umístění"), + ("Create Folder", "Vytvořit složku"), + ("Please enter the folder name", "Zadejte název pro složku"), + ("Fix it", "Opravit to"), + ("Warning", "Upozornení"), + ("Login screen using Wayland is not supported", "Přihlašovací obrazovka prostřednictvím Wayland není podporována"), + ("Reboot required", "Je třeba restartovat"), + ("Unsupported display server ", "Nepodporovaný zobrazovací server"), + ("x11 expected", "očekávány x11"), + ("Port", "Číslo portu"), + ("Settings", "Nastavení"), + ("Username", "Uživatelské jméno"), + ("Invalid port", "Neplatné číslo portu"), + ("Closed manually by the peer", "Ručně ukončeno protějškem"), + ("Enable remote configuration modification", "Umožnit upravování nastavení vzdáleného"), + ("Run without install", "Spustit bez instalování"), + ("Always connected via relay", "Vždy spojováno prostřednictvím brány pro předávání (relay)"), + ("Always connect via relay", "Vždy se spojovat prostřednictvím brány pro předávání (relay)"), + ("whitelist_tip", "Přístup je umožněn pouze z IP adres, nacházejících se na seznamu povolených"), + ("Login", "Přihlásit se"), + ("Logout", "Odhlásit se"), + ("Tags", "Štítky"), + ("Search ID", "Hledat identifikátor"), + ("Current Wayland display server is not supported", "Zobrazovací server Wayland zatím není podporován"), + ("whitelist_sep", "Odělováno čárkou, středníkem, mezerou nebo koncem řádku"), + ("Add ID", "Přidat identifikátor"), + ("Add Tag", "Přidat štítek"), + ("Unselect all tags", "Zrušit výběr všech štítků"), + ("Network error", "Chyba sítě"), + ("Username missed", "Chybí uživatelské jméno"), + ("Password missed", "Chybí heslo"), + ("Wrong credentials", "Nesprávné přihlašovací údaje"), + ("Edit Tag", "Upravit štítek"), + ("Unremember Password", "Přestat si heslo pamatovat"), + ("Favorites", "Oblíbené"), + ("Add to Favorites", "Přidat do oblíbených"), + ("Remove from Favorites", "Odebrat z oblíbených"), + ("Empty", "Prázdné"), + ("Invalid folder name", "Neplatný název složky"), + ("Socks5 Proxy", "Socks5 proxy"), + ("Hostname", "Název stroje"), + ("Discovered", "Objeveno"), + ("install_daemon_tip", "Pokud má být spouštěno při startu systému, je třeba nainstalovat systémovou službu."), + ("Remote ID", "Identif. protějšku"), + ("Paste", "Vložit"), + ("Paste here?", "Vložit sem?"), + ("Are you sure to close the connection?", "Opravdu chcete spojení ukončit?"), + ("Download new version", "Stáhnout si novou verzi"), + ("Touch mode", "Režim dotyku"), + ("Mouse mode", "Režim myši"), + ("One-Finger Tap", "Klepnutí jedním prstem"), + ("Left Mouse", "Levé tlačítko myši"), + ("One-Long Tap", "Jedno dlouhé klepnutí"), + ("Two-Finger Tap", "Klepnutí dvěma prsty"), + ("Right Mouse", "Pravé tlačítko myši"), + ("One-Finger Move", "Přesouvání jedním prstem"), + ("Double Tap & Move", "Dvojité klepnutí a přesun"), + ("Mouse Drag", "Přetažení myší"), + ("Three-Finger vertically", "Třemi prsty svisle"), + ("Mouse Wheel", "Kolečko myši"), + ("Two-Finger Move", "Posun dvěma prsty"), + ("Canvas Move", "Posun zobrazení"), + ("Pinch to Zoom", "Přiblížíte roztažením dvěma prsty"), + ("Canvas Zoom", "Přiblížení zobrazení"), + ("Reset canvas", "Vrátit měřtko zobrazení na výchozí"), + ("No permission of file transfer", "Žádné oprávnění přenosu souboru"), + ("Note", "Poznámka"), + ("Connection", "Připojení"), + ("Share Screen", "Nasdílet obrazovku"), + ("CLOSE", "ZAVŘÍT"), + ("OPEN", "OTEVŘÍT"), + ("Chat", "Chat"), + ("Total", "Celkem"), + ("items", "Položek"), + ("Selected", "Vybráno"), + ("Screen Capture", "Zachytávání obrazovky"), + ("Input Control", "Ovládání vstupních zařízení"), + ("Audio Capture", "Zachytávání zvuku"), + ("File Connection", "Souborové spojení"), + ("Screen Connection", "Spojení obrazovky"), + ("Do you accept?", "Přijímáte?"), + ("Open System Setting", "Otevřít nastavení systému"), + ("How to get Android input permission?", "Jak v systému Android získat oprávnění pro vstupní zařízení?"), + ("android_input_permission_tip1", "Aby vzdálené zařízení mohlo ovládat vaše Android zařízení prostřednictví myši či dotyků, je třeba povolit, aby RustDesk mohlo používat službu „Zpřístupnění hendikepovaným“."), + ("android_input_permission_tip2", "Přejděte na následující stránku nastavení systému, najděte a přejděte do [Nainstalované služby] a zapněte službu [RustDesk vstup]."), + ("android_new_connection_tip", "Obdržen nový požadavek na řízení zařízení, který chce ovládat vaše stávající zařízení."), + ("android_service_will_start_tip", "Zapnutí „Zachytávání obsahu obrazovky“ automaticky spustí službu, což umožní ostatním zařízením žádat o připojení k vašemu zařízení."), + ("android_stop_service_tip", "Zastavení služby automaticky ukončí veškerá navázaná spojení."), + ("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku – přejděte na Android 10 nebo novější."), + ("android_start_service_tip", "Službu pro sdílení obrazovky spustíte klepnutím na [Spustit službu] nebo UDĚLTE pověření pro [Zachytávání obsahu obrazovky]."), + ("Account", "Účet"), + ("Overwrite", "Přepsat"), + ("This file exists, skip or overwrite this file?", "Tento soubor existuje – přeskočit ho nebo přepsat?"), + ("Quit", "Ukončit"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("Help", "Nápověda"), + ("Failed", "Nepodařilo se"), + ("Succeeded", "Uspěl"), + ("Someone turns on privacy mode, exit", "Někdo zapne režim soukromí, ukončete ho"), + ("Unsupported", "Nepodporováno"), + ("Peer denied", "Peer popřel"), + ("Please install plugins", "Nainstalujte si prosím pluginy"), + ("Peer exit", "Peer exit"), + ("Failed to turn off", "Nepodařilo se vypnout"), + ("Turned off", "Vypnutý"), + ("In privacy mode", "v režimu soukromí"), + ("Out privacy mode", "mimo režim soukromí"), + ].iter().cloned().collect(); +} diff --git a/src/lang/da.rs b/src/lang/da.rs new file mode 100644 index 000000000..429b08b05 --- /dev/null +++ b/src/lang/da.rs @@ -0,0 +1,283 @@ +lazy_static::lazy_static! { +pub static ref T: std::collections::HashMap<&'static str, &'static str> = + [ + ("Status", "Status"), + ("Your Desktop", "dit skrivebord"), + ("desk_tip", "Du kan få adgang til dit skrivebord med dette ID og adgangskode."), + ("Password", "Kodeord"), + ("Ready", "Klar"), + ("Established", "Etableret"), + ("connecting_status", "Opretter forbindelse til RustDesk-netværket..."), + ("Enable Service", "Tænd forbindelsesserveren"), + ("Start Service", "Starte forbindelsesserveren"), + ("Service is running", "Tjenesten kører"), + ("Service is not running", "Den tilknyttede tjeneste kører ikke"), + ("not_ready_status", "Ikke klar. Tjek venligst din forbindelse"), + ("Control Remote Desktop", "Styr fjernskrivebord"), + ("Transfer File", "Overføre fil"), + ("Connect", "Forbind"), + ("Recent Sessions", "Sidste sessioner"), + ("Address Book", "Adressebog"), + ("Confirmation", "Bekræftelse"), + ("TCP Tunneling", "TCP tunneling"), + ("Remove", "Fjern"), + ("Refresh random password", "Opdater tilfældig adgangskode"), + ("Set your own password", "Indstil din egen adgangskode"), + ("Enable Keyboard/Mouse", "Tænd for tastatur/mus"), + ("Enable Clipboard", "Tænd for udklipsholderen"), + ("Enable File Transfer", "Aktiver filoverførsel"), + ("Enable TCP Tunneling", "Slå TCP-tunneling til"), + ("IP Whitelisting", "IP-udgivelsesliste"), + ("ID/Relay Server", "ID/forbindelsesserver"), + ("Stop service", "Sluk for forbindelsesserveren"), + ("Change ID", "Ændre ID"), + ("Website", "Hjemmeside"), + ("About", "Omkring"), + ("Mute", "Sluk for mikrofonen"), + ("Audio Input", "Lydindgang"), + ("ID Server", "identifikations Server"), + ("Relay Server", "Relæ Server"), + ("API Server", "API Server"), + ("invalid_http", "Skal begynde med http:// eller https://"), + ("Invalid IP", "Ugyldig IP-adresse"), + ("id_change_tip", "Kun tegnene a-z, A-Z, 0-9 og _ (understregning) er tilladt. Det første bogstav skal være a-z, A-Z. Længde mellem 6 og 16."), + ("Invalid format", "Ugyldigt format"), + ("server_not_support", "Endnu ikke understøttet af serveren"), + ("Not available", "ikke Tilgængelig"), + ("Too frequent", "For ofte"), + ("Cancel", "Abort"), + ("Skip", "Spring over"), + ("Close", "Luk"), + ("Retry", "Prøv igen"), + ("OK", "OK"), + ("Password Required", "Adgangskode kræves"), + ("Please enter your password", "Indtast venligst dit kodeord"), + ("Remember password", "Husk kodeord"), + ("Wrong Password", "Forkert kodeord"), + ("Do you want to enter again?", "Vil du forbinde igen?"), + ("Connection Error", "Forbindelsesfejl"), + ("Error", "fejl"), + ("Reset by the peer", "Nulstil ved peer"), + ("Connecting...", "Opretter forbindelse..."), + ("Connection in progress. Please wait.", "Forbindelsen er etableret. Vent venligst."), + ("Please try 1 minute later", "Prøv igen, 1 minut senere"), + ("Login Error", "Login fejl"), + ("Successful", "Vellykket"), + ("Connected, waiting for image...", "Tilsluttet, venter på billede..."), + ("Name", "Navn"), + ("Type", "Type"), + ("Modified", "Ændret"), + ("Size", "Størrelse"), + ("Show Hidden Files", "Vis skjulte filer"), + ("Receive", "Modtag"), + ("Send", "Send"), + ("Refresh File", "Genopfrisk fil"), + ("Local", "Lokalt"), + ("Remote", "Remote"), + ("Remote Computer", "Fjern computer"), + ("Local Computer", "Lokal Computer"), + ("Confirm Delete", "Bekræft sletning"), + ("Delete", "Slet"), + ("Properties", "Egenskaber"), + ("Multi Select", "Flere valg"), + ("Empty Directory", "Tom bibliotek"), + ("Not an empty directory", "Intet tomt bibliotek"), + ("Are you sure you want to delete this file?", "Er du sikker på, at du vil slette denne fil?"), + ("Are you sure you want to delete this empty directory?", "Er du sikker på, at du vil slette dette tomme bibliotek?"), + ("Are you sure you want to delete the file of this directory?", "Er du sikker på, at du vil slette filen til dette bibliotek?"), + ("Do this for all conflicts", "Gør dette for alle konflikter"), + ("This is irreversible!", "Dette er irreversibelt!"), + ("Deleting", "Sletter"), + ("files", "Filer"), + ("Waiting", "Venter"), + ("Finished", "Færdig"), + ("Speed", "hastighed"), + ("Custom Image Quality", "Individuel billedkvalitet"), + ("Privacy mode", "Databeskyttelsestilstand (Privatlivstilstand)"), + ("Block user input", "Bloker brugerinput"), + ("Unblock user input", "Fjern blokering af brugerinput"), + ("Adjust Window", "Juster vinduet"), + ("Original", "Original"), + ("Shrink", "Krymp"), + ("Stretch", "Strak"), + ("Good image quality", "God billedkvalitet"), + ("Balanced", "Afbalanceret"), + ("Optimize reaction time", "Optimeret responstid"), + ("Custom", "Brugerdefineret"), + ("Show remote cursor", "Vis fjernbetjeningskontrolleret markør"), + ("Disable clipboard", "Deaktiver udklipsholder"), + ("Lock after session end", "Lås efter afslutningen af fjernstyring"), + ("Insert", "Indsæt"), + ("Insert Lock", "Indsæt lås"), + ("Refresh", "Genopfrisk"), + ("ID does not exist", "ID findes ikke"), + ("Failed to connect to rendezvous server", "Forbindelse til forbindelsesserveren mislykkedes"), + ("Please try later", "Prøv det senere"), + ("Remote desktop is offline", "Fjernet desktop er offline"), + ("Key mismatch", "Nøgle uoverensstemmelse"), + ("Timeout", "Timeout"), + ("Failed to connect to relay server", "Forbindelse til relæ-serveren mislykkedes"), + ("Failed to connect via rendezvous server", "Forbindelse via Rendezvous-server mislykkedes"), + ("Failed to connect via relay server", "Forbindelse via relæ-serveren mislykkedes"), + ("Failed to make direct connection to remote desktop", "Direkte forbindelse til fjernskrivebord kunne ikke etableres"), + ("Set Password", "Indstil adgangskode"), + ("OS Password", "Operativsystemadgangskode"), + ("install_tip", "På grund af UAC kan Rustdesk ikke fungere korrekt på den anden side i nogle tilfælde. For at undgå UAC skal du klikke på knappen nedenfor for at installere Rustdesk på systemet"), + ("Click to upgrade", "Klik for at opgradere"), + ("Click to download", "Klik for at downloade"), + ("Click to update", "Klik for at opdatere"), + ("Configure", "Konfigurer"), + ("config_acc", "For at kontrollere dit skrivebord på afstand skal du give Rustdesk \"Access \" Rettigheder."), + ("config_screen", "For at kunne få adgang til dit skrivebord langtfra, skal du give Rustdesk \"skærmstøtte \" tilladelser."), + ("Installing ...", "Installere ..."), + ("Install", "installere"), + ("Installation", "Installation"), + ("Installation Path", "Installationsti"), + ("Create start menu shortcuts", "Opret startmenu links"), + ("Create desktop icon", "Opret skrivebords-symbol"), + ("agreement_tip", "Hvis du starter installationen, skal du acceptere licensaftalen"), + ("Accept and Install", "Accepter og installer"), + ("End-user license agreement", "Licensaftale for slutbrugere"), + ("Generating ...", "Generer kode ..."), + ("Your installation is lower version.", "Din installation er en lavere version."), + ("not_close_tcp_tip", "Luk ikke dette vindue, mens du bruger tunnelen."), + ("Listening ...", "Lytter ..."), + ("Remote Host", "Fjern-Host"), + ("Remote Port", "Fjern-Port"), + ("Action", "Рandling"), + ("Add", "Tilføj"), + ("Local Port", "Lokal Port"), + ("setup_server_tip", "For en hurtigere forbindelse skal du indstille din egen forbindelsesserver"), + ("Too short, at least 6 characters.", "For kort, mindst 6 tegn."), + ("The confirmation is not identical.", "Bekræftelsen er ikke identisk."), + ("Permissions", "Tilladelser"), + ("Accept", "Acceptere"), + ("Dismiss", "Afvise"), + ("Disconnect", "Frakobl"), + ("Allow using keyboard and mouse", "Tillad brug af tastatur og mus"), + ("Allow using clipboard", "Tillad brug af udklipsholderen"), + ("Allow hearing sound", "Tillader hørelse fra lyd"), + ("Allow file copy and paste", "Tillad fil kopiering og indsættelse"), + ("Connected", "Forbundet"), + ("Direct and encrypted connection", "Direkte og krypteret forbindelse"), + ("Relayed and encrypted connection", "Brugt relæet og krypteret forbindelse"), + ("Direct and unencrypted connection", "Direkte og ukrypteret forbindelse"), + ("Relayed and unencrypted connection", "Brugt relæet og ukrypteret forbindelse"), + ("Enter Remote ID", "Indtast Remote-ID"), + ("Enter your password", "Skriv dit kodeord"), + ("Logging in...", "Logger ind..."), + ("Enable RDP session sharing", "RDP-Aktivér sessiongodkendelse"), + ("Auto Login", "Automatisk login (kun gyldigt hvis du har konfigureret \"Lock efter afslutningen af sessionen\")"), + ("Enable Direct IP Access", "Aktivér direkte IP-adgang"), + ("Rename", "Omdøb"), + ("Space", "Plads"), + ("Create Desktop Shortcut", "Opret skrivebords-genvej"), + ("Change Path", "Skift stien"), + ("Create Folder", "Opret mappe"), + ("Please enter the folder name", "Indtast venligst mappenavnet"), + ("Fix it", "Kør reparation"), + ("Warning", "Advarsel"), + ("Login screen using Wayland is not supported", "Registreringsskærm med Wayland understøttes ikke"), + ("Reboot required", "Genstart krævet"), + ("Unsupported display server ", "Ikke-understøttet displayserver"), + ("x11 expected", "X11 Forventet"), + ("Port", "Port"), + ("Settings", "Indstillinger"), + ("Username", " Brugernavn"), + ("Invalid port", "Ugyldig port"), + ("Closed manually by the peer", "Manuelt lukket af peer"), + ("Enable remote configuration modification", "Tillad at ændre afstandskonfigurationen"), + ("Run without install", "Kør uden installation"), + ("Always connected via relay", "Tilslut altid via relæ-server"), + ("Always connect via relay", "Forbindelse via relæ-server"), + ("whitelist_tip", "Kun IP'er på udgivelseslisten kan få adgang til mig"), + ("Login", "Login"), + ("Logout", "logger af"), + ("Tags", "Nøgleord"), + ("Search ID", "Søg ID"), + ("Current Wayland display server is not supported", "Den aktuelle Wayland-Anzege-server understøttes ikke"), + ("whitelist_sep", "Adskilt af komma, semikolon, rum eller linjepaus"), + ("Add ID", "Tilføj ID"), + ("Add Tag", "Tilføj nøgleord"), + ("Unselect all tags", "Fravælg alle nøgleord"), + ("Network error", "Netværksfejl"), + ("Username missed", "Benutzername fehlt"), + ("Password missed", "Glemt kodeord"), + ("Wrong credentials", "Forkerte registreringsdata"), + ("Edit Tag", "Rediger nøgleord"), + ("Unremember Password", "Bemærk ikke adgangskoden"), + ("Favorites", "Favorit"), + ("Add to Favorites", "Tilføj til favoritter"), + ("Remove from Favorites", "Fjern favoritter"), + ("Empty", "Tom"), + ("Invalid folder name", "Ugyldigt mappenavn"), + ("Socks5 Proxy", "Socks5 Proxy"), + ("Hostname", "Computernavn"), + ("Discovered", "Fundet"), + ("install_daemon_tip", "Til at begynde med opstart, skal du installere systemtjenesten"), + ("Remote ID", "Fjern ID"), + ("Paste", "Indsæt"), + ("Paste here?", "Indsæt her?"), + ("Are you sure to close the connection?", "Sind Sie sicher, dass Sie die Verbindung schließen wollen?"), + ("Download new version", "Neue Version herunterladen"), + ("Touch mode", "Touch-tilstand"), + ("Mouse mode", "Musse-tilstand"), + ("One-Finger Tap", "En fingerspids-tap"), + ("Left Mouse", "Venstre mus"), + ("One-Long Tap", "Tryk med en finger lang"), + ("Two-Finger Tap", "Tryk med to fingre-tap"), + ("Right Mouse", "Højre mus"), + ("One-Finger Move", "En fingerbevægelse"), + ("Double Tap & Move", "Dobbelt og flytte"), + ("Mouse Drag", "Mus"), + ("Three-Finger vertically", "Tre fingre lodret"), + ("Mouse Wheel", "Mussehjul"), + ("Two-Finger Move", "To fingreflytning"), + ("Canvas Move", "Flyt lærred"), + ("Pinch to Zoom", "Zoom ind"), + ("Canvas Zoom", "Lærred zoom"), + ("Reset canvas", "Nulstil skærm"), + ("No permission of file transfer", "Ingen tilladelse til at overføre filen"), + ("Note", "Note"), + ("Connection", "Forbindelse"), + ("Share Screen", "Del skærmen"), + ("CLOSE", "LUK"), + ("OPEN", "ÅBEN"), + ("Chat", "Chat"), + ("Total", "Total"), + ("items", "artikel"), + ("Selected", "Valgte"), + ("Screen Capture", "Skærmoptagelse"), + ("Input Control", "Inputkontrol"), + ("Audio Capture", "Lydoptagelse"), + ("File Connection", "Filforbindelse"), + ("Screen Connection", "Færdiggørelse"), + ("Do you accept?", "Accepterer du?"), + ("Open System Setting", "Åbn systemindstillingen"), + ("How to get Android input permission?", "Hvordan får jeg en Android-input tilladelse?"), + ("android_input_permission_tip1", "For at en ekstern enhed kan kontrollere din Android-enhed via mus eller berøring, skal du give Rustdesk mulighed for at bruge tjenesten \"tilgængelighed \"."), + ("android_input_permission_tip2", "Gå til den næste systemindstillingsside, søg og indtast [installerede tjenester], tænd for [Rustdesk Input] Service."), + ("android_new_connection_tip", "En ny kontrolanmodning blev modtaget, der gerne ville kontrollere din nuværende enhed."), + ("android_service_will_start_tip", "Ved at tænde for skærmoptagelsen startes tjenesten automatisk, så andre enheder kan anmode om en forbindelse fra denne enhed."), + ("android_stop_service_tip", "Ved at lukke tjenesten lukkes alle fremstillede forbindelser automatisk."), + ("android_version_audio_tip", "Den aktuelle Android -version understøtter ikke lydoptagelse, skal du opdatere om Android 10 eller højere."), + ("android_start_service_tip", "Tryk på [Start Service] eller åbn autorisationen [skærmoptagelse] for at starte skærmudgivelsen."), + ("Account", "Konto"), + ("Overwrite", "Overskriv"), + ("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"), + ("Quit", "Afslut"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("Help", "Hjælp"), + ("Failed", "Mislykkedet"), + ("Succeeded", "Vellykket"), + ("Someone turns on privacy mode, exit", "Nogen aktiverede databeskyttelsestilstand, slut"), + ("Unsupported", "Ikke understøttet"), + ("Peer denied", "Peer nægtet"), + ("Please install plugins", "Venligst Installer plugins"), + ("Peer exit", "Peer-Afslut"), + ("Failed to turn off", "Slukke"), + ("Turned off", "Slukket"), + ("In privacy mode", "I databeskyttelsestilstand"), + ("Out privacy mode", "Databeskyttelsestilstand fra"), + ].iter().cloned().collect(); +} diff --git a/src/lang/de.rs b/src/lang/de.rs index d05ad481b..8bbfbb4c4 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Erlaubt die Verwendung von Tastatur und Maus"), ("Allow using clipboard", "Verwendung der Zwischenablage zulassen"), ("Allow hearing sound", "Erlaubt das Hören von Sound"), - ("Allow file transfer", "Dateiübertragung zulassen"), - ("File transfer", "Datei Übertragung"), + ("Allow file copy and paste", "Kopieren und Einfügen von Dateien zulassen"), ("Connected", "Verbunden"), ("Direct and encrypted connection", "Direkte und verschlüsselte Verbindung"), ("Relayed and encrypted connection", "Vermittelte und verschlüsselte Verbindung"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "Ausgewählt"), ("Screen Capture", "Bildschirmaufnahme"), ("Input Control", "Eingabesteuerung"), - ("File Transfer", "Datei Übertragung"), ("Audio Capture", "Audioaufnahme"), ("File Connection", "Dateiverbindung"), ("Screen Connection", "Bildschirmanschluss"), @@ -265,8 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "Die aktuelle Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher."), ("android_start_service_tip", "Tippen Sie auf [Dienst starten] oder ÖFFNEN Sie die Berechtigung [Bildschirmaufnahme], um den Bildschirmfreigabedienst zu starten."), ("Account", "Konto"), - ("Quit", "Ausgang"), + ("Overwrite", "Überschreiben"), + ("This file exists, skip or overwrite this file?", "Diese Datei existiert, diese Datei überspringen oder überschreiben?"), + ("Quit", "Aufhören"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Hilfe"), + ("Failed", "Gescheitert"), + ("Succeeded", "Erfolgreich"), + ("Someone turns on privacy mode, exit", "Jemand aktiviert den Datenschutzmodus, beenden"), + ("Unsupported", "Nicht unterstützt"), + ("Peer denied", "Peer verweigert"), + ("Please install plugins", "Bitte installieren Sie Plugins"), + ("Peer exit", "Peer-Ausgang"), + ("Failed to turn off", "Ausschalten fehlgeschlagen"), + ("Turned off", "Ausgeschaltet"), + ("In privacy mode", "im Datenschutzmodus"), + ("Out privacy mode", "Datenschutzmodus aus"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index e7be14e51..49c9f38fa 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -42,6 +42,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Invalid IP", "IP nevalida"), ("id_change_tip", "Nur la signoj a-z, A-Z, 0-9, _ (substreko) povas esti uzataj. La unua litero povas esti inter a-z, A-Z. La longeco devas esti inter 6 kaj 16."), ("Invalid format", "Formato nevalida"), + ("server_not_support", "Ankoraŭ ne subtenata de la servilo"), ("Not available", "Nedisponebla"), ("Too frequent", "Tro ofte ŝanĝita, bonvolu reprovi poste"), ("Cancel", "Nuligi"), @@ -156,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Permesi la uzon de la klavaro kaj muso"), ("Allow using clipboard", "Permesi la uzon de la poŝo"), ("Allow hearing sound", "Permesi la uzon de la sono"), - ("Allow file transfer", "Permesi la dosiertransigadon"), - ("File transfer", "Dosiertransigado"), + ("Allow file copy and paste", "Permesu kopii kaj alglui dosierojn"), ("Connected", "Konektata"), ("Direct and encrypted connection", "Konekcio direkta ĉifrata"), ("Relayed and encrypted connection", "Konekcio relajsa ĉifrata"), @@ -249,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", ""), ("Screen Capture", ""), ("Input Control", ""), - ("File Transfer", ""), ("Audio Capture", ""), ("File Connection", ""), ("Screen Connection", ""), @@ -264,8 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", ""), ("android_start_service_tip", ""), ("Account", ""), + ("Overwrite", ""), + ("This file exists, skip or overwrite this file?", ""), ("Quit", ""), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", ""), + ("Failed", ""), + ("Succeeded", ""), + ("Someone turns on privacy mode, exit", ""), + ("Unsupported", ""), + ("Peer denied", ""), + ("Please install plugins", ""), + ("Peer exit", ""), + ("Failed to turn off", ""), + ("Turned off", ""), + ("In privacy mode", ""), + ("Out privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs new file mode 100644 index 000000000..ba4c671a8 --- /dev/null +++ b/src/lang/es.rs @@ -0,0 +1,283 @@ +lazy_static::lazy_static! { +pub static ref T: std::collections::HashMap<&'static str, &'static str> = + [ + ("Status", "Estado"), + ("Your Desktop", "Tu escritorio"), + ("desk_tip", "Puoi accedere al tuo desktop usando l'ID e la password riportati qui."), + ("Password", "Contraseña"), + ("Ready", "Listo"), + ("Established", "Establecido"), + ("connecting_status", "Conexión a la red RustDesk en progreso..."), + ("Enable Service", "Habilitar Servicio"), + ("Start Service", "Iniciar Servicio"), + ("Service is running", "Servicio se está ejecutando"), + ("Service is not running", "Servicio no se está ejecutando"), + ("not_ready_status", "No está listo. Comprueba tu conexión"), + ("Control Remote Desktop", "Controlar Escritorio Remoto"), + ("Transfer File", "Transferir archivo"), + ("Connect", "Conectar"), + ("Recent Sessions", "Sesiones recientes"), + ("Address Book", "Directorio"), + ("Confirmation", "Confirmación"), + ("TCP Tunneling", "Tunel TCP"), + ("Remove", "Remover"), + ("Refresh random password", "Actualizar contraseña aleatoria"), + ("Set your own password", "Establece tu propia contraseña"), + ("Enable Keyboard/Mouse", "Habilitar teclado/ratón"), + ("Enable Clipboard", "Habilitar portapapeles"), + ("Enable File Transfer", "Habilitar transferencia de archivos"), + ("Enable TCP Tunneling", "Habilitar tunel TCP"), + ("IP Whitelisting", "Lista blanca IP"), + ("ID/Relay Server", "Servidor de ID/Relay"), + ("Stop service", "Parar servicio"), + ("Change ID", "Cambiar identificación"), + ("Website", "Sitio web"), + ("About", "Sobre"), + ("Mute", "Silencio"), + ("Audio Input", "Entrada de audio"), + ("ID Server", "ID server"), + ("Relay Server", "Server relay"), + ("API Server", "Server API"), + ("invalid_http", "debe comenzar con http:// o https://"), + ("Invalid IP", "IP inválida"), + ("id_change_tip", "Solo puedes usar caracteres a-z, A-Z, 0-9 e _ (guion bajo). El primer carácter debe ser a-z o A-Z. La longitud debe estar entre 6 a 16 caracteres."), + ("Invalid format", "Formato inválido"), + ("server_not_support", "Aún no es compatible con el servidor"), + ("Not available", "Indisponible"), + ("Too frequent", "Demasiado frecuente"), + ("Cancel", "Cancelar"), + ("Skip", "Saltar"), + ("Close", "Cerrar"), + ("Retry", "Volver"), + ("OK", "OK"), + ("Password Required", "Se requiere contraseña"), + ("Please enter your password", "Por favor, introduzca su contraseña"), + ("Remember password", "Recordar contraseña"), + ("Wrong Password", "Contraseña incorrecta"), + ("Do you want to enter again?", "Quieres volver a entrar?"), + ("Connection Error", "Error de conexión"), + ("Error", "Error"), + ("Reset by the peer", "Restablecido por el par"), + ("Connecting...", "Conectando..."), + ("Connection in progress. Please wait.", "Conexión en curso. Espere por favor."), + ("Please try 1 minute later", "Intente 1 minuto más tarde"), + ("Login Error", "Error de inicio de sesión"), + ("Successful", "Exitoso"), + ("Connected, waiting for image...", "Conectado, esperando imagen..."), + ("Name", "Nombre"), + ("Type", "Tipo"), + ("Modified", "Modificado"), + ("Size", "Tamaño"), + ("Show Hidden Files", "Mostrar archivos ocultos"), + ("Receive", "Recibir"), + ("Send", "Enviar"), + ("Refresh File", "Actualizar archivo"), + ("Local", "Local"), + ("Remote", "Remoto"), + ("Remote Computer", "Computadora remota"), + ("Local Computer", "Computadora local"), + ("Confirm Delete", "Confirmar eliminación"), + ("Delete", "Borrar"), + ("Properties", "Propiedades"), + ("Multi Select", "Selección múltiple"), + ("Empty Directory", "Directorio vacío"), + ("Not an empty directory", "No es un directorio vacío"), + ("Are you sure you want to delete this file?", "Estás seguro de que quieres eliminar este archivo?"), + ("Are you sure you want to delete this empty directory?", "Está seguro de que desea eliminar este directorio vacío?"), + ("Are you sure you want to delete the file of this directory?", "Está seguro de que desea eliminar el archivo de este directorio?"), + ("Do this for all conflicts", "Haga esto para todos los conflictos"), + ("This is irreversible!", "Esto es irreversible!"), + ("Deleting", "Borrando"), + ("files", "archivos"), + ("Waiting", "Esperando"), + ("Finished", "Acabado"), + ("Speed", "Velocidad"), + ("Custom Image Quality", "Calidad de imagen personalizada"), + ("Privacy mode", "Modo privado"), + ("Block user input", "Bloquear entrada de usuario"), + ("Unblock user input", "Desbloquear entrada de usuario"), + ("Adjust Window", "Ajustar ventana"), + ("Original", "Original"), + ("Shrink", "Encogerse"), + ("Stretch", "Estirar"), + ("Good image quality", "Buena calidad de imagen"), + ("Balanced", "Equilibrado"), + ("Optimize reaction time", "Optimizar el tiempo de reacción"), + ("Custom", "Personalizado"), + ("Show remote cursor", "Mostrar cursor remoto"), + ("Disable clipboard", "Deshabilitar portapapeles"), + ("Lock after session end", "Bloquear después del final de la sesión"), + ("Insert", "Insertar"), + ("Insert Lock", "Insertar bloqueo"), + ("Refresh", "Actualizar"), + ("ID does not exist", "ID no existe"), + ("Failed to connect to rendezvous server", "No se pudo conectar al servidor de encuentro"), + ("Please try later", "Por favor intente mas tarde"), + ("Remote desktop is offline", "El escritorio remoto está fuera de línea"), + ("Key mismatch", "La clave no coincide"), + ("Timeout", "Timeout"), + ("Failed to connect to relay server", "No se pudo conectar al servidor de retransmisión"), + ("Failed to connect via rendezvous server", "No se pudo conectar a través del servidor de encuentro"), + ("Failed to connect via relay server", "No se pudo conectar a través del servidor de retransmisión"), + ("Failed to make direct connection to remote desktop", "No se pudo establecer la conexión directa con el escritorio remoto"), + ("Set Password", "Configurar la clave"), + ("OS Password", "Contraseña del sistema operativo"), + ("install_tip", "Debido al Control de cuentas de usuario, es posible que RustDesk no funcione correctamente como escritorio remoto. Para evitar este problema, haga clic en el botón de abajo para instalar RustDesk a nivel de sistema."), + ("Click to upgrade", "Clic para actualizar"), + ("Click to download", "Clic para descargar"), + ("Click to update", "Fare clic per aggiornare"), + ("Configure", "Configurar"), + ("config_acc", "Para controlar su escritorio desde el exterior, debe otorgar permiso a RustDesk de \"Accesibilidad\"."), + ("config_screen", "Para controlar su escritorio desde el exterior, debe otorgar permiso a RustDesk de \"Grabación de pantalla\"."), + ("Installing ...", "Instalando ..."), + ("Install", "Instalar"), + ("Installation", "Instalación"), + ("Installation Path", "Ruta de instalación"), + ("Create start menu shortcuts", "Crear accesos directos al menú de inicio"), + ("Create desktop icon", "Crear icono de escritorio"), + ("agreement_tip", "Al iniciar la instalación, acepta los términos del acuerdo de licencia."), + ("Accept and Install", "Aceptar e instalar"), + ("End-user license agreement", "Acuerdo de licencia de usuario final"), + ("Generating ...", "Generando ..."), + ("Your installation is lower version.", "Su instalación es una versión inferior."), + ("not_close_tcp_tip", "No cierre esta ventana mientras esté usando el túnel"), + ("Listening ...", "Escuchando ..."), + ("Remote Host", "Servidor remoto"), + ("Remote Port", "Puerto remoto"), + ("Action", "Acción"), + ("Add", "Agregar"), + ("Local Port", "Puerto local"), + ("setup_server_tip", "Para una conexión más rápida, configure su propio servidor"), + ("Too short, at least 6 characters.", "Demasiado corto, al menos 6 caracteres."), + ("The confirmation is not identical.", "La confirmación no es idéntica."), + ("Permissions", "Permisos"), + ("Accept", "Aceptar"), + ("Dismiss", "Cancelar"), + ("Disconnect", "Desconectar"), + ("Allow using keyboard and mouse", "Permitir el uso del teclado y el mouse"), + ("Allow using clipboard", "Permitir usar portapapeles"), + ("Allow hearing sound", "Permitir escuchar sonido"), + ("Allow file copy and paste", "Permitir copiar y pegar archivos"), + ("Connected", "Conectado"), + ("Direct and encrypted connection", "Conexión directa y encriptada"), + ("Relayed and encrypted connection", "Conexión retransmitida y cifrada"), + ("Direct and unencrypted connection", "Conexión directa y sin cifrar"), + ("Relayed and unencrypted connection", "Conexión retransmitida y sin cifrar"), + ("Enter Remote ID", "Ingrese el ID remoto"), + ("Enter your password", "Ingrese su contraseña"), + ("Logging in...", "Iniciando sesión..."), + ("Enable RDP session sharing", "Habilitar el uso compartido de sesiones RDP"), + ("Auto Login", "Ingreso automático"), + ("Enable Direct IP Access", "Habilitar acceso IP directo"), + ("Rename", "Renombrar"), + ("Space", "Espacio"), + ("Create Desktop Shortcut", "Crear acceso directo del escritorio"), + ("Change Path", "Cambiar ruta"), + ("Create Folder", "Crear carpeta"), + ("Please enter the folder name", "Por favor ingrese el nombre de la carpeta"), + ("Fix it", "Resolver"), + ("Warning", "Aviso"), + ("Login screen using Wayland is not supported", "La pantalla de inicio de sesión con Wayland no es compatible"), + ("Reboot required", "Reinicio requerido"), + ("Unsupported display server ", "Servidor de visualización no compatible"), + ("x11 expected", "x11 necesario"), + ("Port", "Puerto"), + ("Settings", "Ajustes"), + ("Username", " Nombre de usuario"), + ("Invalid port", "Puerto inválido"), + ("Closed manually by the peer", "Cerrado manualmente por el par"), + ("Enable remote configuration modification", "Habilitar modificación de configuración remota"), + ("Run without install", "Ejecutar sin instalar"), + ("Always connected via relay", "Siempre conectado a través de relay"), + ("Always connect via relay", "Conéctese siempre a través de relay"), + ("whitelist_tip", "Solo las direcciones IP autorizadas pueden conectarse a este escritorio"), + ("Login", "Iniciar sesión"), + ("Logout", "Salir"), + ("Tags", "Tags"), + ("Search ID", "Buscar ID"), + ("Current Wayland display server is not supported", "El servidor de visualización actual de Wayland no es compatible"), + ("whitelist_sep", "Separados por coma, punto y coma, espacio o nueva línea"), + ("Add ID", "Agregar ID"), + ("Add Tag", "Agregar tag"), + ("Unselect all tags", "Deseleccionar todos los tags"), + ("Network error", "Error de red"), + ("Username missed", "Olvidó su nombre de usuario"), + ("Password missed", "Olvidó su contraseña"), + ("Wrong credentials", "Credenciales incorrectas"), + ("Edit Tag", "Editar tag"), + ("Unremember Password", "Olvidaste tu contraseña"), + ("Favorites", "Favoritos"), + ("Add to Favorites", "Agregar a favoritos"), + ("Remove from Favorites", "Quitar de favoritos"), + ("Empty", "Vacío"), + ("Invalid folder name", "Nombre de carpeta no válido"), + ("Socks5 Proxy", "Proxy Socks5"), + ("Hostname", "Nombre de host"), + ("Discovered", "Descubierto"), + ("install_daemon_tip", "Para comenzar en el encendido, debe instalar el servicio del sistema."), + ("Remote ID", "ID remoto"), + ("Paste", "Pegar"), + ("Paste here?", "Pegar aqui?"), + ("Are you sure to close the connection?", "Estás seguro de cerrar la conexión?"), + ("Download new version", "Descargar nueva versión"), + ("Touch mode", "Modo táctil"), + ("Mouse mode", "Modo ratón"), + ("One-Finger Tap", "Toque con un dedo"), + ("Left Mouse", "Ratón izquierdo"), + ("One-Long Tap", "Un toque largo"), + ("Two-Finger Tap", "Toque con dos dedos"), + ("Right Mouse", "Botón derecho"), + ("One-Finger Move", "Movimiento con un dedo"), + ("Double Tap & Move", "Toca dos veces y mueve"), + ("Mouse Drag", "Arrastre de ratón"), + ("Three-Finger vertically", "Tres dedos verticalmente"), + ("Mouse Wheel", "Rueda de ratón"), + ("Two-Finger Move", "Movimiento con dos dedos"), + ("Canvas Move", "Movimiento de lienzo"), + ("Pinch to Zoom", "Pellizcar para ampliar"), + ("Canvas Zoom", "Ampliar lienzo"), + ("Reset canvas", "Restablecer lienzo"), + ("No permission of file transfer", "Sin permiso de transferencia de archivos"), + ("Note", "Nota"), + ("Connection", "Conexión"), + ("Share Screen", "Compartir pantalla"), + ("CLOSE", "CERRAR"), + ("OPEN", "ABRIR"), + ("Chat", "Chat"), + ("Total", "Total"), + ("items", "items"), + ("Selected", "Seleccionado"), + ("Screen Capture", "Captura de pantalla"), + ("Input Control", "Control de entrada"), + ("Audio Capture", "Captura de audio"), + ("File Connection", "Conexión de archivos"), + ("Screen Connection", "Conexión de pantalla"), + ("Do you accept?", "Aceptas?"), + ("Open System Setting", "Configuración del sistema abierto"), + ("How to get Android input permission?", "Cómo obtener el permiso de entrada de Android?"), + ("android_input_permission_tip1", "Para que un dispositivo remoto controle su dispositivo Android a través del mouse o toque, debe permitir que RustDesk use el servicio de \"Accesibilidad\"."), + ("android_input_permission_tip2", "Vaya a la página de configuración del sistema que se abrirá a continuación, busque y acceda a [Servicios instalados], active el servicio [RustDesk Input]."), + ("android_new_connection_tip", "Se recibió una nueva solicitud de control para el dispositivo actual."), + ("android_service_will_start_tip", "Habilitar la captura de pantalla iniciará automáticamente el servicio, lo que permitirá que otros dispositivos soliciten una conexión desde este dispositivo."), + ("android_stop_service_tip", "Cerrar el servicio cerrará automáticamente todas las conexiones establecidas."), + ("android_version_audio_tip", "La versión actual de Android no admite la captura de audio, actualice a Android 10 o posterior."), + ("android_start_service_tip", "Toque el permiso [Iniciar servicio] o ABRIR [Captura de pantalla] para iniciar el servicio de uso compartido de pantalla."), + ("Account", "Cuenta"), + ("Overwrite", "Sobrescribir"), + ("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"), + ("Quit", "Salir"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("Help", "Ayuda"), + ("Failed", "Fallido"), + ("Succeeded", "Logrado"), + ("Someone turns on privacy mode, exit", "Alguien active el modo privacidad, salga"), + ("Unsupported", "No soportado"), + ("Peer denied", "Par negado"), + ("Please install plugins", "Instale complementos"), + ("Peer exit", "Par salio"), + ("Failed to turn off", "Error al apagar"), + ("Turned off", "Apagado"), + ("In privacy mode", "En modo de privacidad"), + ("Out privacy mode", "Fuera del modo de privacidad"), + ].iter().cloned().collect(); +} diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 8f6c3f83b..d2d41c550 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Autoriser l'utilisation du clavier et de la souris"), ("Allow using clipboard", "Autoriser l'utilisation du presse-papier"), ("Allow hearing sound", "Autoriser l'audition du son"), - ("Allow file transfer", "Autoriser le transfert de fichiers"), - ("File transfer", "Transfert de fichiers"), + ("Allow file copy and paste", "Autoriser le copier-coller de fichiers"), ("Connected", "Connecté"), ("Direct and encrypted connection", "Connexion directe chiffrée"), ("Relayed and encrypted connection", "Connexion relais chiffrée"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "Choisi"), ("Screen Capture", "Capture d'écran"), ("Input Control", "Contrôle de saisie"), - ("File Transfer", "Transfert de fichier"), ("Audio Capture", "Capture audio"), ("File Connection", "Connexion de fichier"), ("Screen Connection", "Connexion de l'écran"), @@ -265,8 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."), ("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."), ("Account", "Compte"), + ("Overwrite", "Écraser"), + ("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"), ("Quit", "Quitter"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Aider"), + ("Failed", "échouer"), + ("Succeeded", "Succès"), + ("Someone turns on privacy mode, exit", "Quelqu'un active le mode de confidentialité, quittez"), + ("Unsupported", "Non pris en charge"), + ("Peer denied", "Pair refusé"), + ("Please install plugins", "Veuillez installer les plugins"), + ("Peer exit", "Sortie des pairs"), + ("Failed to turn off", "Échec de la désactivation"), + ("Turned off", "Éteindre"), + ("In privacy mode", "en mode privé"), + ("Out privacy mode", "hors mode de confidentialité"), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 5b8475995..2e2f2fd1c 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -157,7 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Izinkan menggunakan keyboard dan mouse"), ("Allow using clipboard", "Izinkan menggunakan papan klip"), ("Allow hearing sound", "Izinkan mendengarkan suara"), - ("Allow file transfer", "Izinkan transfer file"), + ("Allow file copy and paste", "Izinkan penyalinan dan tempel file"), ("File transfer", "Transfer file"), ("Connected", "Terkoneksi"), ("Direct and encrypted connection", "Koneksi langsung dan terenkripsi"), @@ -250,7 +250,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "Dipilih"), ("Screen Capture", "Rekam Layar"), ("Input Control", "kontrol input"), - ("File Transfer", "Transfer File"), ("Audio Capture", "Rekam Suara"), ("File Connection", "Koneksi File"), ("Screen Connection", "koneksi layar"), @@ -265,8 +264,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."), ("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."), ("Account", "Akun"), + ("Overwrite", "Timpa"), + ("This file exists, skip or overwrite this file?", ""), ("Quit", "Keluar"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Bantuan"), + ("Failed", "Gagal"), + ("Succeeded", "Berhasil"), + ("Someone turns on privacy mode, exit", "Seseorang mengaktifkan mode privasi, keluar"), + ("Unsupported", "Tidak didukung"), + ("Peer denied", "Rekan ditolak"), + ("Please install plugins", "Silakan instal plugin"), + ("Peer exit", "keluar rekan"), + ("Failed to turn off", "Gagal mematikan"), + ("Turned off", "Matikan"), + ("In privacy mode", "Dalam mode privasi"), + ("Out privacy mode", "Keluar dari mode privasi"), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 15d741297..4bf692169 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Consenti l'uso di tastiera e mouse"), ("Allow using clipboard", "Consenti l'uso degli appunti"), ("Allow hearing sound", "Consenti la riproduzione dell'audio"), - ("Allow file transfer", "Consenti trasferimento file"), - ("File transfer", "Trasferimento di file"), + ("Allow file copy and paste", "Consenti copia e incolla di file"), ("Connected", "Connesso"), ("Direct and encrypted connection", "Connessione diretta e cifrata"), ("Relayed and encrypted connection", "Connessione tramite relay e cifrata"), @@ -206,20 +205,20 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Password missed", "Password dimenticata"), ("Wrong credentials", "Credenziali errate"), ("Edit Tag", "Modifica tag"), - ("Unremember Password", "Non ricordare la password"), + ("Unremember Password", "Dimentica password"), ("Favorites", "Preferiti"), ("Add to Favorites", "Aggiungi ai preferiti"), ("Remove from Favorites", "Rimuovi dai preferiti"), ("Empty", "Vuoto"), ("Invalid folder name", "Nome della cartella non valido"), - ("Socks5 Proxy", "Socks5 Proxy"), + ("Socks5 Proxy", "Proxy Socks5"), ("Hostname", "Nome host"), - ("Discovered", "Scoperto"), - ("install_daemon_tip", "Per iniziare all'avvio, è necessario installare il servizio di sistema."), + ("Discovered", "Rilevati"), + ("install_daemon_tip", "Per avviarsi all'accensione, è necessario installare il servizio di sistema."), ("Remote ID", "ID remoto"), ("Paste", "Impasto"), ("Paste here?", "Incolla qui?"), - ("Are you sure to close the connection?", "Sei sicuro di chiudere la connessione?"), + ("Are you sure to close the connection?", "Sei sicuro di voler chiudere la connessione?"), ("Download new version", "Scarica nuova versione"), ("Touch mode", "Modalità tocco"), ("Mouse mode", "Modalità mouse"), @@ -244,29 +243,41 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Share Screen", "Condividi schermo"), ("CLOSE", "CHIUDERE"), ("OPEN", "APRIRE"), - ("Chat", "Chiacchierata"), + ("Chat", "Chat"), ("Total", "Totale"), ("items", "Oggetti"), ("Selected", "Selezionato"), ("Screen Capture", "Cattura schermo"), ("Input Control", "Controllo di input"), - ("File Transfer", "Trasferimento di file"), ("Audio Capture", "Acquisizione audio"), ("File Connection", "Connessione file"), ("Screen Connection", "Connessione schermo"), ("Do you accept?", "Accetti?"), - ("Open System Setting", "Apri Impostazioni di sistema"), - ("How to get Android input permission?", "Come ottenere l'autorizzazione di input Android?"), + ("Open System Setting", "Apri impostazioni di sistema"), + ("How to get Android input permission?", "Come ottenere l'autorizzazione di input su Android?"), ("android_input_permission_tip1", "Affinché un dispositivo remoto possa controllare il tuo dispositivo Android tramite mouse o tocco, devi consentire a RustDesk di utilizzare il servizio \"Accessibilità\"."), - ("android_input_permission_tip2", "Vai alla pagina delle impostazioni di sistema successiva, trova e accedi a [Servizi installati], attiva il servizio [RustDesk Input]."), - ("android_new_connection_tip", "È stata ricevuta una nuova richiesta di controllo, desidera controllare il dispositivo corrente."), + ("android_input_permission_tip2", "Vai alla pagina delle impostazioni di sistema che si aprirà di seguito, trova e accedi a [Servizi installati], attiva il servizio [RustDesk Input]."), + ("android_new_connection_tip", "È stata ricevuta una nuova richiesta di controllo per il dispositivo corrente."), ("android_service_will_start_tip", "L'attivazione di Cattura schermo avvierà automaticamente il servizio, consentendo ad altri dispositivi di richiedere una connessione da questo dispositivo."), ("android_stop_service_tip", "La chiusura del servizio chiuderà automaticamente tutte le connessioni stabilite."), ("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."), ("android_start_service_tip", "Toccare [Avvia servizio] o APRI l'autorizzazione [Cattura schermo] per avviare il servizio di condivisione dello schermo."), ("Account", "Account"), + ("Overwrite", "Sovrascrivi"), + ("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"), ("Quit", "Esci"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Aiuto"), + ("Failed", "Fallito"), + ("Succeeded", "Successo"), + ("Someone turns on privacy mode, exit", "Qualcuno attiva la modalità privacy, esci"), + ("Unsupported", "Non supportato"), + ("Peer denied", "Pari negato"), + ("Please install plugins", "Si prega di installare i plugin"), + ("Peer exit", "Uscita tra pari"), + ("Failed to turn off", "Impossibile spegnere"), + ("Turned off", "Spegni"), + ("In privacy mode", "In modalità privacy"), + ("Out privacy mode", "Fuori modalità privacy"), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index d4a6824bf..e51d5f5f9 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Permitir o uso de teclado e mouse"), ("Allow using clipboard", "Permitir o uso da área de transferência"), ("Allow hearing sound", "Permitir escutar som"), - ("Allow file transfer", "Permitir transferência de arquivo"), - ("File transfer", "Transferência de arquivo"), + ("Allow file copy and paste", "Permitir copiar e pegar arquivos"), ("Connected", "Conectado"), ("Direct and encrypted connection", "Conexão direta e criptografada"), ("Relayed and encrypted connection", "Conexão via relay e criptografada"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "Selecionado"), ("Screen Capture", "Captura de Tela"), ("Input Control", "Controle de Entrada"), - ("File Transfer", "Transferência de Arquivo"), ("Audio Capture", "Captura de Áudio"), ("File Connection", "Conexão de Arquivo"), ("Screen Connection", "Conexão de Tela"), @@ -265,8 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou maior."), ("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Tela] para iniciar o serviço de compartilhamento de tela."), ("Account", "Conta"), + ("Overwrite", "Substituir"), + ("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"), ("Quit", "Saída"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ajuda"), + ("Failed", "Falhou"), + ("Succeeded", "Conseguiu"), + ("Someone turns on privacy mode, exit", "Alguém liga o modo de privacidade, saia"), + ("Unsupported", "Sem suporte"), + ("Peer denied", "Par negado"), + ("Please install plugins", "Por favor instale plugins"), + ("Peer exit", "Saída de pares"), + ("Failed to turn off", "Falha ao desligar"), + ("Turned off", "Desligado"), + ("In privacy mode", "No modo de privacidade"), + ("Out privacy mode", "Fora do modo de privacidade"), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 8cc699895..88245b1d0 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -35,9 +35,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("About", "О RustDesk"), ("Mute", "Отключить звук"), ("Audio Input", "Аудиовход"), - ("ID Server", "ID сервер"), + ("ID Server", "ID-сервер"), ("Relay Server", "Сервер ретрансляции"), - ("API Server", "API сервер"), + ("API Server", "API-сервер"), ("invalid_http", "Должен начинаться с http:// или https://"), ("Invalid IP", "Неверный IP-адрес"), ("id_change_tip", "Допускаются только символы a-z, A-Z, 0-9 и _ (подчеркивание). Первая буква должна быть a-z, A-Z. Длина от 6 до 16"), @@ -48,7 +48,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Cancel", "Отменить"), ("Skip", "Пропустить"), ("Close", "Закрыть"), - ("Retry", "Попробовать еще раз"), + ("Retry", "Попробовать снова"), ("OK", "ОК"), ("Password Required", "Требуется пароль"), ("Please enter your password", "Пожалуйста, введите ваш пароль"), @@ -57,9 +57,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Do you want to enter again?", "Вы хотите снова войти?"), ("Connection Error", "Ошибка подключения"), ("Error", "Ошибка"), - ("Reset by the peer", "Сброшено узлом"), + ("Reset by the peer", "Сброшено пиром"), ("Connecting...", "Подключение..."), - ("Connection in progress. Please wait.", "Выполняется подключение. Подождите."), + ("Connection in progress. Please wait.", "Выполняется подключение. Пожалуйста, подождите."), ("Please try 1 minute later", "Попробуйте через 1 минуту"), ("Login Error", "Ошибка входа"), ("Successful", "Операция успешна"), @@ -92,7 +92,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Waiting", "Ожидание"), ("Finished", "Завершено"), ("Speed", "Скорость"), - ("Custom Image Quality", "Пользовательские настройки изображения"), + ("Custom Image Quality", "Пользовательское качество изображения"), ("Privacy mode", "Режим конфиденциальности"), ("Block user input", "Блокировать пользовательский ввод"), ("Unblock user input", "Разблокировать пользовательский ввод"), @@ -113,7 +113,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("ID does not exist", "ID не существует"), ("Failed to connect to rendezvous server", "Не удалось подключиться к промежуточному серверу"), ("Please try later", "Пожалуйста, попробуйте позже"), - ("Remote desktop is offline", "Удаленный рабочий стол не онлайн"), + ("Remote desktop is offline", "Удаленный рабочий стол не в сети"), ("Key mismatch", "Несоответствие ключей"), ("Timeout", "Тайм-аут"), ("Failed to connect to relay server", "Не удалось подключиться к серверу ретрансляции"), @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Разрешить использование клавиатуры и мыши"), ("Allow using clipboard", "Разрешить использование буфера обмена"), ("Allow hearing sound", "Разрешить передачу звука"), - ("Allow file transfer", "Разрешить передачу файлов"), - ("File transfer", "Передача файлов"), + ("Allow file copy and paste", "Разрешить копирование и вставку файлов"), ("Connected", "Подключено"), ("Direct and encrypted connection", "Прямое и шифрованное соединение"), ("Relayed and encrypted connection", "Коммутируемое и зашифрованное соединение"), @@ -168,7 +167,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enter your password", "Введите пароль"), ("Logging in...", "Вход..."), ("Enable RDP session sharing", "Включить общий доступ к сеансу RDP"), - ("Auto Login", "Автоматический вход (действителен, только если вы установили \"Завершения пользовательского сеанса после завершения удалённого подключения\""), + ("Auto Login", "Автоматический вход (действителен, только если вы установили \"Завершение пользовательского сеанса после завершения удалённого подключения\""), ("Enable Direct IP Access", "Включить прямой IP-доступ"), ("Rename", "Переименовать"), ("Space", "Место"), @@ -246,11 +245,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("OPEN", "ОТКРЫТЬ"), ("Chat", "Чат"), ("Total", "Всего"), - ("items", "обьекты"), + ("items", "элементы"), ("Selected", "Выбрано"), ("Screen Capture", "Захват экрана"), ("Input Control", "Входной контроль"), - ("File Transfer", "Передача файлов"), ("Audio Capture", "Захват аудио"), ("File Connection", "Файловое соединение"), ("Screen Connection", "Подключение экрана"), @@ -270,5 +268,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать этот файл?"), ("doc_mac_permission", "https://rustdesk.com/docs/ru/manual/mac/#включение-разрешений"), ("Help", "Помощь"), + ("Failed", "Неуспешный"), + ("Succeeded", "Успешно"), + ("Someone turns on privacy mode, exit", "Кто-то включает режим конфиденциальности, выйдите"), + ("Unsupported", "Не поддерживается"), + ("Peer denied", "Отказано в пире"), + ("Please install plugins", "Пожалуйста, установите плагины"), + ("Peer exit", "Одноранговый выход"), + ("Failed to turn off", "Не удалось отключить"), + ("Turned off", "Выключен"), + ("In privacy mode", "В режиме конфиденциальности"), + ("Out privacy mode", "Выход из режима конфиденциальности"), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs new file mode 100644 index 000000000..6cea1479b --- /dev/null +++ b/src/lang/sk.rs @@ -0,0 +1,283 @@ +lazy_static::lazy_static! { +pub static ref T: std::collections::HashMap<&'static str, &'static str> = + [ + ("Status", "Stav"), + ("Your Desktop", "Vaša plocha"), + ("desk_tip", "K svojej ploche sa môžete pripojiť pomocou zobrazeného ID a hesla."), + ("Password", "Heslo"), + ("Ready", "Pripravené"), + ("Established", "Nadviazané"), + ("connecting_status", "Pripájam sa na RusDesk server..."), + ("Enable Service", "Povoliť službu"), + ("Start Service", "Spustiť službu"), + ("Service is running", "Služba je aktívna"), + ("Service is not running", "Služba je vypnutá"), + ("not_ready_status", "Nepripravené. Skontrolujte svoje sieťové pripojenie."), + ("Control Remote Desktop", "Ovládať vzdialenú plochu"), + ("Transfer File", "Prenos súborov"), + ("Connect", "Pripojiť"), + ("Recent Sessions", "Nedávne pripojenie"), + ("Address Book", "Adresár kontaktov"), + ("Confirmation", "Potvrdenie"), + ("TCP Tunneling", "TCP tunelovanie"), + ("Remove", "Odstrániť"), + ("Refresh random password", "Aktualizovať náhodné heslo"), + ("Set your own password", "Nastavte si svoje vlastné heslo"), + ("Enable Keyboard/Mouse", "Povoliť klávesnicu/myš"), + ("Enable Clipboard", "Povoliť schránku"), + ("Enable File Transfer", "Povoliť prenos súborov"), + ("Enable TCP Tunneling", "Povoliť TCP tunelovanie"), + ("IP Whitelisting", "Zoznam povolených IP adries"), + ("ID/Relay Server", "ID/Prepojovací server"), + ("Stop service", "Zastaviť službu"), + ("Change ID", "Zmeniť ID"), + ("Website", "Webová stránka"), + ("About", "O RustDesk"), + ("Mute", "Stíšiť"), + ("Audio Input", "Zvukový vstup"), + ("ID Server", "ID server"), + ("Relay Server", "Prepojovací server"), + ("API Server", "API server"), + ("invalid_http", "Musí začínať http:// alebo https://"), + ("Invalid IP", "Neplatná IP adresa"), + ("id_change_tip", "Povolené sú len znaky a-z, A-Z, 0-9 a _ (podčiarkovník). Prvý znak musí byť a-z, A-Z. Dĺžka musí byť medzi 6 a 16 znakmi."), + ("Invalid format", "Neplatný formát"), + ("server_not_support", "Zatiaľ serverom nepodporované"), + ("Not available", "Nie je k dispozícii"), + ("Too frequent", "Príliš často"), + ("Cancel", "Zrušiť"), + ("Skip", "Preskočiť"), + ("Close", "Zatvoriť"), + ("Retry", "Zopakovať"), + ("OK", "OK"), + ("Password Required", "Vyžaduje sa heslo"), + ("Please enter your password", "Zadajte vaše heslo"), + ("Remember password", "Zapamätať heslo"), + ("Wrong Password", "Chybné heslo"), + ("Do you want to enter again?", "Chcete ho znova zadať?"), + ("Connection Error", "Chyba spojenia"), + ("Error", "Chyba"), + ("Reset by the peer", "Odmietnuté druhou stranou spojenia"), + ("Connecting...", "Pripájanie sa..."), + ("Connection in progress. Please wait.", "Pokúšam sa pripojiť. Počkajte chvíľu."), + ("Please try 1 minute later", "Skúte znova za minútu, alebo ešte neskôr"), + ("Login Error", "Chyba prihlásenia"), + ("Successful", "Úspech"), + ("Connected, waiting for image...", "Pripojené, čakám na obraz..."), + ("Name", "Názov"), + ("Type", "Typ"), + ("Modified", "Zmenené"), + ("Size", "Veľkosť"), + ("Show Hidden Files", "Zobrazovať skryté súbory"), + ("Receive", "Prijať"), + ("Send", "Odoslať"), + ("Refresh File", "Aktualizovať súbor"), + ("Local", "Miestne"), + ("Remote", "Vzdialené"), + ("Remote Computer", "Vzdialený počítač"), + ("Local Computer", "Miestny počítač"), + ("Confirm Delete", "Potvrdenie zmazania"), + ("Delete", "Zmazať"), + ("Properties", "Vlastnosti"), + ("Multi Select", "Viacnásobný výber"), + ("Empty Directory", "Prázdny adresár"), + ("Not an empty directory", "Nie prázdny adresár"), + ("Are you sure you want to delete this file?", "Ste si istý, že chcete zmazať tento súbor?"), + ("Are you sure you want to delete this empty directory?", "Ste si istý, že chcete zmazať tento adresár?"), + ("Are you sure you want to delete the file of this directory?", "Ste si istý, že chcete zmazať tento súbor alebo adresár?"), + ("Do this for all conflicts", "Všetky konflikty riešiť týmto spôsobom"), + ("This is irreversible!", "Toto je nezvratná operácia"), + ("Deleting", "Mazanie"), + ("files", "súbory"), + ("Waiting", "Čaká sa"), + ("Finished", "Ukončené"), + ("Speed", "Rýchlosť"), + ("Custom Image Quality", "Vlastná kvalita obrazu"), + ("Privacy mode", "Režim súkromia"), + ("Block user input", "Blokovať vstupné zariadenia užívateľa"), + ("Unblock user input", "Odblokovať vstupné zariadenia užívateľa"), + ("Adjust Window", "Prispôsobiť okno"), + ("Original", "Pôvodný"), + ("Shrink", "Zmenšené"), + ("Stretch", "Roztiahnuté"), + ("Good image quality", "Dobrá kvalita obrazu"), + ("Balanced", "Vyvážené"), + ("Optimize reaction time", "Optimalizované pre čas odozvy"), + ("Custom", "Vlastné"), + ("Show remote cursor", "Zobrazovať vzdialený ukazovateľ myši"), + ("Disable clipboard", "Vypnúť schránku"), + ("Lock after session end", "Po skončení uzamknúť plochu"), + ("Insert", "Vložiť"), + ("Insert Lock", "Uzamknúť"), + ("Refresh", "Aktualizovať"), + ("ID does not exist", "ID neexistuje"), + ("Failed to connect to rendezvous server", "Nepodarilo sa pripojiť k zoznamovaciemu serveru"), + ("Please try later", "Vyskúšajte neskôr"), + ("Remote desktop is offline", "Vzdialená plocha je pripojená"), + ("Key mismatch", "Kľúče sa nezhodujú"), + ("Timeout", "Čas pre nadviazanie pripojenia vypršal"), + ("Failed to connect to relay server", "Nepodarilo sa pripojiť k prepojovaciemu serveru"), + ("Failed to connect via rendezvous server", "Nepodarilo sa pripojiť cez zoznamovací server"), + ("Failed to connect via relay server", "Nepodarilo sa pripojiť cez prepojovací server"), + ("Failed to make direct connection to remote desktop", "Nepodarilo sa nadviazať priamu komunikáciu so vzdialenou plochou"), + ("Set Password", "Nastaviť heslo"), + ("OS Password", "Heslo do operačného systému"), + ("install_tip", "V niektorých prípadoch RustDesk nefunguje správne z dôvodu riadenia užívateľských oprávnení (UAC). Vyhnete sa tomu kliknutím na nižšie zobrazene tlačítko a nainštalovaním RuskDesk do systému."), + ("Click to upgrade", "Kliknutím nainštalujete aktualizáciu"), + ("Click to download", "Kliknutím potvrďte stiahnutie"), + ("Click to update", "Kliknutím aktualizovať"), + ("Configure", "Nastaviť"), + ("config_acc", "Aby bolo možné na diaľku ovládať vašu plochu, je potrebné aplikácii RustDesk udeliť práva \"Dostupnosť\"."), + ("config_screen", "Aby bolo možné na diaľku sledovať vašu obrazovku, je potrebné aplikácii RustDesk udeliť práva \"Zachytávanie obsahu obrazovky\"."), + ("Installing ...", "Inštaluje sa"), + ("Install", "Inštalovať"), + ("Installation", "Inštalácia"), + ("Installation Path", "Inštalačný adresár"), + ("Create start menu shortcuts", "Vytvoriť zástupcu do ponuky Štart"), + ("Create desktop icon", "Vytvoriť zástupcu na plochu"), + ("agreement_tip", "Spustením inštalácie prijímate licenčné podmienky."), + ("Accept and Install", "Prijať a inštalovať"), + ("End-user license agreement", "Licenčné podmienky dohodnuté s koncovým užívateľom"), + ("Generating ...", "Generujem ..."), + ("Your installation is lower version.", "Vaša inštalácia je staršia"), + ("not_close_tcp_tip", "Nezatvárajte toto okno po celý čas, kedy používate TCP tunel"), + ("Listening ...", "Čakám na pripojenie ..."), + ("Remote Host", "Vzdialený počítač"), + ("Remote Port", "Vzdialený port"), + ("Action", "Akcia"), + ("Add", "Pridať"), + ("Local Port", "Lokálny port"), + ("setup_server_tip", "Pre zrýchlenie pripojenia si nainštalujte svoj vlastný server"), + ("Too short, at least 6 characters.", "Príliš krátke, vyžaduje sa aspoň 6 znakov."), + ("The confirmation is not identical.", "Potvrdenie nie je zhodné."), + ("Permissions", "Práva"), + ("Accept", "Prijať"), + ("Dismiss", "Odmietnuť"), + ("Disconnect", "Odpojiť"), + ("Allow using keyboard and mouse", "Povoliť používanie klávesnice a myši"), + ("Allow using clipboard", "Povoliť používanie schránky"), + ("Allow hearing sound", "Povoliť zvuky"), + ("Allow file copy and paste", "Povoliť kopírovanie a vkladanie súborov"), + ("Connected", "Pripojené"), + ("Direct and encrypted connection", "Priame a šifrované spojenie"), + ("Relayed and encrypted connection", "Sprostredkované a šifrované spojenie"), + ("Direct and unencrypted connection", "Priame a nešifrované spojenie"), + ("Relayed and unencrypted connection", "Sprostredkované a nešifrované spojenie"), + ("Enter Remote ID", "Zadajte ID vzdialenej plochy"), + ("Enter your password", "Zadajte svoje heslo"), + ("Logging in...", "Prihlasovanie sa...."), + ("Enable RDP session sharing", "Povoliť zdieľanie RDP relácie"), + ("Auto Login", "Automatické prihlásenie"), + ("Enable Direct IP Access", "Povoliť priame pripojenie cez IP"), + ("Rename", "Premenovať"), + ("Space", "Medzera"), + ("Create Desktop Shortcut", "Vytvoriť zástupcu na ploche"), + ("Change Path", "Zmeniť adresár"), + ("Create Folder", "Vytvoriť adresár"), + ("Please enter the folder name", "Zadajte názov adresára"), + ("Fix it", "Opraviť to"), + ("Warning", "Upozornenie"), + ("Login screen using Wayland is not supported", "Prihlasovacia obrazovka prostredníctvom Wayland nie je podporovaná"), + ("Reboot required", "Vyžaduje sa reštart"), + ("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"), + ("x11 expected", "očakáva sa x11"), + ("Port", "Port"), + ("Settings", "Nastavenia"), + ("Username", "Uživateľské meno"), + ("Invalid port", "Neplatný port"), + ("Closed manually by the peer", "Manuálne ukončené opačnou stranou pripojenia"), + ("Enable remote configuration modification", "Povoliť zmeny konfigurácie zo vzdialeného PC"), + ("Run without install", "Spustiť bez inštalácie"), + ("Always connected via relay", "Vždy pripojené cez prepájací server"), + ("Always connect via relay", "Vždy pripájať cez prepájací server"), + ("whitelist_tip", "Len vymenované IP adresy majú oprávnenie sa pripojiť k vzdialenej správe"), + ("Login", "Prihlásenie"), + ("Logout", "Odhlásenie"), + ("Tags", "Štítky"), + ("Search ID", "Hľadať ID"), + ("Current Wayland display server is not supported", "Zobrazovací (display) server Wayland nie je podporovaný"), + ("whitelist_sep", "Oddelené čiarkou, bodkočiarkou, medzerou alebo koncom riadku"), + ("Add ID", "Pridať ID"), + ("Add Tag", "Pridať štítok"), + ("Unselect all tags", "Zrušiť výber všetkých štítkov"), + ("Network error", "Chyba siete"), + ("Username missed", "Chýba užívateľské meno"), + ("Password missed", "Chýba heslo"), + ("Wrong credentials", "Nesprávne prihlasovacie údaje"), + ("Edit Tag", "Upraviť štítok"), + ("Unremember Password", "Zabudnúť heslo"), + ("Favorites", "Obľúbené"), + ("Add to Favorites", "Pridať medzi obľúbené"), + ("Remove from Favorites", "Odstrániť z obľúbených"), + ("Empty", "Prázdne"), + ("Invalid folder name", "Neplatný názov adresára"), + ("Socks5 Proxy", "Socks5 Proxy"), + ("Hostname", "Názov počítača"), + ("Discovered", "Objavené"), + ("install_daemon_tip", "Ak chcete, aby sa spúšťal pri štarte systému, musíte nainštalovať systémovú službu."), + ("Remote ID", "Vzdialené ID"), + ("Paste", "Vložiť"), + ("Paste here?", "Vložiť sem?"), + ("Are you sure to close the connection?", "Ste si istý, že chcete ukončiť spojenie?"), + ("Download new version", "Stiahnuť novú verziu"), + ("Touch mode", "Dotykový režim"), + ("Mouse mode", "Režim ovládania myšou"), + ("One-Finger Tap", "Klepnutie jedným prstom"), + ("Left Mouse", "Ľavé tlačidlo myši"), + ("One-Long Tap", "Jedno dlhé klepnutie"), + ("Two-Finger Tap", "Klepnutie dvoma prstami"), + ("Right Mouse", "Pravé tlačidlo myši"), + ("One-Finger Move", "Presúvanie jedným prstom"), + ("Double Tap & Move", "Dvojité klepnutie a presun"), + ("Mouse Drag", "Presun myšou"), + ("Three-Finger vertically", "Pohyb tromi prstami zvisle"), + ("Mouse Wheel", "Koliesko myši"), + ("Two-Finger Move", "Pohyb dvoma prstami"), + ("Canvas Move", "Pohyb zobrazenia"), + ("Pinch to Zoom", "Roztiahnutím prstov priblížiť"), + ("Canvas Zoom", "Priblíženie zobrazenia"), + ("Reset canvas", "Obnoviť zobrazenie"), + ("No permission of file transfer", "Prenos súborov nie je povolený"), + ("Note", "Poznámka"), + ("Connection", "Pripojenie"), + ("Share Screen", "Zdielať obrazovku"), + ("CLOSE", "ZATVORIŤ"), + ("OPEN", "OTVORIŤ"), + ("Chat", "Chat"), + ("Total", "Celkom"), + ("items", "položiek"), + ("Selected", "Vybrané"), + ("Screen Capture", "Snímanie obrazovky"), + ("Input Control", "Ovládanie vstupných zariadení"), + ("Audio Capture", "Snímanie zvuku"), + ("File Connection", "Pripojenie súborov"), + ("Screen Connection", "Pripojenie obrazu"), + ("Do you accept?", "Súhlasíte?"), + ("Open System Setting", "Otvorenie nastavení systému"), + ("How to get Android input permission?", "Ako v systéme Android povoliť oprávnenie písať zo vstupného zariadenia?"), + ("android_input_permission_tip1", "Aby bolo možné na diaľku ovládať vašu plochu pomocou myši alebo dotykov, je potrebné aplikácii RustDesk udeliť práva \"Dostupnosť\"."), + ("android_input_permission_tip2", "Prejdite na stránku nastavení systému, nájdite a vstúpte do [Stiahnuté služby], zapnite [RustDesk Input] službu."), + ("android_new_connection_tip", "Bola prijatá nová požiadavka na ovládanie vášho zariadenia."), + ("android_service_will_start_tip", "Zapnutie \"Zachytávanie obsahu obrazovky\" automaticky spistí službu, čo iným zariadeniam umožní požiadať o pripojenie k tomuto zariadeniu."), + ("android_stop_service_tip", "Zastavenie služby automaticky ukončí všetky naviazané spojenia."), + ("android_version_audio_tip", "Vaša verzia Androidu neumožňuje zaznamenávanie zvuku. Prejdite na verziu Android 10 alebo vyššiu."), + ("android_start_service_tip", "Klepnite na [Spustiť službu] alebo OTVORTE oprávnenie [Zachytávanie obsahu obrazovky], aby sa aktivovala služba zdieľania obrazovky."), + ("Account", "Účet"), + ("Overwrite", "Prepísať"), + ("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"), + ("Quit", "Ukončiť"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), + ("Help", "Nápoveda"), + ("Failed", "Nepodarilo sa"), + ("Succeeded", "Podarilo sa"), + ("Someone turns on privacy mode, exit", "Niekto zapne režim súkromia, ukončite ho"), + ("Unsupported", "Nepodporované"), + ("Peer denied", "Peer poprel"), + ("Please install plugins", "Nainštalujte si prosím pluginy"), + ("Peer exit", "Peer exit"), + ("Failed to turn off", "Nepodarilo sa vypnúť"), + ("Turned off", "Vypnutý"), + ("In privacy mode", "V režime súkromia"), + ("Out privacy mode", "Mimo režimu súkromia"), + ].iter().cloned().collect(); +} diff --git a/src/lang/template.rs b/src/lang/template.rs index 84be6ed8d..91c572e44 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -42,7 +42,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Invalid IP", ""), ("id_change_tip", ""), ("Invalid format", ""), - ("This function is turned off by the server", ""), + ("server_not_support", ""), ("Not available", ""), ("Too frequent", ""), ("Cancel", ""), @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", ""), ("Allow using clipboard", ""), ("Allow hearing sound", ""), - ("Allow file transfer", ""), - ("File transfer", ""), + ("Allow file copy and paste", ""), ("Connected", ""), ("Direct and encrypted connection", ""), ("Relayed and encrypted connection", ""), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", ""), ("Screen Capture", ""), ("Input Control", ""), - ("File Transfer", ""), ("Audio Capture", ""), ("File Connection", ""), ("Screen Connection", ""), @@ -270,5 +268,16 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Quit", ""), ("doc_mac_permission", ""), ("Help", ""), + ("Failed", ""), + ("Succeeded", ""), + ("Someone turns on privacy mode, exit", ""), + ("Unsupported", ""), + ("Peer denied", ""), + ("Please install plugins", ""), + ("Peer exit", ""), + ("Failed to turn off", ""), + ("Turned off", ""), + ("In privacy mode", ""), + ("Out privacy mode", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 02da22e2b..d82901298 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -42,7 +42,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Invalid IP", "Geçersiz IP adresi"), ("id_change_tip", "Yalnızca a-z, A-Z, 0-9 ve _ (alt çizgi) karakterlerini kullanabilirsiniz. İlk karakter a-z veya A-Z olmalıdır. Uzunluk 6 ile 16 karakter arasında olmalıdır."), ("Invalid format", "Hatalı Format"), - ("This function is turned off by the server", "Bu özellik sunucu tarafından kapatıldı"), + ("server_not_support", "Henüz sunucu tarafından desteklenmiyor"), ("Not available", "Erişilebilir değil"), ("Too frequent", "Çok sık"), ("Cancel", "İptal"), @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Klavye ve fare kullanımına izin ver"), ("Allow using clipboard", "Pano kullanımına izin ver"), ("Allow hearing sound", "Sesi duymaya izin ver"), - ("Allow file transfer", "Dosya aktarımına izin ver"), - ("File transfer", "Dosya transferi"), + ("Allow file copy and paste", "Dosya kopyalamaya ve yapıştırmaya izin ver"), ("Connected", "Bağlandı"), ("Direct and encrypted connection", "Doğrudan ve şifreli bağlantı"), ("Relayed and encrypted connection", "Aktarmalı ve şifreli bağlantı"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "Seçildi"), ("Screen Capture", "Ekran görüntüsü"), ("Input Control", "Giriş Kontrolü"), - ("File Transfer", "Dosya transferi"), ("Audio Capture", "Ses Yakalama"), ("File Connection", "Dosya Bağlantısı"), ("Screen Connection", "Ekran Bağlantısı"), @@ -265,8 +263,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."), ("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."), ("Account", "Hesap"), + ("Overwrite", "üzerine yaz"), + ("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"), ("Quit", "Çıkış"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Yardım"), + ("Failed", "Arızalı"), + ("Succeeded", "başarılı"), + ("Someone turns on privacy mode, exit", "Birisi gizlilik modunu açar, çık"), + ("Unsupported", "desteklenmiyor"), + ("Peer denied", "akran reddedildi"), + ("Please install plugins", "Lütfen eklentileri yükleyin"), + ("Peer exit", "akran çıkışı"), + ("Failed to turn off", "kapatılamadı"), + ("Turned off", "Kapalı"), + ("In privacy mode", "Gizlilik modunda"), + ("Out privacy mode", "Gizlilik modu dışında"), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index f72140af6..2de3f4414 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -157,8 +157,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "允許使用鍵盤和滑鼠"), ("Allow using clipboard", "允許使用剪貼簿"), ("Allow hearing sound", "允許分享音訊"), - ("Allow file transfer", "允許文件傳輸"), - ("File transfer", "文件傳輸"), + ("Allow file copy and paste", "允許文件複製和粘貼"), ("Connected", "已連接"), ("Direct and encrypted connection", "加密直接連線"), ("Relayed and encrypted connection", "加密轉送連線"), @@ -184,7 +183,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("x11 expected", "預期 x11"), ("Port", "連接埠"), ("Settings", "設定"), - ("Username", " 使用者名稱"), + ("Username", "使用者名稱"), ("Invalid port", "連接埠無效"), ("Closed manually by the peer", "由對方手動關閉"), ("Enable remote configuration modification", "啟用遠端更改設定"), @@ -250,7 +249,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Selected", "已選擇"), ("Screen Capture", "畫面錄製"), ("Input Control", "輸入控制"), - ("File Transfer", "檔案傳輸"), ("Audio Capture", "音訊錄製"), ("File Connection", "檔案連線"), ("Screen Connection", "畫面連線"), @@ -270,5 +268,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"), ("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"), ("Help", "幫助"), + ("Account", "帳戶"), + ("Failed", "失敗"), + ("Succeeded", "成功"), + ("Someone turns on privacy mode, exit", "其他用戶開啟隱私模式,退出"), + ("Unsupported", "不支持"), + ("Peer denied", "被控端拒絕"), + ("Please install plugins", "請安裝插件"), + ("Peer exit", "被控端退出"), + ("Failed to turn off", "退出失敗"), + ("Turned off", "退出"), + ("In privacy mode", "開啟隱私模式"), + ("Out privacy mode", "退出隱私模式"), ].iter().cloned().collect(); } diff --git a/src/lib.rs b/src/lib.rs index 1dda032e1..6aa186d96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,3 +49,6 @@ mod ui_interface; #[cfg(windows)] pub mod clipboard_file; + +#[cfg(all(windows, feature = "with_rc"))] +pub mod rc; diff --git a/src/main.rs b/src/main.rs index d802b8ae9..a5b1d7b04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,13 +95,17 @@ fn main() { hbb_common::allow_err!(platform::uninstall_me()); hbb_common::allow_err!(platform::install_me( "desktopicon startmenu", - "".to_owned() + "".to_owned(), + false, + false, )); return; } else if args[0] == "--silent-install" { hbb_common::allow_err!(platform::install_me( "desktopicon startmenu", - "".to_owned() + "".to_owned(), + true, + args.len() > 1, )); return; } @@ -130,7 +134,16 @@ fn main() { } } else if args[0] == "--import-config" { if args.len() == 2 { - import_config(&args[1]); + let filepath; + let path = std::path::Path::new(&args[1]); + if !path.is_absolute() { + let mut cur = std::env::current_dir().unwrap(); + cur.push(path); + filepath = cur.to_str().unwrap().to_string(); + } else { + filepath = path.to_str().unwrap().to_string(); + } + import_config(&filepath); } return; } else if args[0] == "--password" { diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 79d87492a..efd6476b6 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,7 +1,6 @@ use super::{CursorData, ResultType}; use hbb_common::{allow_err, bail, log}; use libc::{c_char, c_int, c_void}; -use std::io::prelude::*; use std::{ cell::RefCell, sync::{ @@ -371,8 +370,7 @@ fn get_display_server_of_session(session: &str) -> String { .replace("TTY=", "") .trim_end() .into(); - if let Ok(Some(xorg_results)) = - run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) + if let Ok(xorg_results) = run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) // And check if Xorg is running on that tty { if xorg_results.trim_end().to_string() != "" { @@ -441,9 +439,7 @@ pub fn current_is_wayland() -> bool { pub fn modify_default_login() -> String { let dsession = std::env::var("DESKTOP_SESSION").unwrap(); let user_name = std::env::var("USERNAME").unwrap(); - if let Ok(Some(x)) = - run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) - { + if let Ok(x) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION}-xorg.desktop".to_owned()) { if x.trim_end().to_string() != "" { match std::process::Command::new("pkexec") .args(vec![ @@ -471,7 +467,7 @@ pub fn modify_default_login() -> String { return "Fix failed! Please re-login with X server manually".to_owned(); } } - } else if let Ok(Some(z)) = + } else if let Ok(z) = run_cmds("ls /usr/share/* | grep ${DESKTOP_SESSION:0:-8}.desktop".to_owned()) { if z.trim_end().to_string() != "" { @@ -605,20 +601,11 @@ pub fn is_installed() -> bool { true } -fn run_cmds(cmds: String) -> ResultType> { - let mut tmp = std::env::temp_dir(); - tmp.push(format!("{}_{}", crate::get_app_name(), crate::get_time())); - let mut file = std::fs::File::create(&tmp)?; - file.write_all(cmds.as_bytes())?; - file.sync_all()?; - if let Ok(output) = std::process::Command::new("bash") - .arg(tmp.to_str().unwrap_or("")) - .output() - { - Ok(Some(String::from_utf8_lossy(&output.stdout).to_string())) - } else { - Ok(None) - } +pub fn run_cmds(cmds: String) -> ResultType { + let output = std::process::Command::new("sh") + .args(vec!["-c", &cmds]) + .output()?; + Ok(String::from_utf8_lossy(&output.stdout).to_string()) } fn get_env_tries(name: &str, uid: &str, n: usize) -> String { @@ -635,7 +622,7 @@ fn get_env_tries(name: &str, uid: &str, n: usize) -> String { fn get_env(name: &str, uid: &str) -> String { let cmd = format!("ps -u {} -o pid= | xargs -I__ cat /proc/__/environ 2>/dev/null | tr '\\0' '\\n' | grep '^{}=' | tail -1 | sed 's/{}=//g'", uid, name, name); log::debug!("Run: {}", &cmd); - if let Ok(Some(x)) = run_cmds(cmd) { + if let Ok(x) = run_cmds(cmd) { x.trim_end().to_string() } else { "".to_owned() diff --git a/src/platform/windows.rs b/src/platform/windows.rs index bcb9a8545..4e8f7e16a 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -9,7 +9,7 @@ use hbb_common::{ use std::io::prelude::*; use std::{ ffi::OsString, - io, mem, + fs, io, mem, sync::{Arc, Mutex}, time::{Duration, Instant}, }; @@ -404,6 +404,7 @@ extern "C" { fn has_rdp_service() -> BOOL; fn get_current_session(rdp: BOOL) -> DWORD; fn LaunchProcessWin(cmd: *const u16, session_id: DWORD, as_user: BOOL) -> HANDLE; + fn GetSessionUserTokenWin(lphUserToken: LPHANDLE, dwSessionId: DWORD, as_user: BOOL) -> BOOL; fn selectInputDesktop() -> BOOL; fn inputDesktopSelected() -> BOOL; fn is_windows_server() -> BOOL; @@ -558,7 +559,7 @@ async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType String { @@ -796,6 +797,49 @@ fn get_default_install_path() -> String { format!("{}\\{}", pf, crate::get_app_name()) } +pub fn check_update_broker_process() -> ResultType<()> { + // let (_, path, _, _) = get_install_info(); + let process_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE; + let origin_process_exe = crate::ui::win_privacy::ORIGIN_PROCESS_EXE; + + let exe_file = std::env::current_exe()?; + if exe_file.parent().is_none() { + bail!("Cannot get parent of current exe file"); + } + let cur_dir = exe_file.parent().unwrap(); + let cur_exe = cur_dir.join(process_exe); + + let ori_modified = fs::metadata(origin_process_exe)?.modified()?; + if let Ok(metadata) = fs::metadata(&cur_exe) { + if let Ok(cur_modified) = metadata.modified() { + if cur_modified == ori_modified { + return Ok(()); + } else { + log::info!( + "broker process updated, modify time from {:?} to {:?}", + cur_modified, + ori_modified + ); + } + } + } + + // Force update broker exe if failed to check modified time. + let cmds = format!( + " + chcp 65001 + taskkill /F /IM {broker_exe} + copy /Y \"{origin_process_exe}\" \"{cur_exe}\" + ", + broker_exe = process_exe, + origin_process_exe = origin_process_exe, + cur_exe = cur_exe.to_string_lossy().to_string(), + ); + run_cmds(cmds, false, "update_broker")?; + + Ok(()) +} + fn get_install_info_with_subkey(subkey: String) -> (String, String, String, String) { let mut path = get_reg_of(&subkey, "InstallLocation"); if path.is_empty() { @@ -811,24 +855,28 @@ fn get_install_info_with_subkey(subkey: String) -> (String, String, String, Stri } pub fn update_me() -> ResultType<()> { - let (_, _, _, exe) = get_install_info(); + let (_, path, _, exe) = get_install_info(); let src_exe = std::env::current_exe()?.to_str().unwrap_or("").to_owned(); let cmds = format!( " chcp 65001 sc stop {app_name} + taskkill /F /IM {broker_exe} taskkill /F /IM {app_name}.exe copy /Y \"{src_exe}\" \"{exe}\" + \"{src_exe}\" --extract \"{path}\" sc start {app_name} {lic} ", src_exe = src_exe, exe = exe, + broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE, + path = path, app_name = crate::get_app_name(), lic = register_licence(), ); std::thread::sleep(std::time::Duration::from_millis(1000)); - run_cmds(cmds, false)?; + run_cmds(cmds, false, "update")?; std::thread::sleep(std::time::Duration::from_millis(2000)); std::process::Command::new(&exe).arg("--tray").spawn().ok(); std::process::Command::new(&exe).spawn().ok(); @@ -857,7 +905,7 @@ fn get_after_install(exe: &str) -> String { ", ext=ext, exe=exe, app_name=app_name) } -pub fn install_me(options: &str, path: String) -> ResultType<()> { +pub fn install_me(options: &str, path: String, silent: bool, debug: bool) -> ResultType<()> { let uninstall_str = get_uninstall(); let mut path = path.trim_end_matches('\\').to_owned(); let (subkey, _path, start_menu, exe) = get_default_install_info(); @@ -881,7 +929,7 @@ pub fn install_me(options: &str, path: String) -> ResultType<()> { version_build = versions[2]; } - let tmp_path = "C:\\Windows\\temp"; + let tmp_path = std::env::temp_dir().to_string_lossy().to_string(); let mk_shortcut = write_cmds( format!( " @@ -897,6 +945,7 @@ oLink.Save exe = exe, ), "vbs", + "mk_shortcut", )? .to_str() .unwrap_or("") @@ -918,6 +967,7 @@ oLink.Save exe = exe, ), "vbs", + "uninstall_shortcut", )? .to_str() .unwrap_or("") @@ -938,6 +988,7 @@ oLink.Save exe = exe, ), "vbs", + "tray_shortcut", )? .to_str() .unwrap_or("") @@ -975,9 +1026,12 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\" chcp 65001 md \"{path}\" copy /Y \"{src_exe}\" \"{exe}\" +copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\" +\"{src_exe}\" --extract \"{path}\" reg add {subkey} /f reg add {subkey} /f /v DisplayIcon /t REG_SZ /d \"{exe}\" reg add {subkey} /f /v DisplayName /t REG_SZ /d \"{app_name}\" +reg add {subkey} /f /v DisplayVersion /t REG_SZ /d \"{version}\" reg add {subkey} /f /v Version /t REG_SZ /d \"{version}\" reg add {subkey} /f /v InstallLocation /t REG_SZ /d \"{path}\" reg add {subkey} /f /v Publisher /t REG_SZ /d \"{app_name}\" @@ -988,10 +1042,10 @@ reg add {subkey} /f /v UninstallString /t REG_SZ /d \"\\\"{exe}\\\" --uninstall\ reg add {subkey} /f /v EstimatedSize /t REG_DWORD /d {size} reg add {subkey} /f /v WindowsInstaller /t REG_DWORD /d 0 {lic} -\"{mk_shortcut}\" -\"{uninstall_shortcut}\" -\"{tray_shortcut}\" -copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\" +cscript \"{mk_shortcut}\" +cscript \"{uninstall_shortcut}\" +cscript \"{tray_shortcut}\" +copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\" {shortcuts} copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\" del /f \"{mk_shortcut}\" @@ -1010,6 +1064,8 @@ sc delete {app_name} path=path, src_exe=std::env::current_exe()?.to_str().unwrap_or(""), exe=exe, + ORIGIN_PROCESS_EXE = crate::ui::win_privacy::ORIGIN_PROCESS_EXE, + broker_exe=crate::ui::win_privacy::INJECTED_PROCESS_EXE, subkey=subkey, app_name=crate::get_app_name(), version=crate::VERSION, @@ -1026,21 +1082,23 @@ sc delete {app_name} lic=register_licence(), after_install=get_after_install(&exe), ); - run_cmds(cmds, false)?; + run_cmds(cmds, debug, "install")?; std::thread::sleep(std::time::Duration::from_millis(2000)); - std::process::Command::new(&exe).spawn()?; - std::process::Command::new(&exe).arg("--tray").spawn()?; - std::thread::sleep(std::time::Duration::from_millis(1000)); + if !silent { + std::process::Command::new(&exe).spawn()?; + std::process::Command::new(&exe).arg("--tray").spawn()?; + std::thread::sleep(std::time::Duration::from_millis(1000)); + } Ok(()) } pub fn run_after_install() -> ResultType<()> { let (_, _, _, exe) = get_install_info(); - run_cmds(get_after_install(&exe), true) + run_cmds(get_after_install(&exe), true, "after_install") } pub fn run_before_uninstall() -> ResultType<()> { - run_cmds(get_before_uninstall(), true) + run_cmds(get_before_uninstall(), true, "before_install") } fn get_before_uninstall() -> String { @@ -1051,11 +1109,13 @@ fn get_before_uninstall() -> String { chcp 65001 sc stop {app_name} sc delete {app_name} + taskkill /F /IM {broker_exe} taskkill /F /IM {app_name}.exe reg delete HKEY_CLASSES_ROOT\\.{ext} /f netsh advfirewall firewall delete rule name=\"{app_name} Service\" ", app_name = app_name, + broker_exe = crate::ui::win_privacy::INJECTED_PROCESS_EXE, ext = ext ) } @@ -1069,7 +1129,7 @@ fn get_uninstall() -> String { rd /s /q \"{path}\" rd /s /q \"{start_menu}\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}*\" - del /f /q \"C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" + del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" ", before_uninstall=get_before_uninstall(), subkey=subkey, @@ -1080,21 +1140,12 @@ fn get_uninstall() -> String { } pub fn uninstall_me() -> ResultType<()> { - run_cmds(get_uninstall(), true) + run_cmds(get_uninstall(), true, "uninstall") } -fn write_cmds(cmds: String, ext: &str) -> ResultType { +fn write_cmds(cmds: String, ext: &str, tip: &str) -> ResultType { let mut tmp = std::env::temp_dir(); - tmp.push(format!( - "{}_{:?}.{}", - crate::get_app_name(), - cmds.as_ptr(), - ext - )); - let mut cmds = cmds; - if ext == "cmd" { - cmds = format!("{}\ndel /f \"{}\"", cmds, tmp.to_str().unwrap_or("")); - } + tmp.push(format!("{}_{}.{}", crate::get_app_name(), tip, ext)); let mut file = std::fs::File::create(&tmp)?; // in case cmds mixed with \r\n and \n, make sure all ending with \r\n // in some windows, \r\n required for cmd file to run @@ -1117,15 +1168,20 @@ fn to_le(v: &mut [u16]) -> &[u8] { unsafe { v.align_to().1 } } -fn run_cmds(cmds: String, show: bool) -> ResultType<()> { - let tmp = write_cmds(cmds, "cmd")?; - let res = runas::Command::new(tmp.to_str().unwrap_or("")) +fn run_cmds(cmds: String, show: bool, tip: &str) -> ResultType<()> { + let tmp = write_cmds(cmds, "bat", tip)?; + let tmp_fn = tmp.to_str().unwrap_or(""); + let res = runas::Command::new("cmd") + .args(&["/C", &tmp_fn]) .show(show) .force_prompt(true) .status(); - // double confirm delete, because below delete not work if program - // exit immediately such as --uninstall - allow_err!(std::fs::remove_file(tmp)); + // leave the file for debug if execution failed + if let Ok(res) = res { + if res.success() { + allow_err!(std::fs::remove_file(tmp)); + } + } let _ = res?; Ok(()) } @@ -1290,6 +1346,7 @@ oLink.Save id = id, ), "vbs", + "connect_shortcut", )? .to_str() .unwrap_or("") @@ -1325,3 +1382,20 @@ pub fn quit_gui() { std::process::exit(0); // unsafe { PostQuitMessage(0) }; // some how not work } + +pub fn get_user_token(session_id: u32, as_user: bool) -> HANDLE { + let mut token = NULL as HANDLE; + unsafe { + if FALSE + == GetSessionUserTokenWin( + &mut token as _, + session_id, + if as_user { TRUE } else { FALSE }, + ) + { + NULL as _ + } else { + token + } + } +} diff --git a/src/rc.rs b/src/rc.rs new file mode 100644 index 000000000..ef86caaa3 --- /dev/null +++ b/src/rc.rs @@ -0,0 +1,38 @@ +use hbb_common::{bail, ResultType}; +use std::{ + fs::{self, File}, + io::prelude::*, + path::Path, +}; + +#[allow(dead_code)] +pub fn extract_resources(root_path: &str) -> ResultType<()> { + let mut resources: Vec<(&str, &[u8])> = Vec::new(); + resources.push((outfile_4, &outdata_4)); + do_extract(root_path, resources)?; + Ok(()) +} + +#[allow(dead_code)] +fn do_extract(root_path: &str, resources: Vec<(&str, &[u8])>) -> ResultType<()> { + let mut root_path = root_path.replace("\\", "/"); + if !root_path.ends_with('/') { + root_path.push('/'); + } + let root_path = Path::new(&root_path); + for (outfile, data) in resources { + let outfile_path = root_path.join(outfile); + match outfile_path.parent().and_then(|p| p.to_str()) { + None => { + bail!("Failed to get parent of {}", outfile_path.display()); + } + Some(p) => { + fs::create_dir_all(p)?; + let mut of = File::create(outfile_path)?; + of.write_all(data)?; + of.flush()?; + } + } + } + Ok(()) +} diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index f575f684f..a7f90b977 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -216,7 +216,7 @@ impl RendezvousMediator { }, Some(Err(e)) => bail!("Failed to receive next {}", e), // maybe socks5 tcp disconnected None => { - // unreachable!() + bail!("Socket receive none. Maybe socks5 server is down."); }, } }, @@ -231,7 +231,7 @@ impl RendezvousMediator { } last_timer = now; let elapsed_resp = last_register_resp.map(|x| x.elapsed().as_millis() as i64).unwrap_or(REG_INTERVAL); - let timeout = (last_register_sent.map(|x| x.elapsed().as_millis() as i64).unwrap_or(REG_INTERVAL) - elapsed_resp) > REG_TIMEOUT; + let timeout = (elapsed_resp - last_register_sent.map(|x| x.elapsed().as_millis() as i64).unwrap_or(REG_INTERVAL)) > REG_TIMEOUT; if timeout || elapsed_resp >= REG_INTERVAL { allow_err!(rz.register_peer(&mut socket).await); last_register_sent = now; diff --git a/src/server.rs b/src/server.rs index 0782c7231..2b4be9f14 100644 --- a/src/server.rs +++ b/src/server.rs @@ -91,6 +91,15 @@ async fn accept_connection_(server: ServerPtr, socket: Stream, secure: bool) -> Ok(()) } +async fn check_privacy_mode_on(stream: &mut Stream) -> ResultType<()> { + if video_service::get_privacy_mode_conn_id() > 0 { + let msg_out = + crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::OnByOther); + timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??; + } + Ok(()) +} + pub async fn create_tcp_connection( server: ServerPtr, stream: Stream, @@ -98,6 +107,8 @@ pub async fn create_tcp_connection( secure: bool, ) -> ResultType<()> { let mut stream = stream; + check_privacy_mode_on(&mut stream).await?; + let id = { let mut w = server.write().unwrap(); w.id_count += 1; @@ -423,5 +434,5 @@ async fn sync_and_watch_config_dir() { } } } - log::error!("skipped config sync"); + log::warn!("skipped config sync"); } diff --git a/src/server/connection.rs b/src/server/connection.rs index c6313d16a..967140a1d 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -22,7 +22,7 @@ use hbb_common::{ tokio_util::codec::{BytesCodec, Framed}, }; #[cfg(any(target_os = "android", target_os = "ios"))] -use scrap::android::call_input_service_mouse_input; +use scrap::android::call_main_service_mouse_input; use serde_json::{json, value::Value}; use sha2::{Digest, Sha256}; use std::sync::{ @@ -50,8 +50,6 @@ enum MessageInput { Key((KeyEvent, bool)), BlockOn, BlockOff, - PrivacyOn, - PrivacyOff, } pub struct Connection { @@ -74,7 +72,6 @@ pub struct Connection { image_quality: i32, lock_after_session_end: bool, show_remote_cursor: bool, // by peer - privacy_mode: bool, ip: String, disable_clipboard: bool, // by peer disable_audio: bool, // by peer @@ -160,7 +157,6 @@ impl Connection { image_quality: ImageQuality::Balanced.value(), lock_after_session_end: false, show_remote_cursor: false, - privacy_mode: false, ip: "".to_owned(), disable_audio: false, enable_file_transfer: false, @@ -281,6 +277,34 @@ impl Connection { allow_err!(conn.stream.send(&clip_2_msg(_clip)).await); } } + ipc::Data::PrivacyModeState((_, state)) => { + let msg_out = match state { + ipc::PrivacyModeState::OffSucceeded => { + video_service::set_privacy_mode_conn_id(0); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffSucceeded, + ) + } + ipc::PrivacyModeState::OffFailed => { + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffFailed, + ) + } + ipc::PrivacyModeState::OffByPeer => { + video_service::set_privacy_mode_conn_id(0); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffByPeer, + ) + } + ipc::PrivacyModeState::OffUnknown => { + video_service::set_privacy_mode_conn_id(0); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffUnknown, + ) + } + }; + conn.send(msg_out).await; + } _ => {} } }, @@ -362,9 +386,16 @@ impl Connection { } } + let video_privacy_conn_id = video_service::get_privacy_mode_conn_id(); + if video_privacy_conn_id == id { + video_service::set_privacy_mode_conn_id(0); + let _ = privacy_mode::turn_off_privacy(id); + } else if video_privacy_conn_id == 0 { + let _ = privacy_mode::turn_off_privacy(0); + } video_service::notify_video_frame_feched(id, None); - super::video_service::update_test_latency(id, 0); - super::video_service::update_image_quality(id, None); + video_service::update_test_latency(id, 0); + video_service::update_image_quality(id, None); if let Err(err) = conn.try_port_forward_loop(&mut rx_from_cm).await { conn.on_close(&err.to_string(), false); } @@ -378,9 +409,6 @@ impl Connection { #[cfg(not(any(target_os = "android", target_os = "ios")))] fn handle_input(receiver: std_mpsc::Receiver, tx: Sender) { let mut block_input_mode = false; - let (tx_blank, rx_blank) = std_mpsc::channel(); - - std::thread::spawn(|| Self::handle_blank(rx_blank)); loop { match receiver.recv_timeout(std::time::Duration::from_millis(500)) { @@ -402,28 +430,22 @@ impl Connection { if crate::platform::block_input(true) { block_input_mode = true; } else { - Self::send_option_error(&tx, "Failed to turn on block input mode"); + Self::send_block_input_error( + &tx, + back_notification::BlockInputState::OnFailed, + ); } } MessageInput::BlockOff => { if crate::platform::block_input(false) { block_input_mode = false; } else { - Self::send_option_error(&tx, "Failed to turn off block input mode"); + Self::send_block_input_error( + &tx, + back_notification::BlockInputState::OffFailed, + ); } } - MessageInput::PrivacyOn => { - if crate::platform::block_input(true) { - block_input_mode = true; - } - tx_blank.send(MessageInput::PrivacyOn).ok(); - } - MessageInput::PrivacyOff => { - if crate::platform::block_input(false) { - block_input_mode = false; - } - tx_blank.send(MessageInput::PrivacyOff).ok(); - } }, Err(err) => { #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -439,35 +461,6 @@ impl Connection { log::info!("Input thread exited"); } - #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn handle_blank(receiver: std_mpsc::Receiver) { - let mut last_privacy = false; - loop { - match receiver.recv_timeout(std::time::Duration::from_millis(500)) { - Ok(v) => match v { - MessageInput::PrivacyOn => { - crate::platform::toggle_blank_screen(true); - last_privacy = true; - } - MessageInput::PrivacyOff => { - crate::platform::toggle_blank_screen(false); - last_privacy = false; - } - _ => break, - }, - Err(err) => { - if last_privacy { - crate::platform::toggle_blank_screen(true); - } - if std_mpsc::RecvTimeoutError::Disconnected == err { - break; - } - } - } - } - log::info!("Blank thread exited"); - } - async fn try_port_forward_loop( &mut self, rx_from_cm: &mut mpsc::UnboundedReceiver, @@ -657,8 +650,15 @@ impl Connection { } } self.authorized = true; + pi.username = username; pi.sas_enabled = sas_enabled; + pi.features = Some(Features { + privacy_mode: video_service::is_privacy_mode_supported(), + ..Default::default() + }) + .into(); + let mut sub_service = false; if self.file_transfer.is_some() { res.set_peer_info(pi); @@ -755,13 +755,13 @@ impl Connection { self.send(msg_out).await; } - fn send_option_error(s: &Sender, err: T) { - let mut msg_out = Message::new(); - let mut res = OptionResponse::new(); + #[inline] + pub fn send_block_input_error(s: &Sender, state: back_notification::BlockInputState) { let mut misc = Misc::new(); - res.error = err.to_string(); - - misc.set_option_response(res); + let mut back_notification = BackNotification::new(); + back_notification.set_block_input_state(state); + misc.set_back_notification(back_notification); + let mut msg_out = Message::new(); msg_out.set_misc(misc); s.send((Instant::now(), Arc::new(msg_out))).ok(); } @@ -895,8 +895,8 @@ impl Connection { match msg.union { Some(message::Union::mouse_event(me)) => { #[cfg(any(target_os = "android", target_os = "ios"))] - if let Err(e) = call_input_service_mouse_input(me.mask, me.x, me.y) { - log::debug!("call_input_service_mouse_input fail:{}", e); + if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) { + log::debug!("call_main_service_mouse_input fail:{}", e); } #[cfg(not(any(target_os = "android", target_os = "ios")))] if self.keyboard { @@ -1162,12 +1162,55 @@ impl Connection { if self.keyboard { match q { BoolOption::Yes => { - self.privacy_mode = true; - self.tx_input.send(MessageInput::PrivacyOn).ok(); + let msg_out = if !video_service::is_privacy_mode_supported() { + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::NotSupported, + ) + } else { + match privacy_mode::turn_on_privacy(self.inner.id) { + Ok(true) => { + if video_service::test_create_capturer(self.inner.id, 5_000) { + video_service::set_privacy_mode_conn_id(self.inner.id); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OnSucceeded, + ) + } else { + log::error!( + "Wait privacy mode timeout, turn off privacy mode" + ); + video_service::set_privacy_mode_conn_id(0); + let _ = privacy_mode::turn_off_privacy(self.inner.id); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OnFailed, + ) + } + } + Ok(false) => crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OnFailedPlugin, + ), + Err(e) => { + log::error!("Failed to turn on privacy mode. {}", e); + if video_service::get_privacy_mode_conn_id() == 0 { + let _ = privacy_mode::turn_off_privacy(0); + } + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OnFailed, + ) + } + } + }; + self.send(msg_out).await; } BoolOption::No => { - self.privacy_mode = false; - self.tx_input.send(MessageInput::PrivacyOff).ok(); + let msg_out = if !video_service::is_privacy_mode_supported() { + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::NotSupported, + ) + } else { + video_service::set_privacy_mode_conn_id(0); + privacy_mode::turn_off_privacy(self.inner.id) + }; + self.send(msg_out).await; } _ => {} } @@ -1318,3 +1361,43 @@ fn try_activate_screen() { mouse_move_relative(6, 6); }); } + +mod privacy_mode { + use super::*; + + pub(super) fn turn_off_privacy(_conn_id: i32) -> Message { + #[cfg(windows)] + { + use crate::ui::win_privacy::*; + + let res = turn_off_privacy(_conn_id, None); + match res { + Ok(_) => crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffSucceeded, + ), + Err(e) => { + log::error!("Failed to turn off privacy mode {}", e); + crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OffFailed, + ) + } + } + } + #[cfg(not(windows))] + { + crate::common::make_privacy_mode_msg(back_notification::PrivacyModeState::OffFailed) + } + } + + pub(super) fn turn_on_privacy(_conn_id: i32) -> ResultType { + #[cfg(windows)] + { + let plugin_exitst = crate::ui::win_privacy::turn_on_privacy(_conn_id)?; + Ok(plugin_exitst) + } + #[cfg(not(windows))] + { + Ok(true) + } + } +} diff --git a/src/server/service.rs b/src/server/service.rs index 35cfdbd77..9cc1e860c 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -89,7 +89,12 @@ impl> Service for ServiceTmpl { fn join(&self) { self.0.write().unwrap().active = false; - self.0.write().unwrap().handle.take().map(JoinHandle::join); + let handle = self.0.write().unwrap().handle.take(); + if let Some(handle) = handle { + if let Err(e) = handle.join() { + log::error!("Failed to join thread for service {}, {:?}", self.name(), e); + } + } } } @@ -143,6 +148,16 @@ impl> ServiceTmpl { } } + pub fn send_to_others(&self, msg: Message, id: i32) { + let msg = Arc::new(msg); + let mut lock = self.0.write().unwrap(); + for (sid, s) in lock.subscribes.iter_mut() { + if *sid != id { + s.send(msg.clone()); + } + } + } + pub fn send_shared(&self, msg: Arc) { let mut lock = self.0.write().unwrap(); for s in lock.subscribes.values_mut() { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 17b545426..b486cd311 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -19,19 +19,18 @@ // https://slhck.info/video/2017/03/01/rate-control.html use super::*; -use hbb_common::tokio::{ - runtime::Runtime, - sync::{ - mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as TokioMutex, - }, +use hbb_common::tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as TokioMutex, }; -use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, VideoCodecId, STRIDE_ALIGN}; +use scrap::{Capturer, Config, Display, EncodeFrame, Encoder, Frame, VideoCodecId, STRIDE_ALIGN}; use std::{ collections::HashSet, - io::ErrorKind::WouldBlock, + io::{ErrorKind::WouldBlock, Result}, time::{self, Duration, Instant}, }; +#[cfg(windows)] +use virtual_display; pub const NAME: &'static str = "video"; @@ -45,16 +44,39 @@ lazy_static::lazy_static! { let (tx, rx) = unbounded_channel(); (tx, Arc::new(TokioMutex::new(rx))) }; + static ref PRIVACY_MODE_CONN_ID: Mutex = Mutex::new(0); + static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported(); +} + +fn is_capturer_mag_supported() -> bool { + #[cfg(windows)] + return scrap::CapturerMag::is_supported(); + #[cfg(not(windows))] + false } pub fn notify_video_frame_feched(conn_id: i32, frame_tm: Option) { FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).unwrap() } +pub fn set_privacy_mode_conn_id(conn_id: i32) { + *PRIVACY_MODE_CONN_ID.lock().unwrap() = conn_id +} + +pub fn get_privacy_mode_conn_id() -> i32 { + *PRIVACY_MODE_CONN_ID.lock().unwrap() +} + +pub fn is_privacy_mode_supported() -> bool { + #[cfg(windows)] + return *IS_CAPTURER_MAGNIFIER_SUPPORTED; + #[cfg(not(windows))] + return false; +} + struct VideoFrameController { cur: Instant, send_conn_ids: HashSet, - rt: Runtime, } impl VideoFrameController { @@ -62,7 +84,6 @@ impl VideoFrameController { Self { cur: Instant::now(), send_conn_ids: HashSet::new(), - rt: Runtime::new().unwrap(), } } @@ -77,46 +98,69 @@ impl VideoFrameController { } } - fn blocking_wait_next(&mut self, timeout_millis: u128) { + #[tokio::main(flavor = "current_thread")] + async fn try_wait_next(&mut self, fetched_conn_ids: &mut HashSet, timeout_millis: u64) { if self.send_conn_ids.is_empty() { return; } - let send_conn_ids = self.send_conn_ids.clone(); - self.rt.block_on(async move { - let mut fetched_conn_ids = HashSet::new(); - let begin = Instant::now(); - while begin.elapsed().as_millis() < timeout_millis { - let timeout_dur = - Duration::from_millis((timeout_millis - begin.elapsed().as_millis()) as u64); - match tokio::time::timeout( - timeout_dur, - FRAME_FETCHED_NOTIFIER.1.lock().await.recv(), - ) - .await - { - Err(_) => { - // break if timeout - // log::error!("blocking wait frame receiving timeout {}", timeout_millis); - break; - } - Ok(Some((id, instant))) => { - if let Some(tm) = instant { - log::trace!("Channel recv latency: {}", tm.elapsed().as_secs_f32()); - } - fetched_conn_ids.insert(id); - - // break if all connections have received current frame - if fetched_conn_ids.len() >= send_conn_ids.len() { - break; - } - } - Ok(None) => { - // this branch would nerver be reached - } - } + let timeout_dur = Duration::from_millis(timeout_millis as u64); + match tokio::time::timeout(timeout_dur, FRAME_FETCHED_NOTIFIER.1.lock().await.recv()).await + { + Err(_) => { + // break if timeout + // log::error!("blocking wait frame receiving timeout {}", timeout_millis); } - }); + Ok(Some((id, instant))) => { + if let Some(tm) = instant { + log::trace!("Channel recv latency: {}", tm.elapsed().as_secs_f32()); + } + fetched_conn_ids.insert(id); + } + Ok(None) => { + // this branch would nerver be reached + } + } + } +} + +trait TraitCapturer { + fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result>; + + #[cfg(windows)] + fn is_gdi(&self) -> bool; + #[cfg(windows)] + fn set_gdi(&mut self) -> bool; +} + +impl TraitCapturer for Capturer { + fn frame<'a>(&'a mut self, timeout_ms: u32) -> Result> { + self.frame(timeout_ms) + } + + #[cfg(windows)] + fn is_gdi(&self) -> bool { + self.is_gdi() + } + + #[cfg(windows)] + fn set_gdi(&mut self) -> bool { + self.set_gdi() + } +} + +#[cfg(windows)] +impl TraitCapturer for scrap::CapturerMag { + fn frame<'a>(&'a mut self, _timeout_ms: u32) -> Result> { + self.frame(_timeout_ms) + } + + fn is_gdi(&self) -> bool { + false + } + + fn set_gdi(&mut self) -> bool { + false } } @@ -132,7 +176,7 @@ fn check_display_changed( last_width: usize, last_hegiht: usize, ) -> bool { - let displays = match Display::all() { + let displays = match try_get_displays() { Ok(d) => d, _ => return false, }; @@ -156,7 +200,126 @@ fn check_display_changed( return false; } +// Capturer object is expensive, avoiding to create it frequently. +fn create_capturer(privacy_mode_id: i32, display: Display) -> ResultType> { + let use_yuv = true; + + #[cfg(not(windows))] + let c: Option> = None; + #[cfg(windows)] + let mut c: Option> = None; + if privacy_mode_id > 0 { + #[cfg(windows)] + { + use crate::ui::win_privacy::*; + + match scrap::CapturerMag::new( + display.origin(), + display.width(), + display.height(), + use_yuv, + ) { + Ok(mut c1) => { + let mut ok = false; + let check_begin = Instant::now(); + while check_begin.elapsed().as_secs() < 5 { + match c1.exclude("", PRIVACY_WINDOW_NAME) { + Ok(false) => { + ok = false; + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Err(e) => { + bail!( + "Failed to exclude privacy window {} - {}, err: {}", + "", + PRIVACY_WINDOW_NAME, + e + ); + } + _ => { + ok = true; + break; + } + } + } + if !ok { + bail!( + "Failed to exclude privacy window {} - {} ", + "", + PRIVACY_WINDOW_NAME + ); + } + log::debug!("Create maginifier capture for {}", privacy_mode_id); + c = Some(Box::new(c1)); + } + Err(e) => { + bail!(format!("Failed to create magnifier capture {}", e)); + } + } + } + } + + let c = match c { + Some(c1) => c1, + None => { + let c1 = + Capturer::new(display, use_yuv).with_context(|| "Failed to create capturer")?; + log::debug!("Create capturer dxgi|gdi"); + Box::new(c1) + } + }; + + Ok(c) +} + +#[cfg(windows)] +fn ensure_close_virtual_device() -> ResultType<()> { + let num_displays = Display::all()?.len(); + if num_displays == 0 { + // Device may sometimes be uninstalled by user in "Device Manager" Window. + // Closing device will clear the instance data. + virtual_display::close_device(); + } else if num_displays > 1 { + // Try close device, if display device changed. + if virtual_display::is_device_created() { + virtual_display::close_device(); + } + } + Ok(()) +} + +pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> bool { + let test_begin = Instant::now(); + while test_begin.elapsed().as_millis() < timeout_millis as _ { + if let Ok((_, _, display)) = get_current_display() { + if let Ok(_) = create_capturer(privacy_mode_id, display) { + return true; + } + } + std::thread::sleep(Duration::from_millis(300)); + } + false +} + +#[cfg(windows)] +fn check_uac_switch(privacy_mode_id: i32, captuerer_privacy_mode_id: i32) -> ResultType<()> { + if captuerer_privacy_mode_id != 0 { + if privacy_mode_id != captuerer_privacy_mode_id { + if !crate::ui::win_privacy::is_process_consent_running()? { + bail!("consent.exe is running"); + } + } + if crate::ui::win_privacy::is_process_consent_running()? { + bail!("consent.exe is running"); + } + } + Ok(()) +} + fn run(sp: GenericService) -> ResultType<()> { + #[cfg(windows)] + ensure_close_virtual_device()?; + let fps = 30; let wait = 1000 / fps; let spf = time::Duration::from_secs_f32(1. / (fps as f32)); @@ -172,8 +335,27 @@ fn run(sp: GenericService) -> ResultType<()> { num_cpus::get_physical(), num_cpus::get(), ); - // Capturer object is expensive, avoiding to create it frequently. - let mut c = Capturer::new(display, true).with_context(|| "Failed to create capturer")?; + + let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap(); + #[cfg(not(windows))] + let captuerer_privacy_mode_id = privacy_mode_id; + #[cfg(windows)] + let mut captuerer_privacy_mode_id = privacy_mode_id; + #[cfg(windows)] + if crate::ui::win_privacy::is_process_consent_running()? { + captuerer_privacy_mode_id = 0; + } + log::debug!( + "Try create capturer with captuerer privacy mode id {}", + captuerer_privacy_mode_id, + ); + + if privacy_mode_id != captuerer_privacy_mode_id { + log::info!("In privacy mode, but show UAC prompt window for now"); + } else { + log::info!("In privacy mode, the peer side cannot watch the screen"); + } + let mut c = create_capturer(captuerer_privacy_mode_id, display)?; let q = get_image_quality(); let (bitrate, rc_min_quantizer, rc_max_quantizer, speed) = get_quality(width, height, q); @@ -220,6 +402,9 @@ fn run(sp: GenericService) -> ResultType<()> { #[cfg(windows)] log::info!("gdi: {}", c.is_gdi()); while sp.ok() { + #[cfg(windows)] + check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?; + if *SWITCH.lock().unwrap() { bail!("SWITCH"); } @@ -227,6 +412,7 @@ fn run(sp: GenericService) -> ResultType<()> { *SWITCH.lock().unwrap() = true; bail!("SWITCH"); } + check_privacy_mode_changed(&sp, privacy_mode_id)?; if get_image_quality() != q { bail!("SWITCH"); } @@ -245,12 +431,13 @@ fn run(sp: GenericService) -> ResultType<()> { bail!("SWITCH"); } } + *LAST_ACTIVE.lock().unwrap() = now; frame_controller.reset(); #[cfg(any(target_os = "android", target_os = "ios"))] - let res = match c.frame(wait as _) { + let res = match (*c).frame(wait as _) { Ok(frame) => { let time = now - start; let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64; @@ -273,7 +460,7 @@ fn run(sp: GenericService) -> ResultType<()> { }; #[cfg(not(any(target_os = "android", target_os = "ios")))] - let res = match c.frame(wait as _) { + let res = match (*c).frame(wait as _) { Ok(frame) => { let time = now - start; let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64; @@ -320,8 +507,19 @@ fn run(sp: GenericService) -> ResultType<()> { _ => {} } - // i love 3, 6, 8 - frame_controller.blocking_wait_next(3_000); + let mut fetched_conn_ids = HashSet::new(); + let timeout_millis = 3_000u64; + let wait_begin = Instant::now(); + while wait_begin.elapsed().as_millis() < timeout_millis as _ { + check_privacy_mode_changed(&sp, privacy_mode_id)?; + #[cfg(windows)] + check_uac_switch(privacy_mode_id, captuerer_privacy_mode_id)?; + frame_controller.try_wait_next(&mut fetched_conn_ids, 300); + // break if all connections have received current frame + if fetched_conn_ids.len() >= frame_controller.send_conn_ids.len() { + break; + } + } let elapsed = now.elapsed(); // may need to enable frame(timeout) @@ -333,6 +531,21 @@ fn run(sp: GenericService) -> ResultType<()> { Ok(()) } +#[inline] +fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> ResultType<()> { + let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap(); + if privacy_mode_id != privacy_mode_id_2 { + if privacy_mode_id_2 != 0 { + let msg_out = crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::OnByOther, + ); + sp.send_to_others(msg_out, privacy_mode_id_2); + } + bail!("SWITCH"); + } + Ok(()) +} + #[inline] fn create_msg(vp9s: Vec) -> Message { let mut msg_out = Message::new(); @@ -416,7 +629,7 @@ pub fn handle_one_frame_encoded( } fn get_display_num() -> usize { - if let Ok(d) = Display::all() { + if let Ok(d) = try_get_displays() { d.len() } else { 0 @@ -430,7 +643,7 @@ pub fn get_displays() -> ResultType<(usize, Vec)> { } let mut displays = Vec::new(); let mut primary = 0; - for (i, d) in Display::all()?.iter().enumerate() { + for (i, d) in try_get_displays()?.iter().enumerate() { if d.is_primary() { primary = i; } @@ -467,7 +680,7 @@ pub fn refresh() { } fn get_primary() -> usize { - if let Ok(all) = Display::all() { + if let Ok(all) = try_get_displays() { for (i, d) in all.iter().enumerate() { if d.is_primary() { return i; @@ -481,9 +694,44 @@ pub fn switch_to_primary() { switch_display(get_primary() as _); } +#[cfg(not(windows))] +fn try_get_displays() -> ResultType> { + Ok(Display::all()?) +} + +#[cfg(windows)] +fn try_get_displays() -> ResultType> { + let mut displays = Display::all()?; + if displays.len() == 0 { + log::debug!("no displays, create virtual display"); + // Try plugin monitor + if !virtual_display::is_device_created() { + if let Err(e) = virtual_display::create_device() { + log::debug!("Create device failed {}", e); + } + } + if virtual_display::is_device_created() { + if let Err(e) = virtual_display::plug_in_monitor() { + log::debug!("Plug in monitor failed {}", e); + } else { + if let Err(e) = virtual_display::update_monitor_modes() { + log::debug!("Update monitor modes failed {}", e); + } + } + } + displays = Display::all()?; + } else if displays.len() > 1 { + // If more than one displays exists, close RustDeskVirtualDisplay + if virtual_display::is_device_created() { + virtual_display::close_device() + } + } + Ok(displays) +} + fn get_current_display() -> ResultType<(usize, usize, Display)> { let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize; - let mut displays = Display::all()?; + let mut displays = try_get_displays()?; if displays.len() == 0 { bail!("No displays"); } @@ -567,5 +815,15 @@ fn get_quality(w: usize, h: usize, q: i32) -> (u32, u32, u32, i32) { let bitrate = q >> 8 & 0xFF; let quantizer = q & 0xFF; let b = ((w * h) / 1000) as u32; + + #[cfg(target_os = "android")] + { + // fix when andorid screen shrinks + let fix = Display::fix_quality() as u32; + log::debug!("Android screen, fix quality:{}", fix); + let b = b * fix; + return (bitrate as u32 * b / 100, quantizer as _, 56, 7); + } + (bitrate as u32 * b / 100, quantizer as _, 56, 7) } diff --git a/src/ui.rs b/src/ui.rs index 92af4328a..5d6a6dce3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -3,6 +3,8 @@ mod cm; mod inline; #[cfg(target_os = "macos")] mod macos; +#[cfg(target_os = "windows")] +pub mod win_privacy; pub mod remote; use crate::ui_interface::*; use hbb_common::{allow_err, config::PeerConfig, log}; @@ -29,7 +31,13 @@ pub fn start(args: &mut [String]) { macos::show_dock(); } #[cfg(all(target_os = "linux", feature = "inline"))] - sciter::set_library("/usr/lib/rustdesk/libsciter-gtk.so").ok(); + { + #[cfg(feature = "appimage")] + let prefix = std::env::var("APPDIR").unwrap_or("".to_string()); + #[cfg(not(feature = "appimage"))] + let prefix = "".to_string(); + sciter::set_library(&(prefix + "/usr/lib/rustdesk/libsciter-gtk.so")).ok(); + } // https://github.com/c-smile/sciter-sdk/blob/master/include/sciter-x-types.h // https://github.com/rustdesk/rustdesk/issues/132#issuecomment-886069737 #[cfg(windows)] diff --git a/src/ui/cm.rs b/src/ui/cm.rs index 91ea8e513..90d066d62 100644 --- a/src/ui/cm.rs +++ b/src/ui/cm.rs @@ -399,6 +399,13 @@ impl ConnectionManager { } } + fn send_data(&self, id: i32, data: Data) { + let lock = self.read().unwrap(); + if let Some(s) = lock.senders.get(&id) { + allow_err!(s.send(data)); + } + } + fn authorize(&self, id: i32) { let lock = self.read().unwrap(); if let Some(s) = lock.senders.get(&id) { @@ -442,6 +449,21 @@ async fn start_ipc(cm: ConnectionManager) { #[cfg(windows)] std::thread::spawn(move || start_clipboard_file(cm_clip, _rx_file)); + #[cfg(windows)] + std::thread::spawn(move || { + log::info!("try create privacy mode window"); + #[cfg(windows)] + { + if let Err(e) = crate::platform::windows::check_update_broker_process() { + log::warn!( + "Failed to check update broker process. Privacy mode may not work properly. {}", + e + ); + } + } + allow_err!(crate::ui::win_privacy::start()); + }); + match new_listener("_cm").await { Ok(mut incoming) => { while let Some(result) = incoming.next().await { @@ -452,6 +474,8 @@ async fn start_ipc(cm: ConnectionManager) { let cm = cm.clone(); let tx_file = tx_file.clone(); tokio::spawn(async move { + // for tmp use, without real conn id + let conn_id_tmp = -1; let mut conn_id: i32 = 0; let (tx, mut rx) = mpsc::unbounded_channel::(); let mut write_jobs: Vec = Vec::new(); @@ -476,6 +500,10 @@ async fn start_ipc(cm: ConnectionManager) { log::info!("cm ipc connection closed from connection request"); break; } + Data::PrivacyModeState((id, _)) => { + conn_id = conn_id_tmp; + cm.send_data(id, data) + } _ => { cm.handle_data(conn_id, data, &tx_file, &mut write_jobs, &mut stream).await; } @@ -491,7 +519,9 @@ async fn start_ipc(cm: ConnectionManager) { } } } - cm.remove_connection(conn_id); + if conn_id != conn_id_tmp { + cm.remove_connection(conn_id); + } }); } Err(err) => { diff --git a/src/ui/cm.tis b/src/ui/cm.tis index dfb853436..9bc92661b 100644 --- a/src/ui/cm.tis +++ b/src/ui/cm.tis @@ -45,7 +45,7 @@ class Body: Reactor.Component
-
+
} {c.port_forward ?
Port Forwarding: {c.port_forward}
: ""}
@@ -267,6 +267,7 @@ function bring_to_top(idx=-1) { } handler.addConnection = function(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file) { + stdout.println("new connection #" + id + ": " + peer_id); var conn; connections.map(function(c) { if (c.id == id) conn = c; diff --git a/src/ui/header.tis b/src/ui/header.tis index 2520549b7..4b2615a45 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -126,6 +126,7 @@ class Header: Reactor.Component { updateWindowToolbarPosition(); var style = "flow:horizontal;"; if (is_osx) style += "margin:*"; + self.timer(1ms, updatePrivacyMode); self.timer(1ms, toggleMenuState); return
{is_osx || is_xfce ? "" : {svg_fullscreen}} @@ -159,10 +160,10 @@ class Header: Reactor.Component {
  • {svg_checkmark}{translate('Show remote cursor')}
  • {audio_enabled ?
  • {svg_checkmark}{translate('Mute')}
  • : ""} - {is_win && pi.platform == 'Windows' && file_enabled ?
  • {svg_checkmark}{translate('File transfer')}
  • : ""} + {is_win && pi.platform == 'Windows' && file_enabled ?
  • {svg_checkmark}{translate('Allow file copy and paste')}
  • : ""} {keyboard_enabled && clipboard_enabled ?
  • {svg_checkmark}{translate('Disable clipboard')}
  • : ""} {keyboard_enabled ?
  • {svg_checkmark}{translate('Lock after session end')}
  • : ""} - {false && keyboard_enabled && pi.platform == "Windows" ?
  • {svg_checkmark}{translate('Privacy mode')}
  • : ""} + {keyboard_enabled && pi.platform == "Windows" ?
  • {svg_checkmark}{translate('Privacy mode')}
  • : ""} ; } @@ -312,6 +313,8 @@ class Header: Reactor.Component { event click $(menu#display-options>li) (_, me) { if (me.id == "custom") { handle_custom_image_quality(); + } else if (me.id == "privacy-mode") { + togglePrivacyMode(me.id); } else if (me.attributes.hasClass("toggle-option")) { handler.toggle_option(me.id); toggleMenuState(); @@ -354,17 +357,11 @@ function toggleMenuState() { for (var el in $$(menu#display-options>li)) { el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0); } - for (var id in ["show-remote-cursor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end", "privacy-mode"]) { + for (var id in ["show-remote-cursor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) { var el = self.select('#' + id); if (el) { var value = handler.get_toggle_option(id); el.attributes.toggleClass("selected", value); - if (id == "privacy-mode") { - var el = $(li#block-input); - if (el) { - el.state.disabled = value; - } - } } } } @@ -400,6 +397,46 @@ handler.updatePi = function(v) { } } +function updatePrivacyMode() { + var el = $(li#privacy-mode); + if (el) { + var supported = handler.is_privacy_mode_supported(); + if (!supported) { + // el.attributes.toggleClass("line-through", true); + el.style["display"]="none"; + } else { + var value = handler.get_toggle_option("privacy-mode"); + el.attributes.toggleClass("selected", value); + var el = $(li#block-input); + if (el) { + el.state.disabled = value; + } + } + } +} +handler.updatePrivacyMode = updatePrivacyMode; + +function togglePrivacyMode(privacy_id) { + var supported = handler.is_privacy_mode_supported(); + if (!supported) { + msgbox("nocancel", translate("Privacy mode"), translate("Unsupported"), function() { }); + } else { + handler.toggle_option(privacy_id); + } +} + +handler.updateBlockInputState = function(input_blocked) { + if (!input_blocked) { + handler.toggle_option("block-input"); + input_blocked = true; + $(#block-input).text = translate("Unblock user input"); + } else { + handler.toggle_option("unblock-input"); + input_blocked = false; + $(#block-input).text = translate("Block user input"); + } +} + handler.switchDisplay = function(i) { pi.current_display = i; header.update(); diff --git a/src/ui/index.tis b/src/ui/index.tis index 43222eb97..70cc8f870 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -490,7 +490,6 @@ class App: Reactor.Component
    -
    ; } @@ -666,9 +665,7 @@ class FixWayland: Reactor.Component { return
    {translate('Warning')}
    {translate('Login screen using Wayland is not supported')}
    -
    {translate('Fix it')}
    {translate('Help')}
    -
    ({translate('Reboot required')})
    ; } @@ -687,9 +684,7 @@ class ModifyDefaultLogin: Reactor.Component { return
    {translate('Warning')}
    {translate('Current Wayland display server is not supported')}
    -
    {translate('Fix it')}
    {translate('Help')}
    -
    ({translate('Reboot required')})
    ; } @@ -858,7 +853,7 @@ event keydown (evt) { } } -$(body).content(); +$(body).content(
    ); function self.closing() { var (x, y, w, h) = view.box(#rectw, #border, #screen); diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis index 070ec6588..b4ca1c490 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.tis @@ -91,8 +91,8 @@ class MsgboxComponent: Reactor.Component { var color = this.getColor(); var icon = this.getIcon(color); var content = this.getContent(); - var hasCancel = this.type.indexOf("error") < 0 && this.type != "success" && this.type.indexOf("nocancel") < 0; - var hasOk = this.type != "connecting" && this.type.indexOf("nook") < 0; + var hasCancel = this.type.indexOf("error") < 0 && this.type.indexOf("nocancel") < 0; + var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0; var hasClose = this.type.indexOf("hasclose") >= 0; var show_progress = this.type == "connecting"; var me = this; diff --git a/src/ui/remote.rs b/src/ui/remote.rs index ac32726aa..a073b81c6 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -1,7 +1,10 @@ use std::{ collections::HashMap, ops::Deref, - sync::{Arc, Mutex, RwLock}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, RwLock, + }, }; use sciter::{ @@ -21,8 +24,8 @@ use clipboard::{ }; use enigo::{self, Enigo, KeyboardControllable}; use hbb_common::fs::{ - can_enable_overwrite_detection, get_string, new_send_confirm, - DigestCheckResult, RemoveJobMeta, get_job, + can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, + RemoveJobMeta, }; use hbb_common::{ allow_err, @@ -45,7 +48,7 @@ use hbb_common::{config::TransferSerde, fs::TransferJobMeta}; use crate::clipboard_file::*; use crate::{ client::*, - common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL} + common::{self, check_clipboard, update_clipboard, ClipboardContext, CLIPBOARD_INTERVAL}, }; type Video = AssetPtr; @@ -63,11 +66,11 @@ fn get_key_state(key: enigo::Key) -> bool { ENIGO.lock().unwrap().get_key_state(key) } -static mut IS_IN: bool = false; -static mut KEYBOARD_HOOKED: bool = false; -static mut SERVER_KEYBOARD_ENABLED: bool = true; -static mut SERVER_FILE_TRANSFER_ENABLED: bool = true; -static mut SERVER_CLIPBOARD_ENABLED: bool = true; +static IS_IN: AtomicBool = AtomicBool::new(false); +static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false); +static SERVER_KEYBOARD_ENABLED: AtomicBool = AtomicBool::new(true); +static SERVER_FILE_TRANSFER_ENABLED: AtomicBool = AtomicBool::new(true); +static SERVER_CLIPBOARD_ENABLED: AtomicBool = AtomicBool::new(true); #[cfg(windows)] static mut IS_ALT_GR: bool = false; @@ -223,6 +226,7 @@ impl sciter::EventHandler for Handler { fn save_custom_image_quality(i32, i32); fn refresh_video(); fn get_toggle_option(String); + fn is_privacy_mode_supported(); fn toggle_option(String); fn get_remember(); fn peer_platform(); @@ -249,12 +253,9 @@ impl Handler { if self.is_port_forward() || self.is_file_transfer() { return; } - if unsafe { KEYBOARD_HOOKED } { + if KEYBOARD_HOOKED.swap(true, Ordering::SeqCst) { return; } - unsafe { - KEYBOARD_HOOKED = true; - } log::info!("keyboard hooked"); let mut me = self.clone(); let peer = self.peer_platform(); @@ -266,7 +267,8 @@ impl Handler { std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev use rdev::{EventType::*, *}; let func = move |evt: Event| { - if unsafe { !IS_IN || !SERVER_KEYBOARD_ENABLED } { + if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) + { return; } let (key, down) = match evt.event_type { @@ -496,7 +498,7 @@ impl Handler { } #[inline] - fn save_config(&self, config: PeerConfig) { + pub(super) fn save_config(&self, config: PeerConfig) { self.lc.write().unwrap().save_config(config); } @@ -505,7 +507,7 @@ impl Handler { } #[inline] - fn load_config(&self) -> PeerConfig { + pub(super) fn load_config(&self) -> PeerConfig { load_config(&self.id) } @@ -523,6 +525,10 @@ impl Handler { self.lc.read().unwrap().get_toggle_option(&name) } + fn is_privacy_mode_supported(&self) -> bool { + self.lc.read().unwrap().is_privacy_mode_supported() + } + fn refresh_video(&mut self) { self.send(Data::Message(LoginConfigHandler::refresh())); } @@ -865,17 +871,13 @@ impl Handler { fn enter(&mut self) { #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(true); - unsafe { - IS_IN = true; - } + IS_IN.store(true, Ordering::SeqCst); } fn leave(&mut self) { #[cfg(windows)] crate::platform::windows::stop_system_key_propagate(false); - unsafe { - IS_IN = false; - } + IS_IN.store(false, Ordering::SeqCst); } fn send_mouse( @@ -1380,11 +1382,9 @@ impl Remote { }; match Client::start(&self.handler.id, key, token, conn_type).await { Ok((mut peer, direct)) => { - unsafe { - SERVER_KEYBOARD_ENABLED = true; - SERVER_CLIPBOARD_ENABLED = true; - SERVER_FILE_TRANSFER_ENABLED = true; - } + SERVER_KEYBOARD_ENABLED.store(true, Ordering::SeqCst); + SERVER_CLIPBOARD_ENABLED.store(true, Ordering::SeqCst); + SERVER_FILE_TRANSFER_ENABLED.store(true, Ordering::SeqCst); self.handler .call("setConnectionType", &make_args!(peer.is_secured(), direct)); @@ -1462,11 +1462,9 @@ impl Remote { if let Some(stop) = stop_clipboard { stop.send(()).ok(); } - unsafe { - SERVER_KEYBOARD_ENABLED = false; - SERVER_CLIPBOARD_ENABLED = false; - SERVER_FILE_TRANSFER_ENABLED = false; - } + SERVER_KEYBOARD_ENABLED.store(false, Ordering::SeqCst); + SERVER_CLIPBOARD_ENABLED.store(false, Ordering::SeqCst); + SERVER_FILE_TRANSFER_ENABLED.store(false, Ordering::SeqCst); } fn handle_job_status(&mut self, id: i32, file_num: i32, err: Option) { @@ -1518,8 +1516,8 @@ impl Remote { } _ => {} } - if !unsafe { SERVER_CLIPBOARD_ENABLED } - || !unsafe { SERVER_KEYBOARD_ENABLED } + if !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst) + || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) || lc.read().unwrap().disable_clipboard { continue; @@ -1663,7 +1661,12 @@ impl Remote { Data::AddJob((id, path, to, file_num, include_hidden, is_remote)) => { let od = can_enable_overwrite_detection(self.handler.lc.read().unwrap().version); if is_remote { - log::debug!("new write waiting job {}, write to {} from remote {}", id, to, path); + log::debug!( + "new write waiting job {}, write to {} from remote {}", + id, + to, + path + ); let mut job = fs::TransferJob::new_write( id, path.clone(), @@ -1711,15 +1714,27 @@ impl Remote { if let Some(job) = get_job(id, &mut self.write_jobs) { job.is_last_job = false; allow_err!( - peer.send(&fs::new_send(id, job.remote.clone(), job.file_num, job.show_hidden)) - .await + peer.send(&fs::new_send( + id, + job.remote.clone(), + job.file_num, + job.show_hidden + )) + .await ); } } else { if let Some(job) = get_job(id, &mut self.read_jobs) { job.is_last_job = false; - allow_err!(peer.send(&fs::new_receive(id, job.path.to_string_lossy().to_string(), - job.file_num, job.files.clone())).await); + allow_err!( + peer.send(&fs::new_receive( + id, + job.path.to_string_lossy().to_string(), + job.file_num, + job.files.clone() + )) + .await + ); } } } @@ -1947,7 +1962,7 @@ impl Remote { let json_str = serde_json::to_string(&job.gen_meta()).unwrap(); transfer_metas.write_jobs.push(json_str); } - log::info!("meta: {:?}",transfer_metas); + log::info!("meta: {:?}", transfer_metas); config.transfer = transfer_metas; self.handler.save_config(config); true @@ -1978,8 +1993,8 @@ impl Remote { self.check_clipboard_file_context(); if !(self.handler.is_file_transfer() || self.handler.is_port_forward() - || !unsafe { SERVER_CLIPBOARD_ENABLED } - || !unsafe { SERVER_KEYBOARD_ENABLED } + || !SERVER_CLIPBOARD_ENABLED.load(Ordering::SeqCst) + || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) || self.handler.lc.read().unwrap().disable_clipboard) { let txt = self.old_clipboard.lock().unwrap().clone(); @@ -2027,13 +2042,16 @@ impl Remote { Some(message::Union::file_response(fr)) => { match fr.union { Some(file_response::Union::dir(fd)) => { + #[cfg(windows)] + let entries = fd.entries.to_vec(); + #[cfg(not(windows))] let mut entries = fd.entries.to_vec(); #[cfg(not(windows))] { if self.handler.peer_platform() == "Windows" { fs::transform_windows_path(&mut entries); } - } + } let mut m = make_fd(fd.id, &entries, fd.id > 0); if fd.id <= 0 { m.set_item("path", fd.path); @@ -2181,16 +2199,12 @@ impl Remote { log::info!("Change permission {:?} -> {}", p.permission, p.enabled); match p.permission.enum_value_or_default() { Permission::Keyboard => { - unsafe { - SERVER_KEYBOARD_ENABLED = p.enabled; - } + SERVER_KEYBOARD_ENABLED.store(p.enabled, Ordering::SeqCst); self.handler .call2("setPermission", &make_args!("keyboard", p.enabled)); } Permission::Clipboard => { - unsafe { - SERVER_CLIPBOARD_ENABLED = p.enabled; - } + SERVER_CLIPBOARD_ENABLED.store(p.enabled, Ordering::SeqCst); self.handler .call2("setPermission", &make_args!("clipboard", p.enabled)); } @@ -2199,9 +2213,7 @@ impl Remote { .call2("setPermission", &make_args!("audio", p.enabled)); } Permission::File => { - unsafe { - SERVER_FILE_TRANSFER_ENABLED = p.enabled; - } + SERVER_FILE_TRANSFER_ENABLED.store(p.enabled, Ordering::SeqCst); if !p.enabled && self.handler.is_file_transfer() { return true; } @@ -2231,9 +2243,10 @@ impl Remote { self.handler.msgbox("error", "Connection Error", &c); return false; } - Some(misc::Union::option_response(resp)) => { - self.handler - .msgbox("custom-error", "Option Error", &resp.error); + Some(misc::Union::back_notification(notification)) => { + if !self.handle_back_notification(notification).await { + return false; + } } _ => {} }, @@ -2259,10 +2272,131 @@ impl Remote { true } + async fn handle_back_notification(&mut self, notification: BackNotification) -> bool { + match notification.union { + Some(back_notification::Union::block_input_state(state)) => { + self.handle_back_msg_block_input( + state.enum_value_or(back_notification::BlockInputState::StateUnknown), + ) + .await; + } + Some(back_notification::Union::privacy_mode_state(state)) => { + if !self + .handle_back_msg_privacy_mode( + state.enum_value_or(back_notification::PrivacyModeState::StateUnknown), + ) + .await + { + return false; + } + } + _ => {} + } + true + } + + #[inline(always)] + fn update_block_input_state(&mut self, on: bool) { + self.handler.call("updateBlockInputState", &make_args!(on)); + } + + async fn handle_back_msg_block_input(&mut self, state: back_notification::BlockInputState) { + match state { + back_notification::BlockInputState::OnSucceeded => { + self.update_block_input_state(true); + } + back_notification::BlockInputState::OnFailed => { + self.handler + .msgbox("custom-error", "Block user input", "Failed"); + self.update_block_input_state(false); + } + back_notification::BlockInputState::OffSucceeded => { + self.update_block_input_state(false); + } + back_notification::BlockInputState::OffFailed => { + self.handler + .msgbox("custom-error", "Unblock user input", "Failed"); + } + _ => {} + } + } + + #[inline(always)] + fn update_privacy_mode(&mut self, on: bool) { + let mut config = self.handler.load_config(); + config.privacy_mode = on; + self.handler.save_config(config); + + self.handler.call("updatePrivacyMode", &[]); + } + + async fn handle_back_msg_privacy_mode( + &mut self, + state: back_notification::PrivacyModeState, + ) -> bool { + match state { + back_notification::PrivacyModeState::OnByOther => { + self.handler.msgbox( + "error", + "Connecting...", + "Someone turns on privacy mode, exit", + ); + return false; + } + back_notification::PrivacyModeState::NotSupported => { + self.handler + .msgbox("custom-error", "Privacy mode", "Unsupported"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OnSucceeded => { + self.handler + .msgbox("custom-nocancel", "Privacy mode", "In privacy mode"); + self.update_privacy_mode(true); + } + back_notification::PrivacyModeState::OnFailedDenied => { + self.handler + .msgbox("custom-error", "Privacy mode", "Peer denied"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OnFailedPlugin => { + self.handler + .msgbox("custom-error", "Privacy mode", "Please install plugins"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OnFailed => { + self.handler + .msgbox("custom-error", "Privacy mode", "Failed"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OffSucceeded => { + self.handler + .msgbox("custom-nocancel", "Privacy mode", "Out privacy mode"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OffByPeer => { + self.handler + .msgbox("custom-error", "Privacy mode", "Peer exit"); + self.update_privacy_mode(false); + } + back_notification::PrivacyModeState::OffFailed => { + self.handler + .msgbox("custom-error", "Privacy mode", "Failed to turn off"); + } + back_notification::PrivacyModeState::OffUnknown => { + self.handler + .msgbox("custom-error", "Privacy mode", "Turned off"); + // log::error!("Privacy mode is turned off with unknown reason"); + self.update_privacy_mode(false); + } + _ => {} + } + true + } + fn check_clipboard_file_context(&mut self) { #[cfg(windows)] { - let enabled = unsafe { SERVER_FILE_TRANSFER_ENABLED } + let enabled = SERVER_FILE_TRANSFER_ENABLED.load(Ordering::SeqCst) && self.handler.lc.read().unwrap().enable_file_transfer; if enabled == self.clipboard_file_context.is_none() { self.clipboard_file_context = if enabled { @@ -2347,6 +2481,7 @@ impl Interface for Handler { } else if !self.is_port_forward() { if pi.displays.is_empty() { self.lc.write().unwrap().handle_peer_info(username, pi); + self.call("updatePrivacyMode", &[]); self.msgbox("error", "Remote Error", "No Display"); return; } @@ -2385,6 +2520,7 @@ impl Interface for Handler { } } self.lc.write().unwrap().handle_peer_info(username, pi); + self.call("updatePrivacyMode", &[]); self.call("updatePi", &make_args!(pi_sciter)); if self.is_file_transfer() { self.call2("closeSuccess", &make_args!()); diff --git a/src/ui/win_privacy.rs b/src/ui/win_privacy.rs new file mode 100644 index 000000000..0bbb040ec --- /dev/null +++ b/src/ui/win_privacy.rs @@ -0,0 +1,571 @@ +use crate::{ + ipc::{connect, Data, PrivacyModeState}, + platform::windows::get_user_token, +}; +use hbb_common::{allow_err, bail, lazy_static, log, tokio, ResultType}; +use std::{ + ffi::CString, + sync::Mutex, + time::{Duration, Instant}, +}; +use winapi::{ + ctypes::c_int, + shared::{ + minwindef::{DWORD, FALSE, HMODULE, LOBYTE, LPARAM, LRESULT, UINT, WPARAM}, + ntdef::{HANDLE, NULL}, + windef::{HHOOK, HWND, POINT}, + }, + um::{ + errhandlingapi::GetLastError, + handleapi::CloseHandle, + libloaderapi::{GetModuleHandleA, GetModuleHandleExA, GetProcAddress}, + memoryapi::{VirtualAllocEx, WriteProcessMemory}, + processthreadsapi::{ + CreateProcessAsUserW, GetCurrentThreadId, QueueUserAPC, ResumeThread, + PROCESS_INFORMATION, STARTUPINFOW, + }, + winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, + winnt::{MEM_COMMIT, PAGE_READWRITE}, + winuser::*, + }, +}; + +pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; +pub const INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; +pub const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow"; + +pub const GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT: u32 = 2; +pub const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 4; + +const WM_USER_EXIT_HOOK: u32 = WM_USER + 1; + +lazy_static::lazy_static! { + static ref DLL_FOUND: Mutex = Mutex::new(false); + static ref CONN_ID: Mutex = Mutex::new(0); + static ref CUR_HOOK_THREAD_ID: Mutex = Mutex::new(0); + static ref WND_HANDLERS: Mutex = Mutex::new(WindowHandlers{hthread: 0, hprocess: 0}); +} + +struct WindowHandlers { + hthread: u64, + hprocess: u64, +} + +impl Drop for WindowHandlers { + fn drop(&mut self) { + unsafe { + if self.hthread != 0 { + CloseHandle(self.hthread as _); + } + self.hthread = 0; + if self.hprocess != 0 { + CloseHandle(self.hprocess as _); + } + self.hprocess = 0; + } + } +} + +pub fn turn_on_privacy(conn_id: i32) -> ResultType { + let exe_file = std::env::current_exe()?; + if let Some(cur_dir) = exe_file.parent() { + if !cur_dir.join("WindowInjection.dll").exists() { + return Ok(false); + } + } else { + bail!( + "Invalid exe parent for {}", + exe_file.to_string_lossy().as_ref() + ); + } + + if !*DLL_FOUND.lock().unwrap() { + log::info!("turn_on_privacy, dll not found when started, try start"); + start()?; + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + + let pre_conn_id = *CONN_ID.lock().unwrap(); + if pre_conn_id == conn_id { + return Ok(true); + } + if pre_conn_id != 0 { + bail!("Privacy occupied by another one"); + } + + let hwnd = wait_find_privacy_hwnd(0)?; + if hwnd.is_null() { + bail!("No privacy window created"); + } + privacy_hook::hook()?; + unsafe { + ShowWindow(hwnd as _, SW_SHOW); + } + *CONN_ID.lock().unwrap() = conn_id; + Ok(true) +} + +pub fn turn_off_privacy(conn_id: i32, state: Option) -> ResultType<()> { + let pre_conn_id = *CONN_ID.lock().unwrap(); + if pre_conn_id != 0 && conn_id != 0 && pre_conn_id != conn_id { + bail!("Failed to turn off privacy mode that belongs to someone else") + } + + privacy_hook::unhook()?; + + unsafe { + let hwnd = wait_find_privacy_hwnd(0)?; + if !hwnd.is_null() { + ShowWindow(hwnd, SW_HIDE); + } + } + + if pre_conn_id != 0 { + if let Some(state) = state { + allow_err!(set_privacy_mode_state(pre_conn_id, state, 1_000)); + } + *CONN_ID.lock().unwrap() = 0; + } + + Ok(()) +} + +pub fn start() -> ResultType<()> { + let mut wnd_handlers = WND_HANDLERS.lock().unwrap(); + if wnd_handlers.hprocess != 0 { + return Ok(()); + } + + let exe_file = std::env::current_exe()?; + if exe_file.parent().is_none() { + bail!("Cannot get parent of current exe file"); + } + let cur_dir = exe_file.parent().unwrap(); + + let dll_file = cur_dir.join("WindowInjection.dll"); + if !dll_file.exists() { + bail!( + "Failed to find required file {}", + dll_file.to_string_lossy().as_ref() + ); + } + + *DLL_FOUND.lock().unwrap() = true; + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if !hwnd.is_null() { + log::info!("Privacy window is ready"); + return Ok(()); + } + + // let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string(); + let cmdline = cur_dir + .join(INJECTED_PROCESS_EXE) + .to_string_lossy() + .to_string(); + + unsafe { + let cmd_utf16: Vec = cmdline.encode_utf16().chain(Some(0).into_iter()).collect(); + + let mut start_info = STARTUPINFOW { + cb: 0, + lpReserved: NULL as _, + lpDesktop: NULL as _, + lpTitle: NULL as _, + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountChars: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: NULL as _, + hStdInput: NULL as _, + hStdOutput: NULL as _, + hStdError: NULL as _, + }; + let mut proc_info = PROCESS_INFORMATION { + hProcess: NULL as _, + hThread: NULL as _, + dwProcessId: 0, + dwThreadId: 0, + }; + + let session_id = WTSGetActiveConsoleSessionId(); + let token = get_user_token(session_id, true); + if token.is_null() { + bail!("Failed to get token of current user"); + } + + let create_res = CreateProcessAsUserW( + token, + NULL as _, + cmd_utf16.as_ptr() as _, + NULL as _, + NULL as _, + FALSE, + CREATE_SUSPENDED | DETACHED_PROCESS, + NULL, + NULL as _, + &mut start_info, + &mut proc_info, + ); + CloseHandle(token); + if 0 == create_res { + bail!( + "Failed to create privacy window process {}, code {}", + cmdline, + GetLastError() + ); + }; + + inject_dll( + proc_info.hProcess, + proc_info.hThread, + dll_file.to_string_lossy().as_ref(), + )?; + + if 0xffffffff == ResumeThread(proc_info.hThread) { + // CloseHandle + CloseHandle(proc_info.hThread); + CloseHandle(proc_info.hProcess); + + bail!( + "Failed to create privacy window process, {}", + GetLastError() + ); + } + + wnd_handlers.hthread = proc_info.hThread as _; + wnd_handlers.hprocess = proc_info.hProcess as _; + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if hwnd.is_null() { + bail!("Failed to get hwnd after started"); + } + } + + Ok(()) +} + +unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> { + let dll_file_utf16: Vec = dll_file.encode_utf16().chain(Some(0).into_iter()).collect(); + + let buf = VirtualAllocEx( + hproc, + NULL as _, + dll_file_utf16.len() * 2, + MEM_COMMIT, + PAGE_READWRITE, + ); + if buf.is_null() { + bail!("Failed VirtualAllocEx"); + } + + let mut written: usize = 0; + if 0 == WriteProcessMemory( + hproc, + buf, + dll_file_utf16.as_ptr() as _, + dll_file_utf16.len() * 2, + &mut written, + ) { + bail!("Failed WriteProcessMemory"); + } + + let kernel32_modulename = CString::new("kernel32")?; + let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _); + if hmodule.is_null() { + bail!("Failed GetModuleHandleA"); + } + + let load_librarya_name = CString::new("LoadLibraryW")?; + let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _); + if load_librarya.is_null() { + bail!("Failed GetProcAddress of LoadLibraryW"); + } + + if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) { + bail!("Failed QueueUserAPC"); + } + + Ok(()) +} + +fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { + let tm_begin = Instant::now(); + let wndname = CString::new(PRIVACY_WINDOW_NAME)?; + loop { + unsafe { + let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); + if !hwnd.is_null() { + return Ok(hwnd); + } + } + + if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { + return Ok(NULL as _); + } + + std::thread::sleep(Duration::from_millis(100)); + } +} + +pub fn is_process_consent_running() -> ResultType { + let output = std::process::Command::new("cmd") + .args(&["/C", "tasklist | findstr consent.exe"]) + .output()?; + Ok(output.status.success() && !output.stdout.is_empty()) +} + +#[tokio::main(flavor = "current_thread")] +async fn set_privacy_mode_state( + conn_id: i32, + state: PrivacyModeState, + ms_timeout: u64, +) -> ResultType<()> { + let mut c = connect(ms_timeout, "_cm").await?; + c.send(&Data::PrivacyModeState((conn_id, state))).await +} + +pub(super) mod privacy_hook { + use super::*; + use std::sync::mpsc::{channel, Sender}; + + fn do_hook(tx: Sender) -> ResultType<(HHOOK, HHOOK)> { + let invalid_ret = (0 as HHOOK, 0 as HHOOK); + + let mut cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); + if *cur_hook_thread_id != 0 { + // unreachable! + tx.send("Already hooked".to_owned())?; + return Ok(invalid_ret); + } + + unsafe { + let mut hm_keyboard = 0 as HMODULE; + if 0 == GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + DefWindowProcA as _, + &mut hm_keyboard as _, + ) { + tx.send(format!( + "Failed to GetModuleHandleExA, error: {}", + GetLastError() + ))?; + return Ok(invalid_ret); + } + let mut hm_mouse = 0 as HMODULE; + if 0 == GetModuleHandleExA( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + DefWindowProcA as _, + &mut hm_mouse as _, + ) { + tx.send(format!( + "Failed to GetModuleHandleExA, error: {}", + GetLastError() + ))?; + return Ok(invalid_ret); + } + + let hook_keyboard = SetWindowsHookExA( + WH_KEYBOARD_LL, + Some(privacy_mode_hook_keyboard), + hm_keyboard, + 0, + ); + if hook_keyboard.is_null() { + tx.send(format!(" SetWindowsHookExA keyboard {}", GetLastError()))?; + return Ok(invalid_ret); + } + + let hook_mouse = + SetWindowsHookExA(WH_MOUSE_LL, Some(privacy_mode_hook_mouse), hm_mouse, 0); + if hook_mouse.is_null() { + if FALSE == UnhookWindowsHookEx(hook_keyboard) { + // Fatal error + log::error!(" UnhookWindowsHookEx keyboard {}", GetLastError()); + } + tx.send(format!(" SetWindowsHookExA mouse {}", GetLastError()))?; + return Ok(invalid_ret); + } + + *cur_hook_thread_id = GetCurrentThreadId(); + tx.send("".to_owned())?; + return Ok((hook_keyboard, hook_mouse)); + } + } + + pub fn hook() -> ResultType<()> { + let (tx, rx) = channel(); + std::thread::spawn(move || { + let hook_keyboard; + let hook_mouse; + unsafe { + match do_hook(tx.clone()) { + Ok(hooks) => { + hook_keyboard = hooks.0; + hook_mouse = hooks.1; + } + Err(e) => { + // Fatal error + tx.send(format!("Unexpected err when hook {}", e)).unwrap(); + return; + } + } + if hook_keyboard.is_null() { + return; + } + + let mut msg = MSG { + hwnd: NULL as _, + message: 0 as _, + wParam: 0 as _, + lParam: 0 as _, + time: 0 as _, + pt: POINT { + x: 0 as _, + y: 0 as _, + }, + }; + while FALSE != GetMessageA(&mut msg, NULL as _, 0, 0) { + if msg.message == WM_USER_EXIT_HOOK { + break; + } + + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + if FALSE == UnhookWindowsHookEx(hook_keyboard as _) { + // Fatal error + log::error!("Failed UnhookWindowsHookEx keyboard {}", GetLastError()); + } + + if FALSE == UnhookWindowsHookEx(hook_mouse as _) { + // Fatal error + log::error!("Failed UnhookWindowsHookEx mouse {}", GetLastError()); + } + + *CUR_HOOK_THREAD_ID.lock().unwrap() = 0; + } + }); + + match rx.recv() { + Ok(msg) => { + if msg == "" { + Ok(()) + } else { + bail!(msg) + } + } + Err(e) => { + bail!("Failed to wait hook result {}", e) + } + } + } + + pub fn unhook() -> ResultType<()> { + unsafe { + let cur_hook_thread_id = CUR_HOOK_THREAD_ID.lock().unwrap(); + if *cur_hook_thread_id != 0 { + if FALSE == PostThreadMessageA(*cur_hook_thread_id, WM_USER_EXIT_HOOK, 0, 0) { + bail!("Failed to post message to exit hook, {}", GetLastError()); + } + } + } + Ok(()) + } + + #[no_mangle] + pub extern "system" fn privacy_mode_hook_keyboard( + code: c_int, + w_param: WPARAM, + l_param: LPARAM, + ) -> LRESULT { + if code < 0 { + unsafe { + return CallNextHookEx(NULL as _, code, w_param, l_param); + } + } + + let ks = l_param as PKBDLLHOOKSTRUCT; + let w_param2 = w_param as UINT; + + unsafe { + if (*ks).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { + // Disable alt key. Alt + Tab will switch windows. + if (*ks).flags & LLKHF_ALTDOWN == LLKHF_ALTDOWN { + return 1; + } + + match w_param2 { + WM_KEYDOWN => { + // Disable all keys other than P and Ctrl. + if ![80, 162, 163].contains(&(*ks).vkCode) { + return 1; + } + + // NOTE: GetKeyboardState may not work well... + + // Check if Ctrl + P is pressed + let cltr_down = (GetKeyState(VK_CONTROL) as u16) & (0x8000 as u16) > 0; + let key = LOBYTE((*ks).vkCode as _); + if cltr_down && (key == 'p' as u8 || key == 'P' as u8) { + // Ctrl + P is pressed, turn off privacy mode + if let Err(e) = + turn_off_privacy(0, Some(crate::ipc::PrivacyModeState::OffByPeer)) + { + log::error!("Failed to off_privacy {}", e); + } + } + } + WM_KEYUP => { + log::trace!("WM_KEYUP {}", (*ks).vkCode); + } + _ => { + log::trace!("KEYBOARD OTHER {} {}", w_param2, (*ks).vkCode); + } + } + } + } + unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } + } + + #[no_mangle] + pub extern "system" fn privacy_mode_hook_mouse( + code: c_int, + w_param: WPARAM, + l_param: LPARAM, + ) -> LRESULT { + if code < 0 { + unsafe { + return CallNextHookEx(NULL as _, code, w_param, l_param); + } + } + + let ms = l_param as PMOUSEHOOKSTRUCT; + unsafe { + if (*ms).dwExtraInfo != enigo::ENIGO_INPUT_EXTRA_VALUE { + return 1; + } + } + unsafe { CallNextHookEx(NULL as _, code, w_param, l_param) } + } +} + +mod test { + #[test] + fn privacy_hook() { + //use super::*; + + // privacy_hook::hook().unwrap(); + // std::thread::sleep(std::time::Duration::from_millis(50)); + // privacy_hook::unhook().unwrap(); + } +} diff --git a/src/windows.cc b/src/windows.cc index 81df780a2..dd3fa2e9e 100644 --- a/src/windows.cc +++ b/src/windows.cc @@ -53,26 +53,28 @@ DWORD GetLogonPid(DWORD dwSessionId, BOOL as_user) return dwLogonPid; } -// if should try WTSQueryUserToken? -// https://stackoverflow.com/questions/7285666/example-code-a-service-calls-createprocessasuser-i-want-the-process-to-run-in -BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken, DWORD dwSessionId, BOOL as_user) -{ - BOOL bResult = FALSE; - DWORD Id = GetLogonPid(dwSessionId, as_user); - if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id)) - { - bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken); - CloseHandle(hProcess); - } - return bResult; -} - +// START the app as system extern "C" { + // if should try WTSQueryUserToken? + // https://stackoverflow.com/questions/7285666/example-code-a-service-calls-createprocessasuser-i-want-the-process-to-run-in + BOOL GetSessionUserTokenWin(OUT LPHANDLE lphUserToken, DWORD dwSessionId, BOOL as_user) + { + BOOL bResult = FALSE; + DWORD Id = GetLogonPid(dwSessionId, as_user); + if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id)) + { + bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken); + CloseHandle(hProcess); + } + return bResult; + } + bool is_windows_server() { return IsWindowsServer(); } + HANDLE LaunchProcessWin(LPCWSTR cmd, DWORD dwSessionId, BOOL as_user) { HANDLE hProcess = NULL;