Merge branch 'master' into dev

This commit is contained in:
xxrl 2022-11-10 00:01:42 +08:00
commit 1b7950fc42
74 changed files with 1987 additions and 433 deletions

@ -38,7 +38,7 @@ jobs:
shell: bash shell: bash
run: | run: |
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
x86_64-unknown-linux-gnu) sudo apt-get -y update ; 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 libclang-dev ninja-build libayatana-appindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev;; x86_64-unknown-linux-gnu) sudo apt-get -y update ; 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 libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev;;
# arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; # arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; # aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac esac

@ -11,6 +11,7 @@ env:
FLUTTER_VERSION: "3.0.5" FLUTTER_VERSION: "3.0.5"
TAG_NAME: "nightly" TAG_NAME: "nightly"
VCPKG_COMMIT_ID: '6ca56aeb457f033d344a7106cb3f9f1abf8f4e98' VCPKG_COMMIT_ID: '6ca56aeb457f033d344a7106cb3f9f1abf8f4e98'
VERSION: "1.2.0"
jobs: jobs:
build-for-windows: build-for-windows:
@ -92,7 +93,7 @@ jobs:
rustdesk-*.exe rustdesk-*.exe
build-for-linux: build-for-linux:
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) name: ${{ matrix.job.target }} (${{ matrix.job.os }},${{ matrix.job.extra-build-args }})
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -104,7 +105,8 @@ jobs:
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } # - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } # - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
# - { target: x86_64-apple-darwin , os: macos-10.15 } # - { target: x86_64-apple-darwin , os: macos-10.15 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-18.04} - { target: x86_64-unknown-linux-gnu , os: ubuntu-18.04, extra-build-args: ""}
- { target: x86_64-unknown-linux-gnu , os: ubuntu-18.04, extra-build-args: "--flatpak"}
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } # - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
steps: steps:
- name: Checkout source code - name: Checkout source code
@ -113,7 +115,7 @@ jobs:
- name: Install prerequisites - name: Install prerequisites
run: | run: |
case ${{ matrix.job.target }} in case ${{ matrix.job.target }} in
x86_64-unknown-linux-gnu) sudo apt-get -y update ; 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 libclang-dev ninja-build libayatana-appindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev;; x86_64-unknown-linux-gnu) sudo apt-get -y update ; 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 libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libvdpau-dev libva-dev libclang-dev llvm-dev libclang-10-dev llvm-10-dev;;
# arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; # arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; # aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac esac
@ -159,7 +161,7 @@ jobs:
- name: Install cargo bundle tools - name: Install cargo bundle tools
run: | run: |
cargo install cargo-bundle --force cargo install cargo-bundle
- name: Show version information (Rust, cargo, GCC) - name: Show version information (Rust, cargo, GCC)
shell: bash shell: bash
@ -172,7 +174,7 @@ jobs:
rustc -V rustc -V
- name: Build rustdesk - name: Build rustdesk
run: ./build.py --flutter --hwcodec run: ./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: Rename rustdesk - name: Rename rustdesk
shell: bash shell: bash
@ -187,9 +189,17 @@ jobs:
prerelease: true prerelease: true
tag_name: ${{ env.TAG_NAME }} tag_name: ${{ env.TAG_NAME }}
files: | files: |
rustdesk*.deb rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-${{ matrix.job.os }}.deb
- name: Upload Artifcat
uses: actions/upload-artifact@master
if: ${{ contains(matrix.job.extra-build-args, 'flatpak') }}
with:
name: rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-${{ matrix.job.os }}.deb
path: rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-${{ matrix.job.os }}.deb
- name: Build archlinux package - name: Build archlinux package
if: ${{ matrix.job.extra-build-args == '' }}
uses: vufa/arch-makepkg-action@master uses: vufa/arch-makepkg-action@master
with: with:
packages: > packages: >
@ -220,14 +230,85 @@ jobs:
python python
ttf-arphic-uming ttf-arphic-uming
libappindicator-gtk3 libappindicator-gtk3
libayatana-appindicator
scripts: | scripts: |
cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f
- name: Publish archlinux package - name: Publish archlinux package
if: ${{ matrix.job.extra-build-args == '' }}
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
prerelease: true prerelease: true
tag_name: ${{ env.TAG_NAME }} tag_name: ${{ env.TAG_NAME }}
files: | files: |
res/rustdesk*.zst res/rustdesk*.zst
# - name: build RPM package
# id: rpm
# uses: Kingtous/rustdesk-rpmbuild@master
# with:
# spec_file: "res/rpm-flutter.spec"
# - name: Publish fedora28/centos8 package
# uses: softprops/action-gh-release@v1
# with:
# prerelease: true
# tag_name: ${{ env.TAG_NAME }}
# files: |
# ${{ steps.rpm.outputs.rpm_dir_path }}/*
build-flatpak:
name: Build Flatpak
needs: [build-for-linux]
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
# - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
# - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
# - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
# - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
# - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
# - { target: x86_64-apple-darwin , os: macos-10.15 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-18.04, arch: x86_64}
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y flatpak flatpak-builder cmake g++ gcc git curl wget nasm yasm libgtk-3-dev
- name: Download Binary
uses: actions/download-artifact@master
with:
name: rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-${{ matrix.job.os }}.deb
path: .
- name: Rename Binary
run: |
mv rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}-${{ matrix.job.os }}.deb rustdesk-${{ env.VERSION }}.deb
- name: Install Flatpak deps
run: |
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/21.08
flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/21.08
- name: Make Flatpak package
run: |
pushd flatpak
git clone https://github.com/flathub/shared-modules.git --depth=1
flatpak-builder --user --force-clean --repo=repo ./build ./rustdesk.json
flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}.flatpak org.rustdesk.rustdesk
- name: Publish flatpak package
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
flatpak/rustdesk-${{ env.VERSION }}-${{ matrix.job.target }}.flatpak

1
.gitignore vendored

@ -36,5 +36,6 @@ flatpak/.flatpak-builder/build/**
flatpak/.flatpak-builder/shared-modules/** flatpak/.flatpak-builder/shared-modules/**
flatpak/.flatpak-builder/shared-modules/*.tar.xz flatpak/.flatpak-builder/shared-modules/*.tar.xz
flatpak/.flatpak-builder/debian-binary flatpak/.flatpak-builder/debian-binary
flatpak/build/**
# bridge file # bridge file
lib/generated_bridge.dart lib/generated_bridge.dart

4
Cargo.lock generated

@ -2463,7 +2463,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hwcodec" name = "hwcodec"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/21pages/hwcodec#1f03d203eca24dc976c21a47228f3bc31484c2bc" source = "git+https://github.com/21pages/hwcodec#bf73e8e650abca3e004e96a245086b3647b9d84a"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
@ -4419,6 +4419,7 @@ dependencies = [
"system_shutdown", "system_shutdown",
"tray-item", "tray-item",
"trayicon", "trayicon",
"url",
"uuid", "uuid",
"virtual_display", "virtual_display",
"whoami", "whoami",
@ -5492,6 +5493,7 @@ dependencies = [
"idna", "idna",
"matches", "matches",
"percent-encoding", "percent-encoding",
"serde 1.0.144",
] ]
[[package]] [[package]]

@ -64,9 +64,9 @@ wol-rs = "0.9.1"
flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true } flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true }
errno = "0.2.8" errno = "0.2.8"
rdev = { git = "https://github.com/asur4s/rdev" } rdev = { git = "https://github.com/asur4s/rdev" }
url = { version = "2.1", features = ["serde"] }
[target.'cfg(not(target_os = "linux"))'.dependencies] reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features=false }
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.13.5" cpal = "0.13.5"
@ -147,7 +147,7 @@ hound = "3.5"
name = "RustDesk" name = "RustDesk"
identifier = "com.carriez.rustdesk" identifier = "com.carriez.rustdesk"
icon = ["res/32x32.png", "res/128x128.png", "res/128x128@2x.png"] icon = ["res/32x32.png", "res/128x128.png", "res/128x128@2x.png"]
deb_depends = ["libgtk-3-0", "libxcb-randr0", "libxdo3", "libxfixes3", "libxcb-shape0", "libxcb-xfixes0", "libasound2", "libsystemd0", "pipewire", "curl", "libayatana-appindicator3-1", "libvdpau1", "libva2"] deb_depends = ["libgtk-3-0", "libxcb-randr0", "libxdo3", "libxfixes3", "libxcb-shape0", "libxcb-xfixes0", "libasound2", "libsystemd0", "curl", "libappindicator3-1", "libvdpau1", "libva2"]
osx_minimum_system_version = "10.14" osx_minimum_system_version = "10.14"
resources = ["res/mac-tray-light.png","res/mac-tray-dark.png"] resources = ["res/mac-tray-light.png","res/mac-tray-dark.png"]

@ -69,7 +69,7 @@ Please download sciter dynamic library yourself.
```sh ```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \ sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \ libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build libayatana-appindicator3-1 libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libayatana-appindicator3-dev libclang-dev ninja-build libappindicator3-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
``` ```
### openSUSE Tumbleweed ### openSUSE Tumbleweed

@ -81,6 +81,11 @@ def make_parser():
action='store_true', action='store_true',
help='Build windows portable' help='Build windows portable'
) )
parser.add_argument(
'--flatpak',
action='store_true',
help='Build rustdesk libs with the flatpak feature enabled'
)
return parser return parser
@ -188,6 +193,8 @@ def get_features(args):
features.append('hwcodec') features.append('hwcodec')
if args.flutter: if args.flutter:
features.append('flutter') features.append('flutter')
if args.flatpak:
features.append('flatpak')
print("features:", features) print("features:", features)
return features return features
@ -201,7 +208,7 @@ Version: %s
Architecture: amd64 Architecture: amd64
Maintainer: open-trade <info@rustdesk.com> Maintainer: open-trade <info@rustdesk.com>
Homepage: https://rustdesk.com Homepage: https://rustdesk.com
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, pipewire, curl Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, pipewire, curl, libappindicator3-1, libva-drm2, libva-x11-2, libvdpau1
Description: A remote control software. Description: A remote control software.
""" % version """ % version

38
flatpak/rustdesk.json Normal file

@ -0,0 +1,38 @@
{
"app-id": "org.rustdesk.rustdesk",
"runtime": "org.freedesktop.Platform",
"runtime-version": "21.08",
"sdk": "org.freedesktop.Sdk",
"command": "rustdesk",
"modules": [
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
"xdotool.json",
{
"name": "rustdesk",
"buildsystem": "simple",
"build-commands": [
"bsdtar -zxvf rustdesk-1.2.0.deb",
"tar -xvf ./data.tar.xz",
"cp -r ./usr /app/",
"mkdir -p /app/bin && ln -s /app/usr/lib/rustdesk/rustdesk /app/bin/rustdesk"
],
"sources": [
{
"type": "file",
"path": "../rustdesk-1.2.0.deb"
}
]
}
],
"finish-args": [
"--share=ipc",
"--socket=x11",
"--socket=fallback-x11",
"--socket=wayland",
"--share=network",
"--filesystem=home",
"--device=dri",
"--socket=pulseaudio",
"--talk-name=org.freedesktop.Flatpak"
]
}

@ -1,31 +0,0 @@
app-id: org.rustdesk.rustdesk
runtime: org.freedesktop.Platform
runtime-version: '21.08'
sdk: org.freedesktop.Sdk
command: rustdesk
modules:
# install appindicator
- shared-modules/libappindicator/libappindicator-gtk3-12.10.json
- name: rustdesk
buildsystem: simple
build-commands:
- bsdtar -zxvf rustdesk-1.2.0.deb
- tar -xvf ./data.tar.xz
- cp -r ./usr /app/
- rm /app/usr/bin/rustdesk
- mkdir -p /app/bin && ln -s /app/usr/lib/rustdesk/flutter_hbb /app/bin/rustdesk
sources:
# Note: replace to deb files with url
- type: file
path: ../rustdesk-1.2.0.deb
finish-args:
# X11 + XShm access
- --share=ipc
- --socket=x11
# Wayland access
- --socket=wayland
# Needs to talk to the network:
- --share=network
# Needs to save files locally
- --filesystem=xdg-documents

15
flatpak/xdotool.json Normal file

@ -0,0 +1,15 @@
{
"name": "xdotool",
"buildsystem": "simple",
"build-commands": [
"make -j4 && PREFIX=./build make install",
"cp -r ./build/* /app/"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz",
"sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada"
}
]
}

File diff suppressed because one or more lines are too long

After

(image error) Size: 48 KiB

File diff suppressed because one or more lines are too long

After

(image error) Size: 38 KiB

30
flutter/assets/Okta.svg Normal file

@ -0,0 +1,30 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="220.000000pt" height="74.000000pt" viewBox="0 0 220.000000 74.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,74.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M627 734 c-4 -4 -7 -165 -7 -359 0 -260 3 -354 12 -363 13 -13 85
-16 112 -6 13 5 16 24 16 100 0 133 7 133 138 1 l105 -107 67 0 c115 0 113 10
-28 154 -67 67 -122 130 -122 139 0 10 37 58 83 108 133 147 133 149 24 149
l-72 0 -79 -86 c-48 -52 -85 -84 -95 -82 -14 3 -17 27 -21 178 l-5 175 -60 3
c-34 2 -64 0 -68 -4z"/>
<path d="M1184 727 c-3 -8 -4 -127 -2 -264 l3 -250 30 -58 c50 -98 163 -165
260 -153 28 3 30 6 33 47 5 75 -3 91 -46 91 -50 0 -102 29 -124 71 -21 40 -26
177 -6 197 7 7 42 12 85 12 l73 0 0 65 0 65 -72 0 c-40 0 -78 4 -85 8 -7 5
-13 40 -15 92 l-3 85 -63 3 c-47 2 -64 -1 -68 -11z"/>
<path d="M185 537 c-119 -48 -179 -133 -179 -257 -1 -62 4 -84 26 -125 52 -99
134 -149 243 -148 82 0 128 18 186 70 54 49 81 104 87 179 8 110 -41 205 -135
261 -37 21 -61 27 -122 30 -45 1 -89 -2 -106 -10z m152 -136 c70 -32 96 -117
59 -189 -34 -66 -112 -89 -183 -55 -100 47 -95 194 7 245 44 23 65 22 117 -1z"/>
<path d="M1713 533 c-51 -18 -122 -83 -147 -137 -45 -96 -27 -218 45 -299 56
-64 117 -91 204 -91 59 0 79 4 128 31 31 17 57 27 57 23 0 -5 15 -18 34 -29
42 -27 116 -37 145 -22 19 11 22 19 19 69 -3 55 -4 56 -38 65 -63 16 -64 19
-70 220 l-5 182 -63 3 c-46 2 -64 -1 -68 -11 -4 -12 -11 -12 -42 -1 -49 17
-147 16 -199 -3z m150 -127 c54 -22 87 -70 87 -124 0 -109 -93 -170 -195 -128
-75 32 -101 144 -48 207 27 32 74 58 106 59 10 0 32 -6 50 -14z"/>
</g>
</svg>

After

(image error) Size: 1.7 KiB

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:back_button_interceptor/back_button_interceptor.dart';
@ -1036,6 +1037,7 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
final isMaximized = await wc.isMaximized(); final isMaximized = await wc.isMaximized();
final pos = LastWindowPosition( final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized); sz.width, sz.height, position.dx, position.dy, isMaximized);
debugPrint("saving frame: ${windowId}: ${pos.width}/${pos.height}, offset:${pos.offsetWidth}/${pos.offsetHeight}");
await Get.find<SharedPreferences>() await Get.find<SharedPreferences>()
.setString(kWindowPrefix + type.name, pos.toString()); .setString(kWindowPrefix + type.name, pos.toString());
break; break;
@ -1081,7 +1083,7 @@ Future<Size> _adjustRestoreMainWindowSize(double? width, double? height) async {
restoreWidth = maxWidth; restoreWidth = maxWidth;
} }
if (restoreHeight > maxHeight) { if (restoreHeight > maxHeight) {
restoreWidth = maxHeight; restoreHeight = maxHeight;
} }
return Size(restoreWidth, restoreHeight); return Size(restoreWidth, restoreHeight);
} }
@ -1092,11 +1094,11 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
if (left == null || top == null) { if (left == null || top == null) {
await windowManager.center(); await windowManager.center();
} else { } else {
double windowLeft = left; double windowLeft = max(0.0, left);
double windowTop = top; double windowTop = max(0.0, top);
double frameLeft = 0; double frameLeft = double.infinity;
double frameTop = 0; double frameTop = double.infinity;
double frameRight = ((isDesktop || isWebDesktop) double frameRight = ((isDesktop || isWebDesktop)
? kDesktopMaxDisplayWidth ? kDesktopMaxDisplayWidth
: kMobileMaxDisplayWidth) : kMobileMaxDisplayWidth)
@ -1107,12 +1109,11 @@ Future<Offset?> _adjustRestoreMainWindowOffset(
.toDouble(); .toDouble();
if (isDesktop || isWebDesktop) { if (isDesktop || isWebDesktop) {
final screen = (await window_size.getWindowInfo()).screen; for(final screen in await window_size.getScreenList()) {
if (screen != null) { frameLeft = min(screen.visibleFrame.left, frameLeft);
frameLeft = screen.visibleFrame.left; frameTop = min(screen.visibleFrame.top, frameTop);
frameTop = screen.visibleFrame.top; frameRight = max(screen.visibleFrame.right, frameRight);
frameRight = screen.visibleFrame.right; frameBottom = max(screen.visibleFrame.bottom, frameBottom);
frameBottom = screen.visibleFrame.bottom;
} }
} }
@ -1174,6 +1175,7 @@ Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
await _adjustRestoreMainWindowSize(lpos.width, lpos.height); await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offset = await _adjustRestoreMainWindowOffset( final offset = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth, lpos.offsetHeight); lpos.offsetWidth, lpos.offsetHeight);
debugPrint("restore lpos: ${size.width}/${size.height}, offset:${offset?.dx}/${offset?.dy}");
if (offset == null) { if (offset == null) {
await wc.center(); await wc.center();
} else { } else {

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
import 'package:flutter_hbb/desktop/widgets/login.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu; import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import 'package:get/get.dart'; import 'package:get/get.dart';

@ -21,7 +21,7 @@ const String kTabLabelSettingPage = "Settings";
const String kWindowPrefix = "wm_"; const String kWindowPrefix = "wm_";
// the executable name of the portable version // the executable name of the portable version
const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59); const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
@ -60,6 +60,12 @@ const kInvalidValueStr = "InvalidValueStr";
const kMobilePageConstraints = BoxConstraints(maxWidth: 600); const kMobilePageConstraints = BoxConstraints(maxWidth: 600);
/// [kMouseControlDistance] indicates the distance that self-side move to get control of mouse.
const kMouseControlDistance = 12;
/// [kMouseControlTimeoutMSec] indicates the timeout (in milliseconds) that self-side can get control of mouse.
const kMouseControlTimeoutMSec = 1000;
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels /// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
/// see [LogicalKeyboardKey.keyLabel] /// see [LogicalKeyboardKey.keyLabel]
const Map<int, String> logicalKeyMap = <int, String>{ const Map<int, String> logicalKeyMap = <int, String>{

@ -288,7 +288,7 @@ class _ConnectionPageState extends State<ConnectionPage>
offstage: !svcStopped.value, offstage: !svcStopped.value,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
bool checked = bool checked = !bind.mainIsInstalled() ||
await bind.mainCheckSuperUserPermission(); await bind.mainCheckSuperUserPermission();
if (checked) { if (checked) {
bind.mainSetOption(key: "stop-service", value: ""); bind.mainSetOption(key: "stop-service", value: "");

@ -470,141 +470,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
} }
/// common login dialog for desktop
/// call this directly
Future<bool> loginDialog() async {
String userName = "";
var userNameMsg = "";
String pass = "";
var passMsg = "";
var userController = TextEditingController(text: userName);
var pwdController = TextEditingController(text: pass);
var isInProgress = false;
var completer = Completer<bool>();
gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
userNameMsg = "";
passMsg = "";
isInProgress = true;
});
cancel() {
setState(() {
isInProgress = false;
});
}
userName = userController.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
cancel();
return;
}
if (pass.isEmpty) {
passMsg = translate("Password missed");
cancel();
return;
}
try {
final resp = await gFFI.userModel.login(userName, pass);
if (resp.containsKey('error')) {
passMsg = resp['error'];
cancel();
return;
}
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
debugPrint("$resp");
completer.complete(true);
} catch (err) {
debugPrint(err.toString());
cancel();
return;
}
close();
}
cancel() {
completer.complete(false);
close();
}
return CustomAlertDialog(
title: Text(translate("Login")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
"${translate('Username')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userController,
focusNode: FocusNode()..requestFocus(),
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController,
),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
),
actions: [
TextButton(onPressed: cancel, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: cancel,
);
});
return completer.future;
}
void setPasswordDialog() async { void setPasswordDialog() async {
final pw = await bind.mainGetPermanentPassword(); final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw); final p0 = TextEditingController(text: pw);

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/widgets/login.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';

@ -96,8 +96,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
void onRemoveId(String id) { void onRemoveId(String id) {
if (tabController.state.value.tabs.isEmpty) { if (tabController.state.value.tabs.isEmpty) {
WindowController.fromWindowId(windowId()).hide(); WindowController.fromWindowId(windowId()).close();
rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId()});
} }
} }

@ -107,8 +107,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
void onRemoveId(String id) { void onRemoveId(String id) {
if (tabController.state.value.tabs.isEmpty) { if (tabController.state.value.tabs.isEmpty) {
WindowController.fromWindowId(windowId()).hide(); WindowController.fromWindowId(windowId()).close();
rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId()});
} }
} }

@ -97,9 +97,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
} }
_update_remote_count(); _update_remote_count();
}); });
Future.delayed(Duration.zero, () {
restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId());
});
} }
@override @override
@ -321,10 +318,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
); );
} }
void onRemoveId(String id) { void onRemoveId(String id) async {
if (tabController.state.value.tabs.isEmpty) { if (tabController.state.value.tabs.isEmpty) {
WindowController.fromWindowId(windowId()).hide(); await WindowController.fromWindowId(windowId()).close();
rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId()});
} }
ConnectionTypeState.delete(id); ConnectionTypeState.delete(id);
_update_remote_count(); _update_remote_count();

@ -134,6 +134,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
showMaximize: false, showMaximize: false,
showMinimize: true, showMinimize: true,
showClose: true, showClose: true,
onWindowCloseButton: handleWindowCloseButton,
controller: serverModel.tabController, controller: serverModel.tabController,
maxLabelWidth: 100, maxLabelWidth: 100,
tail: buildScrollJumper(), tail: buildScrollJumper(),
@ -206,6 +207,27 @@ class ConnectionManagerState extends State<ConnectionManager> {
], ],
)); ));
} }
Future<bool> handleWindowCloseButton() async {
var tabController = gFFI.serverModel.tabController;
final connLength = tabController.length;
if (connLength <= 1) {
windowManager.close();
return true;
} else {
final opt = "enable-confirm-closing-tabs";
final bool res;
if (!option2bool(opt, await bind.mainGetOption(key: opt))) {
res = true;
} else {
res = await closeConfirmDialog();
}
if (res) {
windowManager.close();
}
return res;
}
}
} }
Widget buildConnectionCard(Client client) { Widget buildConnectionCard(Client client) {

@ -0,0 +1,521 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
final kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0);
class _IconOP extends StatelessWidget {
final String icon;
final double iconWidth;
const _IconOP({Key? key, required this.icon, required this.iconWidth})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
child: SvgPicture.asset(
'assets/$icon.svg',
width: iconWidth,
),
);
}
}
class ButtonOP extends StatelessWidget {
final String op;
final RxString curOP;
final double iconWidth;
final Color primaryColor;
final double height;
final Function() onTap;
const ButtonOP({
Key? key,
required this.op,
required this.curOP,
required this.iconWidth,
required this.primaryColor,
required this.height,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(children: [
Expanded(
child: Container(
height: height,
padding: kMidButtonPadding,
child: Obx(() => ElevatedButton(
style: ElevatedButton.styleFrom(
primary: curOP.value.isEmpty || curOP.value == op
? primaryColor
: Colors.grey,
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
onPressed:
curOP.value.isEmpty || curOP.value == op ? onTap : null,
child: Stack(children: [
Center(child: Text('${translate("Continue with")} $op')),
Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: 120,
child: _IconOP(
icon: op,
iconWidth: iconWidth,
)),
),
]),
)),
),
)
]);
}
}
class ConfigOP {
final String op;
final double iconWidth;
ConfigOP({required this.op, required this.iconWidth});
}
class WidgetOP extends StatefulWidget {
final ConfigOP config;
final RxString curOP;
final Function(String) cbLogin;
const WidgetOP({
Key? key,
required this.config,
required this.curOP,
required this.cbLogin,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _WidgetOPState();
}
}
class _WidgetOPState extends State<WidgetOP> {
Timer? _updateTimer;
String _stateMsg = '';
String _FailedMsg = '';
String _url = '';
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
_updateTimer?.cancel();
}
_beginQueryState() {
_updateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
_updateState();
});
}
_updateState() {
bind.mainAccountAuthResult().then((result) {
if (result.isEmpty) {
return;
}
final resultMap = jsonDecode(result);
if (resultMap == null) {
return;
}
final String stateMsg = resultMap['state_msg'];
String failedMsg = resultMap['failed_msg'];
final String? url = resultMap['url'];
final authBody = resultMap['auth_body'];
if (_stateMsg != stateMsg || _FailedMsg != failedMsg) {
if (_url.isEmpty && url != null && url.isNotEmpty) {
launchUrl(Uri.parse(url));
_url = url;
}
if (authBody != null) {
_updateTimer?.cancel();
final String username = authBody['user']['name'];
widget.curOP.value = '';
widget.cbLogin(username);
}
setState(() {
_stateMsg = stateMsg;
_FailedMsg = failedMsg;
if (failedMsg.isNotEmpty) {
widget.curOP.value = '';
_updateTimer?.cancel();
}
});
}
});
}
_resetState() {
_stateMsg = '';
_FailedMsg = '';
_url = '';
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ButtonOP(
op: widget.config.op,
curOP: widget.curOP,
iconWidth: widget.config.iconWidth,
primaryColor: str2color(widget.config.op, 0x7f),
height: 36,
onTap: () async {
_resetState();
widget.curOP.value = widget.config.op;
await bind.mainAccountAuth(op: widget.config.op);
_beginQueryState();
},
),
Obx(() {
if (widget.curOP.isNotEmpty &&
widget.curOP.value != widget.config.op) {
_FailedMsg = '';
}
return Offstage(
offstage:
_FailedMsg.isEmpty && widget.curOP.value != widget.config.op,
child: Row(
children: [
Text(
_stateMsg,
style: TextStyle(fontSize: 12),
),
SizedBox(width: 8),
Text(
_FailedMsg,
style: TextStyle(
fontSize: 14,
color: Colors.red,
),
),
],
));
}),
Obx(
() => Offstage(
offstage: widget.curOP.value != widget.config.op,
child: const SizedBox(
height: 5.0,
),
),
),
Obx(
() => Offstage(
offstage: widget.curOP.value != widget.config.op,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 20),
child: ElevatedButton(
onPressed: () {
widget.curOP.value = '';
_updateTimer?.cancel();
_resetState();
bind.mainAccountAuthCancel();
},
child: Text(
translate('Cancel'),
style: TextStyle(fontSize: 15),
),
),
),
),
),
],
);
}
}
class LoginWidgetOP extends StatelessWidget {
final List<ConfigOP> ops;
final RxString curOP;
final Function(String) cbLogin;
LoginWidgetOP({
Key? key,
required this.ops,
required this.curOP,
required this.cbLogin,
}) : super(key: key);
@override
Widget build(BuildContext context) {
var children = ops
.map((op) => [
WidgetOP(
config: op,
curOP: curOP,
cbLogin: cbLogin,
),
const Divider(
indent: 5,
endIndent: 5,
)
])
.expand((i) => i)
.toList();
if (children.isNotEmpty) {
children.removeLast();
}
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children,
));
}
}
class LoginWidgetUserPass extends StatelessWidget {
final String username;
final String pass;
final String usernameMsg;
final String passMsg;
final bool isInProgress;
final RxString curOP;
final Function(String, String) onLogin;
const LoginWidgetUserPass({
Key? key,
required this.username,
required this.pass,
required this.usernameMsg,
required this.passMsg,
required this.isInProgress,
required this.curOP,
required this.onLogin,
}) : super(key: key);
@override
Widget build(BuildContext context) {
var userController = TextEditingController(text: username);
var pwdController = TextEditingController(text: pass);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Container(
padding: kMidButtonPadding,
child: Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
'${translate("Username")}:',
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: usernameMsg.isNotEmpty ? usernameMsg : null),
controller: userController,
focusNode: FocusNode()..requestFocus(),
),
),
],
),
),
const SizedBox(
height: 8.0,
),
Container(
padding: kMidButtonPadding,
child: Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text('${translate("Password")}:')
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController,
),
),
],
),
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator()),
const SizedBox(
height: 12.0,
),
Row(children: [
Expanded(
child: Container(
height: 38,
padding: kMidButtonPadding,
child: Obx(() => ElevatedButton(
style: curOP.value.isEmpty || curOP.value == 'rustdesk'
? null
: ElevatedButton.styleFrom(
primary: Colors.grey,
),
child: Text(
translate('Login'),
style: TextStyle(fontSize: 16),
),
onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk'
? () {
onLogin(userController.text, pwdController.text);
}
: null,
)),
),
),
]),
],
);
}
}
/// common login dialog for desktop
/// call this directly
Future<bool> loginDialog() async {
String username = '';
var usernameMsg = '';
String pass = '';
var passMsg = '';
var isInProgress = false;
var completer = Completer<bool>();
final RxString curOP = ''.obs;
gFFI.dialogManager.show((setState, close) {
cancel() {
isInProgress = false;
completer.complete(false);
close();
}
onLogin(String username0, String pass0) async {
setState(() {
usernameMsg = '';
passMsg = '';
isInProgress = true;
});
cancel() {
curOP.value = '';
if (isInProgress) {
setState(() {
isInProgress = false;
});
}
}
curOP.value = 'rustdesk';
username = username0;
pass = pass0;
if (username.isEmpty) {
usernameMsg = translate('Username missed');
cancel();
return;
}
if (pass.isEmpty) {
passMsg = translate('Password missed');
cancel();
return;
}
try {
final resp = await gFFI.userModel.login(username, pass);
if (resp.containsKey('error')) {
passMsg = resp['error'];
cancel();
return;
}
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
debugPrint('$resp');
completer.complete(true);
} catch (err) {
debugPrint(err.toString());
cancel();
return;
}
close();
}
return CustomAlertDialog(
title: Text(translate('Login')),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
LoginWidgetUserPass(
username: username,
pass: pass,
usernameMsg: usernameMsg,
passMsg: passMsg,
isInProgress: isInProgress,
curOP: curOP,
onLogin: onLogin,
),
const SizedBox(
height: 8.0,
),
Center(
child: Text(
translate('or'),
style: TextStyle(fontSize: 16),
)),
const SizedBox(
height: 8.0,
),
LoginWidgetOP(
ops: [
ConfigOP(op: 'Github', iconWidth: 20),
ConfigOP(op: 'Google', iconWidth: 20),
ConfigOP(op: 'Okta', iconWidth: 38),
],
curOP: curOP,
cbLogin: (String username) {
gFFI.userModel.userName.value = username;
completer.complete(true);
close();
},
),
],
),
),
actions: [msgBoxButton(translate('Close'), cancel)],
onCancel: cancel,
);
});
return completer.future;
}

@ -506,7 +506,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
rustDeskWinManager.unregisterActiveWindow(0); rustDeskWinManager.unregisterActiveWindow(0);
} else { } else {
widget.onClose?.call(); widget.onClose?.call();
WindowController.fromWindowId(windowId!).hide(); await WindowController.fromWindowId(windowId!).hide();
rustDeskWinManager rustDeskWinManager
.call(WindowType.Main, kWindowEventHide, {"id": windowId!}); .call(WindowType.Main, kWindowEventHide, {"id": windowId!});
} }
@ -555,12 +555,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
// note: the main window can be restored by tray icon // note: the main window can be restored by tray icon
Future.delayed(Duration.zero, () async { Future.delayed(Duration.zero, () async {
if (widget.isMainWindow) { if (widget.isMainWindow) {
await windowManager.hide(); await windowManager.close();
rustDeskWinManager.unregisterActiveWindow(0);
} else { } else {
await WindowController.fromWindowId(windowId!).hide(); await WindowController.fromWindowId(windowId!).close();
rustDeskWinManager.call(
WindowType.Main, kWindowEventHide, {"id": windowId!});
} }
}); });
} }

@ -168,6 +168,20 @@ void runMultiWindow(
widget, widget,
MyTheme.currentThemeMode(), MyTheme.currentThemeMode(),
); );
switch (appType) {
case kAppTypeDesktopRemote:
await restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId!);
break;
case kAppTypeDesktopFileTransfer:
await restoreWindowPosition(WindowType.FileTransfer, windowId: windowId!);
break;
case kAppTypeDesktopPortForward:
await restoreWindowPosition(WindowType.PortForward, windowId: windowId!);
break;
default:
// no such appType
exit(0);
}
} }
void runConnectionManagerScreen() async { void runConnectionManagerScreen() async {

@ -291,12 +291,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
return SettingsList( return SettingsList(
sections: [ sections: [
SettingsSection( SettingsSection(
title: Text(translate("Account")), title: Text(translate('Account')),
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile.navigation(
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate("Login") ? translate('Login')
: '${translate("Logout")} (${gFFI.userModel.userName.value})')), : '${translate('Logout')} (${gFFI.userModel.userName.value})')),
leading: Icon(Icons.person), leading: Icon(Icons.person),
onPressed: (context) { onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import '../consts.dart';
import '../common.dart'; import '../common.dart';
import '../common/widgets/overlay.dart'; import '../common/widgets/overlay.dart';
import 'model.dart'; import 'model.dart';
@ -183,8 +184,11 @@ class ChatModel with ChangeNotifier {
if (_isShowCMChatPage) { if (_isShowCMChatPage) {
_isShowCMChatPage = !_isShowCMChatPage; _isShowCMChatPage = !_isShowCMChatPage;
notifyListeners(); notifyListeners();
await windowManager.setSizeAlignment(Size(300, 400), Alignment.topRight); await windowManager.show();
await windowManager.setSizeAlignment(
kConnectionManagerWindowSize, Alignment.topRight);
} else { } else {
await windowManager.show();
await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight); await windowManager.setSizeAlignment(Size(600, 400), Alignment.topRight);
_isShowCMChatPage = !_isShowCMChatPage; _isShowCMChatPage = !_isShowCMChatPage;
notifyListeners(); notifyListeners();

@ -42,6 +42,7 @@ class InputModel {
// mouse // mouse
final isPhysicalMouse = false.obs; final isPhysicalMouse = false.obs;
int _lastMouseDownButtons = 0; int _lastMouseDownButtons = 0;
Offset last_mouse_pos = Offset.zero;
get id => parent.target?.id ?? ""; get id => parent.target?.id ?? "";
@ -303,6 +304,28 @@ class InputModel {
} }
void handleMouse(Map<String, dynamic> evt) { void handleMouse(Map<String, dynamic> evt) {
double x = evt['x'];
double y = max(0.0, evt['y']);
final cursorModel = parent.target!.cursorModel;
if (cursorModel.is_peer_control_protected) {
last_mouse_pos = ui.Offset(x, y);
return;
}
if (!cursorModel.got_mouse_control) {
bool self_get_control =
(x - last_mouse_pos.dx).abs() > kMouseControlDistance ||
(y - last_mouse_pos.dy).abs() > kMouseControlDistance;
if (self_get_control) {
cursorModel.got_mouse_control = true;
} else {
last_mouse_pos = ui.Offset(x, y);
return;
}
}
last_mouse_pos = ui.Offset(x, y);
var type = ''; var type = '';
var isMove = false; var isMove = false;
switch (evt['type']) { switch (evt['type']) {
@ -319,8 +342,6 @@ class InputModel {
return; return;
} }
evt['type'] = type; evt['type'] = type;
double x = evt['x'];
double y = max(0.0, evt['y']);
if (isDesktop) { if (isDesktop) {
y = y - stateGlobal.tabBarHeight; y = y - stateGlobal.tabBarHeight;
} }

@ -740,6 +740,9 @@ class CursorModel with ChangeNotifier {
double _hoty = 0; double _hoty = 0;
double _displayOriginX = 0; double _displayOriginX = 0;
double _displayOriginY = 0; double _displayOriginY = 0;
bool got_mouse_control = true;
DateTime _last_peer_mouse = DateTime.now()
.subtract(Duration(milliseconds: 2 * kMouseControlTimeoutMSec));
String id = ''; String id = '';
WeakReference<FFI> parent; WeakReference<FFI> parent;
@ -748,15 +751,17 @@ class CursorModel with ChangeNotifier {
CursorData? get defaultCache => _getDefaultCache(); CursorData? get defaultCache => _getDefaultCache();
double get x => _x - _displayOriginX; double get x => _x - _displayOriginX;
double get y => _y - _displayOriginY; double get y => _y - _displayOriginY;
Offset get offset => Offset(_x, _y); Offset get offset => Offset(_x, _y);
double get hotx => _hotx; double get hotx => _hotx;
double get hoty => _hoty; double get hoty => _hoty;
bool get is_peer_control_protected =>
DateTime.now().difference(_last_peer_mouse).inMilliseconds <
kMouseControlTimeoutMSec;
CursorModel(this.parent); CursorModel(this.parent);
Set<String> get cachedKeys => _cacheKeys; Set<String> get cachedKeys => _cacheKeys;
@ -918,7 +923,7 @@ class CursorModel with ChangeNotifier {
if (parent.target?.id != pid) return; if (parent.target?.id != pid) return;
_image = image; _image = image;
_images[id] = Tuple3(image, _hotx, _hoty); _images[id] = Tuple3(image, _hotx, _hoty);
await _updateCacheLinux(image, id, width, height); await _updateCache(image, id, width, height);
try { try {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
notifyListeners(); notifyListeners();
@ -927,7 +932,7 @@ class CursorModel with ChangeNotifier {
} }
} }
_updateCacheLinux(ui.Image image, int id, int w, int h) async { _updateCache(ui.Image image, int id, int w, int h) async {
Uint8List? data; Uint8List? data;
img2.Image? image2; img2.Image? image2;
if (Platform.isWindows) { if (Platform.isWindows) {
@ -981,6 +986,8 @@ class CursorModel with ChangeNotifier {
/// Update the cursor position. /// Update the cursor position.
updateCursorPosition(Map<String, dynamic> evt, String id) async { updateCursorPosition(Map<String, dynamic> evt, String id) async {
got_mouse_control = false;
_last_peer_mouse = DateTime.now();
_x = double.parse(evt['x']); _x = double.parse(evt['x']);
_y = double.parse(evt['y']); _y = double.parse(evt['y']);
try { try {

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -9,7 +10,7 @@ import 'model.dart';
import 'platform_model.dart'; import 'platform_model.dart';
class UserModel { class UserModel {
var userName = "".obs; var userName = ''.obs;
WeakReference<FFI> parent; WeakReference<FFI> parent;
UserModel(this.parent) { UserModel(this.parent) {
@ -18,7 +19,7 @@ class UserModel {
void refreshCurrentUser() async { void refreshCurrentUser() async {
await getUserName(); await getUserName();
final token = await bind.mainGetLocalOption(key: "access_token"); final token = await bind.mainGetLocalOption(key: 'access_token');
if (token == '') return; if (token == '') return;
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final body = { final body = {
@ -28,8 +29,8 @@ class UserModel {
try { try {
final response = await http.post(Uri.parse('$url/api/currentUser'), final response = await http.post(Uri.parse('$url/api/currentUser'),
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"Authorization": "Bearer $token" 'Authorization': 'Bearer $token'
}, },
body: json.encode(body)); body: json.encode(body));
final status = response.statusCode; final status = response.statusCode;
@ -44,9 +45,9 @@ class UserModel {
} }
void resetToken() async { void resetToken() async {
await bind.mainSetLocalOption(key: "access_token", value: ""); await bind.mainSetLocalOption(key: 'access_token', value: '');
await bind.mainSetLocalOption(key: "user_info", value: ""); await bind.mainSetLocalOption(key: 'user_info', value: '');
userName.value = ""; userName.value = '';
} }
Future<String> _parseResp(String body) async { Future<String> _parseResp(String body) async {
@ -57,13 +58,13 @@ class UserModel {
} }
final token = data['access_token']; final token = data['access_token'];
if (token != null) { if (token != null) {
await bind.mainSetLocalOption(key: "access_token", value: token); await bind.mainSetLocalOption(key: 'access_token', value: token);
} }
final info = data['user']; final info = data['user'];
if (info != null) { if (info != null) {
final value = json.encode(info); final value = json.encode(info);
await bind.mainSetOption(key: "user_info", value: value); await bind.mainSetOption(key: 'user_info', value: value);
userName.value = info["name"]; userName.value = info['name'];
} }
return ''; return '';
} }
@ -74,10 +75,12 @@ class UserModel {
} }
final userInfo = await bind.mainGetLocalOption(key: 'user_info'); final userInfo = await bind.mainGetLocalOption(key: 'user_info');
if (userInfo.trim().isEmpty) { if (userInfo.trim().isEmpty) {
return ""; return '';
} }
final m = jsonDecode(userInfo); final m = jsonDecode(userInfo);
if (m != null) { if (m == null) {
userName.value = '';
} else {
userName.value = m['name'] ?? ''; userName.value = m['name'] ?? '';
} }
return userName.value; return userName.value;
@ -86,10 +89,10 @@ class UserModel {
Future<void> logOut() async { Future<void> logOut() async {
final tag = gFFI.dialogManager.showLoading(translate('Waiting')); final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
final _ = await http.post(Uri.parse("$url/api/logout"), final _ = await http.post(Uri.parse('$url/api/logout'),
body: { body: {
"id": await bind.mainGetMyId(), 'id': await bind.mainGetMyId(),
"uuid": await bind.mainGetUuid(), 'uuid': await bind.mainGetUuid(),
}, },
headers: await getHttpHeaders()); headers: await getHttpHeaders());
await Future.wait([ await Future.wait([
@ -98,30 +101,30 @@ class UserModel {
bind.mainSetLocalOption(key: 'selected-tags', value: ''), bind.mainSetLocalOption(key: 'selected-tags', value: ''),
]); ]);
parent.target?.abModel.clear(); parent.target?.abModel.clear();
userName.value = ""; userName.value = '';
gFFI.dialogManager.dismissByTag(tag); gFFI.dialogManager.dismissByTag(tag);
} }
Future<Map<String, dynamic>> login(String userName, String pass) async { Future<Map<String, dynamic>> login(String userName, String pass) async {
final url = await bind.mainGetApiServer(); final url = await bind.mainGetApiServer();
try { try {
final resp = await http.post(Uri.parse("$url/api/login"), final resp = await http.post(Uri.parse('$url/api/login'),
headers: {"Content-Type": "application/json"}, headers: {'Content-Type': 'application/json'},
body: jsonEncode({ body: jsonEncode({
"username": userName, 'username': userName,
"password": pass, 'password': pass,
"id": await bind.mainGetMyId(), 'id': await bind.mainGetMyId(),
"uuid": await bind.mainGetUuid() 'uuid': await bind.mainGetUuid()
})); }));
final body = jsonDecode(resp.body); final body = jsonDecode(resp.body);
bind.mainSetLocalOption( bind.mainSetLocalOption(
key: "access_token", value: body['access_token'] ?? ""); key: 'access_token', value: body['access_token'] ?? '');
bind.mainSetLocalOption( bind.mainSetLocalOption(
key: "user_info", value: jsonEncode(body['user'])); key: 'user_info', value: jsonEncode(body['user']));
this.userName.value = body['user']?['name'] ?? ""; this.userName.value = body['user']?['name'] ?? '';
return body; return body;
} catch (err) { } catch (err) {
return {"error": "$err"}; return {'error': '$err'};
} }
} }
} }

@ -564,6 +564,7 @@ message Misc {
bool restart_remote_device = 14; bool restart_remote_device = 14;
bool uac = 15; bool uac = 15;
bool foreground_window_elevated = 16; bool foreground_window_elevated = 16;
bool stop_service = 17;
} }
} }

@ -7,7 +7,7 @@ arch=('x86_64')
url="" url=""
license=('AGPL-3.0') license=('AGPL-3.0')
groups=() groups=()
depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'pipewire' 'curl' 'libva' 'libvdpau' 'libayatana-appindicator') depends=('gtk3' 'xdotool' 'libxcb' 'libxfixes' 'alsa-lib' 'curl' 'libva' 'libvdpau' 'libappindicator-gtk3')
makedepends=() makedepends=()
checkdepends=() checkdepends=()
optdepends=() optdepends=()

88
res/rpm-flutter.spec Normal file

@ -0,0 +1,88 @@
Name: rustdesk
Version: 1.2.0
Release: 0
Summary: RPM package
License: GPL-3.0
Requires: gtk3 libxcb libxdo libXfixes pipewire alsa-lib curl libappindicator libvdpau1 libva2
%description
The best open-source remote desktop client software, written in Rust.
%prep
# we have no source, so nothing here
%build
# we have no source, so nothing here
# %global __python %{__python3}
%install
mkdir -p "${buildroot}/usr/lib/rustdesk" && cp -r ${HBB}/flutter/build/linux/x64/release/bundle/* -t "${buildroot}/usr/lib/rustdesk"
mkdir -p "${buildroot}/usr/bin"
pushd ${buildroot} && ln -s /usr/lib/rustdesk/rustdesk usr/bin/rustdesk && popd
install -Dm 644 $HBB/res/rustdesk.service -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk.desktop -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/rustdesk-link.desktop -t "${buildroot}/usr/share/rustdesk/files"
install -Dm 644 $HBB/res/128x128@2x.png "${buildroot}/usr/share/rustdesk/files/rustdesk.png"
%files
/usr/bin/rustdesk
/usr/lib/rustdesk/*
/usr/share/rustdesk/files/rustdesk.service
/usr/share/rustdesk/files/rustdesk.png
/usr/share/rustdesk/files/rustdesk.desktop
/usr/share/rustdesk/files/rustdesk-link.desktop
%changelog
# let's skip this for now
# https://www.cnblogs.com/xingmuxin/p/8990255.html
%pre
# can do something for centos7
case "$1" in
1)
# for install
;;
2)
# for upgrade
systemctl stop rustdesk || true
;;
esac
%post
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
cp /usr/share/rustdesk/files/rustdesk-link.desktop /usr/share/applications/
systemctl daemon-reload
systemctl enable rustdesk
systemctl start rustdesk
update-desktop-database
%preun
case "$1" in
0)
# for uninstall
systemctl stop rustdesk || true
systemctl disable rustdesk || true
rm /etc/systemd/system/rustdesk.service || true
;;
1)
# for upgrade
;;
esac
%postun
case "$1" in
0)
# for uninstall
rm /usr/share/applications/rustdesk.desktop || true
rm /usr/share/applications/rustdesk-link.desktop || true
update-desktop-database
;;
1)
# for upgrade
;;
esac

@ -3,7 +3,7 @@ Version: 1.1.9
Release: 0 Release: 0
Summary: RPM package Summary: RPM package
License: GPL-3.0 License: GPL-3.0
Requires: gtk3 libxcb1 xdotool libXfixes3 pipewire alsa-utils curl libXtst6 libayatana-appindicator3-1 libvdpau1 libva2 Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils curl libXtst6 libayatana-appindicator3-1 libvdpau1 libva2
%description %description
The best open-source remote desktop client software, written in Rust. The best open-source remote desktop client software, written in Rust.

@ -1,9 +1,9 @@
Name: rustdesk Name: rustdesk
Version: 1.1.9 Version: 1.2.0
Release: 0 Release: 0
Summary: RPM package Summary: RPM package
License: GPL-3.0 License: GPL-3.0
Requires: gtk3 libxcb libxdo libXfixes pipewire alsa-lib curl libayatana-appindicator3-1 libvdpau1 libva2 Requires: gtk3 libxcb libxdo libXfixes alsa-lib curl libappindicator libvdpau1 libva2
%description %description
The best open-source remote desktop client software, written in Rust. The best open-source remote desktop client software, written in Rust.

@ -13,6 +13,8 @@ use hbb_common::{
fs, log, fs, log,
}; };
// use crate::hbbs_http::account::AuthResult;
use crate::flutter::{self, SESSIONS}; use crate::flutter::{self, SESSIONS};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use crate::start_server; use crate::start_server;
@ -1082,6 +1084,20 @@ pub fn install_install_path() -> SyncReturn<String> {
SyncReturn(install_path()) SyncReturn(install_path())
} }
pub fn main_account_auth(op: String) {
let id = get_id();
let uuid = get_uuid();
account_auth(op, id, uuid);
}
pub fn main_account_auth_cancel() {
account_auth_cancel()
}
pub fn main_account_auth_result() -> String {
account_auth_result()
}
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use jni::{ use jni::{

34
src/hbbs_http.rs Normal file

@ -0,0 +1,34 @@
use reqwest::blocking::Response;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
#[cfg(feature = "flutter")]
pub mod account;
#[derive(Debug)]
pub enum HbbHttpResponse<T> {
ErrorFormat,
Error(String),
DataTypeFormat,
Data(T),
}
impl<T: DeserializeOwned> TryFrom<Response> for HbbHttpResponse<T> {
type Error = reqwest::Error;
fn try_from(resp: Response) -> Result<Self, <Self as TryFrom<Response>>::Error> {
let map = resp.json::<Map<String, Value>>()?;
if let Some(error) = map.get("error") {
if let Some(err) = error.as_str() {
Ok(Self::Error(err.to_owned()))
} else {
Ok(Self::ErrorFormat)
}
} else {
match serde_json::from_value(Value::Object(map)) {
Ok(v) => Ok(Self::Data(v)),
Err(_) => Ok(Self::DataTypeFormat),
}
}
}
}

255
src/hbbs_http/account.rs Normal file

@ -0,0 +1,255 @@
use super::HbbHttpResponse;
use hbb_common::{
config::{Config, LocalConfig},
log, sleep, tokio, ResultType,
};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize};
use std::{
collections::HashMap,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use url::Url;
lazy_static::lazy_static! {
static ref API_SERVER: String = crate::get_api_server(
Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"));
static ref OIDC_SESSION: Arc<RwLock<OidcSession>> = Arc::new(RwLock::new(OidcSession::new()));
}
const QUERY_INTERVAL_SECS: f32 = 1.0;
const QUERY_TIMEOUT_SECS: u64 = 60 * 3;
const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth";
const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth";
const LOGIN_ACCOUNT_AUTH: &str = "Login account auth";
#[derive(Deserialize, Clone, Debug)]
pub struct OidcAuthUrl {
code: String,
url: Url,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct UserPayload {
pub id: String,
pub name: String,
pub email: Option<String>,
pub note: Option<String>,
pub status: Option<i64>,
pub grp: Option<String>,
pub is_admin: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthBody {
pub access_token: String,
pub token_type: String,
pub user: UserPayload,
}
pub struct OidcSession {
client: Client,
state_msg: &'static str,
failed_msg: String,
code_url: Option<OidcAuthUrl>,
auth_body: Option<AuthBody>,
keep_querying: bool,
running: bool,
query_timeout: Duration,
}
#[derive(Serialize)]
pub struct AuthResult {
pub state_msg: String,
pub failed_msg: String,
pub url: Option<String>,
pub auth_body: Option<AuthBody>,
}
impl OidcSession {
fn new() -> Self {
Self {
client: Client::new(),
state_msg: REQUESTING_ACCOUNT_AUTH,
failed_msg: "".to_owned(),
code_url: None,
auth_body: None,
keep_querying: false,
running: false,
query_timeout: Duration::from_secs(QUERY_TIMEOUT_SECS),
}
}
fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
Ok(OIDC_SESSION
.read()
.unwrap()
.client
.post(format!("{}/api/oidc/auth", *API_SERVER))
.json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)]))
.send()?
.try_into()?)
}
fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> {
let url = reqwest::Url::parse_with_params(
&format!("{}/api/oidc/auth-query", *API_SERVER),
&[("code", code), ("id", id), ("uuid", uuid)],
)?;
Ok(OIDC_SESSION
.read()
.unwrap()
.client
.get(url)
.send()?
.try_into()?)
}
fn reset(&mut self) {
self.state_msg = REQUESTING_ACCOUNT_AUTH;
self.failed_msg = "".to_owned();
self.keep_querying = true;
self.running = false;
self.code_url = None;
self.auth_body = None;
}
fn before_task(&mut self) {
self.reset();
self.running = true;
}
fn after_task(&mut self) {
self.running = false;
}
fn sleep(secs: f32) {
std::thread::sleep(std::time::Duration::from_secs_f32(secs));
}
fn auth_task(op: String, id: String, uuid: String) {
let auth_request_res = Self::auth(&op, &id, &uuid);
log::info!("Request oidc auth result: {:?}", &auth_request_res);
let code_url = match auth_request_res {
Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url,
Ok(HbbHttpResponse::<_>::Error(err)) => {
OIDC_SESSION
.write()
.unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, err);
return;
}
Ok(_) => {
OIDC_SESSION
.write()
.unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, "Invalid auth response".to_owned());
return;
}
Err(err) => {
OIDC_SESSION
.write()
.unwrap()
.set_state(REQUESTING_ACCOUNT_AUTH, err.to_string());
return;
}
};
OIDC_SESSION
.write()
.unwrap()
.set_state(WAITING_ACCOUNT_AUTH, "".to_owned());
OIDC_SESSION.write().unwrap().code_url = Some(code_url.clone());
let begin = Instant::now();
let query_timeout = OIDC_SESSION.read().unwrap().query_timeout;
while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout {
match Self::query(&code_url.code, &id, &uuid) {
Ok(HbbHttpResponse::<_>::Data(auth_body)) => {
LocalConfig::set_option(
"access_token".to_owned(),
auth_body.access_token.clone(),
);
LocalConfig::set_option(
"user_info".to_owned(),
serde_json::to_string(&auth_body.user).unwrap_or_default(),
);
OIDC_SESSION
.write()
.unwrap()
.set_state(LOGIN_ACCOUNT_AUTH, "".to_owned());
OIDC_SESSION.write().unwrap().auth_body = Some(auth_body);
return;
}
Ok(HbbHttpResponse::<_>::Error(err)) => {
if err.contains("No authed oidc is found") {
// ignore, keep querying
} else {
OIDC_SESSION
.write()
.unwrap()
.set_state(WAITING_ACCOUNT_AUTH, err);
return;
}
}
Ok(_) => {
// ignore
}
Err(err) => {
log::trace!("Failed query oidc {}", err);
// ignore
}
}
Self::sleep(QUERY_INTERVAL_SECS);
}
if begin.elapsed() >= query_timeout {
OIDC_SESSION
.write()
.unwrap()
.set_state(WAITING_ACCOUNT_AUTH, "timeout".to_owned());
}
// no need to handle "keep_querying == false"
}
fn set_state(&mut self, state_msg: &'static str, failed_msg: String) {
self.state_msg = state_msg;
self.failed_msg = failed_msg;
}
fn wait_stop_querying() {
let wait_secs = 0.3;
while OIDC_SESSION.read().unwrap().running {
Self::sleep(wait_secs);
}
}
pub fn account_auth(op: String, id: String, uuid: String) {
Self::auth_cancel();
Self::wait_stop_querying();
OIDC_SESSION.write().unwrap().before_task();
std::thread::spawn(|| {
Self::auth_task(op, id, uuid);
OIDC_SESSION.write().unwrap().after_task();
});
}
fn get_result_(&self) -> AuthResult {
AuthResult {
state_msg: self.state_msg.to_string(),
failed_msg: self.failed_msg.clone(),
url: self.code_url.as_ref().map(|x| x.url.to_string()),
auth_body: self.auth_body.clone(),
}
}
pub fn auth_cancel() {
OIDC_SESSION.write().unwrap().keep_querying = false;
}
pub fn get_result() -> AuthResult {
OIDC_SESSION.read().unwrap().get_result_()
}
}

@ -23,6 +23,7 @@ mod tw;
mod vn; mod vn;
mod kz; mod kz;
mod ua; mod ua;
mod fa;
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref LANGS: Value = pub static ref LANGS: Value =
@ -49,6 +50,7 @@ lazy_static::lazy_static! {
("ko", "한국어"), ("ko", "한국어"),
("kz", "Қазақ"), ("kz", "Қазақ"),
("ua", "Українська"), ("ua", "Українська"),
("fa", "فارسی"),
]); ]);
} }
@ -99,6 +101,7 @@ pub fn translate_locale(name: String, locale: &str) -> String {
"ko" => ko::T.deref(), "ko" => ko::T.deref(),
"kz" => kz::T.deref(), "kz" => kz::T.deref(),
"ua" => ua::T.deref(), "ua" => ua::T.deref(),
"fa" => fa::T.deref(),
_ => en::T.deref(), _ => en::T.deref(),
}; };
if let Some(v) = m.get(&name as &str) { if let Some(v) = m.get(&name as &str) {

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "请选择要分享的画面(对端操作)。"), ("Please Select the screen to be shared(Operate on the peer side).", "请选择要分享的画面(对端操作)。"),
("Show RustDesk", "显示rustdesk"), ("Show RustDesk", "显示rustdesk"),
("This PC", "此电脑"), ("This PC", "此电脑"),
("or", ""),
("Continue with", "使用"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."), ("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Vælg venligst den skærm, der skal deles (Betjen på peer-siden)."), ("Please Select the screen to be shared(Operate on the peer side).", "Vælg venligst den skærm, der skal deles (Betjen på peer-siden)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."), ("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -35,5 +35,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."), ("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
("JumpLink", "View"), ("JumpLink", "View"),
("Stop service", "Stop Service"), ("Stop service", "Stop Service"),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Bonvolu Elekti la ekranon por esti dividita (Funkciu ĉe la sama flanko)."), ("Please Select the screen to be shared(Operate on the peer side).", "Bonvolu Elekti la ekranon por esti dividita (Funkciu ĉe la sama flanko)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Seleccione la pantalla que se compartirá (Operar en el lado del par)."), ("Please Select the screen to be shared(Operate on the peer side).", "Seleccione la pantalla que se compartirá (Operar en el lado del par)."),
("Show RustDesk", "Mostrar RustDesk"), ("Show RustDesk", "Mostrar RustDesk"),
("This PC", "Este PC"), ("This PC", "Este PC"),
("or", "o"),
("Continue with", "Continuar con"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

393
src/lang/fa.rs Normal file

@ -0,0 +1,393 @@
lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[
("Status", "وضعیت"),
("Your Desktop", "دسکتاپ شما"),
("desk_tip", "دسکتاپ شما با این شناسه و رمز عبور قابل دسترسی است"),
("Password", "رمز عبور"),
("Ready", "آماده به کار"),
("Established", "اتصال برقرار شد"),
("connecting_status", "...در حال برقراری ارتباط با سرور"),
("Enable Service", "فعالسازی سرویس"),
("Start Service", "اجرا سرویس"),
("Service is running", "سرویس در حال اجرا است"),
("Service is not running", "سرویس اجرا نشده"),
("not_ready_status", "ارتباط برقرار نشد. لطفا شبکه خود را بررسی کنید"),
("Control Remote Desktop", "کنترل دسکتاپ میزبان"),
("Transfer File", "جابه جایی فایل"),
("Connect", "اتصال"),
("Recent Sessions", "جلسات اخیر"),
("Address Book", "دفترچه آدرس"),
("Confirmation", "تایید"),
("TCP Tunneling", "TCP تانل"),
("Remove", "حذف"),
("Refresh random password", "رمز عبور تصادفی را بروز کنید"),
("Set your own password", "!رمز عبور دلخواه بگذارید"),
("Enable Keyboard/Mouse", "Keyboard/Mouse فعالسازی"),
("Enable Clipboard", "Clipboard فعالسازی"),
("Enable File Transfer", "انتقال فایل را فعال کنید"),
("Enable TCP Tunneling", "را فعال کنید TCP تانل"),
("IP Whitelisting", "های مجاز IP لیست"),
("ID/Relay Server", "ID/Relay سرور"),
("Import Server Config", "تنظیم سرور با فایل"),
("Export Server Config", "ایجاد فایل تظیمات از سرور فعلی"),
("Import server configuration successfully", "تنظیمات سرور با فایل کانفیگ با موفقیت انجام شد"),
("Export server configuration successfully", "ایجاد فایل کانفیگ از تنظیمات فعلی با موفقیت انجام شد"),
("Invalid server configuration", "تنظیمات سرور نامعتبر است"),
("Clipboard is empty", "خالی است Clipboard"),
("Stop service", "توقف سرویس"),
("Change ID", "تعویض شناسه"),
("Website", "وب سایت"),
("About", "درباره"),
("Mute", "بستن صدا"),
("Audio Input", "ورودی صدا"),
("Enhancements", "بهبودها"),
("Hardware Codec", "کدک سخت افزاری"),
("Adaptive Bitrate", ""),
("ID Server", "شناسه سرور"),
("Relay Server", "Relay سرور"),
("API Server", "API سرور"),
("invalid_http", "شروع شود http:// یا https:// باید با"),
("Invalid IP", "نامعتبر است IP آدرس"),
("id_change_tip", "شناسه باید طبق این شرایط باشد : حروف کوچک و بزرگ انگلیسی و اعداد از 0 تا 9، _ و همچنین حرف اول آن فقط حروف بزرگ یا کوچک انگلیسی و طول آن بین 6 الی 16 کاراکتر باشد"),
("Invalid format", "فرمت نادرس است"),
("server_not_support", "هنوز توسط سرور مورد نظر پشتیبانی نمی شود"),
("Not available", "در دسترسی نیست"),
("Too frequent", "تعداد زیاد"),
("Cancel", "لغو"),
("Skip", "رد کردن"),
("Close", "بستن"),
("Retry", "تلاش مجدد"),
("OK", "قبول"),
("Password Required", "رمز عبور لازم است"),
("Please enter your password", "رمز عبور خود را وارد کنید"),
("Remember password", "رمز عبور را به خاطر بسپار"),
("Wrong Password", "رمز عبور اشتباه است"),
("Do you want to enter again?", "آیا میخواهید مجددا وارد شوید؟"),
("Connection Error", "خطا در اتصال"),
("Error", "خطا"),
("Reset by the peer", "توسط میزبان حذف شد"),
("Connecting...", "...در حال اتصال"),
("Connection in progress. Please wait.", "در حال اتصال. لطفا متظر بمانید"),
("Please try 1 minute later", "لطفا بعد از 1 دقیقه مجددا تلاش کنید"),
("Login Error", "ورود ناموفق بود"),
("Successful", "ورود با موفقیت انجام شد"),
("Connected, waiting for image...", "ارتباط وصل شد. برای دریافت تصویر دسکتاپ میزبان منتظر بمانید..."),
("Name", "نام"),
("Type", "نوع فایل"),
("Modified", "تاریخ تغییر"),
("Size", "سایز"),
("Show Hidden Files", "نمایش فایل های مخفی"),
("Receive", "دریافت"),
("Send", "ارسال"),
("Refresh File", "به روزرسانی فایل"),
("Local", "محلی"),
("Remote", "از راه دور"),
("Remote Computer", "سیستم میزبان"),
("Local Computer", "سیستم از راه دور"),
("Confirm Delete", "حذف را تایید کنید"),
("Delete", "حذف"),
("Properties", "Properties"),
("Multi Select", "انتخاب همزمان"),
("Select All", "انتخاب همه"),
("Unselect All", "عدم انتخاب همه"),
("Empty Directory", "پوشه خالی"),
("Not an empty directory", "پوشه خالی نیست"),
("Are you sure you want to delete this file?", "از حذف این فایل مطمئن هستید؟"),
("Are you sure you want to delete this empty directory?", "از حذف این پوشه خالی مطمئن هستید؟"),
("Are you sure you want to delete the file of this directory?", "از حذف فایل موجود در این پوشه مطمئن هستید؟"),
("Do this for all conflicts", "این عمل را برای همه ی تضادها انجام شود"),
("This is irreversible!", "این برگشت ناپذیر است!"),
("Deleting", "در حال حذف"),
("files", "فایل ها"),
("Waiting", "انتظار"),
("Finished", "تکمیل شد"),
("Speed", "سرعت"),
("Custom Image Quality", "سفارشی سازی کیفیت تصاویر"),
("Privacy mode", "حالت حریم خصوصی"),
("Block user input", "ورودی کاربر را مسدود کنید"),
("Unblock user input", "قفل ورودی کاربر را باز کنید"),
("Adjust Window", "پنجره را تنظیم کنید"),
("Original", "اصل"),
("Shrink", ""),
("Stretch", ""),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", "کیفیت خوب تصویر"),
("Balanced", "متعادل"),
("Optimize reaction time", "زمان واکنش را بهینه کنید"),
("Custom", "سفارشی"),
("Show remote cursor", "نمایش مکان نما موس میزبان"),
("Show quality monitor", "نمایش کیفیت مانیتور"),
("Disable clipboard", "Clipboard غیرفعالسازی"),
("Lock after session end", "قفل کردن حساب کاربری سیستم عامل پس از پایان جلسه"),
("Insert", "افزودن"),
("Insert Lock", "افزودن قفل"),
("Refresh", "تازه سازی"),
("ID does not exist", "شناسه وجود ندارد"),
("Failed to connect to rendezvous server", "اتصال به سرور تولید شناسه انجام نشد"),
("Please try later", "لطفا بعدا تلاش کنید"),
("Remote desktop is offline", "دسکتاپ از راه دور خاموش است"),
("Key mismatch", "عدم تطابق کلید"),
("Timeout", "زمان انتظار به پایان رسید"),
("Failed to connect to relay server", "سرور وصل نشد Relay به"),
("Failed to connect via rendezvous server", "اتصال از طریق سرور تولید شناسه انجام نشد"),
("Failed to connect via relay server", "انجام نشد Relay اتصال از طریق سرور"),
("Failed to make direct connection to remote desktop", "اتصال مستقیم به دسکتاپ از راه دور با موفقیت انجام نشد"),
("Set Password", "اختصاص رمزعبور"),
("OS Password", "رمز عیور سیستم عامل"),
("install_tip", "لطفا برنامه را نصب کنید UAC و جلوگیری از خطای RustDesk برای راحتی در استفاده از نرم افزار"),
("Click to upgrade", "برای ارتقا کلیک کنید"),
("Click to download", "برای دانلود کلیک کنید"),
("Click to update", "برای به روز رسانی کلیک کنید"),
("Configure", "تنظیم"),
("config_acc", "برای کنترل از راه دور دسکتاپ، باید به RustDesk مجوز \"access\" بدهید"),
("config_screen", "برای دسترسی از راه دور به دسکتاپ خود، باید به RustDesk مجوزهای \"screenshot\" بدهید."),
("Installing ...", "در حال نصب..."),
("Install", "نصب"),
("Installation", "نصب و راه اندازی"),
("Installation Path", "محل نصب"),
("Create start menu shortcuts", "Start ایجاد میانبرها در منوی"),
("Create desktop icon", "ایجاد آیکن در دسکتاپ"),
("agreement_tip", "با شروع نصب، شرایط توافق نامه مجوز را می پذیرید"),
("Accept and Install", "قبول و شروع نصب"),
("End-user license agreement", "قرارداد مجوز کاربر نهایی"),
("Generating ...", "پدید آوردن..."),
("Your installation is lower version.", "نسخه قبلی نصب شده است"),
("not_close_tcp_tip", "هنگام استفاده از تونل این پنجره را نبندید"),
("Listening ...", "انتظار..."),
("Remote Host", "دستگاه از راه دور"),
("Remote Port", "پورت راه دور"),
("Action", "عملیات"),
("Add", "افزودن"),
("Local Port", "پورت محلی"),
("Local Address", "آدرس محلی"),
("Change Local Port", "تغییر پورت محلی"),
("setup_server_tip", "برای اتصال سریعتر، سرور اتصال خود را راه اندازی کنید"),
("Too short, at least 6 characters.", "بسیار کوتاه حداقل 6 کاراکتر مورد نیاز است"),
("The confirmation is not identical.", "تأیید ناموفق بود."),
("Permissions", "دسترسی ها"),
("Accept", "پذیرفتن"),
("Dismiss", "رد کردن"),
("Disconnect", "قطع اتصال"),
("Allow using keyboard and mouse", "اجازه استفاده از صفحه کلید و ماوس را بدهید"),
("Allow using clipboard", "را بدهید Clipboard اجازه استفاده از"),
("Allow hearing sound", "اجازه شنیدن صدا را بدهید"),
("Allow file copy and paste", "اجازه کپی و چسباندن فایل را بدهید"),
("Connected", "متصل شده"),
("Direct and encrypted connection", "اتصال مستقیم و رمزگذاری شده"),
("Relayed and encrypted connection", "و رمزگذاری شده Relay اتصال از طریق"),
("Direct and unencrypted connection", "اتصال مستقیم و بدون رمزگذاری"),
("Relayed and unencrypted connection", "و رمزگذاری نشده Relay اتصال از طریق"),
("Enter Remote ID", "شناسه از راه دور را وارد کنید"),
("Enter your password", "زمر عبور خود را وارد کنید"),
("Logging in...", "در حال ورود..."),
("Enable RDP session sharing", "اشتراک گذاری جلسه RDP را فعال کنید"),
("Auto Login", "ورود خودکار"),
("Enable Direct IP Access", "دسترسی مستقیم IP را فعال کنید"),
("Rename", "تغییر نام"),
("Space", "فضا"),
("Create Desktop Shortcut", "ساخت میانبر روی دسکتاپ"),
("Change Path", "تغییر مسیر"),
("Create Folder", "ایجاد پوشه"),
("Please enter the folder name", "نام پوشه را وارد کنید"),
("Fix it", "بازسازی"),
("Warning", "هشدار"),
("Login screen using Wayland is not supported", "ورود به سیستم با استفاده از Wayland پشتیبانی نمی شود"),
("Reboot required", "راه اندازی مجدد مورد نیاز است"),
("Unsupported display server ", "سرور تصویر پشتیبانی نشده است"),
("x11 expected", ""),
("Port", "پورت"),
("Settings", "تنظیمات"),
("Username", "نام کاربری"),
("Invalid port", "پورت نامعتبر است"),
("Closed manually by the peer", "به صورت دستی توسط میزبان بسته شد"),
("Enable remote configuration modification", "تغییرات پیکربندی از راه دور را مجاز کنید"),
("Run without install", "بدون نصب اجرا شود"),
("Always connected via relay", "متصل است Relay همیشه با"),
("Always connect via relay", "برای اتصال استفاده کنید Relay از"),
("whitelist_tip", "فقط آدرس های IP مجاز می توانند به این دسکتاپ متصل شوند"),
("Login", "ورود"),
("Logout", "خروج"),
("Tags", "برچسب ها"),
("Search ID", "جستجوی شناسه"),
("Current Wayland display server is not supported", "سرور نمای فعلی Wayland پشتیبانی نمی شود"),
("whitelist_sep", "با کاما، نقطه ویرگول، فاصله یا خط جدید از هم جدا می شوند"),
("Add ID", "افزودن شناسه"),
("Add Tag", "افزودن برچسب"),
("Unselect all tags", "همه برچسب ها را لغو انتخاب کنید"),
("Network error", "خطای شبکه"),
("Username missed", "نام کاربری وجود ندارد"),
("Password missed", "رمزعبور وجود ندارد"),
("Wrong credentials", "اعتبارنامه نادرست است"),
("Edit Tag", "برچسب را تغییر دهید"),
("Unremember Password", "رمز عبور را ذخیره نکنید"),
("Favorites", "موارد دلخواه"),
("Add to Favorites", "افزودن به علاقه مندی ها"),
("Remove from Favorites", "از علاقه مندی ها حذف شود"),
("Empty", "موردی وجود ندارد"),
("Invalid folder name", "نام پوشه نامعتبر است"),
("Socks5 Proxy", "Socks5 Proxy"),
("Hostname", "Hostname"),
("Discovered", "پیدا شده"),
("install_daemon_tip", "برای شروع در هنگام راه اندازی، باید سرویس سیستم را نصب کنید"),
("Remote ID", "شناسه از راه دور"),
("Paste", "درج کنید"),
("Paste here?", "اینجا درج شود؟"),
("Are you sure to close the connection?", "آیا مطمئن هستید که می خواهید اتصال را پایان دهید؟"),
("Download new version", "دانلود نسخه جدید"),
("Touch mode", "حالت لمسی"),
("Mouse mode", "حالت ماوس"),
("One-Finger Tap", "با یک انگشت لمس کنید"),
("Left Mouse", "دکمه سمت چپ ماوس"),
("One-Long Tap", "لمس طولانی با یک انگشت"),
("Two-Finger Tap", "با دو انگشت لمس کنید"),
("Right Mouse", "دکمه سمت راست ماوس"),
("One-Finger Move", "با یک انگشت حرکت کنید"),
("Double Tap & Move", "دو ضربه سریع بزنید و حرکت دهید"),
("Mouse Drag", "کشیدن ماوس"),
("Three-Finger vertically", "سه انگشت عمودی"),
("Mouse Wheel", "چرخ ماوس"),
("Two-Finger Move", "با دو انگشت حرکت کنید"),
("Canvas Move", ""),
("Pinch to Zoom", "زوم را کوچک کنید"),
("Canvas Zoom", ""),
("Reset canvas", ""),
("No permission of file transfer", "مجوز انتقال فایل داده نشده"),
("Note", "یادداشت"),
("Connection", "ارتباط"),
("Share Screen", "اشتراک گذاری صفحه"),
("CLOSE", "بستن"),
("OPEN", "باز کردن"),
("Chat", "چت"),
("Total", "مجموع"),
("items", "موارد"),
("Selected", "انتخاب شده"),
("Screen Capture", "ضبط صفحه"),
("Input Control", "کنترل ورودی"),
("Audio Capture", "ضبط صدا"),
("File Connection", "ارتباط فایل"),
("Screen Connection", "ارتباط صفحه"),
("Do you accept?", "شما می پذیرید؟"),
("Open System Setting", "باز کردن تنظیمات سیستم"),
("How to get Android input permission?", "چگونه مجوز ورود به سیستم اندروید را دریافت کنیم؟"),
("android_input_permission_tip1", "برای اینکه یک دستگاه راه دور بتواند دستگاه Android شما را از طریق ماوس یا لمسی کنترل کند، باید به RustDesk اجازه دهید از ویژگی \"Accessibility\" استفاده کند."),
("android_input_permission_tip2", "به صفحه تنظیمات سیستم زیر بروید، \"Installed Services\" را پیدا کرده و وارد کنید، سرویس \"RustDesk Input\" را فعال کنید"),
("android_new_connection_tip", "درخواست جدیدی برای مدیریت دستگاه فعلی شما دریافت شده است."),
("android_service_will_start_tip", "فعال کردن ضبط صفحه به طور خودکار سرویس را راه اندازی می کند و به دستگاه های دیگر امکان می دهد درخواست اتصال به آن دستگاه را داشته باشند."),
("android_stop_service_tip", "با بستن سرویس، تمام اتصالات برقرار شده به طور خودکار بسته می شود"),
("android_version_audio_tip", "نسخه فعلی اندروید از ضبط صدا پشتیبانی نمی‌کند، لطفاً به اندروید 10 یا بالاتر به‌روزرسانی کنید"),
("android_start_service_tip", "برای شروع سرویس اشتراک‌گذاری صفحه، روی مجوز \"شروع مرحله‌بندی سرور\" یا OPEN \"Screen Capture\" کلیک کنید."),
("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", "خارج از حالت حریم خصوصی"),
("Language", "زبان"),
("Keep RustDesk background service", "سرویس RustDesk را در پس زمینه نگه دارید"),
("Ignore Battery Optimizations", "بهینه سازی باتری را نادیده بگیرید"),
("android_open_battery_optimizations_tip", "به صفحه تنظیمات بعدی بروید"),
("Connection not allowed", "اتصال مجاز نیست"),
("Legacy mode", "پشتیبانی موارد قدیمی"),
("Map mode", "حالت نقشه"),
("Translate mode", "حالت ترجمه"),
("Use temporary password", "از رمز عبور موقت استفاده کنید"),
("Use permanent password", "از رمز عبور دائمی استفاده کنید"),
("Use both passwords", "از هر دو رمز عبور استفاده کنید"),
("Set permanent password", "یک رمز عبور دائمی تنظیم کنید"),
("Set temporary password length", "تنظیم طول رمز عبور موقت"),
("Enable Remote Restart", "فعال کردن راه‌اندازی مجدد از راه دور"),
("Allow remote restart", "اجازه راه اندازی مجدد از راه دور"),
("Restart Remote Device", "راه‌اندازی مجدد دستگاه از راه دور"),
("Are you sure you want to restart", "ایا مطمئن هستید میخواهید راه اندازی مجدد انجام بدید؟"),
("Restarting Remote Device", "راه اندازی مجدد یک دستگاه راه دور"),
("remote_restarting_tip", "دستگاه راه دور دوباره راه اندازی می شود. این پیام را ببندید و پس از مدتی با استفاده از یک رمز عبور دائمی دوباره وصل شوید."),
("Copied", "کپی شده است"),
("Exit Fullscreen", "از حالت تمام صفحه خارج شوید"),
("Fullscreen", "تمام صفحه"),
("Mobile Actions", "اقدامات موبایل"),
("Select Monitor", "مانیتور را انتخاب کنید"),
("Control Actions", "اقدامات مدیریتی"),
("Display Settings", "تنظیمات نمایشگر"),
("Ratio", "نسبت"),
("Image Quality", "کیفیت تصویر"),
("Scroll Style", "سبک اسکرول"),
("Show Menubar", "نمایش نوار منو"),
("Hide Menubar", "پنهان کردن نوار منو"),
("Direct Connection", "ارتباط مستقیم"),
("Relay Connection", "Relay ارتباط"),
("Secure Connection", "ارتباط امن"),
("Insecure Connection", "ارتباط غیر امن"),
("Scale original", "مقیاس اصلی"),
("Scale adaptive", "مقیاس تطبیقی"),
("General", "عمومی"),
("Security", "امنیت"),
("Account", "حساب کاربری"),
("Theme", "نمایه"),
("Dark Theme", "نمایه تیره"),
("Dark", "تیره"),
("Light", "روشن"),
("Follow System", "سیستم را دنبال کنید"),
("Enable hardware codec", "از کدک سخت افزاری استفاده کنید"),
("Unlock Security Settings", "تنظیمات امنیتی را باز کنید"),
("Enable Audio", "صدا را روشن کنید"),
("Temporary Password Length", "طول رمز عبور موقت"),
("Unlock Network Settings", "باز کردن قفل تنظیمات شبکه"),
("Server", "سرور"),
("Direct IP Access", "دسترسی مستقیم به IP"),
("Proxy", "پروکسی"),
("Port", "پورت"),
("Apply", "ثبت"),
("Disconnect all devices?", "همه دستگاه ها را غیرفعال کنید؟"),
("Clear", "پاک کردن"),
("Audio Input Device", "منبع صدا"),
("Deny remote access", "دسترسی از راه دور را رد کنید"),
("Use IP Whitelisting", "از لیست سفید IP استفاده کنید"),
("Network", "شبکه"),
("Enable RDP", "RDP را فعال کنید"),
("Pin menubar", "نوار منو ثابت کنید"),
("Unpin menubar", "پین نوار منو را بردارید"),
("Recording", "در حال ضبط"),
("Directory", "مسیر"),
("Automatically record incoming sessions", "ضبط خودکار جلسات ورودی"),
("Change", "تغییر"),
("Start session recording", "شروع ضبط جلسه"),
("Stop session recording", "توقف ضبط جلسه"),
("Enable Recording Session", "فعالسازی ضبط جلسه"),
("Allow recording session", "مجوز ضبط جلسه"),
("Enable LAN Discovery", "فعالسازی جستجو در شبکه"),
("Deny LAN Discovery", "غیر فعالسازی جستجو در شبکه"),
("Write a message", "یک پیام بنویسید"),
("Prompt", ""),
("elevation_prompt", "اجرای نرم‌افزار بدون افزایش امتیاز می‌تواند باعث ایجاد مشکلاتی در هنگام کار کردن کاربران راه دور با ویندوزهای خاص شود"),
("uac_warning", "به دلیل درخواست دسترسی سطح بالا، به طور موقت از دسترسی رد شد. منتظر بمانید تا کاربر راه دور گفتگوی UAC را بپذیرد. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی دستگاه از راه دور نصب کنید یا آن را با دسترسی مدیر اجرا کنید."),
("elevated_foreground_window_warning", "به طور موقت استفاده از ماوس و صفحه کلید امکان پذیر نیست زیرا پنجره دسکتاپ از راه دور فعلی برای کار کردن به دسترسی های بالاتر نیاز دارد، می توانید از کاربر راه دور بخواهید که پنجره فعلی را به حداقل برساند. برای جلوگیری از این مشکل، توصیه می شود نرم افزار را روی یک دستگاه راه دور نصب کنید یا آن را با دسترسی مدیر اجرا کنید"),
("Disconnected", "قطع ارتباط"),
("Other", "دیگر"),
("Confirm before closing multiple tabs", "بستن چندین برگه را تأیید کنید"),
("Keyboard Settings", "تنظیمات صفحه کلید"),
("Custom", "سفارشی"),
("Full Access", "دسترسی کامل"),
("Screen Share", "اشتراک گذاری صفحه"),
("Wayland requires Ubuntu 21.04 or higher version.", ""),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", ""),
("JumpLink", ""),
("Please Select the screen to be shared(Operate on the peer side).", "لطفاً صفحه‌ای را برای اشتراک‌گذاری انتخاب کنید (در سمت همتا به همتا کار کنید)."),
("Show RustDesk", "RustDesk را نشان دهید"),
("This PC", "This PC"),
("or", "یا"),
("Continue with", "ادامه با"),
].iter().cloned().collect();
}

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."), ("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."), ("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."), ("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."), ("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(ピア側で操作)。"), ("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(ピア側で操作)。"),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하십시오(피어 측에서 작동)."), ("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하십시오(피어 측에서 작동)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."), ("Please Select the screen to be shared(Operate on the peer side).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."), ("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do peer)."), ("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do peer)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -2,8 +2,8 @@ lazy_static::lazy_static! {
pub static ref T: std::collections::HashMap<&'static str, &'static str> = pub static ref T: std::collections::HashMap<&'static str, &'static str> =
[ [
("Status", "Status"), ("Status", "Status"),
("Your Desktop", "Seu Desktop"), ("Your Desktop", "Seu Computador"),
("desk_tip", "Seu desktop pode ser acessado com este ID e senha."), ("desk_tip", "Seu computador pode ser acessado com este ID e senha."),
("Password", "Senha"), ("Password", "Senha"),
("Ready", "Pronto"), ("Ready", "Pronto"),
("Established", "Estabelecido"), ("Established", "Estabelecido"),
@ -13,37 +13,37 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Service is running", "Serviço está em execução"), ("Service is running", "Serviço está em execução"),
("Service is not running", "Serviço não está em execução"), ("Service is not running", "Serviço não está em execução"),
("not_ready_status", "Não está pronto. Por favor verifique sua conexão"), ("not_ready_status", "Não está pronto. Por favor verifique sua conexão"),
("Control Remote Desktop", "Controle o Desktop à distância"), ("Control Remote Desktop", "Controle um Computador Remoto"),
("Transfer File", "Transferir Arquivo"), ("Transfer File", "Transferir Arquivo"),
("Connect", "Conectar"), ("Connect", "Conectar"),
("Recent Sessions", "Sessões recentes"), ("Recent Sessions", "Sessões Recentes"),
("Address Book", "Lista de Endereços"), ("Address Book", "Lista de Endereços"),
("Confirmation", "Confirmação"), ("Confirmation", "Confirmação"),
("TCP Tunneling", "Tunelamento TCP"), ("TCP Tunneling", "Tunelamento TCP"),
("Remove", "Remover"), ("Remove", "Remover"),
("Refresh random password", "Atualizar senha aleatória"), ("Refresh random password", "Atualizar senha aleatória"),
("Set your own password", "Configure sua própria senha"), ("Set your own password", "Configure sua própria senha"),
("Enable Keyboard/Mouse", "Habilitar Teclado/Mouse"), ("Enable Keyboard/Mouse", "Habilitar teclado/mouse"),
("Enable Clipboard", "Habilitar Área de Transferência"), ("Enable Clipboard", "Habilitar Área de Transferência"),
("Enable File Transfer", "Habilitar Transferência de Arquivos"), ("Enable File Transfer", "Habilitar Transferência de Arquivos"),
("Enable TCP Tunneling", "Habilitar Tunelamento TCP"), ("Enable TCP Tunneling", "Habilitar Tunelamento TCP"),
("IP Whitelisting", "Whitelist de IP"), ("IP Whitelisting", "Lista de IPs Confiáveis"),
("ID/Relay Server", "Servidor ID/Relay"), ("ID/Relay Server", "Servidor ID/Relay"),
("Import Server Config", "Importar Configuração do Servidor"), ("Import Server Config", "Importar Configuração do Servidor"),
("Export Server Config", ""), ("Export Server Config", "Exportar Configuração do Servidor"),
("Import server configuration successfully", "Configuração do servidor importada com sucesso"), ("Import server configuration successfully", "Configuração do servidor importada com sucesso"),
("Export server configuration successfully", ""), ("Export server configuration successfully", "Configuração do servidor exportada com sucesso"),
("Invalid server configuration", "Configuração do servidor inválida"), ("Invalid server configuration", "Configuração do servidor inválida"),
("Clipboard is empty", "A área de transferência está vazia"), ("Clipboard is empty", "A área de transferência está vazia"),
("Stop service", "Parar serviço"), ("Stop service", "Parar serviço"),
("Change ID", "Alterar ID"), ("Change ID", "Alterar ID"),
("Website", "Website"), ("Website", "Website"),
("About", "Sobre"), ("About", "Sobre"),
("Mute", "Emudecer"), ("Mute", "Desativar som"),
("Audio Input", "Entrada de Áudio"), ("Audio Input", "Entrada de Áudio"),
("Enhancements", ""), ("Enhancements", "Melhorias"),
("Hardware Codec", ""), ("Hardware Codec", "Codec de hardware"),
("Adaptive Bitrate", ""), ("Adaptive Bitrate", "Taxa de bits adaptável"),
("ID Server", "Servidor de ID"), ("ID Server", "Servidor de ID"),
("Relay Server", "Servidor de Relay"), ("Relay Server", "Servidor de Relay"),
("API Server", "Servidor da API"), ("API Server", "Servidor da API"),
@ -59,18 +59,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Close", "Fechar"), ("Close", "Fechar"),
("Retry", "Tentar novamente"), ("Retry", "Tentar novamente"),
("OK", "OK"), ("OK", "OK"),
("Password Required", "Senha Necessária"), ("Password Required", "Senha necessária"),
("Please enter your password", "Por favor informe sua senha"), ("Please enter your password", "Por favor informe sua senha"),
("Remember password", "Lembrar senha"), ("Remember password", "Lembrar senha"),
("Wrong Password", "Senha Incorreta"), ("Wrong Password", "Senha incorreta"),
("Do you want to enter again?", "Você quer entrar novamente?"), ("Do you want to enter again?", "Você deseja conectar novamente?"),
("Connection Error", "Erro de Conexão"), ("Connection Error", "Erro de conexão"),
("Error", "Erro"), ("Error", "Erro"),
("Reset by the peer", "Reiniciado pelo par"), ("Reset by the peer", "Reiniciado pelo parceiro"),
("Connecting...", "Conectando..."), ("Connecting...", "Conectando..."),
("Connection in progress. Please wait.", "Conexão em progresso. Aguarde por favor."), ("Connection in progress. Please wait.", "Conexão em progresso. Aguarde por favor."),
("Please try 1 minute later", "Por favor tente após 1 minuto"), ("Please try 1 minute later", "Por favor tente após 1 minuto"),
("Login Error", "Erro de Login"), ("Login Error", "Erro de login"),
("Successful", "Sucesso"), ("Successful", "Sucesso"),
("Connected, waiting for image...", "Conectado. Aguardando pela imagem..."), ("Connected, waiting for image...", "Conectado. Aguardando pela imagem..."),
("Name", "Nome"), ("Name", "Nome"),
@ -88,10 +88,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Confirm Delete", "Confirmar Apagar"), ("Confirm Delete", "Confirmar Apagar"),
("Delete", "Apagar"), ("Delete", "Apagar"),
("Properties", "Propriedades"), ("Properties", "Propriedades"),
("Multi Select", "Seleção Múltipla"), ("Multi Select", "Seleção múltipla"),
("Select All", ""), ("Select All", "Selecionar tudo"),
("Unselect All", ""), ("Unselect All", "Desmarcar tudo"),
("Empty Directory", "Diretório Vazio"), ("Empty Directory", "Diretório vazio"),
("Not an empty directory", "Diretório não está vazio"), ("Not an empty directory", "Diretório não está vazio"),
("Are you sure you want to delete this file?", "Tem certeza que deseja apagar este arquivo?"), ("Are you sure you want to delete this file?", "Tem certeza que deseja apagar este arquivo?"),
("Are you sure you want to delete this empty directory?", "Tem certeza que deseja apagar este diretório vazio?"), ("Are you sure you want to delete this empty directory?", "Tem certeza que deseja apagar este diretório vazio?"),
@ -116,18 +116,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Good image quality", "Qualidade visual boa"), ("Good image quality", "Qualidade visual boa"),
("Balanced", "Balanceada"), ("Balanced", "Balanceada"),
("Optimize reaction time", "Otimizar tempo de reação"), ("Optimize reaction time", "Otimizar tempo de reação"),
("Custom", ""), ("Custom", "Personalizado"),
("Show remote cursor", "Mostrar cursor remoto"), ("Show remote cursor", "Mostrar cursor remoto"),
("Show quality monitor", ""), ("Show quality monitor", "Exibir monitor de qualidade"),
("Disable clipboard", "Desabilitar área de transferência"), ("Disable clipboard", "Desabilitar área de transferência"),
("Lock after session end", "Bloquear após o fim da sessão"), ("Lock after session end", "Bloquear após o fim da sessão"),
("Insert", "Inserir"), ("Insert", "Inserir"),
("Insert Lock", "Inserir Trava"), ("Insert Lock", "Bloquear computador"),
("Refresh", "Atualizar"), ("Refresh", "Atualizar"),
("ID does not exist", "ID não existe"), ("ID does not exist", "ID não existe"),
("Failed to connect to rendezvous server", "Falha ao conectar ao servidor de rendezvous"), ("Failed to connect to rendezvous server", "Falha ao conectar ao servidor de rendezvous"),
("Please try later", "Por favor tente mais tarde"), ("Please try later", "Por favor tente mais tarde"),
("Remote desktop is offline", "Desktop remoto está offline"), ("Remote desktop is offline", "O computador remoto está offline"),
("Key mismatch", "Chaves incompatíveis"), ("Key mismatch", "Chaves incompatíveis"),
("Timeout", "Tempo esgotado"), ("Timeout", "Tempo esgotado"),
("Failed to connect to relay server", "Falha ao conectar ao servidor de relay"), ("Failed to connect to relay server", "Falha ao conectar ao servidor de relay"),
@ -141,14 +141,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Click to download", "Clique para baixar"), ("Click to download", "Clique para baixar"),
("Click to update", "Clique para fazer o update"), ("Click to update", "Clique para fazer o update"),
("Configure", "Configurar"), ("Configure", "Configurar"),
("config_acc", "Para controlar seu Desktop remotamente, você precisa conceder ao RustDesk permissões de \"Acessibilidade\"."), ("config_acc", "Para controlar seu computador remotamente, você precisa conceder ao RustDesk permissões de \"Acessibilidade\"."),
("config_screen", "Para acessar seu Desktop remotamente, você precisa conceder ao RustDesk permissões de \"Gravar a Tela\"/"), ("config_screen", "Para acessar seu computador remotamente, você precisa conceder ao RustDesk permissões de \"Gravar a Tela\"/"),
("Installing ...", "Instalando ..."), ("Installing ...", "Instalando ..."),
("Install", "Instalar"), ("Install", "Instalar"),
("Installation", "Instalação"), ("Installation", "Instalação"),
("Installation Path", "Caminho da Instalação"), ("Installation Path", "Caminho da Instalação"),
("Create start menu shortcuts", "Criar atalhos no menu iniciar"), ("Create start menu shortcuts", "Criar atalhos no Menu Iniciar"),
("Create desktop icon", "Criar ícone na área de trabalho"), ("Create desktop icon", "Criar ícone na Área de Trabalho"),
("agreement_tip", "Ao iniciar a instalação, você concorda com o acordo de licença."), ("agreement_tip", "Ao iniciar a instalação, você concorda com o acordo de licença."),
("Accept and Install", "Aceitar e Instalar"), ("Accept and Install", "Aceitar e Instalar"),
("End-user license agreement", "Acordo de licença do usuário final"), ("End-user license agreement", "Acordo de licença do usuário final"),
@ -161,8 +161,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Action", "Ação"), ("Action", "Ação"),
("Add", "Adicionar"), ("Add", "Adicionar"),
("Local Port", "Porta Local"), ("Local Port", "Porta Local"),
("Local Address", ""), ("Local Address", "Endereço Local"),
("Change Local Port", ""), ("Change Local Port", "Alterar Porta Local"),
("setup_server_tip", "Para uma conexão mais rápida, por favor configure seu próprio servidor"), ("setup_server_tip", "Para uma conexão mais rápida, por favor configure seu próprio servidor"),
("Too short, at least 6 characters.", "Muito curto, pelo menos 6 caracteres."), ("Too short, at least 6 characters.", "Muito curto, pelo menos 6 caracteres."),
("The confirmation is not identical.", "A confirmação não é idêntica."), ("The confirmation is not identical.", "A confirmação não é idêntica."),
@ -173,7 +173,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 keyboard and mouse", "Permitir o uso de teclado e mouse"),
("Allow using clipboard", "Permitir o uso da área de transferência"), ("Allow using clipboard", "Permitir o uso da área de transferência"),
("Allow hearing sound", "Permitir escutar som"), ("Allow hearing sound", "Permitir escutar som"),
("Allow file copy and paste", "Permitir copiar e pegar arquivos"), ("Allow file copy and paste", "Permitir copiar e colar arquivos"),
("Connected", "Conectado"), ("Connected", "Conectado"),
("Direct and encrypted connection", "Conexão direta e criptografada"), ("Direct and encrypted connection", "Conexão direta e criptografada"),
("Relayed and encrypted connection", "Conexão via relay e criptografada"), ("Relayed and encrypted connection", "Conexão via relay e criptografada"),
@ -186,39 +186,39 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Auto Login", "Login Automático (Somente válido se você habilitou \"Bloquear após o fim da sessão\")"), ("Auto Login", "Login Automático (Somente válido se você habilitou \"Bloquear após o fim da sessão\")"),
("Enable Direct IP Access", "Habilitar Acesso IP Direto"), ("Enable Direct IP Access", "Habilitar Acesso IP Direto"),
("Rename", "Renomear"), ("Rename", "Renomear"),
("Space", "Espaõ"), ("Space", "Espaço"),
("Create Desktop Shortcut", "Criar Atalho na Área de Trabalho"), ("Create Desktop Shortcut", "Criar Atalho na Área de Trabalho"),
("Change Path", "Alterar Caminho"), ("Change Path", "Alterar Caminho"),
("Create Folder", "Criar Diretório"), ("Create Folder", "Criar Diretório"),
("Please enter the folder name", "Por favor informe o nome do diretório"), ("Please enter the folder name", "Por favor informe o nome do diretório"),
("Fix it", "Conserte"), ("Fix it", "Corrigir"),
("Warning", "Aguardando"), ("Warning", "Aviso"),
("Login screen using Wayland is not supported", "Tela de Login utilizando Wayland não é suportada"), ("Login screen using Wayland is not supported", "Tela de Login utilizando Wayland não é suportada"),
("Reboot required", "Reinicialização necessária"), ("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"), ("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 esperado"), ("x11 expected", "x11 esperado"),
("Port", ""), ("Port", "Porta"),
("Settings", "Configurações"), ("Settings", "Configurações"),
("Username", "Nome de usuário"), ("Username", "Nome de usuário"),
("Invalid port", "Porta inválida"), ("Invalid port", "Porta inválida"),
("Closed manually by the peer", "Fechada manualmente pelo par"), ("Closed manually by the peer", "Fechada manualmente pelo parceiro"),
("Enable remote configuration modification", "Habilitar modificações de configuração remotas"), ("Enable remote configuration modification", "Habilitar modificações de configuração remotas"),
("Run without install", "Executar sem instalar"), ("Run without install", "Executar sem instalar"),
("Always connected via relay", "Sempre conectado via relay"), ("Always connected via relay", "Sempre conectado via relay"),
("Always connect via relay", "Sempre conectar via relay"), ("Always connect via relay", "Sempre conectar via relay"),
("whitelist_tip", "Somente IPs na whitelist podem me acessar"), ("whitelist_tip", "Somente IPs confiáveis podem me acessar"),
("Login", "Login"), ("Login", "Login"),
("Logout", "Sair"), ("Logout", "Sair"),
("Tags", "Tags"), ("Tags", "Tags"),
("Search ID", "Buscar ID"), ("Search ID", "Pesquisar ID"),
("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"), ("Current Wayland display server is not supported", "Servidor de display Wayland atual não é suportado"),
("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"), ("whitelist_sep", "Separado por vírcula, ponto-e-vírgula, espaços ou nova linha"),
("Add ID", "Adicionar ID"), ("Add ID", "Adicionar ID"),
("Add Tag", "Adicionar Tag"), ("Add Tag", "Adicionar Tag"),
("Unselect all tags", "Desselecionar todas as tags"), ("Unselect all tags", "Desmarcar todas as tags"),
("Network error", "Erro de rede"), ("Network error", "Erro de rede"),
("Username missed", "Nome de usuário faltante"), ("Username missed", "Nome de usuário requerido"),
("Password missed", "Senha faltante"), ("Password missed", "Senha requerida"),
("Wrong credentials", "Nome de usuário ou senha incorretos"), ("Wrong credentials", "Nome de usuário ou senha incorretos"),
("Edit Tag", "Editar Tag"), ("Edit Tag", "Editar Tag"),
("Unremember Password", "Esquecer Senha"), ("Unremember Password", "Esquecer Senha"),
@ -250,10 +250,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Mouse Wheel", "Roda do Mouse"), ("Mouse Wheel", "Roda do Mouse"),
("Two-Finger Move", "Mover com dois dedos"), ("Two-Finger Move", "Mover com dois dedos"),
("Canvas Move", "Mover Tela"), ("Canvas Move", "Mover Tela"),
("Pinch to Zoom", "Beliscar para Zoom"), ("Pinch to Zoom", "Pinçar para Zoom"),
("Canvas Zoom", "Zoom na Tela"), ("Canvas Zoom", "Zoom na tela"),
("Reset canvas", "Reiniciar tela"), ("Reset canvas", "Reiniciar tela"),
("No permission of file transfer", "Sem permissões de transferência de arquivo"), ("No permission of file transfer", "Sem permissão para transferência de arquivo"),
("Note", "Nota"), ("Note", "Nota"),
("Connection", "Conexão"), ("Connection", "Conexão"),
("Share Screen", "Compartilhar Tela"), ("Share Screen", "Compartilhar Tela"),
@ -276,116 +276,118 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_new_connection_tip", "Nova requisição de controle recebida, solicita o controle de seu dispositivo atual."), ("android_new_connection_tip", "Nova requisição de controle recebida, solicita o controle de seu dispositivo atual."),
("android_service_will_start_tip", "Habilitar a Captura de Tela irá automaticamente inicalizar o serviço, permitindo que outros dispositivos solicitem uma conexão deste dispositivo."), ("android_service_will_start_tip", "Habilitar a Captura de Tela irá automaticamente inicalizar o serviço, permitindo que outros dispositivos solicitem uma conexão deste dispositivo."),
("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as conexões estabelecidas."), ("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as conexões estabelecidas."),
("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_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou superior."),
("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."), ("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", ""), ("Account", "Conta"),
("Overwrite", "Substituir"), ("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"), ("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
("Quit", "Saída"), ("Quit", "Sair"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("Help", "Ajuda"), ("Help", "Ajuda"),
("Failed", "Falhou"), ("Failed", "Falhou"),
("Succeeded", "Conseguiu"), ("Succeeded", "Sucesso"),
("Someone turns on privacy mode, exit", "Alguém liga o modo de privacidade, saia"), ("Someone turns on privacy mode, exit", "Alguém habilitou o modo de privacidade, sair"),
("Unsupported", "Sem suporte"), ("Unsupported", "Não suportado"),
("Peer denied", "Par negado"), ("Peer denied", "Parceiro negou"),
("Please install plugins", "Por favor instale plugins"), ("Please install plugins", "Por favor instale plugins"),
("Peer exit", "Saída de pares"), ("Peer exit", "Parceiro saiu"),
("Failed to turn off", "Falha ao desligar"), ("Failed to turn off", "Falha ao desligar"),
("Turned off", "Desligado"), ("Turned off", "Desligado"),
("In privacy mode", "No modo de privacidade"), ("In privacy mode", "No modo de privacidade"),
("Out privacy mode", "Fora do modo de privacidade"), ("Out privacy mode", "Fora do modo de privacidade"),
("Language", ""), ("Language", "Idioma"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", "Manter o serviço do RustDesk executando em segundo plano"),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", "Ignorar otimizações de bateria"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", "Abrir otimizações de bateria"),
("Connection not allowed", ""), ("Connection not allowed", "Conexão não permitida"),
("Legacy mode", ""), ("Legacy mode", "Modo legado"),
("Map mode", ""), ("Map mode", "Modo mapa"),
("Translate mode", ""), ("Translate mode", "Modo traduzido"),
("Use temporary password", ""), ("Use temporary password", "Utilizar senha temporária"),
("Use permanent password", ""), ("Use permanent password", "Utilizar senha permanente"),
("Use both passwords", ""), ("Use both passwords", "Utilizar ambas as senhas"),
("Set permanent password", ""), ("Set permanent password", "Configurar senha permanente"),
("Set temporary password length", ""), ("Set temporary password length", "Configurar extensão da senha temporária"),
("Enable Remote Restart", ""), ("Enable Remote Restart", "Habilitar reinicialização remota"),
("Allow remote restart", ""), ("Allow remote restart", "Permitir reinicialização remota"),
("Restart Remote Device", ""), ("Restart Remote Device", "Reiniciar dispositivo remoto"),
("Are you sure you want to restart", ""), ("Are you sure you want to restart", "Você tem certeza que deseja reiniciar?"),
("Restarting Remote Device", ""), ("Restarting Remote Device", "Reiniciando dispositivo remoto"),
("remote_restarting_tip", ""), ("remote_restarting_tip", ""),
("Copied", ""), ("Copied", "Copiado"),
("Exit Fullscreen", ""), ("Exit Fullscreen", "Sair da Tela Cheia"),
("Fullscreen", ""), ("Fullscreen", "Tela Cheia"),
("Mobile Actions", ""), ("Mobile Actions", "Ações móveis"),
("Select Monitor", ""), ("Select Monitor", "Selecionar monitor"),
("Control Actions", ""), ("Control Actions", "Controlar ações"),
("Display Settings", ""), ("Display Settings", "Configurações de exibição"),
("Ratio", ""), ("Ratio", "Proporção"),
("Image Quality", ""), ("Image Quality", "Qualidade de imagem"),
("Scroll Style", ""), ("Scroll Style", "Estilo de rolagem"),
("Show Menubar", ""), ("Show Menubar", "Exibir barra de menu"),
("Hide Menubar", ""), ("Hide Menubar", "Ocultar barra de menu"),
("Direct Connection", ""), ("Direct Connection", "Conexão direta"),
("Relay Connection", ""), ("Relay Connection", "Conexão relay"),
("Secure Connection", ""), ("Secure Connection", "Conexão segura"),
("Insecure Connection", ""), ("Insecure Connection", "Conexão insegura"),
("Scale original", ""), ("Scale original", "Escala original"),
("Scale adaptive", ""), ("Scale adaptive", "Escala adaptada"),
("General", ""), ("General", "Geral"),
("Security", ""), ("Security", "Segurança"),
("Account", ""), ("Account", "Conta"),
("Theme", ""), ("Theme", "Tema"),
("Dark Theme", ""), ("Dark Theme", "Tema escuro"),
("Dark", ""), ("Dark", "Escuro"),
("Light", ""), ("Light", "Claro"),
("Follow System", ""), ("Follow System", "Seguir sistema"),
("Enable hardware codec", ""), ("Enable hardware codec", "Habilitar codec de hardware"),
("Unlock Security Settings", ""), ("Unlock Security Settings", "Desabilitar configurações de segurança"),
("Enable Audio", ""), ("Enable Audio", "Habilitar áudio"),
("Temporary Password Length", ""), ("Temporary Password Length", "Extensão da senha temporária"),
("Unlock Network Settings", ""), ("Unlock Network Settings", "Desbloquear configurações de rede"),
("Server", ""), ("Server", "Servidor"),
("Direct IP Access", ""), ("Direct IP Access", "Acesso direto por IP"),
("Proxy", ""), ("Proxy", "Proxy"),
("Port", ""), ("Port", "Porta"),
("Apply", ""), ("Apply", "Aplicar"),
("Disconnect all devices?", ""), ("Disconnect all devices?", "Desconectar todos os dispositivos?"),
("Clear", ""), ("Clear", "Limpar"),
("Audio Input Device", ""), ("Audio Input Device", "Dispositivo de entrada de áudio"),
("Deny remote access", ""), ("Deny remote access", "Negar acesso remoto"),
("Use IP Whitelisting", ""), ("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"),
("Network", ""), ("Network", "Rede"),
("Enable RDP", ""), ("Enable RDP", "Habilitar RDP"),
("Pin menubar", ""), ("Pin menubar", "Fixar barra de menu"),
("Unpin menubar", ""), ("Unpin menubar", "Desafixar barra de menu"),
("Recording", ""), ("Recording", "Gravando"),
("Directory", ""), ("Directory", "Diretório"),
("Automatically record incoming sessions", ""), ("Automatically record incoming sessions", "Gravar automaticamente sessões de entrada"),
("Change", ""), ("Change", "Alterar"),
("Start session recording", ""), ("Start session recording", "Iniciar gravação da sessão"),
("Stop session recording", ""), ("Stop session recording", "Parar gravação da sessão"),
("Enable Recording Session", ""), ("Enable Recording Session", "Habilitar gravação da sessão"),
("Allow recording session", ""), ("Allow recording session", "Permitir gravação da sessão"),
("Enable LAN Discovery", ""), ("Enable LAN Discovery", "Habilitar descoberta da LAN"),
("Deny LAN Discovery", ""), ("Deny LAN Discovery", "Negar descoberta da LAN"),
("Write a message", ""), ("Write a message", "Escrever uma mensagem"),
("Prompt", ""), ("Prompt", "Prompt de comando"),
("elevation_prompt", ""), ("elevation_prompt", "Prompt de comando (Admin)"),
("uac_warning", ""), ("uac_warning", "Aviso UAC"),
("elevated_foreground_window_warning", ""), ("elevated_foreground_window_warning", "Aviso de janela de primeiro plano elevado"),
("Disconnected", ""), ("Disconnected", "Desconectado"),
("Other", ""), ("Other", "Outro"),
("Confirm before closing multiple tabs", ""), ("Confirm before closing multiple tabs", "Confirmar antes de fechar múltiplas abas"),
("Keyboard Settings", ""), ("Keyboard Settings", "Configurações de teclado"),
("Custom", ""), ("Custom", "Personalizado"),
("Full Access", ""), ("Full Access", "Acesso completo"),
("Screen Share", ""), ("Screen Share", "Compartilhamento de tela"),
("Wayland requires Ubuntu 21.04 or higher version.", ""), ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland requer Ubuntu 21.04 ou versão superior."),
("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", ""), ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland requer uma versão superior da distribuição linux. Por favor, tente o desktop X11 ou mude seu sistema operacional."),
("JumpLink", "View"), ("JumpLink", "JumpLink"),
("Please Select the screen to be shared(Operate on the peer side).", ""), ("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do parceiro)."),
("Show RustDesk", ""), ("Show RustDesk", "Exibir RustDesk"),
("This PC", ""), ("This PC", "Este PC"),
("or", "ou"),
("Continue with", "Continuar com"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."), ("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."),
("Show RustDesk", "Показать RustDesk"), ("Show RustDesk", "Показать RustDesk"),
("This PC", "Этот компьютер"), ("This PC", "Этот компьютер"),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte obrazovku, ktorú chcete zdieľať (Ovládajte na strane partnera)."), ("Please Select the screen to be shared(Operate on the peer side).", "Vyberte obrazovku, ktorú chcete zdieľať (Ovládajte na strane partnera)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", ""), ("Please Select the screen to be shared(Operate on the peer side).", ""),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."), ("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"), ("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."), ("Please Select the screen to be shared(Operate on the peer side).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Please Select the screen to be shared(Operate on the peer side).", "Vui lòng Chọn màn hình để chia sẻ (Hoạt động ở phía ngang hàng)."), ("Please Select the screen to be shared(Operate on the peer side).", "Vui lòng Chọn màn hình để chia sẻ (Hoạt động ở phía ngang hàng)."),
("Show RustDesk", ""), ("Show RustDesk", ""),
("This PC", ""), ("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

@ -48,6 +48,8 @@ mod ui_cm_interface;
mod ui_interface; mod ui_interface;
mod ui_session_interface; mod ui_session_interface;
mod hbbs_http;
#[cfg(windows)] #[cfg(windows)]
pub mod clipboard_file; pub mod clipboard_file;

@ -32,8 +32,8 @@ fn main() {
if !common::global_init() { if !common::global_init() {
return; return;
} }
use hbb_common::log;
use clap::App; use clap::App;
use hbb_common::log;
let args = format!( let args = format!(
"-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]' "-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]'
-k, --key=[KEY] '' -k, --key=[KEY] ''
@ -45,7 +45,7 @@ fn main() {
.about("RustDesk command line tool") .about("RustDesk command line tool")
.args_from_usage(&args) .args_from_usage(&args)
.get_matches(); .get_matches();
use hbb_common::{env_logger::*, config::LocalConfig}; use hbb_common::{config::LocalConfig, env_logger::*};
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
if let Some(p) = matches.value_of("port-forward") { if let Some(p) = matches.value_of("port-forward") {
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect(); let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
@ -73,7 +73,14 @@ fn main() {
} }
let key = matches.value_of("key").unwrap_or("").to_owned(); let key = matches.value_of("key").unwrap_or("").to_owned();
let token = LocalConfig::get_option("access_token"); let token = LocalConfig::get_option("access_token");
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token); cli::start_one_port_forward(
options[0].clone(),
port,
remote_host,
remote_port,
key,
token,
);
} }
common::global_clean(); common::global_clean();
} }

@ -90,6 +90,8 @@ impl RendezvousMediator {
})); }));
} }
join_all(futs).await; join_all(futs).await;
} else {
server.write().unwrap().close_connections();
} }
sleep(1.).await; sleep(1.).await;
} }

@ -1,4 +1,5 @@
use crate::ipc::Data; use crate::ipc::Data;
use bytes::Bytes;
pub use connection::*; pub use connection::*;
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
@ -20,7 +21,6 @@ use std::{
sync::{Arc, Mutex, RwLock, Weak}, sync::{Arc, Mutex, RwLock, Weak},
time::Duration, time::Duration,
}; };
use bytes::Bytes;
pub mod audio_service; pub mod audio_service;
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -140,7 +140,8 @@ pub async fn create_tcp_connection(
.write_to_bytes() .write_to_bytes()
.unwrap_or_default(), .unwrap_or_default(),
&sk, &sk,
).into(), )
.into(),
..Default::default() ..Default::default()
}); });
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??; timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
@ -263,6 +264,17 @@ impl Server {
self.connections.remove(&conn.id()); self.connections.remove(&conn.id());
} }
pub fn close_connections(&mut self) {
let conn_inners: Vec<_> = self.connections.values_mut().collect();
for c in conn_inners {
let mut misc = Misc::new();
misc.set_stop_service(true);
let mut msg = Message::new();
msg.set_misc(misc);
c.send(Arc::new(msg));
}
}
fn add_service(&mut self, service: Box<dyn Service>) { fn add_service(&mut self, service: Box<dyn Service>) {
let name = service.name(); let name = service.name();
self.services.insert(name, service); self.services.insert(name, service);
@ -310,9 +322,9 @@ pub fn check_zombie() {
} }
/// Start the host server that allows the remote peer to control the current machine. /// Start the host server that allows the remote peer to control the current machine.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `is_server` - Whether the current client is definitely the server. /// * `is_server` - Whether the current client is definitely the server.
/// If true, the server will be started. /// If true, the server will be started.
/// Otherwise, client will check if there's already a server and start one if not. /// Otherwise, client will check if there's already a server and start one if not.
@ -323,9 +335,9 @@ pub async fn start_server(is_server: bool) {
} }
/// Start the host server that allows the remote peer to control the current machine. /// Start the host server that allows the remote peer to control the current machine.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `is_server` - Whether the current client is definitely the server. /// * `is_server` - Whether the current client is definitely the server.
/// If true, the server will be started. /// If true, the server will be started.
/// Otherwise, client will check if there's already a server and start one if not. /// Otherwise, client will check if there's already a server and start one if not.

@ -250,14 +250,7 @@ impl Connection {
} }
} }
ipc::Data::Close => { ipc::Data::Close => {
conn.close_manually = true; conn.on_close_manually("connection manager").await;
let mut misc = Misc::new();
misc.set_close_reason("Closed manually by the peer".into());
let mut msg_out = Message::new();
msg_out.set_misc(misc);
conn.send(msg_out).await;
conn.on_close("Close requested from connection manager", false).await;
SESSIONS.lock().unwrap().remove(&conn.lr.my_id);
break; break;
} }
ipc::Data::ChatMessage{text} => { ipc::Data::ChatMessage{text} => {
@ -404,6 +397,18 @@ impl Connection {
_ => {} _ => {}
} }
} }
match &msg.union {
Some(message::Union::Misc(m)) => {
match &m.union {
Some(misc::Union::StopService(_)) => {
conn.on_close_manually("stop service").await;
break;
}
_ => {},
}
}
_ => {}
}
if let Err(err) = conn.stream.send(msg).await { if let Err(err) = conn.stream.send(msg).await {
conn.on_close(&err.to_string(), false).await; conn.on_close(&err.to_string(), false).await;
break; break;
@ -1490,6 +1495,18 @@ impl Connection {
self.port_forward_socket.take(); self.port_forward_socket.take();
} }
async fn on_close_manually(&mut self, close_from: &str) {
self.close_manually = true;
let mut misc = Misc::new();
misc.set_close_reason("Closed manually by the peer".into());
let mut msg_out = Message::new();
msg_out.set_misc(misc);
self.send(msg_out).await;
self.on_close(&format!("Close requested from {}", close_from), false)
.await;
SESSIONS.lock().unwrap().remove(&self.lr.my_id);
}
fn read_dir(&mut self, dir: &str, include_hidden: bool) { fn read_dir(&mut self, dir: &str, include_hidden: bool) {
let dir = dir.to_string(); let dir = dir.to_string();
self.send_fs(ipc::FS::ReadDir { self.send_fs(ipc::FS::ReadDir {

@ -1,4 +1,5 @@
use super::*; use super::*;
#[cfg(target_os = "linux")]
use crate::common::IS_X11; use crate::common::IS_X11;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use dispatch::Queue; use dispatch::Queue;
@ -7,6 +8,7 @@ use hbb_common::{config::COMPRESS_LEVEL, get_time, protobuf::EnumOrUnknown};
use rdev::{simulate, EventType, Key as RdevKey}; use rdev::{simulate, EventType, Key as RdevKey};
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
ops::Sub,
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
time::Instant, time::Instant,
}; };
@ -37,10 +39,12 @@ impl super::service::Reset for StatePos {
} }
} }
#[derive(Default)] #[derive(Default, Clone, Copy)]
struct Input { struct Input {
conn: i32, conn: i32,
time: i64, time: i64,
x: i32,
y: i32,
} }
const KEY_CHAR_START: u64 = 9999; const KEY_CHAR_START: u64 = 9999;
@ -100,8 +104,16 @@ pub fn new_pos() -> GenericService {
sp sp
} }
fn update_last_cursor_pos(x: i32, y: i32) {
let mut lock = LATEST_CURSOR_POS.lock().unwrap();
if lock.1 .0 != x || lock.1 .1 != y {
(lock.0, lock.1) = (Instant::now(), (x, y))
}
}
fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> { fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
if let Some((x, y)) = crate::get_cursor_pos() { if let Some((x, y)) = crate::get_cursor_pos() {
update_last_cursor_pos(x, y);
if state.cursor_pos.0 != x || state.cursor_pos.1 != y { if state.cursor_pos.0 != x || state.cursor_pos.1 != y {
state.cursor_pos = (x, y); state.cursor_pos = (x, y);
let mut msg_out = Message::new(); let mut msg_out = Message::new();
@ -112,7 +124,7 @@ fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
}); });
let exclude = { let exclude = {
let now = get_time(); let now = get_time();
let lock = LATEST_INPUT.lock().unwrap(); let lock = LATEST_INPUT_CURSOR.lock().unwrap();
if now - lock.time < 300 { if now - lock.time < 300 {
lock.conn lock.conn
} else { } else {
@ -170,10 +182,14 @@ lazy_static::lazy_static! {
Arc::new(Mutex::new(Enigo::new())) Arc::new(Mutex::new(Enigo::new()))
}; };
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default(); static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
static ref LATEST_INPUT: Arc<Mutex<Input>> = Default::default(); static ref LATEST_INPUT_CURSOR: Arc<Mutex<Input>> = Default::default();
static ref LATEST_CURSOR_POS: Arc<Mutex<(Instant, (i32, i32))>> = Arc::new(Mutex::new((Instant::now().sub(MOUSE_MOVE_PROTECTION_TIMEOUT), (0, 0))));
} }
static EXITING: AtomicBool = AtomicBool::new(false); static EXITING: AtomicBool = AtomicBool::new(false);
const MOUSE_MOVE_PROTECTION_TIMEOUT: Duration = Duration::from_millis(1_000);
const MOUSE_ACTIVE_DISTANCE: i32 = 5;
// mac key input must be run in main thread, otherwise crash on >= osx 10.15 // mac key input must be run in main thread, otherwise crash on >= osx 10.15
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -357,17 +373,54 @@ fn fix_modifiers(modifiers: &[EnumOrUnknown<ControlKey>], en: &mut Enigo, ck: i3
} }
} }
fn is_mouse_active_by_conn(conn: i32) -> bool {
// out of time protection
if LATEST_CURSOR_POS.lock().unwrap().0.elapsed() > MOUSE_MOVE_PROTECTION_TIMEOUT {
return true;
}
let mut last_input = LATEST_INPUT_CURSOR.lock().unwrap();
// last conn input may be protected
if last_input.conn != conn {
return false;
}
// check if input is in valid range
match crate::get_cursor_pos() {
Some((x, y)) => {
let is_same_input = (last_input.x - x).abs() < MOUSE_ACTIVE_DISTANCE
&& (last_input.y - y).abs() < MOUSE_ACTIVE_DISTANCE;
if !is_same_input {
last_input.x = -MOUSE_ACTIVE_DISTANCE * 2;
last_input.y = -MOUSE_ACTIVE_DISTANCE * 2;
}
is_same_input
}
None => true,
}
}
fn handle_mouse_(evt: &MouseEvent, conn: i32) { fn handle_mouse_(evt: &MouseEvent, conn: i32) {
if EXITING.load(Ordering::SeqCst) { if EXITING.load(Ordering::SeqCst) {
return; return;
} }
if !is_mouse_active_by_conn(conn) {
return;
}
#[cfg(windows)] #[cfg(windows)]
crate::platform::windows::try_change_desktop(); crate::platform::windows::try_change_desktop();
let buttons = evt.mask >> 3; let buttons = evt.mask >> 3;
let evt_type = evt.mask & 0x7; let evt_type = evt.mask & 0x7;
if evt_type == 0 { if evt_type == 0 {
let time = get_time(); let time = get_time();
*LATEST_INPUT.lock().unwrap() = Input { time, conn }; *LATEST_INPUT_CURSOR.lock().unwrap() = Input {
time,
conn,
x: evt.x,
y: evt.y,
};
} }
let mut en = ENIGO.lock().unwrap(); let mut en = ENIGO.lock().unwrap();
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
@ -674,19 +727,19 @@ fn sync_status(evt: &KeyEvent) -> (bool, bool) {
let code = evt.chr(); let code = evt.chr();
let key = rdev::get_win_key(code, 0); let key = rdev::get_win_key(code, 0);
match key { match key {
RdevKey::Home | RdevKey::Home
RdevKey::UpArrow | | RdevKey::UpArrow
RdevKey::PageUp | | RdevKey::PageUp
RdevKey::LeftArrow | | RdevKey::LeftArrow
RdevKey::RightArrow | | RdevKey::RightArrow
RdevKey::End | | RdevKey::End
RdevKey::DownArrow | | RdevKey::DownArrow
RdevKey::PageDown | | RdevKey::PageDown
RdevKey::Insert | | RdevKey::Insert
RdevKey::Delete => en.get_key_state(enigo::Key::NumLock), | RdevKey::Delete => en.get_key_state(enigo::Key::NumLock),
_ => click_numlock, _ => click_numlock,
} }
}; };
return (click_capslock, click_numlock); return (click_capslock, click_numlock);
} }

@ -1,10 +1,13 @@
use hbb_common::log::{debug, error, info}; use hbb_common::log::debug;
#[cfg(target_os = "linux")]
use hbb_common::log::{error, info};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use libappindicator::AppIndicator; use libappindicator::AppIndicator;
#[cfg(target_os = "linux")]
use std::env::temp_dir; use std::env::temp_dir;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex},
}; };
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use trayicon::{MenuBuilder, TrayIconBuilder}; use trayicon::{MenuBuilder, TrayIconBuilder};

@ -20,8 +20,9 @@ use hbb_common::{
tokio::{self, sync::mpsc, time}, tokio::{self, sync::mpsc, time},
}; };
use crate::ipc; use crate::{common::SOFTWARE_UPDATE_URL, ipc, platform};
use crate::{common::SOFTWARE_UPDATE_URL, platform}; #[cfg(feature = "flutter")]
use crate::hbbs_http::account;
type Message = RendezvousMessage; type Message = RendezvousMessage;
@ -808,6 +809,8 @@ pub fn is_root() -> bool {
#[inline] #[inline]
pub fn check_super_user_permission() -> bool { pub fn check_super_user_permission() -> bool {
#[cfg(feature = "flatpak")]
return true;
#[cfg(any(windows, target_os = "linux"))] #[cfg(any(windows, target_os = "linux"))]
return crate::platform::check_super_user_permission().unwrap_or(false); return crate::platform::check_super_user_permission().unwrap_or(false);
#[cfg(not(any(windows, target_os = "linux")))] #[cfg(not(any(windows, target_os = "linux")))]
@ -843,6 +846,21 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
tx tx
} }
#[cfg(feature = "flutter")]
pub fn account_auth(op: String, id: String, uuid: String) {
account::OidcSession::account_auth(op, id, uuid);
}
#[cfg(feature = "flutter")]
pub fn account_auth_cancel() {
account::OidcSession::auth_cancel();
}
#[cfg(feature = "flutter")]
pub fn account_auth_result() -> String {
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
}
// notice: avoiding create ipc connecton repeatly, // notice: avoiding create ipc connecton repeatly,
// because windows named pipe has serious memory leak issue. // because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]

@ -22,7 +22,6 @@ use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
/// IS_IN KEYBOARD_HOOKED sciter only /// IS_IN KEYBOARD_HOOKED sciter only
pub static IS_IN: AtomicBool = AtomicBool::new(false); pub static IS_IN: AtomicBool = AtomicBool::new(false);
@ -1323,7 +1322,7 @@ impl<T: InvokeUiSession> Session<T> {
#[cfg(any(target_os = "windows", target_os = "macos"))] #[cfg(any(target_os = "windows", target_os = "macos"))]
std::thread::spawn(move || { std::thread::spawn(move || {
let func = move |event: Event| match event.event_type { let func = move |event: Event| match event.event_type {
EventType::KeyPress(key) | EventType::KeyRelease(key) => { EventType::KeyPress(..) | EventType::KeyRelease(..) => {
// grab all keys // grab all keys
if !IS_IN.load(Ordering::SeqCst) if !IS_IN.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)