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

View File

@ -38,7 +38,7 @@ jobs:
shell: bash
run: |
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 ;;
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac

View File

@ -11,6 +11,7 @@ env:
FLUTTER_VERSION: "3.0.5"
TAG_NAME: "nightly"
VCPKG_COMMIT_ID: '6ca56aeb457f033d344a7106cb3f9f1abf8f4e98'
VERSION: "1.2.0"
jobs:
build-for-windows:
@ -92,7 +93,7 @@ jobs:
rustdesk-*.exe
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 }}
strategy:
fail-fast: false
@ -104,7 +105,8 @@ jobs:
# - { 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}
- { 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 }
steps:
- name: Checkout source code
@ -113,7 +115,7 @@ jobs:
- name: Install prerequisites
run: |
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 ;;
# aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
@ -159,7 +161,7 @@ jobs:
- name: Install cargo bundle tools
run: |
cargo install cargo-bundle --force
cargo install cargo-bundle
- name: Show version information (Rust, cargo, GCC)
shell: bash
@ -172,7 +174,7 @@ jobs:
rustc -V
- name: Build rustdesk
run: ./build.py --flutter --hwcodec
run: ./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
- name: Rename rustdesk
shell: bash
@ -187,9 +189,17 @@ jobs:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
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
if: ${{ matrix.job.extra-build-args == '' }}
uses: vufa/arch-makepkg-action@master
with:
packages: >
@ -220,14 +230,85 @@ jobs:
python
ttf-arphic-uming
libappindicator-gtk3
libayatana-appindicator
scripts: |
cd res && HBB=`pwd`/.. FLUTTER=1 makepkg -f
- name: Publish archlinux package
if: ${{ matrix.job.extra-build-args == '' }}
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ env.TAG_NAME }}
files: |
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
View File

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

4
Cargo.lock generated
View File

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

View File

@ -64,9 +64,9 @@ wol-rs = "0.9.1"
flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true }
errno = "0.2.8"
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 = ["json", "rustls-tls"], default-features=false }
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
cpal = "0.13.5"
@ -147,7 +147,7 @@ hound = "3.5"
name = "RustDesk"
identifier = "com.carriez.rustdesk"
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"
resources = ["res/mac-tray-light.png","res/mac-tray-dark.png"]

View File

@ -69,7 +69,7 @@ Please download sciter dynamic library yourself.
```sh
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
libclang-dev ninja-build 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

View File

@ -81,6 +81,11 @@ def make_parser():
action='store_true',
help='Build windows portable'
)
parser.add_argument(
'--flatpak',
action='store_true',
help='Build rustdesk libs with the flatpak feature enabled'
)
return parser
@ -188,6 +193,8 @@ def get_features(args):
features.append('hwcodec')
if args.flutter:
features.append('flutter')
if args.flatpak:
features.append('flatpak')
print("features:", features)
return features
@ -201,7 +208,7 @@ Version: %s
Architecture: amd64
Maintainer: open-trade <info@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.
""" % version

38
flatpak/rustdesk.json Normal file
View 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"
]
}

View File

@ -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
View 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

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

30
flutter/assets/Okta.svg Normal file
View 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

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_card.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/login.dart';
import '../../consts.dart';
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
import 'package:get/get.dart';

View File

@ -21,7 +21,7 @@ const String kTabLabelSettingPage = "Settings";
const String kWindowPrefix = "wm_";
// the executable name of the portable version
// the executable name of the portable version
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
@ -60,6 +60,12 @@ const kInvalidValueStr = "InvalidValueStr";
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
/// see [LogicalKeyboardKey.keyLabel]
const Map<int, String> logicalKeyMap = <int, String>{

View File

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

View File

@ -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 {
final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw);

View File

@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.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/widgets/login.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';

View File

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

View File

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

View File

@ -97,9 +97,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}
_update_remote_count();
});
Future.delayed(Duration.zero, () {
restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId());
});
}
@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) {
WindowController.fromWindowId(windowId()).hide();
rustDeskWinManager.call(WindowType.Main, kWindowEventHide, {"id": windowId()});
await WindowController.fromWindowId(windowId()).close();
}
ConnectionTypeState.delete(id);
_update_remote_count();

View File

@ -134,6 +134,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
showMaximize: false,
showMinimize: true,
showClose: true,
onWindowCloseButton: handleWindowCloseButton,
controller: serverModel.tabController,
maxLabelWidth: 100,
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) {

View File

@ -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;
}

View File

@ -506,7 +506,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
rustDeskWinManager.unregisterActiveWindow(0);
} else {
widget.onClose?.call();
WindowController.fromWindowId(windowId!).hide();
await WindowController.fromWindowId(windowId!).hide();
rustDeskWinManager
.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
Future.delayed(Duration.zero, () async {
if (widget.isMainWindow) {
await windowManager.hide();
rustDeskWinManager.unregisterActiveWindow(0);
await windowManager.close();
} else {
await WindowController.fromWindowId(windowId!).hide();
rustDeskWinManager.call(
WindowType.Main, kWindowEventHide, {"id": windowId!});
await WindowController.fromWindowId(windowId!).close();
}
});
}

View File

@ -168,6 +168,20 @@ void runMultiWindow(
widget,
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 {

View File

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

View File

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

View File

@ -42,6 +42,7 @@ class InputModel {
// mouse
final isPhysicalMouse = false.obs;
int _lastMouseDownButtons = 0;
Offset last_mouse_pos = Offset.zero;
get id => parent.target?.id ?? "";
@ -303,6 +304,28 @@ class InputModel {
}
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 isMove = false;
switch (evt['type']) {
@ -319,8 +342,6 @@ class InputModel {
return;
}
evt['type'] = type;
double x = evt['x'];
double y = max(0.0, evt['y']);
if (isDesktop) {
y = y - stateGlobal.tabBarHeight;
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ arch=('x86_64')
url=""
license=('AGPL-3.0')
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=()
checkdepends=()
optdepends=()

88
res/rpm-flutter.spec Normal file
View 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

View File

@ -3,7 +3,7 @@ Version: 1.1.9
Release: 0
Summary: RPM package
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
The best open-source remote desktop client software, written in Rust.

View File

@ -1,9 +1,9 @@
Name: rustdesk
Version: 1.1.9
Version: 1.2.0
Release: 0
Summary: RPM package
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
The best open-source remote desktop client software, written in Rust.

View File

@ -13,6 +13,8 @@ use hbb_common::{
fs, log,
};
// use crate::hbbs_http::account::AuthResult;
use crate::flutter::{self, SESSIONS};
#[cfg(target_os = "android")]
use crate::start_server;
@ -1082,6 +1084,20 @@ pub fn install_install_path() -> SyncReturn<String> {
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")]
pub mod server_side {
use jni::{

34
src/hbbs_http.rs Normal file
View 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
View 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_()
}
}

View File

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

View File

@ -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).", "请选择要分享的画面(对端操作)。"),
("Show RustDesk", "显示rustdesk"),
("This PC", "此电脑"),
("or", ""),
("Continue with", "使用"),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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."),
("JumpLink", "View"),
("Stop service", "Stop Service"),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", "Mostrar RustDesk"),
("This PC", "Este PC"),
("or", "o"),
("Continue with", "Continuar con"),
].iter().cloned().collect();
}

393
src/lang/fa.rs Normal file
View 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();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", "共有する画面を選択してください(ピア側で操作)。"),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", "공유할 화면을 선택하십시오(피어 측에서 작동)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

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

View File

@ -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).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."),
("Show RustDesk", "Показать RustDesk"),
("This PC", "Этот компьютер"),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", ""),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", "請選擇要分享的畫面(在對端操作)。"),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

@ -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)."),
("Show RustDesk", ""),
("This PC", ""),
("or", ""),
("Continue with", ""),
].iter().cloned().collect();
}

View File

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

View File

@ -32,8 +32,8 @@ fn main() {
if !common::global_init() {
return;
}
use hbb_common::log;
use clap::App;
use hbb_common::log;
let args = format!(
"-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]'
-k, --key=[KEY] ''
@ -45,7 +45,7 @@ fn main() {
.about("RustDesk command line tool")
.args_from_usage(&args)
.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"));
if let Some(p) = matches.value_of("port-forward") {
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 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();
}
}

View File

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

View File

@ -1,4 +1,5 @@
use crate::ipc::Data;
use bytes::Bytes;
pub use connection::*;
use hbb_common::{
allow_err,
@ -20,7 +21,6 @@ use std::{
sync::{Arc, Mutex, RwLock, Weak},
time::Duration,
};
use bytes::Bytes;
pub mod audio_service;
cfg_if::cfg_if! {
@ -140,7 +140,8 @@ pub async fn create_tcp_connection(
.write_to_bytes()
.unwrap_or_default(),
&sk,
).into(),
)
.into(),
..Default::default()
});
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
@ -263,6 +264,17 @@ impl Server {
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>) {
let name = service.name();
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.
///
///
/// # Arguments
///
///
/// * `is_server` - Whether the current client is definitely the server.
/// If true, the server will be started.
/// 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.
///
///
/// # Arguments
///
///
/// * `is_server` - Whether the current client is definitely the server.
/// If true, the server will be started.
/// Otherwise, client will check if there's already a server and start one if not.

View File

@ -250,14 +250,7 @@ impl Connection {
}
}
ipc::Data::Close => {
conn.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);
conn.send(msg_out).await;
conn.on_close("Close requested from connection manager", false).await;
SESSIONS.lock().unwrap().remove(&conn.lr.my_id);
conn.on_close_manually("connection manager").await;
break;
}
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 {
conn.on_close(&err.to_string(), false).await;
break;
@ -1490,6 +1495,18 @@ impl Connection {
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) {
let dir = dir.to_string();
self.send_fs(ipc::FS::ReadDir {

View File

@ -1,4 +1,5 @@
use super::*;
#[cfg(target_os = "linux")]
use crate::common::IS_X11;
#[cfg(target_os = "macos")]
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 std::{
convert::TryFrom,
ops::Sub,
sync::atomic::{AtomicBool, Ordering},
time::Instant,
};
@ -37,10 +39,12 @@ impl super::service::Reset for StatePos {
}
}
#[derive(Default)]
#[derive(Default, Clone, Copy)]
struct Input {
conn: i32,
time: i64,
x: i32,
y: i32,
}
const KEY_CHAR_START: u64 = 9999;
@ -100,8 +104,16 @@ pub fn new_pos() -> GenericService {
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<()> {
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 {
state.cursor_pos = (x, y);
let mut msg_out = Message::new();
@ -112,7 +124,7 @@ fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
});
let exclude = {
let now = get_time();
let lock = LATEST_INPUT.lock().unwrap();
let lock = LATEST_INPUT_CURSOR.lock().unwrap();
if now - lock.time < 300 {
lock.conn
} else {
@ -170,10 +182,14 @@ lazy_static::lazy_static! {
Arc::new(Mutex::new(Enigo::new()))
};
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);
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
#[cfg(target_os = "macos")]
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) {
if EXITING.load(Ordering::SeqCst) {
return;
}
if !is_mouse_active_by_conn(conn) {
return;
}
#[cfg(windows)]
crate::platform::windows::try_change_desktop();
let buttons = evt.mask >> 3;
let evt_type = evt.mask & 0x7;
if evt_type == 0 {
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();
#[cfg(not(target_os = "macos"))]
@ -674,19 +727,19 @@ fn sync_status(evt: &KeyEvent) -> (bool, bool) {
let code = evt.chr();
let key = rdev::get_win_key(code, 0);
match key {
RdevKey::Home |
RdevKey::UpArrow |
RdevKey::PageUp |
RdevKey::LeftArrow |
RdevKey::RightArrow |
RdevKey::End |
RdevKey::DownArrow |
RdevKey::PageDown |
RdevKey::Insert |
RdevKey::Delete => en.get_key_state(enigo::Key::NumLock),
RdevKey::Home
| RdevKey::UpArrow
| RdevKey::PageUp
| RdevKey::LeftArrow
| RdevKey::RightArrow
| RdevKey::End
| RdevKey::DownArrow
| RdevKey::PageDown
| RdevKey::Insert
| RdevKey::Delete => en.get_key_state(enigo::Key::NumLock),
_ => click_numlock,
}
};
};
return (click_capslock, click_numlock);
}

View File

@ -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")]
use libappindicator::AppIndicator;
#[cfg(target_os = "linux")]
use std::env::temp_dir;
use std::{
collections::HashMap,
sync::{Arc, Mutex, RwLock},
sync::{Arc, Mutex},
};
#[cfg(target_os = "windows")]
use trayicon::{MenuBuilder, TrayIconBuilder};

View File

@ -20,8 +20,9 @@ use hbb_common::{
tokio::{self, sync::mpsc, time},
};
use crate::ipc;
use crate::{common::SOFTWARE_UPDATE_URL, platform};
use crate::{common::SOFTWARE_UPDATE_URL, ipc, platform};
#[cfg(feature = "flutter")]
use crate::hbbs_http::account;
type Message = RendezvousMessage;
@ -808,6 +809,8 @@ pub fn is_root() -> bool {
#[inline]
pub fn check_super_user_permission() -> bool {
#[cfg(feature = "flatpak")]
return true;
#[cfg(any(windows, target_os = "linux"))]
return crate::platform::check_super_user_permission().unwrap_or(false);
#[cfg(not(any(windows, target_os = "linux")))]
@ -843,6 +846,21 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
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,
// because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")]

View File

@ -22,7 +22,6 @@ use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;
/// IS_IN KEYBOARD_HOOKED sciter only
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"))]
std::thread::spawn(move || {
let func = move |event: Event| match event.event_type {
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
EventType::KeyPress(..) | EventType::KeyRelease(..) => {
// grab all keys
if !IS_IN.load(Ordering::SeqCst)
|| !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)