Compare commits

..

5 Commits

Author SHA1 Message Date
rustdesk 6c578292e8 bump to 1.4.9 2026-07-06 18:00:39 +08:00
fufesou 28930c0463 fix: non-E2EE show dialog (#15514)
* fix: non-E2EE show dialog

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: build web, bridge

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: direct IP access, do not snow non-E2EE dialog

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non E2EE dialog, update contents

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE, show dialog, port forward

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, port forward, ignore direct IP access

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE is_direct_ip_access()

Signed-off-by: fufesou <linlong1266@gmail.com>

* Simple refactor

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, port forward, close socket on disconnect

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: non-E2EE dialog, incorrect reuse of Data::Close

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-06 17:05:11 +08:00
fufesou 37141afece refact: remove feature cli (#15524)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-06 16:17:32 +08:00
fufesou 493b14ba78 Fix/session scope permission audit (#15469)
* fix: enforce session-scoped permissions

Restrict non-remote sessions to their allowed message types, filter
out-of-scope login options, and audit rejected or filtered messages.
Hide screenshot controls outside default remote sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: typo

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: prevent privacy mode in view-camera sessions

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: switch display, check non-view-camera

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: avoid sending unsupported messages

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, add option to control close/alarm

Signed-off-by: fufesou <linlong1266@gmail.com>

* Fix: scoped session handling for view-camera compatibility

  - Skip view-camera auto-login and display-management side effects
  - Allow harmless render broadcasts without affecting non-video sessions
  - Keep legacy view-camera management messages compatible as no-ops
  - Preserve stricter scope violations for non-video session types

Signed-off-by: fufesou <linlong1266@gmail.com>

* update libs/hbb_common

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: ignore repeated login request

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: view camera, support "Take screenshot"

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scoped messages, check update options

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, check portforward before conn type voolations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: scoped messages, reduce changes.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: keep scoped sessions compatible with render broadcasts

Allow legacy render-broadcast no-op messages for file transfer and terminal
sessions while keeping port forward and mixed options scoped. Also avoid sending
new render updates to non-video Flutter sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: scope screenshot requests by video source

Key screenshot requests by video source and display index so camera and
monitor sessions cannot consume each other's requests. Deduplicate the Flutter
render-target predicate while keeping render updates limited to video sessions.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Harden scoped session message handling

Filter option updates by authenticated connection type,
keep legacy no-op messages compatible, and avoid noisy repeated
scope violation alarms.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Send close reason for scoped session violations

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: Enforce scoped session message filtering

  - filter out-of-scope messages for limited session types
  - scope option updates by authenticated connection type
  - keep render-broadcast no-op compatibility for non-video scoped sessions
  - restore view-camera screenshot handling
  - improve session scope violation audit labels
  - avoid cloning option messages on the remote hot path

Signed-off-by: fufesou <linlong1266@gmail.com>

* Fix scoped session clipboard broadcast compatibility

Treat text clipboard broadcasts as no-op compatibility messages for FileTransfer and Terminal sessions, matching existing
handler behavior and preventing optional scope-violation close from disconnecting those sessions. Keep ViewCamera and
PortForward clipboard messages subject to normal scope enforcement.

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: log warn

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: restrict Flutter clipboard sync to default sessions

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, comments and tests

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, reset sessions in login handle

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: session scope, view camera, allow clipboard noop

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-07-05 23:28:32 +08:00
StealUrKill cf1de4de62 Feature: Restore the last viewed monitor on auto reconnect (#15441)
* Feature: Restore the last viewed monitor on auto reconnect

Remembers the users last manually selected remote monitor and returns to it after an auto reconnect.

In memory, reconnect only, and bounds checked against the current display count.

It is skipped in "use all my displays" mode.

Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>

* Address review on reconnect monitor restore

Avoid a crash if the session closes during a reconnect.

Don't overwrite the remembered monitor on auto restore.

Defer the switch until the view is ready so a monitor with a different size renders correctly.

Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>

* Guard all-displays reconnect restore against empty display list

* Harden reconnect monitor restore against races and multi-UI sessions

Cancel a queued restore when the user manually selects a monitor, so a
newer choice is not overridden by a stale pending restore.

Compare the remembered monitor against the reconnect event's display
instead of the stale _pi.currentDisplay, which is intentionally left
unchanged when the peer has multiple sessions.

Add a frame-independent fallback so a multi-UI tab that never receives
the first-image event (its display is filtered to the owning tab) still
restores the remembered monitor.

Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>

* Harden reconnect monitor restore: fallback timer, lifecycle, cursor

Follow-up hardening on the auto-reconnect monitor restore:

- Cancel the fallback timer synchronously once this tab owns the restore,
  so it can no longer fire while onEvent2UIRgba is awaiting canvas setup
  and switch displays before the canvas is ready (the offset the deferred
  restore exists to avoid). The multi-UI no-frame fallback stays intact.
- Apply the restore in a finally so a throwing canvas init still runs it
  instead of stranding a queued restore with the timer already cancelled.
- Cancel the fallback timer on a manual monitor switch, so a newer user
  selection supersedes a queued restore instead of racing it.
- Restore with updateCursorPos: false, matching other programmatic
  display switches so an auto-restore does not reposition the cursor.

---------

Signed-off-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
2026-07-05 22:32:47 +08:00
90 changed files with 1373 additions and 434 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ env:
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version
VERSION: "1.4.8"
VERSION: "1.4.9"
NDK_VERSION: "r28c"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
+1 -1
View File
@@ -17,7 +17,7 @@ env:
TAG_NAME: "nightly"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
VERSION: "1.4.8"
VERSION: "1.4.9"
NDK_VERSION: "r26d"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
+85
View File
@@ -0,0 +1,85 @@
name: wf-cliprdr CI
on:
workflow_dispatch:
pull_request:
paths:
- "libs/clipboard/src/windows/**"
- "tests/test_invariant_wf_cliprdr.c"
- ".github/workflows/wf-cliprdr-ci.yml"
push:
branches:
- master
paths:
- "libs/clipboard/src/windows/**"
- "tests/test_invariant_wf_cliprdr.c"
- ".github/workflows/wf-cliprdr-ci.yml"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: wf_cliprdr invariant test
runs-on: windows-2022
steps:
- name: Checkout source code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
with:
arch: x64
- name: Setup vcpkg with GitHub Actions binary cache
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
with:
vcpkgDirectory: C:\vcpkg
doNotCache: false
- name: Install vcpkg dependency
shell: pwsh
run: |
& "$env:VCPKG_ROOT\vcpkg.exe" install check:x64-windows --classic --x-install-root="$env:VCPKG_ROOT\installed"
- name: Build test
shell: pwsh
run: |
$testRoot = Join-Path $env:GITHUB_WORKSPACE 'build\wf-cliprdr'
New-Item -ItemType Directory -Force $testRoot | Out-Null
$testSource = (($env:GITHUB_WORKSPACE -replace '\\', '/') + '/tests/test_invariant_wf_cliprdr.c')
$cmakeLists = @(
'cmake_minimum_required(VERSION 3.20)'
'project(test_invariant_wf_cliprdr C)'
''
'set(CMAKE_C_STANDARD 11)'
'set(CMAKE_C_STANDARD_REQUIRED ON)'
'set(CMAKE_C_EXTENSIONS OFF)'
''
'find_package(check CONFIG REQUIRED)'
''
'add_executable(test_invariant_wf_cliprdr'
' "TEST_SOURCE"'
')'
''
'target_link_libraries(test_invariant_wf_cliprdr PRIVATE'
' $<$<TARGET_EXISTS:Check::check>:Check::check>'
' $<$<NOT:$<TARGET_EXISTS:Check::check>>:Check::checkShared>'
')'
) -join [Environment]::NewLine
$cmakeLists.Replace('TEST_SOURCE', $testSource) | Set-Content -NoNewline (Join-Path $testRoot 'CMakeLists.txt')
cmake -S $testRoot -B (Join-Path $testRoot 'out') -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows
cmake --build (Join-Path $testRoot 'out') --config Release
- name: Run test
shell: pwsh
run: .\build\wf-cliprdr\out\Release\test_invariant_wf_cliprdr.exe
Generated
+3 -26
View File
@@ -6588,7 +6588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556af5f5c953a2ee13f45753e581a38f9778e6551bc3ccc56d90b14628fe59d8"
dependencies = [
"cfg-if 0.1.10",
"rpassword 2.1.0",
"rpassword",
"tempfile",
"termios 0.3.3",
"winapi 0.3.9",
@@ -7152,17 +7152,6 @@ dependencies = [
"winapi 0.2.8",
]
[[package]]
name = "rpassword"
version = "7.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
dependencies = [
"libc",
"rtoolbox",
"windows-sys 0.48.0",
]
[[package]]
name = "rtcp"
version = "0.14.0"
@@ -7174,16 +7163,6 @@ dependencies = [
"webrtc-util",
]
[[package]]
name = "rtoolbox"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "rtp"
version = "0.14.0"
@@ -7270,7 +7249,7 @@ dependencies = [
[[package]]
name = "rustdesk"
version = "1.4.8"
version = "1.4.9"
dependencies = [
"android-wakelock",
"android_logger",
@@ -7283,7 +7262,6 @@ dependencies = [
"cfg-if 1.0.0",
"chrono",
"cidr-utils",
"clap 4.5.53",
"clipboard",
"clipboard-master",
"cocoa 0.24.1",
@@ -7341,7 +7319,6 @@ dependencies = [
"repng",
"reqwest",
"ringbuf",
"rpassword 7.3.1",
"rubato",
"runas",
"rust-pulsectl",
@@ -7385,7 +7362,7 @@ dependencies = [
[[package]]
name = "rustdesk-portable-packer"
version = "1.4.8"
version = "1.4.9"
dependencies = [
"brotli",
"dirs 5.0.1",
+1 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "rustdesk"
version = "1.4.8"
version = "1.4.9"
authors = ["rustdesk <info@rustdesk.com>"]
edition = "2021"
build= "build.rs"
@@ -22,7 +22,6 @@ path = "src/service.rs"
[features]
inline = []
cli = []
use_samplerate = ["samplerate"]
use_rubato = ["rubato"]
use_dasp = ["dasp"]
@@ -62,8 +61,6 @@ dasp = { version = "0.11", features = ["signal", "interpolate-linear", "interpol
rubato = { version = "0.12", optional = true }
samplerate = { version = "0.2", optional = true }
uuid = { version = "1.3", features = ["v4"] }
clap = "4.2"
rpassword = "7.2"
num_cpus = "1.15"
bytes = { version = "1.4", features = ["serde"] }
default-net = "0.14"
+1 -1
View File
@@ -18,7 +18,7 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.4.8
version: 1.4.9
exec: usr/share/rustdesk/rustdesk
exec_args: $@
apt:
+1 -1
View File
@@ -18,7 +18,7 @@ AppDir:
id: rustdesk
name: rustdesk
icon: rustdesk
version: 1.4.8
version: 1.4.9
exec: usr/share/rustdesk/rustdesk
exec_args: $@
apt:
+48 -1
View File
@@ -1185,6 +1185,48 @@ void msgBox(SessionID sessionId, String type, String title, String text,
VoidCallback? onSubmit,
int? submitTimeout}) {
dialogManager.dismissAll();
if (type.contains('insecure-connection')) {
Future<void> closeSession() async {
await bind.sessionSetCommon(
sessionId: sessionId,
key: 'continue-insecure-connection',
value: 'N',
);
dialogManager.dismissAll();
closeConnection();
}
void continueSession() {
unawaited(
bind.sessionSetCommon(
sessionId: sessionId,
key: 'continue-insecure-connection',
value: 'Y',
),
);
dialogManager.dismissAll();
}
dialogManager.show(
(setState, close, context) => CustomAlertDialog(
title: null,
content: SelectionArea(child: msgboxContent(type, title, text)),
actions: [
dialogButton(
'Continue',
onPressed: continueSession,
isOutline: true,
),
dialogButton('Disconnect', onPressed: closeSession),
],
onSubmit: closeSession,
onCancel: closeSession,
),
tag: '$sessionId-$type-$title-$text-$link',
);
return;
}
List<Widget> buttons = [];
bool hasOk = false;
submit() {
@@ -3350,7 +3392,12 @@ Future<List<Rect>> getScreenRectList() async {
}
openMonitorInTheSameTab(int i, FFI ffi, PeerInfo pi,
{bool updateCursorPos = true}) {
{bool updateCursorPos = true, bool recordSelection = true}) {
if (recordSelection) {
ffi.ffiModel.lastUserDisplay = i;
ffi.ffiModel.cancelPendingRestoreTimer();
ffi.ffiModel.pendingMonitorRestore = null;
}
final displays = i == kAllDisplayValue
? List.generate(pi.displays.length, (index) => index)
: [i];
+1 -1
View File
@@ -606,7 +606,7 @@ List<TTextMenu> toolbarControls(BuildContext context, String id, FFI ffi) {
// to-do:
// 1. Web desktop
// 2. Mobile, copy the image to the clipboard
if (isDesktop) {
if ((isDefaultConn || ffi.connType == ConnType.viewCamera) && isDesktop) {
final isScreenshotSupported = bind.sessionGetCommonSync(
sessionId: sessionId, key: 'is_screenshot_supported', param: '');
if ('true' == isScreenshotSupported) {
+60 -5
View File
@@ -112,6 +112,9 @@ class CachedPeerData {
class FfiModel with ChangeNotifier {
CachedPeerData cachedPeerData = CachedPeerData();
PeerInfo _pi = PeerInfo();
int? lastUserDisplay;
int? pendingMonitorRestore;
Timer? _pendingRestoreTimer;
Rect? _rect;
var _inputBlocked = false;
@@ -248,6 +251,8 @@ class FfiModel with ChangeNotifier {
clear() {
_pi = PeerInfo();
lastUserDisplay = null;
_cancelPendingMonitorRestore();
_secure = null;
_direct = null;
_inputBlocked = false;
@@ -932,6 +937,7 @@ class FfiModel with ChangeNotifier {
// frame briefly, then shows the Connecting overlay.
if (_restartReconnectDelayTimer == null) {
parent.target?.inputModel.setRelativeMouseMode(false);
_cancelPendingMonitorRestore();
bind.sessionReconnect(sessionId: sessionId, forceRelay: false);
clearPermissions();
// Retry once more after the silent window so restart reconnect attempts
@@ -1084,10 +1090,22 @@ class FfiModel with ChangeNotifier {
}
}
void _cancelPendingMonitorRestore() {
_pendingRestoreTimer?.cancel();
_pendingRestoreTimer = null;
pendingMonitorRestore = null;
}
void cancelPendingRestoreTimer() {
_pendingRestoreTimer?.cancel();
_pendingRestoreTimer = null;
}
void reconnect(OverlayDialogManager dialogManager, SessionID sessionId,
bool forceRelay) {
// Disable relative mouse mode before reconnecting to ensure cursor is released.
parent.target?.inputModel.setRelativeMouseMode(false);
_cancelPendingMonitorRestore();
bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay);
clearPermissions();
dialogManager.dismissAll();
@@ -1401,6 +1419,25 @@ class FfiModel with ChangeNotifier {
// now replaced to _updateCurDisplay
updateCurDisplay(sessionId);
}
// After reconnecting, restore the last selected monitor once the canvas is ready.
// Switching earlier can offset the view if the monitor sizes differ.
final last = lastUserDisplay;
pendingMonitorRestore = (!isCache &&
last != null &&
last != currentDisplay &&
bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
sessionId: sessionId) !=
'Y' &&
((last == kAllDisplayValue && _pi.displays.isNotEmpty) ||
(last >= 0 && last < _pi.displays.length)))
? last
: null;
// Fallback if the first image event never reaches this tab (multi-UI).
_pendingRestoreTimer?.cancel();
if (pendingMonitorRestore != null) {
_pendingRestoreTimer = Timer(const Duration(milliseconds: 1500),
() => parent.target?._applyPendingMonitorRestore());
}
if (displays.isNotEmpty) {
_reconnects = 1;
_offlineReconnectStartTime = null;
@@ -3911,17 +3948,35 @@ class FFI {
}
if (ffiModel.waitForFirstImage.value == true) {
ffiModel.waitForFirstImage.value = false;
ffiModel.cancelPendingRestoreTimer();
ffiModel.resetRestartReconnectState();
dialogManager.dismissAll();
await canvasModel.updateViewStyle();
await canvasModel.updateScrollStyle();
await canvasModel.initializeEdgeScrollEdgeThickness();
for (final cb in imageModel.callbacksOnFirstImage) {
cb(id);
try {
await canvasModel.updateViewStyle();
await canvasModel.updateScrollStyle();
await canvasModel.initializeEdgeScrollEdgeThickness();
for (final cb in imageModel.callbacksOnFirstImage) {
cb(id);
}
} finally {
_applyPendingMonitorRestore();
}
}
}
void _applyPendingMonitorRestore() {
final restore = ffiModel.pendingMonitorRestore;
ffiModel._cancelPendingMonitorRestore();
if (restore == null || closed) return;
// The display list may have changed since the restore was queued.
final displays = ffiModel.pi.displays;
if ((restore == kAllDisplayValue && displays.isNotEmpty) ||
(restore >= 0 && restore < displays.length)) {
openMonitorInTheSameTab(restore, this, ffiModel.pi,
recordSelection: false, updateCursorPos: false);
}
}
/// Login with [password], choose if the client should [remember] it.
void login(String osUsername, String osPassword, SessionID sessionId,
String password, bool remember) {
+9
View File
@@ -1914,6 +1914,15 @@ class RustdeskImpl {
throw UnimplementedError("sessionHandleScreenshot");
}
Future<void> sessionSetCommon(
{required UuidValue sessionId, required String key, required String value, dynamic hint}) {
js.context.callMethod('setByName', [
'common',
jsonEncode({'name': key, 'value': value})
]);
return Future.value();
}
String? sessionGetCommonSync(
{required UuidValue sessionId,
required String key,
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
version: 1.4.8+66
version: 1.4.9+67
environment:
sdk: '^3.1.0'
+14 -54
View File
@@ -232,7 +232,6 @@ struct _CliprdrStream
FILEDESCRIPTORW m_Dsc;
void *m_pData;
UINT32 m_connID;
UINT32 m_streamId; // unique CLIPRDR streamId; avoids leaking a heap pointer
};
typedef struct _CliprdrStream CliprdrStream;
@@ -286,9 +285,6 @@ struct wf_clipboard
char *req_fdata;
HANDLE req_fevent;
BOOL req_f_received;
UINT32 req_f_conn_id_expected; // connID of the outstanding request
UINT32 req_f_stream_id_expected; // streamId of the outstanding request; responses for another are dropped
LONG req_f_stream_id_seq; // source of unique per-stream ids
size_t nFiles;
size_t file_array_size;
@@ -319,7 +315,7 @@ static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format);
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 format);
static UINT cliprdr_send_lock(wfClipboard *clipboard);
static UINT cliprdr_send_unlock(wfClipboard *clipboard);
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId,
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, const void *streamid,
ULONG index, UINT32 flag, DWORD positionhigh,
DWORD positionlow, ULONG request);
@@ -402,7 +398,7 @@ static ULONG STDMETHODCALLTYPE CliprdrStream_Release(IStream *This)
static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULONG cb,
ULONG *pcbRead)
{
UINT ret;
int ret;
CliprdrStream *instance = (CliprdrStream *)This;
wfClipboard *clipboard;
@@ -415,23 +411,12 @@ static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULO
if (instance->m_lOffset.QuadPart >= instance->m_lSize.QuadPart)
return S_FALSE;
ret = cliprdr_send_request_filecontents(clipboard, instance->m_connID, instance->m_streamId, instance->m_lIndex,
ret = cliprdr_send_request_filecontents(clipboard, instance->m_connID, (void *)This, instance->m_lIndex,
FILECONTENTS_RANGE, instance->m_lOffset.HighPart,
instance->m_lOffset.LowPart, cb);
if (ret != CHANNEL_RC_OK)
{
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
if (ret < 0)
return E_FAIL;
}
if (clipboard->req_fsize > cb)
{
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
return STG_E_READFAULT;
}
if (clipboard->req_fdata)
{
@@ -643,7 +628,6 @@ static CliprdrStream *CliprdrStream_New(UINT32 connID, ULONG index, void *pData,
instance->m_pData = pData;
instance->m_lOffset.QuadPart = 0;
instance->m_connID = connID;
instance->m_streamId = (UINT32)InterlockedIncrement(&clipboard->req_f_stream_id_seq);
if (instance->m_Dsc.dwFlags & FD_ATTRIBUTES)
{
@@ -654,28 +638,16 @@ static CliprdrStream *CliprdrStream_New(UINT32 connID, ULONG index, void *pData,
if (((instance->m_Dsc.dwFlags & FD_FILESIZE) == 0) && !isDir)
{
/* get content size of this stream */
if (cliprdr_send_request_filecontents(clipboard, instance->m_connID, instance->m_streamId,
if (cliprdr_send_request_filecontents(clipboard, instance->m_connID, (void *)instance,
instance->m_lIndex, FILECONTENTS_SIZE, 0, 0,
8) == CHANNEL_RC_OK)
{
success = TRUE;
}
if (clipboard->req_fdata != NULL && clipboard->req_fsize >= sizeof(LONGLONG))
{
LONGLONG sz = 0;
CopyMemory(&sz, clipboard->req_fdata, sizeof(sz));
if (sz < 0)
success = FALSE;
else
instance->m_lSize.QuadPart = sz;
}
else
{
success = FALSE;
}
if (clipboard->req_fdata)
if (clipboard->req_fdata != NULL)
{
instance->m_lSize.QuadPart = *((LONGLONG *)clipboard->req_fdata);
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
}
@@ -1801,12 +1773,12 @@ static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UIN
return wait_response_event(connID, clipboard, clipboard->formatDataRespEvent, &clipboard->formatDataRespReceived, &clipboard->hmem);
}
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId, ULONG index,
UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, const void *streamid, ULONG index,
UINT32 flag, DWORD positionhigh, DWORD positionlow,
ULONG nreq)
{
UINT rc;
CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest = { 0 };
CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest;
if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsRequest)
return ERROR_INTERNAL_ERROR;
@@ -1817,11 +1789,12 @@ static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 con
return rc;
}
clipboard->req_f_received = FALSE;
clipboard->req_f_conn_id_expected = connID;
clipboard->req_f_stream_id_expected = streamId;
fileContentsRequest.connID = connID;
fileContentsRequest.streamId = streamId;
// streamId is `IStream*` pointer, though it is not very good on a 64-bit system.
// But it is OK, because it is only used to check if the stream is the same in
// `wf_cliprdr_server_file_contents_request()` function.
fileContentsRequest.streamId = (UINT32)(ULONG_PTR)streamid;
fileContentsRequest.listIndex = index;
fileContentsRequest.dwFlags = flag;
fileContentsRequest.nPositionLow = positionlow;
@@ -3042,7 +3015,6 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
BOOL bIsStreamFile = TRUE;
static LPSTREAM pStreamStc = NULL;
static UINT32 uStreamIdStc = 0;
static UINT32 uConnIdStc = 0;
wfClipboard *clipboard;
UINT rc = ERROR_INTERNAL_ERROR;
UINT sRc;
@@ -3116,8 +3088,7 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
vFormatEtc.lindex = fileContentsRequest->listIndex;
vFormatEtc.ptd = NULL;
if ((uStreamIdStc != fileContentsRequest->streamId) ||
(uConnIdStc != fileContentsRequest->connID) || !pStreamStc)
if ((uStreamIdStc != fileContentsRequest->streamId) || !pStreamStc)
{
LPENUMFORMATETC pEnumFormatEtc;
ULONG CeltFetched;
@@ -3148,7 +3119,6 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
{
pStreamStc = vStgMedium.pstm;
uStreamIdStc = fileContentsRequest->streamId;
uConnIdStc = fileContentsRequest->connID;
bIsStreamFile = TRUE;
}
@@ -3312,9 +3282,6 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
rc = ERROR_INTERNAL_ERROR;
break;
}
if (fileContentsResponse->connID != clipboard->req_f_conn_id_expected ||
fileContentsResponse->streamId != clipboard->req_f_stream_id_expected)
return CHANNEL_RC_OK;
clipboard->req_fsize = 0;
clipboard->req_fdata = NULL;
@@ -3325,13 +3292,6 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
}
clipboard->req_fsize = fileContentsResponse->cbRequested;
/*
* Keep the zero-size allocation: supported Windows builds use the Microsoft
* CRT, where malloc(0) returns a valid pointer. wait_response_event() also
* uses a non-NULL req_fdata to recognize a successful zero-byte response.
* The Rust FFI derives requestedData and cbRequested from the same Vec, so a
* nonzero length cannot have a NULL data pointer on the normal call path.
*/
clipboard->req_fdata = (char *)malloc(fileContentsResponse->cbRequested);
if (!clipboard->req_fdata)
{
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "rustdesk-portable-packer"
version = "1.4.8"
version = "1.4.9"
edition = "2021"
description = "RustDesk Remote Desktop"
+1 -1
View File
@@ -1,5 +1,5 @@
pkgname=rustdesk
pkgver=1.4.8
pkgver=1.4.9
pkgrel=0
epoch=
pkgdesc=""
+1 -1
View File
@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.4.8
Version: 1.4.9
Release: 0
Summary: RPM package
License: GPL-3.0
+1 -1
View File
@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.4.8
Version: 1.4.9
Release: 0
Summary: RPM package
License: GPL-3.0
+1 -1
View File
@@ -1,5 +1,5 @@
Name: rustdesk
Version: 1.4.8
Version: 1.4.9
Release: 0
Summary: RPM package
License: GPL-3.0
-199
View File
@@ -1,199 +0,0 @@
use crate::client::*;
use async_trait::async_trait;
use hbb_common::{
config::PeerConfig,
config::READ_TIMEOUT,
futures::{SinkExt, StreamExt},
log,
message_proto::*,
protobuf::Message as _,
rendezvous_proto::ConnType,
tokio::{self, sync::mpsc},
Stream,
};
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct Session {
id: String,
lc: Arc<RwLock<LoginConfigHandler>>,
sender: mpsc::UnboundedSender<Data>,
password: String,
}
impl Session {
pub fn new(id: &str, sender: mpsc::UnboundedSender<Data>) -> Self {
let mut password = "".to_owned();
if PeerConfig::load(id).password.is_empty() {
match rpassword::prompt_password("Enter password: ") {
Ok(p) => password = p,
Err(e) => {
log::error!("Failed to read password: {:?}", e);
password = "".to_owned();
}
}
}
let session = Self {
id: id.to_owned(),
sender,
password,
lc: Default::default(),
};
session.lc.write().unwrap().initialize(
id.to_owned(),
ConnType::PORT_FORWARD,
None,
false,
None,
None,
);
session
}
}
#[async_trait]
impl Interface for Session {
fn get_login_config_handler(&self) -> Arc<RwLock<LoginConfigHandler>> {
return self.lc.clone();
}
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str) {
match msgtype {
"input-password" => {
self.sender
.send(Data::Login((self.password.clone(), true)))
.ok();
}
"re-input-password" => {
log::error!("{}: {}", title, text);
match rpassword::prompt_password("Enter password: ") {
Ok(password) => {
let login_data = Data::Login((password, true));
self.sender.send(login_data).ok();
}
Err(e) => {
log::error!("reinput password failed, {:?}", e);
}
}
}
msg if msg.contains("error") => {
log::error!("{}: {}: {}", msgtype, title, text);
}
_ => {
log::info!("{}: {}: {}", msgtype, title, text);
}
}
}
fn handle_login_error(&self, err: &str) -> bool {
handle_login_error(self.lc.clone(), err, self)
}
fn handle_peer_info(&self, pi: PeerInfo) {
self.lc.write().unwrap().handle_peer_info(&pi);
}
async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) {
log::info!(
"password={}",
hbb_common::password_security::temporary_password()
);
handle_hash(self.lc.clone(), &pass, hash, self, peer).await;
}
async fn handle_login_from_ui(
&self,
os_username: String,
os_password: String,
password: String,
remember: bool,
peer: &mut Stream,
) {
handle_login_from_ui(
self.lc.clone(),
os_username,
os_password,
password,
remember,
peer,
)
.await;
}
async fn handle_test_delay(&self, t: TestDelay, peer: &mut Stream) {
handle_test_delay(t, peer).await;
}
fn send(&self, data: Data) {
self.sender.send(data).ok();
}
}
#[tokio::main(flavor = "current_thread")]
pub async fn connect_test(id: &str, key: String, token: String) {
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
let handler = Session::new(&id, sender);
match crate::client::Client::start(id, &key, &token, ConnType::PORT_FORWARD, handler).await {
Err(err) => {
log::error!("Failed to connect {}: {}", &id, err);
}
Ok((mut stream, direct)) => {
log::info!("direct: {}", direct);
// rpassword::prompt_password("Input anything to exit").ok();
loop {
tokio::select! {
res = hbb_common::timeout(READ_TIMEOUT, stream.next()) => match res {
Err(_) => {
log::error!("Timeout");
break;
}
Ok(Some(Ok(bytes))) => {
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
match msg_in.union {
Some(message::Union::Hash(hash)) => {
log::info!("Got hash");
break;
}
_ => {}
}
}
}
_ => {}
}
}
}
}
}
}
#[tokio::main(flavor = "current_thread")]
pub async fn start_one_port_forward(
id: String,
port: i32,
remote_host: String,
remote_port: i32,
key: String,
token: String,
) {
crate::common::test_rendezvous_server();
crate::common::test_nat_type();
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
let handler = Session::new(&id, sender);
if let Err(err) = crate::port_forward::listen(
handler.id.clone(),
handler.password.clone(),
port,
handler.clone(),
receiver,
&key,
&token,
handler.lc.clone(),
remote_host,
remote_port,
)
.await
{
log::error!("Failed to listen on {}: {}", port, err);
}
log::info!("port forward (:{}) exit", port);
}
+23
View File
@@ -3792,6 +3792,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
#[derive(Clone)]
pub enum Data {
Close,
RejectInsecureConnection,
Login((String, String, String, bool)),
Message(Message),
SendFiles((i32, JobType, String, String, i32, bool, bool)),
@@ -3815,11 +3816,33 @@ pub enum Data {
ElevateWithLogon(String, String),
NewVoiceCall,
CloseVoiceCall,
ContinueInsecureConnection,
ResetDecoder(Option<usize>),
RenameFile((i32, String, String, bool)),
TakeScreenshot((i32, String)),
}
pub async fn confirm_insecure_connection(
interface: &impl Interface,
receiver: &mut UnboundedReceiver<Data>,
) -> bool {
interface.msgbox(
"insecure-connection-nocancel-hasclose",
"Insecure Connection",
"conn-e2ee-unavailable-tip",
"",
);
while let Some(data) = receiver.recv().await {
match data {
Data::ContinueInsecureConnection => return true,
Data::RejectInsecureConnection => return false,
Data::Close => return false,
_ => {}
}
}
false
}
/// Keycode for key events.
#[derive(Clone, Debug)]
pub enum Key {
-6
View File
@@ -6,7 +6,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn get_home_dir(&self) -> String {
@@ -16,7 +15,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn get_next_job_id(&self) -> i32 {
@@ -26,7 +24,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn update_next_job_id(&self, id: i32) {
@@ -36,7 +33,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn read_dir(&self, path: String, include_hidden: bool) -> sciter::Value {
@@ -90,7 +86,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn confirm_delete_files(&self, id: i32, file_num: i32) {
@@ -100,7 +95,6 @@ pub trait FileManager: Interface {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn set_no_confirm(&self, id: i32) {
+25 -2
View File
@@ -14,6 +14,7 @@ use crate::{
// Empirical no-data window before exposing the restart reconnect state to the UI.
// Restart msgbox text is kept as a legacy UI fallback; Flutter handles the type as a control event.
const RESTART_REMOTE_DEVICE_NO_DATA_TIMEOUT: Duration = Duration::from_secs(5);
const KCP_CLOSE_REASON_FLUSH_DELAY: Duration = Duration::from_millis(30);
#[cfg(feature = "unix-file-copy-paste")]
use crate::{clipboard::try_empty_clipboard_files, clipboard_file::unix_file_clip};
#[cfg(any(
@@ -183,8 +184,20 @@ impl<T: InvokeUiSession> Remote<T> {
.lock()
.unwrap()
.set_connected();
let is_secured = peer.is_secured();
self.handler
.set_connection_type(peer.is_secured(), direct, stream_type); // flutter -> connection_ready
.set_connection_type(is_secured, direct, stream_type); // flutter -> connection_ready
if !is_secured
&& !crate::common::is_direct_ip_access(&self.handler.get_id())
&& !client::confirm_insecure_connection(&self.handler, &mut self.receiver).await
{
self.send_close_reason(&mut peer, "").await;
if kcp.is_some() {
tokio::time::sleep(KCP_CLOSE_REASON_FLUSH_DELAY).await;
}
self.handle_disconnected(round);
return;
}
self.handler.update_direct(Some(direct));
if conn_type == ConnType::DEFAULT_CONN || conn_type == ConnType::VIEW_CAMERA {
self.handler
@@ -338,13 +351,17 @@ impl<T: InvokeUiSession> Remote<T> {
self.send_close_reason(&mut peer, "kcp").await;
// KCP does not send messages immediately, so wait to ensure the last message is sent.
// 1ms works in my test, but 30ms is more reliable.
tokio::time::sleep(Duration::from_millis(30)).await;
tokio::time::sleep(KCP_CLOSE_REASON_FLUSH_DELAY).await;
}
}
Err(err) => {
self.handler.on_establish_connection_error(err.to_string());
}
}
self.handle_disconnected(round);
}
fn handle_disconnected(&self, round: u32) {
// set_disconnected_ok is used to check if new connection round is started.
let _set_disconnected_ok = self
.handler
@@ -1090,6 +1107,9 @@ impl<T: InvokeUiSession> Remote<T> {
}
async fn send_toggle_virtual_display_msg(&self, peer: &mut Stream) {
if self.handler.is_view_camera() {
return;
}
if !self.peer_info.is_support_virtual_display() {
return;
}
@@ -1111,6 +1131,9 @@ impl<T: InvokeUiSession> Remote<T> {
}
async fn send_toggle_privacy_mode_msg(&self, peer: &mut Stream) {
if self.handler.is_view_camera() {
return;
}
let lc = self.handler.lc.read().unwrap();
if lc.version >= hbb_common::get_version_number("1.2.4")
&& lc.get_toggle_option("privacy-mode")
+6 -3
View File
@@ -764,15 +764,14 @@ async fn test_rendezvous_server_() {
Config::reset_online();
}
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
pub fn test_rendezvous_server() {
std::thread::spawn(test_rendezvous_server_);
}
pub fn refresh_rendezvous_server() {
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
#[cfg(any(target_os = "android", target_os = "ios"))]
test_rendezvous_server();
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
std::thread::spawn(|| {
if crate::ipc::test_rendezvous_server().is_err() {
test_rendezvous_server();
@@ -2620,6 +2619,10 @@ pub fn get_control_permission(
}
}
pub fn is_direct_ip_access(peer: &str) -> bool {
hbb_common::is_ip_str(peer) || hbb_common::is_domain_port_str(peer)
}
#[cfg(test)]
mod tests {
use super::*;
+5 -2
View File
@@ -1437,7 +1437,7 @@ fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
pub fn update_text_clipboard_required() {
let is_required = sessions::get_sessions()
.iter()
.any(|s| s.is_text_clipboard_required());
.any(|s| s.is_default() && s.is_text_clipboard_required());
#[cfg(target_os = "android")]
let _ = scrap::android::ffi::call_clipboard_manager_enable_client_clipboard(is_required);
Client::set_is_text_clipboard_required(is_required);
@@ -1447,13 +1447,16 @@ pub fn update_text_clipboard_required() {
pub fn update_file_clipboard_required() {
let is_required = sessions::get_sessions()
.iter()
.any(|s| s.is_file_clipboard_required());
.any(|s| s.is_default() && s.is_file_clipboard_required());
Client::set_is_file_clipboard_required(is_required);
}
#[cfg(not(target_os = "ios"))]
pub fn send_clipboard_msg(msg: Message, _is_file: bool) {
for s in sessions::get_sessions() {
if !s.is_default() {
continue;
}
#[cfg(feature = "unix-file-copy-paste")]
if _is_file {
if crate::is_support_file_copy_paste_num(s.lc.read().unwrap().version)
+19 -1
View File
@@ -1026,7 +1026,7 @@ pub fn main_set_option(key: String, value: String) {
set_option(key, value.clone());
#[cfg(target_os = "android")]
crate::rendezvous_mediator::RendezvousMediator::restart();
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
#[cfg(any(target_os = "android", target_os = "ios"))]
crate::common::test_rendezvous_server();
} else {
set_option(key, value.clone());
@@ -1224,9 +1224,14 @@ pub fn main_set_local_option(key: String, value: String) {
let is_texture_render_key = key.eq(config::keys::OPTION_TEXTURE_RENDER);
let is_d3d_render_key = key.eq(config::keys::OPTION_ALLOW_D3D_RENDER);
set_local_option(key, value.clone());
let is_render_target =
|session: &crate::flutter::FlutterSession| session.is_default() || session.is_view_camera();
if is_texture_render_key {
let session_event = [("v", &value)];
for session in sessions::get_sessions() {
if !is_render_target(&session) {
continue;
}
session.push_event("use_texture_render", &session_event, &[]);
session.use_texture_render_changed();
session.ui_handler.update_use_texture_render();
@@ -1234,6 +1239,9 @@ pub fn main_set_local_option(key: String, value: String) {
}
if is_d3d_render_key {
for session in sessions::get_sessions() {
if !is_render_target(&session) {
continue;
}
session.update_supported_decodings();
}
}
@@ -3020,6 +3028,16 @@ pub fn main_set_common(_key: String, _value: String) {
}
}
pub fn session_set_common(session_id: SessionID, key: String, value: String) {
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
if key == "continue-insecure-connection"
{
s.continue_insecure_connection(value == "Y");
return;
}
}
}
pub fn session_get_common_sync(
session_id: SessionID,
key: String,
+4 -4
View File
@@ -2,7 +2,7 @@
use crate::flutter;
#[cfg(target_os = "windows")]
use crate::platform::windows::{get_char_from_vk, get_unicode_from_vk};
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
use crate::ui::CUR_SESSION;
use crate::ui_session_interface::{InvokeUiSession, Session};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -469,7 +469,7 @@ static mut IS_LEFT_OPTION_DOWN: bool = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn get_keyboard_mode() -> String {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
return session.get_keyboard_mode();
}
@@ -991,7 +991,7 @@ pub fn event_to_key_events(
}
pub fn send_key_event(key_event: &KeyEvent) {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
session.send_key_event(key_event);
}
@@ -1003,7 +1003,7 @@ pub fn send_key_event(key_event: &KeyEvent) {
}
pub fn get_peer_platform() -> String {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {
return session.peer_platform();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "اتصال الوسيط"),
("Secure Connection", "اتصال آمن"),
("Insecure Connection", "اتصال غير آمن"),
("Continue", ""),
("Scale original", "المقياس الأصلي"),
("Scale adaptive", "مقياس التكيف"),
("General", "عام"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "الإظهار على شريط الأدوات المُصغّر"),
("All monitors", "جميع الشاشات"),
("#{} monitor", "الشاشة رقم {}"),
("conn-e2ee-unavailable-tip", "تعذر التحقق من التشفير من طرف إلى طرف.\nقد يكون الجهاز البعيد ما يزال قيد الإعداد. حاول مرة أخرى لاحقًا.\nإذا استمر حدوث ذلك، فقد يكون الخادم غير موثوق به.\nهل تريد المتابعة على أي حال؟"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Рэтрансляванае падключэнне"),
("Secure Connection", "Бяспечнае падключэнне"),
("Insecure Connection", "Нябяспечнае падключэнне"),
("Continue", ""),
("Scale original", "Арыгінальны маштаб"),
("Scale adaptive", "Адаптыўны маштаб"),
("General", "Агульныя"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Паказваць на згорнутай панэлі інструментаў"),
("All monitors", "Усе манітори"),
("#{} monitor", "Манітор {}"),
("conn-e2ee-unavailable-tip", "Не ўдалося праверыць скразное шыфраванне.\nАддаленая прылада, магчыма, яшчэ наладжваецца. Паспрабуйце пазней.\nКалі гэта будзе паўтарацца, сервер можа быць ненадзейным.\nУсё роўна працягнуць?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Релейна връзка"),
("Secure Connection", "Сигурна връзка"),
("Insecure Connection", "Несигурна връзка"),
("Continue", ""),
("Scale original", "Оригинален мащаб"),
("Scale adaptive", "Приспособимо мащабиране"),
("General", "Основен"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Показване в минимизираната лента с инструменти"),
("All monitors", "Всички монитори"),
("#{} monitor", "Монитор {}"),
("conn-e2ee-unavailable-tip", "Шифроването от край до край не може да бъде проверено.\nОтдалеченото устройство може все още да се настройва. Опитайте отново по-късно.\nАко това продължи, сървърът може да не е надежден.\nДа се продължи ли въпреки това?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Connexió amb repetidor"),
("Secure Connection", "Connexió segura"),
("Insecure Connection", "Connexió no segura"),
("Continue", ""),
("Scale original", "Escala original"),
("Scale adaptive", "Escala adaptativa"),
("General", "General"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Mostra a la barra deines minimitzada"),
("All monitors", "Tots els monitors"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "No s'ha pogut verificar el xifratge d'extrem a extrem.\nEl dispositiu remot encara es pot estar configurant. Torneu-ho a provar més tard.\nSi això continua passant, el servidor pot no ser de confiança.\nVoleu continuar igualment?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "中继连接"),
("Secure Connection", "安全连接"),
("Insecure Connection", "非安全连接"),
("Continue", ""),
("Scale original", "原始尺寸"),
("Scale adaptive", "适应窗口"),
("General", "常规"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "在最小化工具栏上显示"),
("All monitors", "所有显示器"),
("#{} monitor", "{}号显示器"),
("conn-e2ee-unavailable-tip", "无法验证端到端加密。\n远程设备可能仍在准备中,请稍后重试。\n如果此问题持续出现,服务器可能不受信任。\n仍要继续吗?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Připojení předávací server"),
("Secure Connection", "Zabezpečené připojení"),
("Insecure Connection", "Nezabezpečené připojení"),
("Continue", ""),
("Scale original", "Originální měřítko"),
("Scale adaptive", "Adaptivní měřítko"),
("General", "Obecné"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Zobrazit na minimalizovaném panelu nástrojů"),
("All monitors", "Všechny monitory"),
("#{} monitor", "Monitor č. {}"),
("conn-e2ee-unavailable-tip", "Nepodařilo se ověřit koncové šifrování.\nVzdálené zařízení se možná stále nastavuje. Zkuste to znovu později.\nPokud se to bude opakovat, server nemusí být důvěryhodný.\nPřesto pokračovat?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Viderestillingsforbindelse"),
("Secure Connection", "Sikker forbindelse"),
("Insecure Connection", "Usikker forbindelse"),
("Continue", ""),
("Scale original", "Original skalering"),
("Scale adaptive", "Adaptiv skalering"),
("General", "Generelt"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Vis på den minimerede værktøjslinje"),
("All monitors", "Alle skærme"),
("#{} monitor", "Skærm {}"),
("conn-e2ee-unavailable-tip", "End-to-end-kryptering kunne ikke bekræftes.\nDen eksterne enhed er muligvis stadig ved at blive konfigureret. Prøv igen senere.\nHvis dette fortsætter, er serveren muligvis ikke pålidelig.\nFortsæt alligevel?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Relay-Verbindung"),
("Secure Connection", "Sichere Verbindung"),
("Insecure Connection", "Unsichere Verbindung"),
("Continue", ""),
("Scale original", "Keine Skalierung"),
("Scale adaptive", "Anpassbare Skalierung"),
("General", "Allgemein"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "In der minimierten Symbolleiste anzeigen"),
("All monitors", "Alle Bildschirme"),
("#{} monitor", "Bildschirm {}"),
("conn-e2ee-unavailable-tip", "Ende-zu-Ende-Verschlüsselung konnte nicht verifiziert werden.\nDas entfernte Gerät wird möglicherweise noch eingerichtet. Versuchen Sie es später erneut.\nWenn dies weiterhin auftritt, ist der Server möglicherweise nicht vertrauenswürdig.\nTrotzdem fortfahren?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Αναμεταδιδόμενη σύνδεση"),
("Secure Connection", "Ασφαλής σύνδεση"),
("Insecure Connection", "Μη ασφαλής σύνδεση"),
("Continue", ""),
("Scale original", "Κλιμάκωση πρωτότυπου"),
("Scale adaptive", "Προσαρμοσμένη κλίμακα"),
("General", "Γενικά"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Εμφάνιση στην ελαχιστοποιημένη γραμμή εργαλείων"),
("All monitors", "Όλες οι οθόνες"),
("#{} monitor", "Οθόνη {}"),
("conn-e2ee-unavailable-tip", "Δεν ήταν δυνατή η επαλήθευση της κρυπτογράφησης από άκρο σε άκρο.\nΗ απομακρυσμένη συσκευή μπορεί να ρυθμίζεται ακόμα. Δοκιμάστε ξανά αργότερα.\nΑν αυτό συνεχιστεί, ο διακομιστής μπορεί να μην είναι αξιόπιστος.\nΣυνέχεια παρ' όλα αυτά;"),
].iter().cloned().collect();
}
+1
View File
@@ -279,5 +279,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("wayland-soft-keyboard-input-label", "Soft keyboard input"),
("wayland-keyboard-input-reset-choice-tip", "Reset keyboard input choice"),
("remember-wayland-keyboard-choice-tip", "Don't ask again for this remote computer"),
("conn-e2ee-unavailable-tip", "Could not verify end-to-end encryption.\nThe remote device may still be setting up. Try again later.\nIf this keeps happening, the server may be untrusted.\nContinue anyway?")
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Relajsa Konekto"),
("Secure Connection", "Sekura Konekto"),
("Insecure Connection", "Nesekura Konekto"),
("Continue", ""),
("Scale original", "Skalo originalo"),
("Scale adaptive", "Skalo adapta"),
("General", "Ĝenerala"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Montri en la minimumigita ilobreto"),
("All monitors", "Ĉiuj monitoroj"),
("#{} monitor", "Monitoro {}"),
("conn-e2ee-unavailable-tip", "Ne eblis kontroli la fin-al-finan ĉifradon.\nLa fora aparato eble ankoraŭ estas agordata. Provu denove poste.\nSe tio daŭre okazas, la servilo eble estas nefidinda.\nĈu daŭrigi tamen?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Conexión Relay"),
("Secure Connection", "Conexión segura"),
("Insecure Connection", "Conexión insegura"),
("Continue", ""),
("Scale original", "Escala original"),
("Scale adaptive", "Escala adaptativa"),
("General", "General"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Mostrar en la barra de herramientas minimizada"),
("All monitors", "Todos los monitores"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "No se pudo verificar el cifrado de extremo a extremo.\nEs posible que el dispositivo remoto aún se esté configurando. Inténtelo de nuevo más tarde.\nSi esto sigue ocurriendo, es posible que el servidor no sea de confianza.\n¿Continuar de todos modos?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Releeühendus"),
("Secure Connection", "Turvaline ühendus"),
("Insecure Connection", "Ebaturvaline ühendus"),
("Continue", ""),
("Scale original", "Originaalskaala"),
("Scale adaptive", "Kohanduv skaala"),
("General", "Üldine"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Näita minimeeritud tööriistaribal"),
("All monitors", "Kõik kuvarid"),
("#{} monitor", "Kuvar {}"),
("conn-e2ee-unavailable-tip", "Otspunktkrüptimist ei saanud kontrollida.\nKaugseade võib olla veel seadistamisel. Proovige hiljem uuesti.\nKui see jätkub, ei pruugi server olla usaldusväärne.\nKas jätkata siiski?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Konexio igorria"),
("Secure Connection", "Konexio segurua"),
("Insecure Connection", "Konexio ez-segurua"),
("Continue", ""),
("Scale original", "Jatorrizko eskala"),
("Scale adaptive", "Eskala moldagarria"),
("General", "Orokorra"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Erakutsi minimizatutako tresna-barran"),
("All monitors", "Monitore guztiak"),
("#{} monitor", "{}. monitorea"),
("conn-e2ee-unavailable-tip", "Ezin izan da muturretik muturrerako enkriptatzea egiaztatu.\nUrruneko gailua oraindik konfiguratzen ari daiteke. Saiatu berriro geroago.\nHonek jarraitzen badu, zerbitzaria fidagaitza izan daiteke.\nHala ere jarraitu?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Relay ارتباط"),
("Secure Connection", "ارتباط امن"),
("Insecure Connection", "ارتباط غیر امن"),
("Continue", ""),
("Scale original", "مقیاس اصلی"),
("Scale adaptive", "مقیاس تطبیقی"),
("General", "عمومی"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "نمایش در نوار ابزار کوچک‌شده"),
("All monitors", "همه نمایشگرها"),
("#{} monitor", "نمایشگر {}"),
("conn-e2ee-unavailable-tip", "رمزنگاری سرتاسری قابل تأیید نیست.\nدستگاه راه دور ممکن است هنوز در حال آماده‌سازی باشد. بعداً دوباره تلاش کنید.\nاگر این مشکل ادامه داشت، سرور ممکن است نامطمئن باشد.\nبا این حال ادامه می‌دهید؟"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Välitetty yhteys"),
("Secure Connection", "Suojattu yhteys"),
("Insecure Connection", "Suojaamaton yhteys"),
("Continue", ""),
("Scale original", "Skaalaa alkuperäinen"),
("Scale adaptive", "Mukautuva skaalaus"),
("General", "Yleiset"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Näytä pienennetyssä työkalurivissä"),
("All monitors", "Kaikki näytöt"),
("#{} monitor", "Näyttö {}"),
("conn-e2ee-unavailable-tip", "Päästä päähän -salausta ei voitu vahvistaa.\nEtälaite voi olla vielä määritettävänä. Yritä myöhemmin uudelleen.\nJos tämä jatkuu, palvelin ei ehkä ole luotettava.\nJatketaanko silti?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Connexion via relais"),
("Secure Connection", "Connexion sécurisée"),
("Insecure Connection", "Connexion non sécurisée"),
("Continue", ""),
("Scale original", "Échelle originale"),
("Scale adaptive", "Échelle adaptative"),
("General", "Général"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Afficher dans la barre doutils réduite"),
("All monitors", "Tous les moniteurs"),
("#{} monitor", "Moniteur {}"),
("conn-e2ee-unavailable-tip", "Impossible de vérifier le chiffrement de bout en bout.\nL'appareil distant est peut-être encore en cours de configuration. Réessayez plus tard.\nSi le problème persiste, le serveur n'est peut-être pas fiable.\nContinuer quand même ?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "რეტრანსლირებული კავშირი"),
("Secure Connection", "უსაფრთხო კავშირი"),
("Insecure Connection", "არაუსაფრთხო კავშირი"),
("Continue", ""),
("Scale original", "ორიგინალური მასშტაბი"),
("Scale adaptive", "ადაპტირებადი მასშტაბი"),
("General", "ზოგადი"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "ჩვენება ჩაკეცილ ხელსაწყოთა ზოლზე"),
("All monitors", "ყველა მონიტორი"),
("#{} monitor", "მონიტორი {}"),
("conn-e2ee-unavailable-tip", "ბოლომდე დაშიფვრის გადამოწმება ვერ მოხერხდა.\nდისტანციური მოწყობილობა შესაძლოა ჯერ კიდევ მზადდება. სცადეთ მოგვიანებით.\nთუ ეს კვლავ გაგრძელდება, სერვერი შესაძლოა არასანდო იყოს.\nმაინც გააგრძელებთ?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "રિલે કનેક્શન"),
("Secure Connection", "સુરક્ષિત કનેક્શન"),
("Insecure Connection", "અસુરક્ષિત કનેક્શન"),
("Continue", ""),
("Scale original", "મૂળ સ્કેલ"),
("Scale adaptive", "એડેપ્ટિવ સ્કેલ"),
("General", "સામાન્ય"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "ન્યૂનતમ કરેલા ટૂલબાર પર બતાવો"),
("All monitors", "બધા મોનિટર"),
("#{} monitor", "મોનિટર {}"),
("conn-e2ee-unavailable-tip", "એન્ડ-ટુ-એન્ડ એન્ક્રિપ્શન ચકાસી શકાયું નથી.\nરિમોટ ઉપકરણ હજી સેટ થઈ રહ્યું હોઈ શકે છે. પછીથી ફરી પ્રયાસ કરો.\nજો આ ચાલુ રહે, તો સર્વર અવિશ્વસનીય હોઈ શકે છે.\nશું તેમ છતાં ચાલુ રાખવું?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "חיבור באמצעות ממסר"),
("Secure Connection", "חיבור מאובטח"),
("Insecure Connection", "חיבור לא מאובטח"),
("Continue", ""),
("Scale original", "קנה מידה מקורי"),
("Scale adaptive", "קנה מידה מותאם"),
("General", "כללי"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "הצגה בסרגל הכלים הממוזער"),
("All monitors", "כל המסכים"),
("#{} monitor", "מסך {}"),
("conn-e2ee-unavailable-tip", "לא ניתן לאמת הצפנה מקצה לקצה.\nייתכן שהמכשיר המרוחק עדיין בתהליך הגדרה. נסה שוב מאוחר יותר.\nאם זה ממשיך לקרות, ייתכן שהשרת אינו מהימן.\nלהמשיך בכל זאת?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "रिले कनेक्शन"),
("Secure Connection", "सुरक्षित कनेक्शन"),
("Insecure Connection", "असुरक्षित कनेक्शन"),
("Continue", ""),
("Scale original", "मूल पैमाना"),
("Scale adaptive", "अनुकूली पैमाना"),
("General", "सामान्य"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "न्यूनतम किए गए टूलबार पर दिखाएं"),
("All monitors", "सभी मॉनिटर"),
("#{} monitor", "मॉनिटर {}"),
("conn-e2ee-unavailable-tip", "एंड-टू-एंड एन्क्रिप्शन सत्यापित नहीं किया जा सका।\nदूरस्थ डिवाइस अभी भी सेट अप हो रहा हो सकता है। बाद में फिर प्रयास करें।\nयदि यह समस्या बनी रहती है, तो सर्वर अविश्वसनीय हो सकता है।\nफिर भी जारी रखें?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Posredna veza"),
("Secure Connection", "Sigurna veza"),
("Insecure Connection", "Nesigurna veza"),
("Continue", ""),
("Scale original", "Skaliraj izvornik"),
("Scale adaptive", "Prilagođeno skaliranje"),
("General", "Općenito"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Prikaži na minimiziranoj alatnoj traci"),
("All monitors", "Svi monitori"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "End-to-end enkripcija nije mogla biti potvrđena.\nUdaljeni uređaj se možda još postavlja. Pokušajte ponovno kasnije.\nAko se to nastavi događati, poslužitelj možda nije pouzdan.\nIpak nastaviti?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Kapcsolódás továbbító-kiszolgálón keresztül"),
("Secure Connection", "Biztonságos kapcsolat"),
("Insecure Connection", "Nem biztonságos kapcsolat"),
("Continue", ""),
("Scale original", "Eredeti méretarány"),
("Scale adaptive", "Adaptív méretarány"),
("General", "Általános"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Megjelenítés a kis méretű eszköztáron"),
("All monitors", "Minden monitor"),
("#{} monitor", "{}. monitor"),
("conn-e2ee-unavailable-tip", "A végpontok közötti titkosítás nem volt ellenőrizhető.\nA távoli eszköz talán még beállítás alatt áll. Próbálja újra később.\nHa ez továbbra is előfordul, a szerver lehet, hogy nem megbízható.\nFolytatja így is?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Koneksi Relay"),
("Secure Connection", "Koneksi aman"),
("Insecure Connection", "Koneksi Tidak Aman"),
("Continue", ""),
("Scale original", "Skala asli"),
("Scale adaptive", "Skala adaptif"),
("General", "Umum"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Tampilkan di bilah alat yang diperkecil"),
("All monitors", "Semua monitor"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "Tidak dapat memverifikasi enkripsi ujung ke ujung.\nPerangkat jarak jauh mungkin masih disiapkan. Coba lagi nanti.\nJika ini terus terjadi, server mungkin tidak tepercaya.\nTetap lanjutkan?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Connessione relay"),
("Secure Connection", "Connessione sicura"),
("Insecure Connection", "Connessione non sicura"),
("Continue", ""),
("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"),
("General", "Generale"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Visualizza nella barra strumenti ridotta a icona"),
("All monitors", "Tutti gli schermi"),
("#{} monitor", "Schermo {}"),
("conn-e2ee-unavailable-tip", "Impossibile verificare la crittografia end-to-end.\nIl dispositivo remoto potrebbe essere ancora in configurazione. Riprova più tardi.\nSe il problema persiste, il server potrebbe non essere attendibile.\nContinuare comunque?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "中継接続"),
("Secure Connection", "安全な接続"),
("Insecure Connection", "安全でない接続"),
("Continue", ""),
("Scale original", "オリジナルのサイズ"),
("Scale adaptive", "ウィンドウに合わせる"),
("General", "一般"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "最小化したツールバーに表示"),
("All monitors", "すべてのディスプレイ"),
("#{} monitor", "ディスプレイ {}"),
("conn-e2ee-unavailable-tip", "エンドツーエンド暗号化を確認できませんでした。\nリモートデバイスはまだ準備中の可能性があります。後でもう一度お試しください。\nこの問題が続く場合、サーバーが信頼できない可能性があります。\nそれでも続行しますか?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "릴레이 연결"),
("Secure Connection", "보안 연결"),
("Insecure Connection", "보안되지 않은 연결"),
("Continue", ""),
("Scale original", "원본 크기 조정"),
("Scale adaptive", "크기 조정 가능"),
("General", "일반"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "최소화된 도구 모음에 표시"),
("All monitors", "모든 모니터"),
("#{} monitor", "#{} 모니터"),
("conn-e2ee-unavailable-tip", "종단 간 암호화를 확인할 수 없습니다.\n원격 장치가 아직 설정 중일 수 있습니다. 나중에 다시 시도하세요.\n이 문제가 계속되면 서버를 신뢰할 수 없을 수 있습니다.\n그래도 계속하시겠습니까?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Релай Қосылым"),
("Secure Connection", "Қауіпсіз Қосылым"),
("Insecure Connection", "Қатерлі Қосылым"),
("Continue", ""),
("Scale original", "Scale original"),
("Scale adaptive", "Scale adaptive"),
("General", "Жалпы"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Кішірейтілген құралдар тақтасында көрсету"),
("All monitors", "Барлық мониторлар"),
("#{} monitor", "Монитор {}"),
("conn-e2ee-unavailable-tip", "Ұштан-ұшқа шифрлауды тексеру мүмкін болмады.\nҚашықтағы құрылғы әлі бапталып жатқан болуы мүмкін. Кейінірек қайталап көріңіз.\nЕгер бұл қайталана берсе, сервер сенімсіз болуы мүмкін.\nСонда да жалғастыру керек пе?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Tarpinė jungtis"),
("Secure Connection", "Saugus ryšys"),
("Insecure Connection", "Nesaugus ryšys"),
("Continue", ""),
("Scale original", "Pakeisti originalų mastelį"),
("Scale adaptive", "Pritaikomas mastelis"),
("General", "Bendra"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Rodyti sumažintoje įrankių juostoje"),
("All monitors", "Visi monitoriai"),
("#{} monitor", "Monitorius {}"),
("conn-e2ee-unavailable-tip", "Nepavyko patikrinti galinio šifravimo.\nNuotolinis įrenginys galbūt vis dar nustatomas. Bandykite dar kartą vėliau.\nJei tai kartojasi, serveris gali būti nepatikimas.\nVis tiek tęsti?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Releja savienojums"),
("Secure Connection", "Drošs savienojums"),
("Insecure Connection", "Nedrošs savienojums"),
("Continue", ""),
("Scale original", "Mērogs oriģināls"),
("Scale adaptive", "Mērogs adaptīvs"),
("General", "Vispārīgi"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Rādīt minimizētajā rīkjoslā"),
("All monitors", "Visi monitori"),
("#{} monitor", "Monitors {}"),
("conn-e2ee-unavailable-tip", "Neizdevās pārbaudīt pilnīgu šifrēšanu.\nAttālā ierīce, iespējams, vēl tiek iestatīta. Mēģiniet vēlreiz vēlāk.\nJa tas turpinās, serveris var nebūt uzticams.\nVai tomēr turpināt?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "റിലേ കണക്ഷൻ"),
("Secure Connection", "സുരക്ഷിതമായ കണക്ഷൻ"),
("Insecure Connection", "സുരക്ഷിതമല്ലാത്ത കണക്ഷൻ"),
("Continue", ""),
("Scale original", "ഒറിജിനൽ വലിപ്പം"),
("Scale adaptive", "അഡാപ്റ്റീവ് വലിപ്പം"),
("General", "പൊതുവായവ"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "ചെറുതാക്കിയ ടൂൾബാറിൽ കാണിക്കുക"),
("All monitors", "എല്ലാ മോണിറ്ററുകളും"),
("#{} monitor", "മോണിറ്റർ {}"),
("conn-e2ee-unavailable-tip", "എൻഡ്-ടു-എൻഡ് എൻക്രിപ്ഷൻ പരിശോധിക്കാൻ കഴിഞ്ഞില്ല.\nദൂരസ്ഥ ഉപകരണം ഇനിയും സജ്ജീകരണത്തിലായിരിക്കാം. പിന്നീട് വീണ്ടും ശ്രമിക്കുക.\nഇത് തുടർന്നാൽ സർവർ വിശ്വസനീയമല്ലായിരിക്കാം.\nഎങ്കിലും തുടരണമോ?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Viderekoblet tilkobling"),
("Secure Connection", "Sikker tilkobling"),
("Insecure Connection", "Usikker tilkobling"),
("Continue", ""),
("Scale original", "Original skalering"),
("Scale adaptive", "Adaptiv skalering"),
("General", "Generelt"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Vis på den minimerte verktøylinjen"),
("All monitors", "Alle skjermer"),
("#{} monitor", "Skjerm {}"),
("conn-e2ee-unavailable-tip", "Ende-til-ende-kryptering kunne ikke verifiseres.\nDen eksterne enheten kan fortsatt være under oppsett. Prøv igjen senere.\nHvis dette fortsetter, kan serveren være upålitelig.\nFortsette likevel?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Relaisverbinding"),
("Secure Connection", "Beveiligde Verbinding"),
("Insecure Connection", "Onveilige Verbinding"),
("Continue", ""),
("Scale original", "Oorspronkelijk formaat"),
("Scale adaptive", "Automatisch schalen"),
("General", "Algemeen"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Weergeven op de geminimaliseerde werkbalk"),
("All monitors", "Alle monitoren"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "End-to-endversleuteling kon niet worden geverifieerd.\nHet externe apparaat wordt mogelijk nog ingesteld. Probeer het later opnieuw.\nAls dit blijft gebeuren, is de server mogelijk niet vertrouwd.\nToch doorgaan?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Połączenie przez bramkę"),
("Secure Connection", "Połączenie szyfrowane"),
("Insecure Connection", "Połączenie nieszyfrowane"),
("Continue", ""),
("Scale original", "Skalowanie oryginalne"),
("Scale adaptive", "Dopasuj do wyświetlacza"),
("General", "Ogólne"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Pokaż na zminimalizowanym pasku narzędzi"),
("All monitors", "Wszystkie ekrany"),
("#{} monitor", "Ekran {}"),
("conn-e2ee-unavailable-tip", "Nie można zweryfikować szyfrowania end-to-end.\nUrządzenie zdalne może nadal się konfigurować. Spróbuj ponownie później.\nJeśli problem będzie się powtarzał, serwer może być niezaufany.\nKontynuować mimo to?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Conexão de relé"),
("Secure Connection", "Conexão segura"),
("Insecure Connection", "Conexão insegura"),
("Continue", ""),
("Scale original", "Escala original"),
("Scale adaptive", "Escala adaptável"),
("General", "Geral"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
("All monitors", "Todos os monitores"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "Não foi possível verificar a encriptação de ponta a ponta.\nO dispositivo remoto ainda pode estar a ser configurado. Tente novamente mais tarde.\nSe isto continuar a acontecer, o servidor pode não ser fidedigno.\nContinuar mesmo assim?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Conexão via Relay"),
("Secure Connection", "Conexão Segura"),
("Insecure Connection", "Conexão Insegura"),
("Continue", ""),
("Scale original", "Escala original"),
("Scale adaptive", "Escala adaptada"),
("General", "Geral"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
("All monitors", "Todas as telas"),
("#{} monitor", "Tela {}"),
("conn-e2ee-unavailable-tip", "Não foi possível verificar a criptografia de ponta a ponta.\nO dispositivo remoto ainda pode estar sendo configurado. Tente novamente mais tarde.\nSe isso continuar acontecendo, o servidor pode não ser confiável.\nContinuar mesmo assim?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Conexiune prin retransmisie"),
("Secure Connection", "Conexiune securizată"),
("Insecure Connection", "Conexiune nesecurizată"),
("Continue", ""),
("Scale original", "Dimensiune originală"),
("Scale adaptive", "Scalare automată"),
("General", "General"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Afișează în bara de instrumente minimizată"),
("All monitors", "Toate monitoarele"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "Criptarea end-to-end nu a putut fi verificată.\nDispozitivul la distanță poate fi încă în curs de configurare. Încercați din nou mai târziu.\nDacă acest lucru continuă, serverul poate să nu fie de încredere.\nContinuați oricum?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Ретранслируемое подключение"),
("Secure Connection", "Безопасное подключение"),
("Insecure Connection", "Небезопасное подключение"),
("Continue", ""),
("Scale original", "Оригинальный масштаб"),
("Scale adaptive", "Адаптивный масштаб"),
("General", "Общие"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Показывать на свёрнутой панели инструментов"),
("All monitors", "Все мониторы"),
("#{} monitor", "Монитор {}"),
("conn-e2ee-unavailable-tip", "Не удалось проверить сквозное шифрование.\nУдаленное устройство, возможно, еще настраивается. Повторите попытку позже.\nЕсли это повторяется, сервер может быть ненадежным.\nВсе равно продолжить?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Connessione tramudada (relay)"),
("Secure Connection", "Connessione segura"),
("Insecure Connection", "Connessione non segura"),
("Continue", ""),
("Scale original", "Iscala originale"),
("Scale adaptive", "Iscala adativa"),
("General", "Generale"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Mustra in sa barra de aina minimizada"),
("All monitors", "Totu sos ischermos"),
("#{} monitor", "Ischermu {}"),
("conn-e2ee-unavailable-tip", "No est istadu possìbile verificare sa tzifratzione de punta a punta.\nSu dispositivu remotu podet èssere ancora in fase de configuratzione. Torra a proare prus a tardu.\nSi custu sighit a acontèssere, su server podet non èssere fidadu.\nBoles sighire comente siat?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Reléové pripojenie"),
("Secure Connection", "Zabezpečené pripojenie"),
("Insecure Connection", "Nezabezpečené pripojenie"),
("Continue", ""),
("Scale original", "Pôvodná mierka"),
("Scale adaptive", "Prispôsobivá mierka"),
("General", "Všeobecné"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Zobraziť na minimalizovanom paneli nástrojov"),
("All monitors", "Všetky monitory"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "Nepodarilo sa overiť koncové šifrovanie.\nVzdialené zariadenie sa možno stále nastavuje. Skúste to znova neskôr.\nAk sa to bude opakovať, server nemusí byť dôveryhodný.\nNapriek tomu pokračovať?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Posredovana povezava"),
("Secure Connection", "Zavarovana povezava"),
("Insecure Connection", "Nezavarovana povezava"),
("Continue", ""),
("Scale original", "Originalna velikost"),
("Scale adaptive", "Prilagojena velikost"),
("General", "Splošno"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Pokaži v pomanjšani orodni vrstici"),
("All monitors", "Vsi zasloni"),
("#{} monitor", "Zaslon {}"),
("conn-e2ee-unavailable-tip", "Šifriranja od konca do konca ni bilo mogoče preveriti.\nOddaljena naprava se morda še nastavlja. Poskusite znova pozneje.\nČe se to še naprej dogaja, strežnik morda ni zaupanja vreden.\nVseeno nadaljevati?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Lidhja rele"),
("Secure Connection", "Lidhje e sigurt"),
("Insecure Connection", "Lidhje e pasigurt"),
("Continue", ""),
("Scale original", "Shkalla origjinale"),
("Scale adaptive", " E përsjhtatshme në shkallë"),
("General", "Gjeneral"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Shfaq te shiriti i minimizuar i veglave"),
("All monitors", "Të gjithë monitorët"),
("#{} monitor", "Monitori {}"),
("conn-e2ee-unavailable-tip", "Enkriptimi nga skaji në skaj nuk mund të verifikohej.\nPajisja e largët mund të jetë ende duke u konfiguruar. Provoni përsëri më vonë.\nNëse kjo vazhdon të ndodhë, serveri mund të mos jetë i besueshëm.\nTë vazhdohet gjithsesi?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Posredna konekcija"),
("Secure Connection", "Bezbedna konekcija"),
("Insecure Connection", "Nebezbedna konekcija"),
("Continue", ""),
("Scale original", "Skaliraj original"),
("Scale adaptive", "Adaptivno skaliranje"),
("General", "Uopšteno"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Прикажи на умањеној траци са алаткама"),
("All monitors", "Svi monitori"),
("#{} monitor", "Monitor {}"),
("conn-e2ee-unavailable-tip", "Није могуће проверити енд-то-енд шифровање.\nУдаљени уређај се можда још подешава. Покушајте поново касније.\nАко се ово настави, сервер можда није поуздан.\nНаставити свеједно?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Relayanslutning"),
("Secure Connection", "Säker anslutning"),
("Insecure Connection", "Osäker anslutning"),
("Continue", ""),
("Scale original", "Skala orginal"),
("Scale adaptive", "Skala adaptivt"),
("General", "Generellt"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Visa i det minimerade verktygsfältet"),
("All monitors", "Alla skärmar"),
("#{} monitor", "Skärm {}"),
("conn-e2ee-unavailable-tip", "End-to-end-kryptering kunde inte verifieras.\nFjärrenheten kan fortfarande konfigureras. Försök igen senare.\nOm detta fortsätter kan servern vara opålitlig.\nFortsätta ändå?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "ரிலே இணைப்பு"),
("Secure Connection", "பாதுகாப்பான இணைப்பு"),
("Insecure Connection", "பாதுகாப்பற்ற இணைப்பு"),
("Continue", ""),
("Scale original", "அசல் அளவு"),
("Scale adaptive", "தகவமைப்பு அளவு"),
("General", "பொது"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "சிறிதாக்கப்பட்ட கருவிப்பட்டையில் காட்டு"),
("All monitors", "அனைத்து மானிட்டர்களும்"),
("#{} monitor", "மானிட்டர் {}"),
("conn-e2ee-unavailable-tip", "முடிவு-முதல்-முடிவு குறியாக்கத்தை சரிபார்க்க முடியவில்லை.\nதொலை சாதனம் இன்னும் அமைக்கப்பட்டுக் கொண்டிருக்கலாம். பின்னர் மீண்டும் முயற்சிக்கவும்.\nஇது தொடர்ந்து நடந்தால், சேவையகம் நம்பகமற்றதாக இருக்கலாம்.\nஎப்படியும் தொடரவா?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", ""),
("Secure Connection", ""),
("Insecure Connection", ""),
("Continue", ""),
("Scale original", ""),
("Scale adaptive", ""),
("General", ""),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", ""),
("All monitors", ""),
("#{} monitor", ""),
("conn-e2ee-unavailable-tip", ""),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "การเชื่อมต่อแบบ Relay "),
("Secure Connection", "การเชื่อมต่อที่ปลอดภัย"),
("Insecure Connection", "การเชื่อมต่อที่ไม่ปลอดภัย"),
("Continue", ""),
("Scale original", "ขนาดเดิม"),
("Scale adaptive", "ขนาดยืดหยุ่น"),
("General", "ทั่วไป"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "แสดงบนแถบเครื่องมือที่ย่อเล็กสุด"),
("All monitors", "จอภาพทั้งหมด"),
("#{} monitor", "จอภาพ {}"),
("conn-e2ee-unavailable-tip", "ไม่สามารถยืนยันการเข้ารหัสแบบต้นทางถึงปลายทางได้\nอุปกรณ์ระยะไกลอาจยังอยู่ระหว่างการตั้งค่า โปรดลองอีกครั้งภายหลัง\nหากปัญหานี้ยังเกิดขึ้นต่อไป เซิร์ฟเวอร์อาจไม่น่าเชื่อถือ\nต้องการดำเนินการต่อหรือไม่?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Aktarmalı Bağlantı"),
("Secure Connection", "Güvenli Bağlantı"),
("Insecure Connection", "Güvenli Olmayan Bağlantı"),
("Continue", ""),
("Scale original", "Orijinal ölçekte"),
("Scale adaptive", "Uyarlanabilir ölçekte"),
("General", "Genel"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Simge durumuna küçültülmüş araç çubuğunda göster"),
("All monitors", "Tüm monitörler"),
("#{} monitor", "Monitör {}"),
("conn-e2ee-unavailable-tip", "Uçtan uca şifreleme doğrulanamadı.\nUzak cihaz hâlâ kuruluyor olabilir. Daha sonra tekrar deneyin.\nBu sorun devam ederse sunucu güvenilir olmayabilir.\nYine de devam edilsin mi?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "中繼連線"),
("Secure Connection", "安全連線"),
("Insecure Connection", "非安全連線"),
("Continue", ""),
("Scale original", "原始尺寸"),
("Scale adaptive", "適應視窗"),
("General", "一般"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "在最小化工具列上顯示"),
("All monitors", "所有顯示器"),
("#{} monitor", "{}號顯示器"),
("conn-e2ee-unavailable-tip", "無法驗證端對端加密。\n遠端裝置可能仍在準備中,請稍後重試。\n如果此問題持續發生,伺服器可能不受信任。\n仍要繼續嗎?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Ретрансльоване підключення"),
("Secure Connection", "Безпечне підключення"),
("Insecure Connection", "Небезпечне підключення"),
("Continue", ""),
("Scale original", "Оригінальний масштаб"),
("Scale adaptive", "Адаптивний масштаб"),
("General", "Загальні"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Показувати на згорнутій панелі інструментів"),
("All monitors", "Усі монітори"),
("#{} monitor", "Монітор {}"),
("conn-e2ee-unavailable-tip", "Не вдалося перевірити наскрізне шифрування.\nВіддалений пристрій, можливо, ще налаштовується. Спробуйте пізніше.\nЯкщо це повторюється, сервер може бути ненадійним.\nПродовжити все одно?"),
].iter().cloned().collect();
}
+2
View File
@@ -332,6 +332,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Relay Connection", "Kết nối chuyển tiếp"),
("Secure Connection", "Kết nối bảo mật"),
("Insecure Connection", "Kết nối không bảo mật"),
("Continue", ""),
("Scale original", "Tỷ lệ gốc"),
("Scale adaptive", "Tỷ lệ thích ứng"),
("General", "Chung"),
@@ -763,5 +764,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Show on the minimized toolbar", "Hiển thị trên thanh công cụ thu nhỏ"),
("All monitors", "Tất cả màn hình"),
("#{} monitor", "Màn hình {}"),
("conn-e2ee-unavailable-tip", "Không thể xác minh mã hóa đầu cuối.\nThiết bị từ xa có thể vẫn đang được thiết lập. Hãy thử lại sau.\nNếu điều này tiếp tục xảy ra, máy chủ có thể không đáng tin cậy.\nVẫn tiếp tục?"),
].iter().cloned().collect();
}
+1 -4
View File
@@ -24,7 +24,6 @@ pub mod ipc;
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
pub mod ui;
@@ -38,11 +37,9 @@ pub mod flutter;
pub mod flutter_ffi;
use common::*;
mod auth_2fa;
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(not(target_os = "ios"))]
mod clipboard;
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub mod core_main;
mod custom_server;
mod lang;
-71
View File
@@ -19,7 +19,6 @@ fn main() {
#[cfg(not(any(
target_os = "android",
target_os = "ios",
feature = "cli",
feature = "flutter"
)))]
fn main() {
@@ -32,73 +31,3 @@ fn main() {
}
common::global_clean();
}
#[cfg(feature = "cli")]
fn main() {
if !common::global_init() {
return;
}
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]'
-c, --connect=[REMOTE_ID] 'test only'
-k, --key=[KEY] ''
-s, --server=[] 'Start server'",
);
let matches = App::new("rustdesk")
.version(crate::VERSION)
.author("Purslane Tech Pte. Ltd.<info@rustdesk.com>")
.about("RustDesk command line tool")
.args_from_usage(&args)
.get_matches();
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();
if options.len() < 3 {
log::error!("Wrong port-forward options");
return;
}
let mut port = 0;
if let Ok(v) = options[1].parse::<i32>() {
port = v;
} else {
log::error!("Wrong local-port");
return;
}
let mut remote_port = 0;
if let Ok(v) = options[2].parse::<i32>() {
remote_port = v;
} else {
log::error!("Wrong remote-port");
return;
}
let mut remote_host = "localhost".to_owned();
if options.len() > 3 {
remote_host = options[3].clone();
}
common::test_rendezvous_server();
common::test_nat_type();
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,
);
} else if let Some(p) = matches.value_of("connect") {
common::test_rendezvous_server();
common::test_nat_type();
let key = matches.value_of("key").unwrap_or("").to_owned();
let token = LocalConfig::get_option("access_token");
cli::connect_test(p, key, token);
} else if let Some(p) = matches.value_of("server") {
log::info!("id={}", hbb_common::config::Config::get_id());
crate::start_server(true, false);
}
common::global_clean();
}
+12 -1
View File
@@ -69,7 +69,8 @@ pub async fn listen(
let id = id.clone();
let password = password.clone();
let mut forward = Framed::new(forward, BytesCodec::new());
match connect_and_login(&id, &password, &mut ui_receiver, interface.clone(), &mut forward, key, token, is_rdp).await {
let mut close_port_forward = false;
match connect_and_login(&id, &password, &mut ui_receiver, interface.clone(), &mut forward, key, token, is_rdp, &mut close_port_forward).await {
Ok(Some(stream)) => {
let interface = interface.clone();
tokio::spawn(async move {
@@ -79,6 +80,9 @@ pub async fn listen(
log::info!("connection from {:?} closed", addr);
});
}
_ if close_port_forward => {
break;
}
Err(err) => {
interface.on_establish_connection_error(err.to_string());
}
@@ -111,6 +115,7 @@ async fn connect_and_login(
key: &str,
token: &str,
is_rdp: bool,
close_port_forward: &mut bool,
) -> ResultType<Option<Stream>> {
let conn_type = if is_rdp {
ConnType::RDP
@@ -120,6 +125,12 @@ async fn connect_and_login(
let ((mut stream, direct, _pk, _kcp, _stream_type), (feedback, rendezvous_server)) =
Client::start(id, key, token, conn_type, interface.clone()).await?;
interface.update_direct(Some(direct));
if !stream.is_secured() && !crate::common::is_direct_ip_access(id) {
if !confirm_insecure_connection(&interface, ui_receiver).await {
*close_port_forward = true;
return Ok(None);
}
}
let mut buffer = Vec::new();
let mut received = false;
+801 -21
View File
@@ -240,6 +240,18 @@ pub enum AuthConnType {
Terminal,
}
impl AuthConnType {
fn as_str(self) -> &'static str {
match self {
AuthConnType::Remote => "remote",
AuthConnType::FileTransfer => "file_transfer",
AuthConnType::PortForward => "port_forward",
AuthConnType::ViewCamera => "view_camera",
AuthConnType::Terminal => "terminal",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i64)]
enum ConnAuditPrimaryAuth {
@@ -384,6 +396,8 @@ pub struct Connection {
cm_read_job_ids: HashSet<i32>,
terminal_service_id: String,
terminal_persistent: bool,
// Used to avoid too many repeated scope violation warnings.
scope_violation_messages: HashSet<&'static str>,
// The user token must be set when terminal is enabled.
// 0 indicates SYSTEM user
// other values indicate current user
@@ -571,6 +585,7 @@ impl Connection {
cm_read_job_ids: HashSet::new(),
terminal_service_id: "".to_owned(),
terminal_persistent: false,
scope_violation_messages: HashSet::new(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
terminal_user_token: None,
terminal_generic_service: None,
@@ -1501,6 +1516,23 @@ impl Connection {
});
}
fn post_session_scope_violation_alarm(&self, message: &'static str) {
let conn_type = self
.authed_conn_type()
.map(AuthConnType::as_str)
.unwrap_or("unknown");
self.post_alarm_audit(
AlarmAuditType::SessionScopeViolation,
json!({
"id": self.lr.my_id.clone(),
"name": self.lr.my_name.clone(),
"ip": &self.ip,
"conn_type": conn_type,
"message": message,
}),
);
}
#[inline]
async fn post_audit_async(url: String, v: Value) -> ResultType<String> {
crate::post_request(url, v.to_string(), "").await
@@ -1897,10 +1929,9 @@ impl Connection {
let mut msg_out = Message::new();
msg_out.set_login_response(res);
self.send(msg_out).await;
if let Some(o) = self.options_in_login.take() {
self.update_options(&o).await;
}
self.update_scoped_login_options().await;
if let Some((dir, show_hidden)) = self.file_transfer.clone() {
self.keyboard = false;
let dir = if !dir.is_empty() && std::path::Path::new(&dir).is_dir() {
&dir
} else {
@@ -2407,6 +2438,14 @@ impl Connection {
)
}
fn reset_session_scope_for_login(&mut self) {
self.file_transfer = None;
self.view_camera = false;
self.terminal = false;
self.port_forward_address.clear();
self.terminal_persistent = false;
}
async fn handle_login_request_without_validation(&mut self, lr: &LoginRequest) {
self.lr = lr.clone();
self.peer_argb = crate::str2color(&format!("{}{}", &lr.my_id, &lr.my_platform), 0xff);
@@ -2474,12 +2513,21 @@ impl Connection {
return false;
}
}
if self.authorized {
if matches!(msg.union.as_ref(), Some(message::Union::LoginRequest(_))) {
return true;
}
if let Some(message) = self.authorized_scope_violation(&msg) {
return self.handle_authorized_scope_violation(message).await;
}
}
// After handling CloseReason messages, proceed to process other message types
if let Some(message::Union::LoginRequest(lr)) = msg.union {
self.handle_login_request_without_validation(&lr).await;
if self.authorized {
return true;
}
self.reset_session_scope_for_login();
match lr.union {
Some(login_request::Union::FileTransfer(ft)) => {
if !Self::permission(
@@ -2539,9 +2587,7 @@ impl Connection {
}
}
if !hbb_common::is_ip_str(&lr.username)
&& !hbb_common::is_domain_port_str(&lr.username)
&& lr.username != Config::get_id()
if !crate::common::is_direct_ip_access(&lr.username) && lr.username != Config::get_id()
{
self.send_login_error(crate::client::LOGIN_MSG_OFFLINE)
.await;
@@ -2989,7 +3035,7 @@ impl Connection {
self.update_auto_disconnect_timer();
}
Some(message::Union::Clipboard(cb)) => {
if self.clipboard_enabled() {
if self.should_handle_text_clipboard_message() && self.clipboard_enabled() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(vec![cb], ClipboardSide::Host);
// ios as the controlled side is actually not supported for now.
@@ -3017,7 +3063,7 @@ impl Connection {
}
}
Some(message::Union::MultiClipboards(_mcb)) => {
if self.clipboard_enabled() {
if self.should_handle_text_clipboard_message() && self.clipboard_enabled() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(_mcb.clipboards, ClipboardSide::Host);
#[cfg(target_os = "android")]
@@ -3404,10 +3450,14 @@ impl Connection {
}
#[cfg(windows)]
Some(misc::Union::ToggleVirtualDisplay(t)) => {
self.toggle_virtual_display(t).await;
if !self.view_camera {
self.toggle_virtual_display(t).await;
}
}
Some(misc::Union::TogglePrivacyMode(t)) => {
self.toggle_privacy_mode(t).await;
if !self.view_camera {
self.toggle_privacy_mode(t).await;
}
}
Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
@@ -3415,19 +3465,27 @@ impl Connection {
self.update_auto_disconnect_timer();
}
Some(misc::Union::Option(o)) => {
self.update_options(&o).await;
if self.authed_conn_type() == Some(AuthConnType::Remote) {
self.update_options(&o).await;
} else if let Some(option) = self.scoped_update_option_message(&o) {
self.update_options(&option).await;
}
}
Some(misc::Union::RefreshVideo(r)) => {
if r {
// Refresh all videos.
// Compatibility with old versions and sciter(remote).
self.refresh_video_display(None);
if self.should_handle_render_broadcast_message() {
if r {
// Refresh all videos.
// Compatibility with old versions and sciter(remote).
self.refresh_video_display(None);
}
self.update_auto_disconnect_timer();
}
self.update_auto_disconnect_timer();
}
Some(misc::Union::RefreshVideoDisplay(display)) => {
self.refresh_video_display(Some(display as usize));
self.update_auto_disconnect_timer();
if self.should_handle_render_broadcast_message() {
self.refresh_video_display(Some(display as usize));
self.update_auto_disconnect_timer();
}
}
Some(misc::Union::VideoReceived(_)) => {
video_service::notify_video_frame_fetched_by_conn_id(
@@ -3495,10 +3553,16 @@ impl Connection {
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::ChangeResolution(r)) => self.change_resolution(None, &r),
Some(misc::Union::ChangeResolution(r)) => {
if !self.view_camera {
self.change_resolution(None, &r);
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(misc::Union::ChangeDisplayResolution(dr)) => {
self.change_resolution(Some(dr.display as _), &dr.resolution)
if !self.view_camera {
self.change_resolution(Some(dr.display as _), &dr.resolution);
}
}
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -3584,6 +3648,7 @@ impl Connection {
Some(message::Union::ScreenshotRequest(request)) => {
if let Some(tx) = self.inner.tx.clone() {
crate::video_service::set_take_screenshot(
self.video_source(),
request.display as _,
request.sid.clone(),
tx,
@@ -4080,7 +4145,7 @@ impl Connection {
self.switch_display_to(display_idx, server.clone());
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if s.width != 0 && s.height != 0 {
if !self.view_camera && s.width != 0 && s.height != 0 {
self.change_resolution(
None,
&Resolution {
@@ -5204,6 +5269,398 @@ impl Connection {
false
}
fn should_handle_render_broadcast_message(&self) -> bool {
matches!(
self.authed_conn_type(),
Some(AuthConnType::Remote | AuthConnType::ViewCamera)
)
}
fn should_handle_text_clipboard_message(&self) -> bool {
matches!(self.authed_conn_type(), Some(AuthConnType::Remote))
}
fn scoped_update_option_message(&self, option: &OptionMessage) -> Option<OptionMessage> {
match self.authed_conn_type() {
Some(AuthConnType::ViewCamera) => Self::scoped_view_camera_option(option).0,
Some(AuthConnType::Terminal) => Self::scoped_terminal_login_option(option).0,
Some(AuthConnType::Remote | AuthConnType::FileTransfer | AuthConnType::PortForward)
| None => None,
}
}
fn authed_conn_type(&self) -> Option<AuthConnType> {
self.authed_conn_id.as_ref().map(|id| id.conn_type())
}
async fn handle_authorized_scope_violation(&mut self, message: &'static str) -> bool {
let conn_type = self
.authed_conn_type()
.map(AuthConnType::as_str)
.unwrap_or("unknown");
let is_first = self.scope_violation_messages.insert(message);
if is_first {
log::warn!(
"Received out-of-scope message in {} session: {}",
conn_type,
message
);
} else {
log::debug!(
"Received repeated out-of-scope message in {} session: {}",
conn_type,
message
);
}
if is_first && Config::get_bool_option(keys::OPTION_ALLOW_SCOPE_VIOLATION_ALARM) {
self.post_session_scope_violation_alarm(message);
}
if Config::get_bool_option(keys::OPTION_ALLOW_SCOPE_VIOLATION_CLOSE) {
self.send_close_reason_no_retry("Connection not allowed")
.await;
self.on_close("Session scope violation", true).await;
return false;
}
true
}
fn authorized_scope_violation(&self, msg: &Message) -> Option<&'static str> {
let Some(conn_type) = self.authed_conn_type() else {
return (!Self::is_connection_housekeeping_message(msg)).then_some("session.auth_type");
};
Self::authorized_message_scope_violation(conn_type, msg)
}
async fn update_scoped_login_options(&mut self) {
let Some(option) = self.options_in_login.take() else {
return;
};
let Some(conn_type) = self.authed_conn_type() else {
// Unreachable, but just in case, we drop the options if the connection type is unknown.
log::warn!(
"Dropping scoped login options because authorized connection type is unknown"
);
return;
};
let (scoped, violation) = Self::scoped_login_option(conn_type, &option);
if let Some(message) = violation {
log::debug!(
"Filtering {} session login options outside scope: {}",
conn_type.as_str(),
message
);
}
if let Some(option) = scoped {
self.update_options(&option).await;
}
}
fn scoped_login_option(
conn_type: AuthConnType,
option: &OptionMessage,
) -> (Option<OptionMessage>, Option<&'static str>) {
match conn_type {
AuthConnType::Remote => (Some(option.clone()), None),
AuthConnType::ViewCamera => Self::scoped_view_camera_option(option),
AuthConnType::Terminal => Self::scoped_terminal_login_option(option),
AuthConnType::FileTransfer | AuthConnType::PortForward => {
let violation = Self::option_has_any_field(option).then_some("login.option");
(None, violation)
}
}
}
fn scoped_terminal_login_option(
option: &OptionMessage,
) -> (Option<OptionMessage>, Option<&'static str>) {
let mut scoped = OptionMessage::new();
let mut violation = false;
match option.terminal_persistent.enum_value() {
Ok(value) => scoped.terminal_persistent = value.into(),
Err(_) => violation = true,
}
if Self::option_has_non_terminal_login_field(option) {
violation = true;
}
let scoped = Self::option_has_any_field(&scoped).then_some(scoped);
(scoped, violation.then_some("login.option"))
}
fn authorized_message_scope_violation(
conn_type: AuthConnType,
msg: &Message,
) -> Option<&'static str> {
if Self::is_connection_housekeeping_message(msg) {
return None;
}
// Legacy clients can broadcast render-refresh messages to all opened sessions.
// Clipboard messages may also be broadcast to FileTransfer/Terminal sessions while
// the client still considers text clipboard sync required, and handlers ignore them.
let noop_compat = match conn_type {
AuthConnType::FileTransfer | AuthConnType::Terminal => {
Self::is_render_broadcast_noop_compat_message(msg)
|| Self::is_text_clipboard_noop_compat_message(msg)
}
AuthConnType::PortForward => Self::is_render_broadcast_noop_compat_message(msg),
AuthConnType::ViewCamera => Self::is_text_clipboard_noop_compat_message(msg),
_ => false,
};
if noop_compat {
return None;
}
let allowed = match conn_type {
AuthConnType::Remote => true,
AuthConnType::FileTransfer => Self::is_file_transfer_scoped_message(msg),
AuthConnType::PortForward => false,
AuthConnType::ViewCamera => Self::is_view_camera_scoped_message(msg),
AuthConnType::Terminal => Self::is_terminal_scoped_message(msg),
};
(!allowed).then(|| Self::message_family(msg))
}
fn is_render_broadcast_noop_compat_message(msg: &Message) -> bool {
let Some(message::Union::Misc(misc)) = msg.union.as_ref() else {
return false;
};
match misc.union.as_ref() {
Some(misc::Union::RefreshVideo(_)) | Some(misc::Union::RefreshVideoDisplay(_)) => true,
Some(misc::Union::Option(option)) => Self::is_supported_decoding_only_option(option),
_ => false,
}
}
fn is_text_clipboard_noop_compat_message(msg: &Message) -> bool {
matches!(
msg.union.as_ref(),
Some(message::Union::Clipboard(_)) | Some(message::Union::MultiClipboards(_))
)
}
fn is_supported_decoding_only_option(option: &OptionMessage) -> bool {
option.supported_decoding.is_some()
&& option.image_quality.enum_value() == Ok(ImageQuality::NotSet)
&& option.custom_image_quality == 0
&& option.custom_fps == 0
&& Self::is_bool_option_not_set(option.lock_after_session_end)
&& Self::is_bool_option_not_set(option.show_remote_cursor)
&& Self::is_bool_option_not_set(option.privacy_mode)
&& Self::is_bool_option_not_set(option.block_input)
&& Self::is_bool_option_not_set(option.disable_audio)
&& Self::is_bool_option_not_set(option.disable_clipboard)
&& Self::is_bool_option_not_set(option.enable_file_transfer)
&& Self::is_bool_option_not_set(option.disable_keyboard)
&& Self::is_bool_option_not_set(option.follow_remote_cursor)
&& Self::is_bool_option_not_set(option.follow_remote_window)
&& Self::is_bool_option_not_set(option.disable_camera)
&& Self::is_bool_option_not_set(option.terminal_persistent)
&& Self::is_bool_option_not_set(option.show_my_cursor)
}
fn is_connection_housekeeping_message(msg: &Message) -> bool {
match msg.union.as_ref() {
Some(message::Union::LoginRequest(_)) => true,
Some(message::Union::TestDelay(_)) => true,
Some(message::Union::Misc(misc)) => {
matches!(misc.union.as_ref(), Some(misc::Union::CloseReason(_)))
}
_ => false,
}
}
fn is_file_transfer_scoped_message(msg: &Message) -> bool {
match msg.union.as_ref() {
Some(message::Union::FileAction(_)) | Some(message::Union::FileResponse(_)) => true,
Some(message::Union::Misc(misc)) => Self::is_file_transfer_scoped_misc(misc),
_ => false,
}
}
fn is_file_transfer_scoped_misc(misc: &Misc) -> bool {
#[cfg(windows)]
if matches!(misc.union.as_ref(), Some(misc::Union::SelectedSid(_))) {
return true;
}
#[cfg(not(windows))]
let _ = misc;
false
}
fn is_terminal_scoped_message(msg: &Message) -> bool {
match msg.union.as_ref() {
Some(message::Union::TerminalAction(_)) => true,
Some(message::Union::Misc(misc)) => Self::is_terminal_scoped_misc(misc),
_ => false,
}
}
fn is_terminal_scoped_misc(misc: &Misc) -> bool {
match misc.union.as_ref() {
Some(misc::Union::ChatMessage(_)) => true,
Some(misc::Union::Option(option)) => Self::is_terminal_scoped_option(option),
_ => false,
}
}
fn is_terminal_scoped_option(option: &OptionMessage) -> bool {
Self::scoped_terminal_login_option(option).1.is_none()
}
fn is_view_camera_scoped_message(msg: &Message) -> bool {
match msg.union.as_ref() {
Some(message::Union::ScreenshotRequest(_)) => true,
Some(message::Union::Misc(misc)) => Self::is_view_camera_scoped_misc(misc),
// Legacy clients may send auto-login input during view-camera connect.
// The handlers intentionally ignore these messages for view-camera sessions.
Some(message::Union::MouseEvent(_))
| Some(message::Union::PointerDeviceEvent(_))
| Some(message::Union::KeyEvent(_)) => true,
Some(message::Union::AudioFrame(_))
| Some(message::Union::VoiceCallRequest(_))
| Some(message::Union::VoiceCallResponse(_)) => true,
_ => false,
}
}
fn is_view_camera_scoped_misc(misc: &Misc) -> bool {
match misc.union.as_ref() {
Some(misc::Union::SwitchDisplay(_))
| Some(misc::Union::CaptureDisplays(_))
| Some(misc::Union::RefreshVideo(_))
| Some(misc::Union::RefreshVideoDisplay(_))
| Some(misc::Union::VideoReceived(_))
| Some(misc::Union::ChatMessage(_))
| Some(misc::Union::AudioFormat(_))
| Some(misc::Union::ClientRecordStatus(_))
// Though these messages are not expected in normal view-camera sessions,
// keep them allowed to avoid breaking existing clients that may send them.
| Some(misc::Union::MessageQuery(_))
| Some(misc::Union::TogglePrivacyMode(_))
| Some(misc::Union::ToggleVirtualDisplay(_))
| Some(misc::Union::ChangeResolution(_))
| Some(misc::Union::ChangeDisplayResolution(_)) => true,
Some(misc::Union::Option(option)) => Self::is_view_camera_scoped_option(option),
#[cfg(windows)]
Some(misc::Union::SelectedSid(_)) => true,
_ => false,
}
}
fn is_view_camera_scoped_option(option: &OptionMessage) -> bool {
Self::scoped_view_camera_option(option).1.is_none()
}
// Keep these OptionMessage field lists in sync with message.proto and update_options().
// New fields must be classified here before limited session types can receive them.
fn scoped_view_camera_option(
option: &OptionMessage,
) -> (Option<OptionMessage>, Option<&'static str>) {
let mut scoped = OptionMessage::new();
let mut violation = false;
if option.image_quality.enum_value().is_ok() {
scoped.image_quality = option.image_quality;
}
if option.custom_image_quality >= 0 {
scoped.custom_image_quality = option.custom_image_quality;
}
if option.custom_fps >= 0 {
scoped.custom_fps = option.custom_fps;
}
scoped.supported_decoding = option.supported_decoding.clone();
if let Ok(value) = option.disable_audio.enum_value() {
scoped.disable_audio = value.into();
}
if Self::option_has_non_view_camera_login_field(option) {
violation = true;
}
let scoped = Self::option_has_any_field(&scoped).then_some(scoped);
(scoped, violation.then_some("login.option"))
}
fn option_has_non_view_camera_login_field(option: &OptionMessage) -> bool {
!(Self::is_bool_option_not_set(option.lock_after_session_end)
&& Self::is_bool_option_not_set(option.show_remote_cursor)
&& Self::is_bool_option_not_set(option.privacy_mode)
&& Self::is_bool_option_not_set(option.block_input)
&& Self::is_bool_option_not_set(option.disable_clipboard)
&& Self::is_bool_option_not_set(option.enable_file_transfer)
&& Self::is_bool_option_not_set(option.disable_keyboard)
&& Self::is_bool_option_not_set(option.follow_remote_cursor)
&& Self::is_bool_option_not_set(option.follow_remote_window)
&& Self::is_bool_option_not_set(option.disable_camera)
&& Self::is_bool_option_not_set(option.terminal_persistent)
&& Self::is_bool_option_not_set(option.show_my_cursor))
}
fn option_has_non_terminal_login_field(option: &OptionMessage) -> bool {
option.image_quality.enum_value() != Ok(ImageQuality::NotSet)
|| option.custom_image_quality != 0
|| option.custom_fps != 0
|| option.supported_decoding.is_some()
|| !Self::is_bool_option_not_set(option.lock_after_session_end)
|| !Self::is_bool_option_not_set(option.show_remote_cursor)
|| !Self::is_bool_option_not_set(option.privacy_mode)
|| !Self::is_bool_option_not_set(option.block_input)
|| !Self::is_bool_option_not_set(option.disable_audio)
|| !Self::is_bool_option_not_set(option.disable_clipboard)
|| !Self::is_bool_option_not_set(option.enable_file_transfer)
|| !Self::is_bool_option_not_set(option.disable_keyboard)
|| !Self::is_bool_option_not_set(option.follow_remote_cursor)
|| !Self::is_bool_option_not_set(option.follow_remote_window)
|| !Self::is_bool_option_not_set(option.disable_camera)
|| !Self::is_bool_option_not_set(option.show_my_cursor)
}
fn option_has_any_field(option: &OptionMessage) -> bool {
Self::option_has_non_terminal_login_field(option)
|| !Self::is_bool_option_not_set(option.terminal_persistent)
}
fn is_bool_option_not_set(option: hbb_common::protobuf::EnumOrUnknown<BoolOption>) -> bool {
option.enum_value() == Ok(BoolOption::NotSet)
}
fn message_family(msg: &Message) -> &'static str {
match msg.union.as_ref() {
Some(message::Union::MouseEvent(_)) => "mouse_event",
Some(message::Union::AudioFrame(_)) => "audio_frame",
Some(message::Union::PointerDeviceEvent(_)) => "pointer_device_event",
Some(message::Union::KeyEvent(_)) => "key_event",
Some(message::Union::Clipboard(_)) => "clipboard",
Some(message::Union::FileAction(_)) => "file_action",
Some(message::Union::FileResponse(_)) => "file_response",
Some(message::Union::VoiceCallRequest(_)) => "voice_call_request",
Some(message::Union::VoiceCallResponse(_)) => "voice_call_response",
Some(message::Union::MultiClipboards(_)) => "multi_clipboards",
Some(message::Union::ScreenshotRequest(_)) => "screenshot_request",
Some(message::Union::ScreenshotResponse(_)) => "screenshot_response",
Some(message::Union::TerminalAction(_)) => "terminal_action",
Some(message::Union::TerminalResponse(_)) => "terminal_response",
Some(message::Union::Misc(misc)) => Self::misc_message_family(misc),
Some(_) => "message.other",
None => "empty",
}
}
fn misc_message_family(misc: &Misc) -> &'static str {
match misc.union.as_ref() {
Some(misc::Union::ChatMessage(_)) => "misc.chat_message",
Some(misc::Union::SwitchDisplay(_)) => "misc.switch_display",
Some(misc::Union::Option(_)) => "misc.option",
Some(misc::Union::AudioFormat(_)) => "misc.audio_format",
Some(misc::Union::CaptureDisplays(_)) => "misc.capture_displays",
Some(misc::Union::ClientRecordStatus(_)) => "misc.client_record_status",
Some(misc::Union::TogglePrivacyMode(_)) => "misc.toggle_privacy_mode",
Some(misc::Union::ToggleVirtualDisplay(_)) => "misc.toggle_virtual_display",
Some(misc::Union::SelectedSid(_)) => "misc.selected_sid",
Some(misc::Union::ChangeResolution(_)) => "misc.change_resolution",
Some(misc::Union::ChangeDisplayResolution(_)) => "misc.change_display_resolution",
Some(misc::Union::MessageQuery(_)) => "misc.message_query",
Some(misc::Union::FollowCurrentDisplay(_)) => "misc.follow_current_display",
Some(_) => "misc.other",
None => "misc.empty",
}
}
#[cfg(feature = "unix-file-copy-paste")]
async fn handle_file_clip(&mut self, clip: clipboard::ClipboardFile) {
let is_stopping_allowed = clip.is_stopping_allowed();
@@ -5599,6 +6056,7 @@ pub enum AlarmAuditType {
ExceedIPv6PrefixAttempts = 6,
TerminalOsLoginBackoff = 7,
TerminalOsLoginConcurrency = 8,
SessionScopeViolation = 9,
}
pub enum FileAuditType {
@@ -6216,6 +6674,7 @@ mod raii {
}
}
#[cfg(test)]
mod test {
#[allow(unused)]
use super::*;
@@ -6258,4 +6717,325 @@ mod test {
assert!(Ipv6Addr::from_str("127.0.0.1").is_err());
assert!(Ipv6Addr::from_str("0").is_err());
}
fn msg(set: impl FnOnce(&mut Message)) -> Message {
let mut msg = Message::new();
set(&mut msg);
msg
}
fn misc_msg(set: impl FnOnce(&mut Misc)) -> Message {
msg(|msg| {
let mut misc = Misc::new();
set(&mut misc);
msg.set_misc(misc);
})
}
fn option_msg(set: impl FnOnce(&mut OptionMessage)) -> Message {
misc_msg(|misc| {
let mut option = OptionMessage::new();
set(&mut option);
misc.set_option(option);
})
}
fn set_supported_decoding(option: &mut OptionMessage) {
option.supported_decoding = hbb_common::protobuf::MessageField::some(Default::default());
}
fn assert_scopes(
conn_type: AuthConnType,
cases: impl IntoIterator<Item = (Message, Option<&'static str>)>,
) {
for (msg, expected) in cases {
assert_eq!(
Connection::authorized_message_scope_violation(conn_type, &msg),
expected
);
}
}
#[test]
fn session_scope_allows_only_messages_for_authenticated_session_type() {
let cases = [
(
AuthConnType::FileTransfer,
vec![
(msg(|m| m.set_file_action(FileAction::new())), None),
(msg(|m| m.set_file_response(FileResponse::new())), None),
(msg(|m| m.set_login_request(LoginRequest::new())), None),
(
msg(|m| m.set_screenshot_request(ScreenshotRequest::new())),
Some("screenshot_request"),
),
(
misc_msg(|m| m.set_capture_displays(CaptureDisplays::new())),
Some("misc.capture_displays"),
),
(msg(|m| m.set_clipboard(Clipboard::new())), None),
(
msg(|m| m.set_multi_clipboards(MultiClipboards::new())),
None,
),
(misc_msg(|m| m.set_refresh_video(true)), None),
(misc_msg(|m| m.set_refresh_video_display(0)), None),
(
option_msg(|o| {
o.supported_decoding =
hbb_common::protobuf::MessageField::some(Default::default())
}),
None,
),
(
option_msg(|o| {
o.supported_decoding =
hbb_common::protobuf::MessageField::some(Default::default());
o.disable_audio = BoolOption::Yes.into();
}),
Some("misc.option"),
),
],
),
(
AuthConnType::Terminal,
vec![
(msg(|m| m.set_terminal_action(TerminalAction::new())), None),
(
option_msg(|o| o.terminal_persistent = BoolOption::Yes.into()),
None,
),
(
msg(|m| m.set_screenshot_request(ScreenshotRequest::new())),
Some("screenshot_request"),
),
(
msg(|m| m.set_file_action(FileAction::new())),
Some("file_action"),
),
(
misc_msg(|m| m.set_toggle_privacy_mode(TogglePrivacyMode::new())),
Some("misc.toggle_privacy_mode"),
),
(misc_msg(|m| m.set_chat_message(ChatMessage::new())), None),
(msg(|m| m.set_clipboard(Clipboard::new())), None),
(
msg(|m| m.set_multi_clipboards(MultiClipboards::new())),
None,
),
(
misc_msg(|m| m.set_toggle_virtual_display(ToggleVirtualDisplay::new())),
Some("misc.toggle_virtual_display"),
),
(
misc_msg(|m| m.set_change_resolution(Resolution::new())),
Some("misc.change_resolution"),
),
(
misc_msg(|m| m.set_change_display_resolution(DisplayResolution::new())),
Some("misc.change_display_resolution"),
),
(misc_msg(|m| m.set_refresh_video(true)), None),
(misc_msg(|m| m.set_refresh_video_display(0)), None),
(
option_msg(|o| {
o.supported_decoding =
hbb_common::protobuf::MessageField::some(Default::default())
}),
None,
),
(
option_msg(|o| {
o.supported_decoding =
hbb_common::protobuf::MessageField::some(Default::default());
o.disable_audio = BoolOption::Yes.into();
}),
Some("misc.option"),
),
],
),
(
AuthConnType::ViewCamera,
vec![
(
misc_msg(|m| m.set_switch_display(SwitchDisplay::new())),
None,
),
(misc_msg(|m| m.set_chat_message(ChatMessage::new())), None),
(
msg(|m| m.set_voice_call_request(VoiceCallRequest::new())),
None,
),
(msg(|m| m.set_audio_frame(AudioFrame::new())), None),
(
option_msg(|o| o.image_quality = ImageQuality::Balanced.into()),
None,
),
(
misc_msg(|m| m.set_toggle_privacy_mode(TogglePrivacyMode::new())),
None,
),
(
misc_msg(|m| m.set_toggle_virtual_display(ToggleVirtualDisplay::new())),
None,
),
(
misc_msg(|m| m.set_change_resolution(Resolution::new())),
None,
),
(
misc_msg(|m| m.set_change_display_resolution(DisplayResolution::new())),
None,
),
(msg(|m| m.set_mouse_event(MouseEvent::new())), None),
(
msg(|m| m.set_pointer_device_event(PointerDeviceEvent::new())),
None,
),
(msg(|m| m.set_key_event(KeyEvent::new())), None),
(misc_msg(|m| m.set_client_record_status(true)), None),
(
msg(|m| m.set_file_response(FileResponse::new())),
Some("file_response"),
),
(
msg(|m| m.set_terminal_action(TerminalAction::new())),
Some("terminal_action"),
),
],
),
(
AuthConnType::Remote,
vec![
(
msg(|m| m.set_screenshot_request(ScreenshotRequest::new())),
None,
),
(msg(|m| m.set_terminal_action(TerminalAction::new())), None),
],
),
(
AuthConnType::PortForward,
vec![
(msg(|m| m.set_test_delay(TestDelay::new())), None),
(misc_msg(|m| m.set_close_reason("closed".to_owned())), None),
(
msg(|m| m.set_file_action(FileAction::new())),
Some("file_action"),
),
(
msg(|m| m.set_terminal_action(TerminalAction::new())),
Some("terminal_action"),
),
(
msg(|m| m.set_screenshot_request(ScreenshotRequest::new())),
Some("screenshot_request"),
),
(misc_msg(|m| m.set_refresh_video(true)), None),
(misc_msg(|m| m.set_refresh_video_display(0)), None),
(
option_msg(|o| {
o.supported_decoding =
hbb_common::protobuf::MessageField::some(Default::default())
}),
None,
),
],
),
];
for (conn_type, messages) in cases {
assert_scopes(conn_type, messages);
}
}
#[test]
fn session_scope_login_options_are_limited_to_authenticated_session_type() {
let mut option = OptionMessage::new();
option.image_quality = ImageQuality::Balanced.into();
option.disable_audio = BoolOption::Yes.into();
option.block_input = BoolOption::Yes.into();
option.privacy_mode = BoolOption::Yes.into();
let (scoped, violation) =
Connection::scoped_login_option(AuthConnType::ViewCamera, &option);
let scoped = scoped.unwrap();
assert_eq!(violation, Some("login.option"));
assert_eq!(
scoped.image_quality.enum_value(),
Ok(ImageQuality::Balanced)
);
assert_eq!(scoped.disable_audio.enum_value(), Ok(BoolOption::Yes));
assert_eq!(scoped.block_input.enum_value(), Ok(BoolOption::NotSet));
assert_eq!(scoped.privacy_mode.enum_value(), Ok(BoolOption::NotSet));
let (scoped, violation) =
Connection::scoped_login_option(AuthConnType::FileTransfer, &option);
assert!(scoped.is_none());
assert_eq!(violation, Some("login.option"));
}
#[test]
fn session_scope_limited_render_noop_options_reject_mixed_fields() {
for conn_type in [
AuthConnType::FileTransfer,
AuthConnType::Terminal,
AuthConnType::PortForward,
] {
let supported_decoding_only = option_msg(set_supported_decoding);
assert_eq!(
Connection::authorized_message_scope_violation(conn_type, &supported_decoding_only),
None
);
let mixed_option = option_msg(|o| {
set_supported_decoding(o);
o.disable_audio = BoolOption::Yes.into();
});
assert_eq!(
Connection::authorized_message_scope_violation(conn_type, &mixed_option),
Some("misc.option")
);
}
}
#[test]
fn session_scope_view_camera_options_keep_only_camera_fields() {
let mut option = OptionMessage::new();
option.image_quality = ImageQuality::Balanced.into();
option.custom_image_quality = 80;
option.custom_fps = 24;
set_supported_decoding(&mut option);
option.disable_audio = BoolOption::Yes.into();
option.block_input = BoolOption::Yes.into();
option.disable_clipboard = BoolOption::Yes.into();
option.enable_file_transfer = BoolOption::Yes.into();
option.terminal_persistent = BoolOption::Yes.into();
let (scoped, violation) =
Connection::scoped_login_option(AuthConnType::ViewCamera, &option);
let scoped = scoped.unwrap();
assert_eq!(violation, Some("login.option"));
assert_eq!(
scoped.image_quality.enum_value(),
Ok(ImageQuality::Balanced)
);
assert_eq!(scoped.custom_image_quality, 80);
assert_eq!(scoped.custom_fps, 24);
assert!(scoped.supported_decoding.is_some());
assert_eq!(scoped.disable_audio.enum_value(), Ok(BoolOption::Yes));
assert_eq!(scoped.block_input.enum_value(), Ok(BoolOption::NotSet));
assert_eq!(
scoped.disable_clipboard.enum_value(),
Ok(BoolOption::NotSet)
);
assert_eq!(
scoped.enable_file_transfer.enum_value(),
Ok(BoolOption::NotSet)
);
assert_eq!(
scoped.terminal_persistent.enum_value(),
Ok(BoolOption::NotSet)
);
}
}
+10 -6
View File
@@ -77,7 +77,7 @@ lazy_static::lazy_static! {
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
static ref SCREENSHOTS: Mutex<HashMap<usize, Screenshot>> = Default::default();
static ref SCREENSHOTS: Mutex<HashMap<(VideoSource, usize), Screenshot>> = Default::default();
}
struct Screenshot {
@@ -192,7 +192,7 @@ impl VideoFrameController {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum VideoSource {
Monitor,
Camera,
@@ -725,7 +725,8 @@ fn run(vs: VideoService) -> ResultType<()> {
Ok(frame) => {
repeat_encode_counter = 0;
if frame.valid() {
let screenshot = SCREENSHOTS.lock().unwrap().remove(&display_idx);
let screenshot_key = (vs.source, display_idx);
let screenshot = SCREENSHOTS.lock().unwrap().remove(&screenshot_key);
if let Some(mut screenshot) = screenshot {
let restore_vram = screenshot.restore_vram;
let (msg, w, h, data) = match &frame {
@@ -754,7 +755,10 @@ fn run(vs: VideoService) -> ResultType<()> {
#[cfg(all(windows, feature = "vram"))]
VRamEncoder::set_not_use(sp.name(), true);
screenshot.restore_vram = true;
SCREENSHOTS.lock().unwrap().insert(display_idx, screenshot);
SCREENSHOTS
.lock()
.unwrap()
.insert(screenshot_key, screenshot);
_raii.try_vram = false;
bail!("SWITCH");
}
@@ -1348,9 +1352,9 @@ fn check_qos(
Ok(())
}
pub fn set_take_screenshot(display_idx: usize, sid: String, tx: Sender) {
pub fn set_take_screenshot(source: VideoSource, display_idx: usize, sid: String, tx: Sender) {
SCREENSHOTS.lock().unwrap().insert(
display_idx,
(source, display_idx),
Screenshot {
sid,
tx,
+3 -3
View File
@@ -12,7 +12,7 @@ use hbb_common::{
log,
};
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
use crate::ui_session_interface::Session;
use crate::{common::get_app_name, ipc, ui_interface::*};
@@ -29,7 +29,7 @@ lazy_static::lazy_static! {
static ref STUPID_VALUES: Mutex<Vec<Arc<Vec<Value>>>> = Default::default();
}
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
lazy_static::lazy_static! {
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<remote::SciterHandler>>>> = Default::default();
}
@@ -151,7 +151,7 @@ pub fn start(args: &mut [String]) {
frame.register_behavior("native-remote", move || {
let handler =
remote::SciterSession::new(cmd.clone(), id.clone(), pass.clone(), args.clone());
#[cfg(not(any(feature = "flutter", feature = "cli")))]
#[cfg(not(feature = "flutter"))]
{
*CUR_SESSION.lock().unwrap() = Some(handler.inner());
}
+10 -1
View File
@@ -296,6 +296,15 @@ function msgbox(type, title, content, link="", callback=null, height=180, width=
else msgbox("connecting", "Connecting...", "Logging in...");
}
};
} else if (type.indexOf("insecure-connection") >= 0) {
callback = function (res) {
if (!res) {
handler.continue_insecure_connection(false);
view.close();
return;
}
handler.continue_insecure_connection(true);
};
} else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) {
callback = function() { view.close(); }
} else if (type == 'wait-remote-accept-nook') {
@@ -479,4 +488,4 @@ class MultipleSessionComponent extends Reactor.Component {
</select>
</div>;
}
}
}
+13 -3
View File
@@ -152,6 +152,7 @@ class MsgboxComponent: Reactor.Component {
var hasOk = this.type != "connecting" && this.type != "success" && this.type.indexOf("nook") < 0;
var hasLink = this.link != "";
var hasClose = this.type.indexOf("hasclose") >= 0;
var isInsecureConnection = this.type.indexOf("insecure-connection") >= 0;
var show_progress = this.type == "connecting";
var me = this;
self.timer(0, msgboxTimerFunc);
@@ -176,11 +177,12 @@ class MsgboxComponent: Reactor.Component {
<div style="text-align: right;">
<span style="display:inline-block; max-width: 250px; font-size:12px;" #error />
<progress #progress style={"color:" + color + "; display: " + (show_progress ? "inline-block" : "none")} />
{isInsecureConnection && hasOk ? <button .button #submit .outline>{translate('Continue')}</button> : ""}
{hasCancel || this.hasRetry ? <button .button #cancel .outline>{translate(this.hasRetry ? "OK" : "Cancel")}</button> : ""}
{this.hasSkip() ? <button .button #skip .outline>{translate('Skip')}</button> : ""}
{hasOk || this.hasRetry ? <button .button #submit>{translate(this.hasRetry ? "Retry" : "OK")}</button> : ""}
{!isInsecureConnection && (hasOk || this.hasRetry) ? <button .button #submit>{translate(this.hasRetry ? "Retry" : "OK")}</button> : ""}
{hasLink ? <button .button #jumplink .outline>{translate('JumpLink')}</button> : ""}
{hasClose ? <button .button #cancel .outline>{translate('Close')}</button> : ""}
{hasClose ? (isInsecureConnection ? <button .button #cancel>{translate('Disconnect')}</button> : <button .button #cancel .outline>{translate('Close')}</button>) : ""}
{this.getScreenshotButtons()}
</div>
</div>
@@ -193,6 +195,10 @@ class MsgboxComponent: Reactor.Component {
}
function submit() {
if (this.type.indexOf("insecure-connection") >= 0) {
this.cancel();
return;
}
var submit_btn = this.$(button#submit);
if (submit_btn) {
if (submit_btn.state.disabled) return;
@@ -376,7 +382,11 @@ class MsgboxComponent: Reactor.Component {
var el = me.$(.outline-focus);
if (el) view.focus = el;
else {
el = me.$(#submit);
if (me.type.indexOf("insecure-connection") >= 0) {
el = me.$(#cancel);
} else {
el = me.$(#submit);
}
if (el) {
view.focus = el;
}
+1
View File
@@ -513,6 +513,7 @@ impl sciter::EventHandler for SciterSession {
fn is_rdp();
fn login(String, String, String, bool);
fn send2fa(String, bool);
fn continue_insecure_connection(bool);
fn get_enable_trusted_devices();
fn new_rdp();
fn send_mouse(i32, i32, i32, bool, bool, bool, bool);
-1
View File
@@ -711,7 +711,6 @@ pub fn is_can_input_monitoring(_prompt: bool) -> bool {
#[inline]
pub fn get_error() -> String {
#[cfg(not(any(feature = "cli")))]
#[cfg(target_os = "linux")]
{
let dtype = crate::platform::linux::get_display_server();
+15 -4
View File
@@ -1408,6 +1408,15 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Close);
}
pub fn continue_insecure_connection(&self, continue_insecure: bool) {
let data = if continue_insecure {
Data::ContinueInsecureConnection
} else {
Data::RejectInsecureConnection
};
self.send(data);
}
fn try_auto_start_job_str(is_reconnected: bool, job_str: &str) -> Option<String> {
if is_reconnected {
let job_str = job_str.trim();
@@ -1809,10 +1818,12 @@ impl<T: InvokeUiSession> Interface for Session<T> {
self.msgbox("error", "Error", msg, "");
return;
}
self.try_change_init_resolution(pi.current_display);
let p = self.lc.read().unwrap().should_auto_login();
if !p.is_empty() {
input_os_password(p, true, self.clone());
if !self.is_view_camera() {
self.try_change_init_resolution(pi.current_display);
let p = self.lc.read().unwrap().should_auto_login();
if !p.is_empty() {
input_os_password(p, true, self.clone());
}
}
let current = &pi.displays[pi.current_display as usize];
self.set_display(
+92
View File
@@ -0,0 +1,92 @@
#include <check.h>
#include <stdlib.h>
#include <stddef.h>
#include "../libs/clipboard/src/windows/wf_cliprdr.c"
static SIZE_T descriptor_size(UINT count)
{
return offsetof(FILEGROUPDESCRIPTORW, fgd) + (SIZE_T)count * sizeof(FILEDESCRIPTORW);
}
START_TEST(test_descriptor_size_rejects_buffer_smaller_than_header)
{
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid(0, 1), FALSE);
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid(
offsetof(FILEGROUPDESCRIPTORW, fgd) - 1, 1),
FALSE);
}
END_TEST
START_TEST(test_descriptor_size_rejects_zero_items)
{
ck_assert_int_eq(
wf_cliprdr_file_group_descriptor_size_valid(offsetof(FILEGROUPDESCRIPTORW, fgd), 0),
FALSE);
}
END_TEST
START_TEST(test_descriptor_size_accepts_max_stream_count)
{
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid(
descriptor_size(WF_CLIPRDR_MAX_STREAMS), WF_CLIPRDR_MAX_STREAMS),
TRUE);
}
END_TEST
START_TEST(test_descriptor_size_rejects_stream_count_above_limit)
{
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid(
descriptor_size(WF_CLIPRDR_MAX_STREAMS), WF_CLIPRDR_MAX_STREAMS + 1),
FALSE);
}
END_TEST
START_TEST(test_descriptor_size_rejects_truncated_descriptor_array)
{
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid(descriptor_size(2) - 1, 2),
FALSE);
}
END_TEST
START_TEST(test_descriptor_size_rejects_extreme_count)
{
ck_assert_int_eq(wf_cliprdr_file_group_descriptor_size_valid((SIZE_T)-1, (UINT)-1),
FALSE);
}
END_TEST
Suite *wf_cliprdr_invariant_suite(void)
{
Suite *s;
TCase *tc_core;
s = suite_create("wf_cliprdr_invariants");
tc_core = tcase_create("descriptor_size");
tcase_add_test(tc_core, test_descriptor_size_rejects_buffer_smaller_than_header);
tcase_add_test(tc_core, test_descriptor_size_rejects_zero_items);
tcase_add_test(tc_core, test_descriptor_size_accepts_max_stream_count);
tcase_add_test(tc_core, test_descriptor_size_rejects_stream_count_above_limit);
tcase_add_test(tc_core, test_descriptor_size_rejects_truncated_descriptor_array);
tcase_add_test(tc_core, test_descriptor_size_rejects_extreme_count);
suite_add_tcase(s, tc_core);
return s;
}
int main(void)
{
int number_failed;
Suite *s;
SRunner *sr;
s = wf_cliprdr_invariant_suite();
sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
number_failed = srunner_ntests_failed(sr);
srunner_free(sr);
return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}