Compare commits

...

5 Commits

Author SHA1 Message Date
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
6 changed files with 71 additions and 28 deletions

View File

@@ -167,7 +167,7 @@ target/release/rustdesk
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графический пользовательский интерфейс на Sciter (устаревшее) - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: графический пользовательский интерфейс на Sciter (устаревшее)
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: сервисы аудио, буфера обмена, ввода, видео и сетевых подключений - **[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/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)**: специфичный для платформы код - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: код Flutter для ПК-версии и мобильных устройств - **[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 - **[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:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -65,9 +64,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
bool _showGestureHelp = false; bool _showGestureHelp = false;
String _value = ''; String _value = '';
Orientation? _currentOrientation; Orientation? _currentOrientation;
double _viewInsetsBottom = 0;
final _uniqueKey = UniqueKey(); final _uniqueKey = UniqueKey();
Timer? _timerDidChangeMetrics;
Timer? _iosKeyboardWorkaroundTimer; Timer? _iosKeyboardWorkaroundTimer;
final _blockableOverlayState = BlockableOverlayState(); final _blockableOverlayState = BlockableOverlayState();
@@ -140,7 +137,6 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
_physicalFocusNode.dispose(); _physicalFocusNode.dispose();
await gFFI.close(); await gFFI.close();
_timer?.cancel(); _timer?.cancel();
_timerDidChangeMetrics?.cancel();
_iosKeyboardWorkaroundTimer?.cancel(); _iosKeyboardWorkaroundTimer?.cancel();
gFFI.dialogManager.dismissAll(); gFFI.dialogManager.dismissAll();
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
@@ -167,26 +163,6 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
gFFI.invokeMethod("try_sync_clipboard"); 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. // 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. // 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. // 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; final _trackpadAdjustPeerLinux = 0.06;
// This is an experience value. // This is an experience value.
final _trackpadAdjustMacToWin = 2.50; 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; int _trackpadSpeed = kDefaultTrackpadSpeed;
double _trackpadSpeedInner = kDefaultTrackpadSpeed / 100.0; double _trackpadSpeedInner = kDefaultTrackpadSpeed / 100.0;
var _trackpadScrollUnsent = Offset.zero; var _trackpadScrollUnsent = Offset.zero;
@@ -1172,6 +1178,7 @@ class InputModel {
if (isMacOS && peerPlatform == kPeerPlatformWindows) { if (isMacOS && peerPlatform == kPeerPlatformWindows) {
delta *= _trackpadAdjustMacToWin; delta *= _trackpadAdjustMacToWin;
} }
delta = _filterTrackpadDeltaAxis(delta);
_trackpadLastDelta = delta; _trackpadLastDelta = delta;
var x = delta.dx.toInt(); 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) { void _scheduleFling(double x, double y, int delay) {
if (isViewCamera) return; if (isViewCamera) return;
if ((x == 0 && y == 0) || _stopFling) { if ((x == 0 && y == 0) || _stopFling) {

View File

@@ -2152,6 +2152,9 @@ class CanvasModel with ChangeNotifier {
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle(); ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
Timer? _timerMobileFocusCanvasCursor; Timer? _timerMobileFocusCanvasCursor;
Timer? _timerMobileRestoreCanvasOffset;
Offset? _offsetBeforeMobileSoftKeyboard;
double? _scaleBeforeMobileSoftKeyboard;
// `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method // `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method
// after showing the soft keyboard. // after showing the soft keyboard.
@@ -2639,6 +2642,9 @@ class CanvasModel with ChangeNotifier {
_scale = 1.0; _scale = 1.0;
_lastViewStyle = ViewStyle.defaultViewStyle(); _lastViewStyle = ViewStyle.defaultViewStyle();
_timerMobileFocusCanvasCursor?.cancel(); _timerMobileFocusCanvasCursor?.cancel();
_timerMobileRestoreCanvasOffset?.cancel();
_offsetBeforeMobileSoftKeyboard = null;
_scaleBeforeMobileSoftKeyboard = null;
} }
updateScrollPercent() { updateScrollPercent() {
@@ -2667,6 +2673,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 // mobile only
// Move the canvas to make the cursor visible(center) on the screen. // Move the canvas to make the cursor visible(center) on the screen.
void _moveToCenterCursor() { void _moveToCenterCursor() {
@@ -2919,8 +2950,13 @@ class CursorModel with ChangeNotifier {
_lastIsBlocked = true; _lastIsBlocked = true;
} }
if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) { if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) {
parent.target?.canvasModel.mobileFocusCanvasCursor(); if (keyboardIsVisible) {
parent.target?.canvasModel.isMobileCanvasChanged = false; parent.target?.canvasModel.saveMobileOffsetBeforeSoftKeyboard();
parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.isMobileCanvasChanged = false;
} else {
parent.target?.canvasModel.restoreMobileOffsetAfterSoftKeyboard();
}
} }
_lastKeyboardIsVisible = keyboardIsVisible; _lastKeyboardIsVisible = keyboardIsVisible;
} }

View File

@@ -286,10 +286,14 @@ fn heartbeat_url() -> String {
fn handle_config_options(config_options: HashMap<String, String>) { fn handle_config_options(config_options: HashMap<String, String>) {
let mut options = Config::get_options(); let mut options = Config::get_options();
let default_settings = config::DEFAULT_SETTINGS.read().unwrap().clone();
config_options config_options
.iter() .iter()
.map(|(k, v)| { .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); options.remove(k);
} else { } else {
options.insert(k.to_string(), v.to_string()); 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"), ("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-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"), ("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(); ].iter().cloned().collect();
} }