Compare commits

...

8 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
439e3abc79 Fix: show ask-for-note dialog when user clicks OK on reconnecting screen (#14527)
Co-authored-by: rustdesk <71636191+rustdesk@users.noreply.github.com>
2026-03-12 15:18:50 +00:00
copilot-swe-agent[bot]
b8c0787273 Initial plan 2026-03-12 15:12:58 +00:00
Vasyl Gello
682e347be0 Bump Android NDK to r28c (#13685)
Signed-off-by: Vasyl Gello <vasek.gello@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2026-03-12 16:52:33 +08:00
fufesou
b3f43f55c1 fix(mobile): restore canvas offset after hidding the soft keyboard (#14506)
* fix(mobile): restore canvas offset after hidding the soft keyboard

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

* fix(mobile): ingore mobileFocusCanvasCursor in didChangeMetrics

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

* fix(mobile): remove unused code

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

* refact(mobile): simple refactor

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

* fix(mobile): restore canvas, cancel focus timer

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-11 18:28:37 +08:00
21pages
016a0b1141 fix strategy cannot apply over default advanced options (#14502)
Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-03-10 13:24:13 +08:00
John Fowler
fd7bcf54bd Hungarian language file update (#14497)
* Update Hungarian translations in hu.rs

Translation of new strings and some fixes.
John Fowler.

* Escape quotes in Hungarian language strings

Replacing Hungarian quotation marks

* Update Hungarian translations for various terms

Upload a new translation (hu.rs) file.

* Hungarian language file correction

New character strings translation, error correction.

* Hungarian language file update

New string translations.
2026-03-09 21:28:37 +08:00
layla
db3f5fe816 Fix typo: Rustdesk to RustDesk in Russian README (#14468) 2026-03-08 19:18:59 +08:00
fufesou
0d3016fcd8 fix(flutter): reduce accidental horizontal trackpad scrolling during vertical pan (#14460)
* fix(flutter): reduce accidental horizontal trackpad scrolling during vertical pan

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

* refact: comments

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-05 23:10:39 +08:00
7 changed files with 88 additions and 33 deletions

View File

@@ -40,7 +40,7 @@ env:
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.6"
NDK_VERSION: "r27c"
NDK_VERSION: "r28c"
#signing keys env variable checks
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"

View File

@@ -167,7 +167,7 @@ target/release/rustdesk
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графический пользовательский интерфейс на Sciter (устаревшее)
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: сервисы аудио, буфера обмена, ввода, видео и сетевых подключений
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: одноранговое соединение
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: связь с [сервером Rustdesk](https://github.com/rustdesk/rustdesk-server), ожидает удаленного прямого (через TCP hole punching) или ретранслируемого соединения
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: связь с [сервером RustDesk](https://github.com/rustdesk/rustdesk-server), ожидает удаленного прямого (через TCP hole punching) или ретранслируемого соединения
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для ПК-версии и мобильных устройств
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/v1/js)**: JavaScript для Web-клиента Flutter

View File

@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -65,9 +64,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
bool _showGestureHelp = false;
String _value = '';
Orientation? _currentOrientation;
double _viewInsetsBottom = 0;
final _uniqueKey = UniqueKey();
Timer? _timerDidChangeMetrics;
Timer? _iosKeyboardWorkaroundTimer;
final _blockableOverlayState = BlockableOverlayState();
@@ -140,7 +137,6 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
_physicalFocusNode.dispose();
await gFFI.close();
_timer?.cancel();
_timerDidChangeMetrics?.cancel();
_iosKeyboardWorkaroundTimer?.cancel();
gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
@@ -167,26 +163,6 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
gFFI.invokeMethod("try_sync_clipboard");
}
@override
void didChangeMetrics() {
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
// Don't try reset the view style and focus the cursor.
if (gFFI.cursorModel.lastKeyboardIsVisible &&
gFFI.canvasModel.isMobileCanvasChanged) {
return;
}
final newBottom = MediaQueryData.fromView(ui.window).viewInsets.bottom;
_timerDidChangeMetrics?.cancel();
_timerDidChangeMetrics = Timer(Duration(milliseconds: 100), () async {
// We need this comparation because poping up the floating action will also trigger `didChangeMetrics()`.
if (newBottom != _viewInsetsBottom) {
gFFI.canvasModel.mobileFocusCanvasCursor();
_viewInsetsBottom = newBottom;
}
});
}
// to-do: It should be better to use transparent color instead of the bgColor.
// But for now, the transparent color will cause the canvas to be white.
// I'm sure that the white color is caused by the Overlay widget in BlockableOverlay.

View File

@@ -348,6 +348,12 @@ class InputModel {
final _trackpadAdjustPeerLinux = 0.06;
// This is an experience value.
final _trackpadAdjustMacToWin = 2.50;
// Ignore directional locking for very small deltas on both axes (including
// tiny single-axis movement) to avoid over-filtering near zero.
static const double _trackpadAxisNoiseThreshold = 0.2;
// Lock to dominant axis only when one axis is clearly stronger.
// 1.6 means the dominant axis must be >= 60% larger than the other.
static const double _trackpadAxisLockRatio = 1.6;
int _trackpadSpeed = kDefaultTrackpadSpeed;
double _trackpadSpeedInner = kDefaultTrackpadSpeed / 100.0;
var _trackpadScrollUnsent = Offset.zero;
@@ -1172,6 +1178,7 @@ class InputModel {
if (isMacOS && peerPlatform == kPeerPlatformWindows) {
delta *= _trackpadAdjustMacToWin;
}
delta = _filterTrackpadDeltaAxis(delta);
_trackpadLastDelta = delta;
var x = delta.dx.toInt();
@@ -1204,6 +1211,24 @@ class InputModel {
}
}
Offset _filterTrackpadDeltaAxis(Offset delta) {
final absDx = delta.dx.abs();
final absDy = delta.dy.abs();
// Keep diagonal intent when movement is tiny on both axes.
if (absDx < _trackpadAxisNoiseThreshold &&
absDy < _trackpadAxisNoiseThreshold) {
return delta;
}
// Dominant-axis lock to reduce accidental cross-axis scrolling noise.
if (absDy >= absDx * _trackpadAxisLockRatio) {
return Offset(0, delta.dy);
}
if (absDx >= absDy * _trackpadAxisLockRatio) {
return Offset(delta.dx, 0);
}
return delta;
}
void _scheduleFling(double x, double y, int delay) {
if (isViewCamera) return;
if ((x == 0 && y == 0) || _stopFling) {

View File

@@ -1016,19 +1016,31 @@ class FfiModel with ChangeNotifier {
showMsgBox(SessionID sessionId, String type, String title, String text,
String link, bool hasRetry, OverlayDialogManager dialogManager,
{bool? hasCancel}) async {
final showNoteEdit = parent.target != null &&
final noteAllowed = parent.target != null &&
allowAskForNoteAtEndOfConnection(parent.target, false) &&
(title == "Connection Error" || type == "restarting") &&
!hasRetry;
(title == "Connection Error" || type == "restarting");
final showNoteEdit = noteAllowed && !hasRetry;
if (showNoteEdit) {
await showConnEndAuditDialogCloseCanceled(
ffi: parent.target!, type: type, title: title, text: text);
closeConnection();
} else {
VoidCallback? onSubmit;
if (noteAllowed && hasRetry) {
final ffi = parent.target!;
onSubmit = () async {
_timer?.cancel();
_timer = null;
await showConnEndAuditDialogCloseCanceled(
ffi: ffi, type: type, title: title, text: text);
closeConnection();
};
}
msgBox(sessionId, type, title, text, link, dialogManager,
hasCancel: hasCancel,
reconnect: hasRetry ? reconnect : null,
reconnectTimeout: hasRetry ? _reconnects : null);
reconnectTimeout: hasRetry ? _reconnects : null,
onSubmit: onSubmit);
}
_timer?.cancel();
if (hasRetry) {
@@ -2152,6 +2164,9 @@ class CanvasModel with ChangeNotifier {
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
Timer? _timerMobileFocusCanvasCursor;
Timer? _timerMobileRestoreCanvasOffset;
Offset? _offsetBeforeMobileSoftKeyboard;
double? _scaleBeforeMobileSoftKeyboard;
// `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method
// after showing the soft keyboard.
@@ -2639,6 +2654,9 @@ class CanvasModel with ChangeNotifier {
_scale = 1.0;
_lastViewStyle = ViewStyle.defaultViewStyle();
_timerMobileFocusCanvasCursor?.cancel();
_timerMobileRestoreCanvasOffset?.cancel();
_offsetBeforeMobileSoftKeyboard = null;
_scaleBeforeMobileSoftKeyboard = null;
}
updateScrollPercent() {
@@ -2667,6 +2685,31 @@ class CanvasModel with ChangeNotifier {
});
}
void saveMobileOffsetBeforeSoftKeyboard() {
_timerMobileRestoreCanvasOffset?.cancel();
_offsetBeforeMobileSoftKeyboard = Offset(_x, _y);
_scaleBeforeMobileSoftKeyboard = _scale;
}
void restoreMobileOffsetAfterSoftKeyboard() {
_timerMobileRestoreCanvasOffset?.cancel();
_timerMobileFocusCanvasCursor?.cancel();
final targetOffset = _offsetBeforeMobileSoftKeyboard;
final targetScale = _scaleBeforeMobileSoftKeyboard;
if (targetOffset == null || targetScale == null) {
return;
}
_timerMobileRestoreCanvasOffset = Timer(Duration(milliseconds: 100), () {
updateSize();
_x = targetOffset.dx;
_y = targetOffset.dy;
_scale = targetScale;
_offsetBeforeMobileSoftKeyboard = null;
_scaleBeforeMobileSoftKeyboard = null;
notifyListeners();
});
}
// mobile only
// Move the canvas to make the cursor visible(center) on the screen.
void _moveToCenterCursor() {
@@ -2919,8 +2962,13 @@ class CursorModel with ChangeNotifier {
_lastIsBlocked = true;
}
if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) {
parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.isMobileCanvasChanged = false;
if (keyboardIsVisible) {
parent.target?.canvasModel.saveMobileOffsetBeforeSoftKeyboard();
parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.isMobileCanvasChanged = false;
} else {
parent.target?.canvasModel.restoreMobileOffsetAfterSoftKeyboard();
}
}
_lastKeyboardIsVisible = keyboardIsVisible;
}

View File

@@ -286,10 +286,14 @@ fn heartbeat_url() -> String {
fn handle_config_options(config_options: HashMap<String, String>) {
let mut options = Config::get_options();
let default_settings = config::DEFAULT_SETTINGS.read().unwrap().clone();
config_options
.iter()
.map(|(k, v)| {
if v.is_empty() {
// Priority: user config > default advanced options.
// Only when default advanced options are also empty, remove user option (fallback to built-in default);
// otherwise insert an empty value so user config remains present.
if v.is_empty() && default_settings.get(k).map_or("", |v| v).is_empty() {
options.remove(k);
} else {
options.insert(k.to_string(), v.to_string());

View File

@@ -738,5 +738,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Changelog", "Változáslista"),
("keep-awake-during-outgoing-sessions-label", "Képernyő aktív állapotban tartása a kimenő munkamenetek során"),
("keep-awake-during-incoming-sessions-label", "Képernyő aktív állapotban tartása a bejövő munkamenetek során"),
("Continue with {}", "Folytatás ezzel: {}"),
("Display Name", "Kijelző név"),
].iter().cloned().collect();
}