Compare commits

..

4 Commits

Author SHA1 Message Date
21pages
743705545a increase ipc Data enum size limit to 120 bytes
Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-03-04 18:28:39 +08:00
21pages
89a34642a3 web: implement mainResolveAvatarUrl via js getByName
Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-03-04 18:17:05 +08:00
21pages
d9e2107fb8 refactor avatar display: unify rendering and resolve at use time
- Extract buildAvatarWidget() in common.dart to share avatar rendering
    logic across desktop settings, desktop CM and mobile CM
  - Add resolve_avatar_url() in Rust, exposed via FFI (SyncReturn),
    to resolve relative avatar paths (e.g. "/avatar/xxx") to absolute URLs
  - Store avatar as-is in local config, only resolve when displaying
    (settings page) or sending (LoginRequest)
  - Resolve avatar in LoginRequest before sending to remote peer
  - Add error handling for network image load failures
  - Guard against empty client.name[0] crash
  - Show avatar in mobile settings page account tile

Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-03-04 15:58:20 +08:00
rustdesk
890282e385 avatar 2026-03-02 12:27:21 +08:00
17 changed files with 101 additions and 251 deletions

View File

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

@@ -2039,7 +2039,7 @@ class _AccountState extends State<_Account> {
return Row( return Row(
children: [ children: [
if (avatarWidget != null) avatarWidget, if (avatarWidget != null) avatarWidget,
const SizedBox(width: 12), if (avatarWidget != null) const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -569,12 +569,11 @@ class _CmHeaderState extends State<_CmHeader>
Widget _buildClientAvatar() { Widget _buildClientAvatar() {
return buildAvatarWidget( return buildAvatarWidget(
avatar: client.avatar, avatar: client.avatar,
size: 70, size: 70,
borderRadius: 15, borderRadius: 15,
fallback: _buildInitialAvatar(), fallback: _buildInitialAvatar(),
) ?? )!;
_buildInitialAvatar();
} }
Widget _buildInitialAvatar() { Widget _buildInitialAvatar() {

View File

@@ -1,4 +1,5 @@
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';
@@ -64,7 +65,9 @@ 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();
@@ -137,6 +140,7 @@ 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,
@@ -163,6 +167,26 @@ 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

@@ -857,16 +857,16 @@ class ClientInfo extends StatelessWidget {
Widget _buildAvatar(BuildContext context) { Widget _buildAvatar(BuildContext context) {
final fallback = CircleAvatar( final fallback = CircleAvatar(
backgroundColor: str2color(client.name, backgroundColor: str2color(
client.name,
Theme.of(context).brightness == Brightness.light ? 255 : 150), Theme.of(context).brightness == Brightness.light ? 255 : 150),
child: Text(client.name.isNotEmpty ? client.name[0] : '?'), child: Text(client.name.isNotEmpty ? client.name[0] : '?'),
); );
return buildAvatarWidget( return buildAvatarWidget(
avatar: client.avatar, avatar: client.avatar,
size: 40, size: 40,
fallback: fallback, fallback: fallback,
) ?? )!;
fallback;
} }
} }

View File

@@ -617,7 +617,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
onToggle: (bool v) async { onToggle: (bool v) async {
await mainSetLocalBoolOption(kOptionEnableShowTerminalExtraKeys, v); await mainSetLocalBoolOption(kOptionEnableShowTerminalExtraKeys, v);
final newValue = final newValue =
mainGetLocalBoolOptionSync(kOptionEnableShowTerminalExtraKeys); mainGetLocalBoolOptionSync(kOptionEnableShowTerminalExtraKeys);
setState(() { setState(() {
_showTerminalExtraKeys = newValue; _showTerminalExtraKeys = newValue;
}); });
@@ -694,9 +694,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
avatar: gFFI.userModel.avatar.value); avatar: gFFI.userModel.avatar.value);
return buildAvatarWidget( return buildAvatarWidget(
avatar: avatar, avatar: avatar,
size: 28, size: 40,
borderRadius: null,
fallback: Icon(Icons.person),
) ?? ) ??
Icon(Icons.person); Icon(Icons.person);
}), }),
@@ -839,12 +837,10 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
), ),
if (!incomingOnly) if (!incomingOnly)
SettingsTile.switchTile( SettingsTile.switchTile(
title: title: Text(translate('keep-awake-during-outgoing-sessions-label')),
Text(translate('keep-awake-during-outgoing-sessions-label')),
initialValue: _preventSleepWhileConnected, initialValue: _preventSleepWhileConnected,
onToggle: (v) async { onToggle: (v) async {
await mainSetLocalBoolOption( await mainSetLocalBoolOption(kOptionKeepAwakeDuringOutgoingSessions, v);
kOptionKeepAwakeDuringOutgoingSessions, v);
setState(() { setState(() {
_preventSleepWhileConnected = v; _preventSleepWhileConnected = v;
}); });

View File

@@ -348,12 +348,6 @@ 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;
@@ -1178,7 +1172,6 @@ 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();
@@ -1211,24 +1204,6 @@ 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

@@ -1016,31 +1016,19 @@ class FfiModel with ChangeNotifier {
showMsgBox(SessionID sessionId, String type, String title, String text, showMsgBox(SessionID sessionId, String type, String title, String text,
String link, bool hasRetry, OverlayDialogManager dialogManager, String link, bool hasRetry, OverlayDialogManager dialogManager,
{bool? hasCancel}) async { {bool? hasCancel}) async {
final noteAllowed = parent.target != null && final showNoteEdit = parent.target != null &&
allowAskForNoteAtEndOfConnection(parent.target, false) && allowAskForNoteAtEndOfConnection(parent.target, false) &&
(title == "Connection Error" || type == "restarting"); (title == "Connection Error" || type == "restarting") &&
final showNoteEdit = noteAllowed && !hasRetry; !hasRetry;
if (showNoteEdit) { if (showNoteEdit) {
await showConnEndAuditDialogCloseCanceled( await showConnEndAuditDialogCloseCanceled(
ffi: parent.target!, type: type, title: title, text: text); ffi: parent.target!, type: type, title: title, text: text);
closeConnection(); closeConnection();
} else { } 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, msgBox(sessionId, type, title, text, link, dialogManager,
hasCancel: hasCancel, hasCancel: hasCancel,
reconnect: hasRetry ? reconnect : null, reconnect: hasRetry ? reconnect : null,
reconnectTimeout: hasRetry ? _reconnects : null, reconnectTimeout: hasRetry ? _reconnects : null);
onSubmit: onSubmit);
} }
_timer?.cancel(); _timer?.cancel();
if (hasRetry) { if (hasRetry) {
@@ -2164,9 +2152,6 @@ 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.
@@ -2654,9 +2639,6 @@ 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() {
@@ -2685,31 +2667,6 @@ 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() {
@@ -2962,13 +2919,8 @@ class CursorModel with ChangeNotifier {
_lastIsBlocked = true; _lastIsBlocked = true;
} }
if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) { if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) {
if (keyboardIsVisible) { parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.saveMobileOffsetBeforeSoftKeyboard(); parent.target?.canvasModel.isMobileCanvasChanged = false;
parent.target?.canvasModel.mobileFocusCanvasCursor();
parent.target?.canvasModel.isMobileCanvasChanged = false;
} else {
parent.target?.canvasModel.restoreMobileOffsetAfterSoftKeyboard();
}
} }
_lastKeyboardIsVisible = keyboardIsVisible; _lastKeyboardIsVisible = keyboardIsVisible;
} }

View File

@@ -269,7 +269,7 @@ impl KeyboardControllable for Enigo {
for pos in 0..mod_len { for pos in 0..mod_len {
let rpos = mod_len - 1 - pos; let rpos = mod_len - 1 - pos;
if flag & (0x0001 << rpos) != 0 { if flag & (0x0001 << rpos) != 0 {
self.key_up(modifiers[rpos]); self.key_up(modifiers[pos]);
} }
} }
@@ -298,18 +298,7 @@ impl KeyboardControllable for Enigo {
} }
fn key_up(&mut self, key: Key) { fn key_up(&mut self, key: Key) {
match key { keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0);
Key::Layout(c) => {
let code = self.get_layoutdependent_keycode(c);
if code as u16 != 0xFFFF {
let vk = code & 0x00FF;
keybd_event(KEYEVENTF_KEYUP, vk, 0);
}
}
_ => {
keybd_event(KEYEVENTF_KEYUP, self.key_to_keycode(key), 0);
}
}
} }
fn get_key_state(&mut self, key: Key) -> bool { fn get_key_state(&mut self, key: Key) -> bool {

View File

@@ -286,14 +286,10 @@ 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)| {
// Priority: user config > default advanced options. if v.is_empty() {
// 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,7 +738,5 @@ 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();
} }

View File

@@ -859,10 +859,9 @@ on run {app_name, cur_pid, app_dir, user_name}
set app_dir_q to quoted form of app_dir set app_dir_q to quoted form of app_dir
set user_name_q to quoted form of user_name set user_name_q to quoted form of user_name
set check_source to "test -d " & app_dir_q & " || exit 1;"
set kill_others to "pids=$(pgrep -x '" & app_name & "' | grep -vx " & cur_pid & " || true); if [ -n \"$pids\" ]; then echo \"$pids\" | xargs kill -9 || true; fi;" set kill_others to "pids=$(pgrep -x '" & app_name & "' | grep -vx " & cur_pid & " || true); if [ -n \"$pids\" ]; then echo \"$pids\" | xargs kill -9 || true; fi;"
set copy_files to "rm -rf " & app_bundle_q & " && ditto " & app_dir_q & " " & app_bundle_q & " && chown -R " & user_name_q & ":staff " & app_bundle_q & " && (xattr -r -d com.apple.quarantine " & app_bundle_q & " || true);" set copy_files to "rm -rf " & app_bundle_q & " && ditto " & app_dir_q & " " & app_bundle_q & " && chown -R " & user_name_q & ":staff " & app_bundle_q & " && (xattr -r -d com.apple.quarantine " & app_bundle_q & " || true);"
set sh to "set -e;" & check_source & kill_others & copy_files set sh to "set -e;" & kill_others & copy_files
do shell script sh with prompt app_name & " wants to update itself" with administrator privileges do shell script sh with prompt app_name & " wants to update itself" with administrator privileges
end run end run

View File

@@ -4,7 +4,6 @@ on run {daemon_file, agent_file, user, cur_pid, source_dir}
set daemon_plist to "/Library/LaunchDaemons/com.carriez.RustDesk_service.plist" set daemon_plist to "/Library/LaunchDaemons/com.carriez.RustDesk_service.plist"
set app_bundle to "/Applications/RustDesk.app" set app_bundle to "/Applications/RustDesk.app"
set check_source to "test -d " & quoted form of source_dir & " || exit 1;"
set resolve_uid to "uid=$(id -u " & quoted form of user & " 2>/dev/null || true);" set resolve_uid to "uid=$(id -u " & quoted form of user & " 2>/dev/null || true);"
set unload_agent to "if [ -n \"$uid\" ]; then launchctl bootout gui/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl bootout user/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl unload -w " & quoted form of agent_plist & " || true; else launchctl unload -w " & quoted form of agent_plist & " || true; fi;" set unload_agent to "if [ -n \"$uid\" ]; then launchctl bootout gui/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl bootout user/$uid " & quoted form of agent_plist & " 2>/dev/null || launchctl unload -w " & quoted form of agent_plist & " || true; else launchctl unload -w " & quoted form of agent_plist & " || true; fi;"
set unload_service to "launchctl unload -w " & daemon_plist & " || true;" set unload_service to "launchctl unload -w " & daemon_plist & " || true;"
@@ -20,7 +19,7 @@ on run {daemon_file, agent_file, user, cur_pid, source_dir}
set kickstart_agent to "if [ -n \"$uid\" ]; then launchctl kickstart -k gui/$uid/$agent_label 2>/dev/null || launchctl kickstart -k user/$uid/$agent_label 2>/dev/null || true; fi;" set kickstart_agent to "if [ -n \"$uid\" ]; then launchctl kickstart -k gui/$uid/$agent_label 2>/dev/null || launchctl kickstart -k user/$uid/$agent_label 2>/dev/null || true; fi;"
set load_agent to agent_label_cmd & bootstrap_agent & kickstart_agent set load_agent to agent_label_cmd & bootstrap_agent & kickstart_agent
set sh to "set -e;" & check_source & resolve_uid & unload_agent & unload_service & kill_others & copy_files & write_daemon_plist & write_agent_plist & load_service & load_agent set sh to "set -e;" & resolve_uid & unload_agent & unload_service & kill_others & copy_files & write_daemon_plist & write_agent_plist & load_service & load_agent
do shell script sh with prompt "RustDesk wants to update itself" with administrator privileges do shell script sh with prompt "RustDesk wants to update itself" with administrator privileges
end run end run

View File

@@ -560,9 +560,7 @@ impl Connection {
match data { match data {
ipc::Data::Authorize => { ipc::Data::Authorize => {
conn.require_2fa.take(); conn.require_2fa.take();
if !conn.send_logon_response_and_keep_alive().await { conn.send_logon_response().await;
break;
}
if conn.port_forward_socket.is_some() { if conn.port_forward_socket.is_some() {
break; break;
} }
@@ -1340,66 +1338,9 @@ impl Connection {
crate::post_request(url, v.to_string(), "").await crate::post_request(url, v.to_string(), "").await
} }
fn normalize_port_forward_target(pf: &mut PortForward) -> (String, bool) { async fn send_logon_response(&mut self) {
let mut is_rdp = false;
if pf.host == "RDP" && pf.port == 0 {
pf.host = "localhost".to_owned();
pf.port = 3389;
is_rdp = true;
}
if pf.host.is_empty() {
pf.host = "localhost".to_owned();
}
(format!("{}:{}", pf.host, pf.port), is_rdp)
}
async fn connect_port_forward_if_needed(&mut self) -> bool {
if self.port_forward_socket.is_some() {
return true;
}
let Some(login_request::Union::PortForward(pf)) = self.lr.union.as_ref() else {
return true;
};
let mut pf = pf.clone();
let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf);
self.port_forward_address = addr.clone();
match timeout(3000, TcpStream::connect(&addr)).await {
Ok(Ok(sock)) => {
self.port_forward_socket = Some(Framed::new(sock, BytesCodec::new()));
true
}
Ok(Err(e)) => {
log::warn!("Port forward connect failed for {}: {}", addr, e);
if is_rdp {
addr = "RDP".to_owned();
}
self.send_login_error(format!(
"Failed to access remote {}. Please make sure it is reachable/open.",
addr
))
.await;
false
}
Err(e) => {
log::warn!("Port forward connect timed out for {}: {}", addr, e);
if is_rdp {
addr = "RDP".to_owned();
}
self.send_login_error(format!(
"Failed to access remote {}. Please make sure it is reachable/open.",
addr
))
.await;
false
}
}
}
// Returns whether this connection should be kept alive.
// `true` does not necessarily mean authorization succeeded (e.g. REQUIRE_2FA case).
async fn send_logon_response_and_keep_alive(&mut self) -> bool {
if self.authorized { if self.authorized {
return true; return;
} }
if self.require_2fa.is_some() && !self.is_recent_session(true) && !self.from_switch { if self.require_2fa.is_some() && !self.is_recent_session(true) && !self.from_switch {
self.require_2fa.as_ref().map(|totp| { self.require_2fa.as_ref().map(|totp| {
@@ -1430,11 +1371,7 @@ impl Connection {
} }
}); });
self.send_login_error(crate::client::REQUIRE_2FA).await; self.send_login_error(crate::client::REQUIRE_2FA).await;
// Keep the connection alive so the client can continue with 2FA. return;
return true;
}
if !self.connect_port_forward_if_needed().await {
return false;
} }
self.authorized = true; self.authorized = true;
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() { let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
@@ -1557,7 +1494,7 @@ impl Connection {
res.set_peer_info(pi); res.set_peer_info(pi);
msg_out.set_login_response(res); msg_out.set_login_response(res);
self.send(msg_out).await; self.send(msg_out).await;
return true; return;
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if self.is_remote() { if self.is_remote() {
@@ -1580,7 +1517,7 @@ impl Connection {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_login_response(res); msg_out.set_login_response(res);
self.send(msg_out).await; self.send(msg_out).await;
return true; return;
} }
} }
#[allow(unused_mut)] #[allow(unused_mut)]
@@ -1734,7 +1671,6 @@ impl Connection {
self.try_sub_monitor_services(); self.try_sub_monitor_services();
} }
} }
true
} }
fn try_sub_camera_displays(&mut self) { fn try_sub_camera_displays(&mut self) {
@@ -2243,8 +2179,33 @@ impl Connection {
sleep(1.).await; sleep(1.).await;
return false; return false;
} }
let (addr, _is_rdp) = Self::normalize_port_forward_target(&mut pf); let mut is_rdp = false;
self.port_forward_address = addr; if pf.host == "RDP" && pf.port == 0 {
pf.host = "localhost".to_owned();
pf.port = 3389;
is_rdp = true;
}
if pf.host.is_empty() {
pf.host = "localhost".to_owned();
}
let mut addr = format!("{}:{}", pf.host, pf.port);
self.port_forward_address = addr.clone();
match timeout(3000, TcpStream::connect(&addr)).await {
Ok(Ok(sock)) => {
self.port_forward_socket = Some(Framed::new(sock, BytesCodec::new()));
}
_ => {
if is_rdp {
addr = "RDP".to_owned();
}
self.send_login_error(format!(
"Failed to access remote {}, please make sure if it is open",
addr
))
.await;
return false;
}
}
} }
_ => { _ => {
if !self.check_privacy_mode_on().await { if !self.check_privacy_mode_on().await {
@@ -2275,7 +2236,9 @@ impl Connection {
// `is_logon_ui()` is a fallback for logon UI detection on Windows. // `is_logon_ui()` is a fallback for logon UI detection on Windows.
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let is_logon = || { let is_logon = || {
crate::platform::is_prelogin() || crate::platform::is_locked() || { crate::platform::is_prelogin()
|| crate::platform::is_locked()
|| {
match crate::platform::is_logon_ui() { match crate::platform::is_logon_ui() {
Ok(result) => result, Ok(result) => result,
Err(e) => { Err(e) => {
@@ -2314,9 +2277,7 @@ impl Connection {
if err_msg.is_empty() { if err_msg.is_empty() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
self.linux_headless_handle.wait_desktop_cm_ready().await; self.linux_headless_handle.wait_desktop_cm_ready().await;
if !self.send_logon_response_and_keep_alive().await { self.send_logon_response().await;
return false;
}
self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), self.authorized); self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), self.authorized);
} else { } else {
self.send_login_error(err_msg).await; self.send_login_error(err_msg).await;
@@ -2352,9 +2313,7 @@ impl Connection {
if err_msg.is_empty() { if err_msg.is_empty() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
self.linux_headless_handle.wait_desktop_cm_ready().await; self.linux_headless_handle.wait_desktop_cm_ready().await;
if !self.send_logon_response_and_keep_alive().await { self.send_logon_response().await;
return false;
}
self.try_start_cm(lr.my_id, lr.my_name, self.authorized); self.try_start_cm(lr.my_id, lr.my_name, self.authorized);
} else { } else {
self.send_login_error(err_msg).await; self.send_login_error(err_msg).await;
@@ -2372,9 +2331,7 @@ impl Connection {
self.update_failure(failure, true, 1); self.update_failure(failure, true, 1);
self.require_2fa.take(); self.require_2fa.take();
raii::AuthedConnID::set_session_2fa(self.session_key()); raii::AuthedConnID::set_session_2fa(self.session_key());
if !self.send_logon_response_and_keep_alive().await { self.send_logon_response().await;
return false;
}
self.try_start_cm( self.try_start_cm(
self.lr.my_id.to_owned(), self.lr.my_id.to_owned(),
self.lr.my_name.to_owned(), self.lr.my_name.to_owned(),
@@ -2425,9 +2382,7 @@ impl Connection {
if let Some((_instant, uuid_old)) = uuid_old { if let Some((_instant, uuid_old)) = uuid_old {
if uuid == uuid_old { if uuid == uuid_old {
self.from_switch = true; self.from_switch = true;
if !self.send_logon_response_and_keep_alive().await { self.send_logon_response().await;
return false;
}
self.try_start_cm( self.try_start_cm(
lr.my_id.clone(), lr.my_id.clone(),
lr.my_name.clone(), lr.my_name.clone(),
@@ -5393,8 +5348,9 @@ mod raii {
} }
pub fn check_wake_lock_on_setting_changed() { pub fn check_wake_lock_on_setting_changed() {
let current = let current = config::Config::get_bool_option(
config::Config::get_bool_option(keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS); keys::OPTION_KEEP_AWAKE_DURING_INCOMING_SESSIONS,
);
let cached = *WAKELOCK_KEEP_AWAKE_OPTION.lock().unwrap(); let cached = *WAKELOCK_KEEP_AWAKE_OPTION.lock().unwrap();
if cached != Some(current) { if cached != Some(current) {
Self::check_wake_lock(); Self::check_wake_lock();

View File

@@ -809,7 +809,7 @@ fn record_key_is_control_key(record_key: u64) -> bool {
#[inline] #[inline]
fn record_key_is_chr(record_key: u64) -> bool { fn record_key_is_chr(record_key: u64) -> bool {
record_key >= KEY_CHAR_START record_key < KEY_CHAR_START
} }
#[inline] #[inline]
@@ -1513,27 +1513,6 @@ fn get_control_key_value(key_event: &KeyEvent) -> i32 {
} }
} }
#[inline]
fn has_hotkey_modifiers(key_event: &KeyEvent) -> bool {
key_event.modifiers.iter().any(|ck| {
let v = ck.value();
v == ControlKey::Control.value()
|| v == ControlKey::RControl.value()
|| v == ControlKey::Meta.value()
|| v == ControlKey::RWin.value()
|| {
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
v == ControlKey::Alt.value() || v == ControlKey::RAlt.value()
}
#[cfg(target_os = "macos")]
{
false
}
}
})
}
fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) { fn release_unpressed_modifiers(en: &mut Enigo, key_event: &KeyEvent) {
let ck_value = get_control_key_value(key_event); let ck_value = get_control_key_value(key_event);
fix_modifiers(&key_event.modifiers[..], en, ck_value); fix_modifiers(&key_event.modifiers[..], en, ck_value);
@@ -1593,7 +1572,7 @@ fn need_to_uppercase(en: &mut Enigo) -> bool {
get_modifier_state(Key::Shift, en) || get_modifier_state(Key::CapsLock, en) get_modifier_state(Key::Shift, en) || get_modifier_state(Key::CapsLock, en)
} }
fn process_chr(en: &mut Enigo, chr: u32, down: bool, _hotkey: bool) { fn process_chr(en: &mut Enigo, chr: u32, down: bool) {
// On Wayland with uinput mode, use clipboard for character input // On Wayland with uinput mode, use clipboard for character input
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if !crate::platform::linux::is_x11() && wayland_use_uinput() { if !crate::platform::linux::is_x11() && wayland_use_uinput() {
@@ -1608,16 +1587,6 @@ fn process_chr(en: &mut Enigo, chr: u32, down: bool, _hotkey: bool) {
} }
} }
#[cfg(any(target_os = "macos", target_os = "windows"))]
if !_hotkey {
if down {
if let Ok(chr) = char::try_from(chr) {
en.key_sequence(&chr.to_string());
}
}
return;
}
let key = char_value_to_key(chr); let key = char_value_to_key(chr);
if down { if down {
@@ -1887,7 +1856,7 @@ fn legacy_keyboard_mode(evt: &KeyEvent) {
let record_key = chr as u64 + KEY_CHAR_START; let record_key = chr as u64 + KEY_CHAR_START;
record_pressed_key(KeysDown::EnigoKey(record_key), down); record_pressed_key(KeysDown::EnigoKey(record_key), down);
process_chr(&mut en, chr, down, has_hotkey_modifiers(evt)) process_chr(&mut en, chr, down)
} }
Some(key_event::Union::Unicode(chr)) => { Some(key_event::Union::Unicode(chr)) => {
// Same as Chr: release Shift for Unicode input // Same as Chr: release Shift for Unicode input

View File

@@ -1289,7 +1289,8 @@ impl<T: InvokeUiSession> Session<T> {
drop(connection_round_state_lock); drop(connection_round_state_lock);
let cloned = self.clone(); let cloned = self.clone();
*cloned.audit_guid.lock().unwrap() = String::new();
*cloned.last_audit_note.lock().unwrap() = String::new();
// override only if true // override only if true
if true == force_relay { if true == force_relay {
self.lc.write().unwrap().force_relay = true; self.lc.write().unwrap().force_relay = true;
@@ -1812,9 +1813,6 @@ impl<T: InvokeUiSession> Interface for Session<T> {
); );
} }
self.update_privacy_mode(); self.update_privacy_mode();
// Clear audit_guid when connection is established successfully
*self.audit_guid.lock().unwrap() = String::new();
*self.last_audit_note.lock().unwrap() = String::new();
// Save recent peers, then push event to flutter. So flutter can refresh peer page. // Save recent peers, then push event to flutter. So flutter can refresh peer page.
self.lc.write().unwrap().handle_peer_info(&pi); self.lc.write().unwrap().handle_peer_info(&pi);
self.set_peer_info(&pi); self.set_peer_info(&pi);