Compare commits

..

1 Commits

Author SHA1 Message Date
rustdesk
890282e385 avatar 2026-03-02 12:27:21 +08:00
17 changed files with 165 additions and 244 deletions

View File

@@ -4118,43 +4118,3 @@ String mouseButtonsToPeer(int buttons) {
return ''; return '';
} }
} }
/// Build an avatar widget from an avatar URL or data URI string.
/// Returns [fallback] if avatar is empty or cannot be decoded.
/// [borderRadius] defaults to [size]/2 (circle).
Widget? buildAvatarWidget({
required String avatar,
required double size,
double? borderRadius,
Widget? fallback,
}) {
final trimmed = avatar.trim();
if (trimmed.isEmpty) return fallback;
ImageProvider? imageProvider;
if (trimmed.startsWith('data:image/')) {
final comma = trimmed.indexOf(',');
if (comma > 0) {
try {
imageProvider = MemoryImage(base64Decode(trimmed.substring(comma + 1)));
} catch (_) {}
}
} else if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
imageProvider = NetworkImage(trimmed);
}
if (imageProvider == null) return fallback;
final radius = borderRadius ?? size / 2;
return ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: Image(
image: imageProvider,
width: size,
height: size,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
fallback ?? SizedBox.shrink(),
),
);
}

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,
@@ -2061,8 +2061,7 @@ class _AccountState extends State<_Account> {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: color: Theme.of(context).textTheme.bodySmall?.color,
Theme.of(context).textTheme.bodySmall?.color,
), ),
), ),
), ),
@@ -2077,13 +2076,28 @@ class _AccountState extends State<_Account> {
} }
Widget? _buildUserAvatar() { Widget? _buildUserAvatar() {
// Resolve relative avatar path at display time final avatar = gFFI.userModel.avatar.value.trim();
final avatar = if (avatar.isEmpty) return null;
bind.mainResolveAvatarUrl(avatar: gFFI.userModel.avatar.value); const radius = 22.0;
return buildAvatarWidget( if (avatar.startsWith('data:image/')) {
avatar: avatar, final comma = avatar.indexOf(',');
size: 44, if (comma > 0) {
); try {
return CircleAvatar(
radius: radius,
backgroundImage: MemoryImage(base64Decode(avatar.substring(comma + 1))),
);
} catch (_) {
return null;
}
}
} else if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
return CircleAvatar(
radius: radius,
backgroundImage: NetworkImage(avatar),
);
}
return null;
} }
} }

View File

@@ -1,6 +1,7 @@
// original cm window in Sciter version. // original cm window in Sciter version.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -568,13 +569,37 @@ class _CmHeaderState extends State<_CmHeader>
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
Widget _buildClientAvatar() { Widget _buildClientAvatar() {
return buildAvatarWidget( const borderRadius = BorderRadius.all(Radius.circular(15.0));
avatar: client.avatar, final avatar = client.avatar.trim();
size: 70, if (avatar.startsWith('data:image/')) {
borderRadius: 15, final comma = avatar.indexOf(',');
fallback: _buildInitialAvatar(), if (comma > 0) {
) ?? try {
_buildInitialAvatar(); final bytes = base64Decode(avatar.substring(comma + 1));
return ClipRRect(
borderRadius: borderRadius,
child: Image.memory(
bytes,
width: 70,
height: 70,
fit: BoxFit.cover,
),
);
} catch (_) {}
}
} else if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
return ClipRRect(
borderRadius: borderRadius,
child: Image.network(
avatar,
width: 70,
height: 70,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _buildInitialAvatar(),
),
);
}
return _buildInitialAvatar();
} }
Widget _buildInitialAvatar() { Widget _buildInitialAvatar() {
@@ -587,7 +612,7 @@ class _CmHeaderState extends State<_CmHeader>
borderRadius: BorderRadius.circular(15.0), borderRadius: BorderRadius.circular(15.0),
), ),
child: Text( child: Text(
client.name.isNotEmpty ? client.name[0] : '?', client.name[0],
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -856,17 +857,28 @@ class ClientInfo extends StatelessWidget {
} }
Widget _buildAvatar(BuildContext context) { Widget _buildAvatar(BuildContext context) {
final fallback = CircleAvatar( final avatar = client.avatar.trim();
backgroundColor: str2color(client.name, if (avatar.isNotEmpty) {
if (avatar.startsWith('data:image/')) {
final comma = avatar.indexOf(',');
if (comma > 0) {
try {
return CircleAvatar(
backgroundImage: MemoryImage(base64Decode(avatar.substring(comma + 1))),
);
} catch (_) {}
}
} else if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
return CircleAvatar(backgroundImage: NetworkImage(avatar));
}
}
// Show character as before if no avatar
return CircleAvatar(
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[0]),
); );
return buildAvatarWidget(
avatar: client.avatar,
size: 40,
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;
}); });
@@ -689,17 +689,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
? translate('Login') ? translate('Login')
: '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})')), : '${translate('Logout')} (${gFFI.userModel.accountLabelWithHandle})')),
leading: Obx(() { leading: Icon(Icons.person),
final avatar = bind.mainResolveAvatarUrl(
avatar: gFFI.userModel.avatar.value);
return buildAvatarWidget(
avatar: avatar,
size: 28,
borderRadius: null,
fallback: Icon(Icons.person),
) ??
Icon(Icons.person);
}),
onPressed: (context) { onPressed: (context) {
if (gFFI.userModel.userName.value.isEmpty) { if (gFFI.userModel.userName.value.isEmpty) {
loginDialog(); loginDialog();
@@ -839,12 +829,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

@@ -34,7 +34,6 @@ class UserModel {
} }
return '$preferred (@$username)'; return '$preferred (@$username)';
} }
WeakReference<FFI> parent; WeakReference<FFI> parent;
UserModel(this.parent) { UserModel(this.parent) {
@@ -89,6 +88,7 @@ class UserModel {
} }
final user = UserPayload.fromJson(data); final user = UserPayload.fromJson(data);
user.avatar = _resolveAvatar(user.avatar, url);
_parseAndUpdateUser(user); _parseAndUpdateUser(user);
} catch (e) { } catch (e) {
debugPrint('Failed to refreshCurrentUser: $e'); debugPrint('Failed to refreshCurrentUser: $e');
@@ -138,6 +138,7 @@ class UserModel {
avatar.value = user.avatar; avatar.value = user.avatar;
isAdmin.value = user.isAdmin; isAdmin.value = user.isAdmin;
bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(user)); bind.mainSetLocalOption(key: 'user_info', value: jsonEncode(user));
_updateLocalUserInfo();
if (isWeb) { if (isWeb) {
// ugly here, tmp solution // ugly here, tmp solution
bind.mainSetLocalOption(key: 'verifier', value: user.verifier ?? ''); bind.mainSetLocalOption(key: 'verifier', value: user.verifier ?? '');

View File

@@ -2034,9 +2034,5 @@ class RustdeskImpl {
return false; return false;
} }
String mainResolveAvatarUrl({required String avatar, dynamic hint}) {
return js.context.callMethod('getByName', ['resolve_avatar_url', avatar])?.toString() ?? avatar;
}
void dispose() {} void dispose() {}
} }

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

@@ -33,7 +33,7 @@ use crate::{
create_symmetric_key_msg, decode_id_pk, get_rs_pk, is_keyboard_mode_supported, create_symmetric_key_msg, decode_id_pk, get_rs_pk, is_keyboard_mode_supported,
kcp_stream::KcpStream, kcp_stream::KcpStream,
secure_tcp, secure_tcp,
ui_interface::{get_builtin_option, resolve_avatar_url, use_texture_render}, ui_interface::{get_builtin_option, use_texture_render},
ui_session_interface::{InvokeUiSession, Session}, ui_session_interface::{InvokeUiSession, Session},
}; };
#[cfg(feature = "unix-file-copy-paste")] #[cfg(feature = "unix-file-copy-paste")]
@@ -2638,7 +2638,6 @@ impl LoginConfigHandler {
}) })
.unwrap_or_default(); .unwrap_or_default();
} }
avatar = resolve_avatar_url(avatar);
let mut display_name = get_builtin_option(keys::OPTION_DISPLAY_NAME); let mut display_name = get_builtin_option(keys::OPTION_DISPLAY_NAME);
if display_name.is_empty() { if display_name.is_empty() {
display_name = display_name =

View File

@@ -1101,10 +1101,6 @@ pub fn main_get_api_server() -> String {
get_api_server() get_api_server()
} }
pub fn main_resolve_avatar_url(avatar: String) -> SyncReturn<String> {
SyncReturn(resolve_avatar_url(avatar))
}
pub fn main_http_request(url: String, method: String, body: Option<String>, header: String) { pub fn main_http_request(url: String, method: String, body: Option<String>, header: String) {
http_request(url, method, body, header) http_request(url, method, body, header)
} }

View File

@@ -17,7 +17,6 @@ lazy_static::lazy_static! {
const QUERY_INTERVAL_SECS: f32 = 1.0; const QUERY_INTERVAL_SECS: f32 = 1.0;
const QUERY_TIMEOUT_SECS: u64 = 60 * 3; const QUERY_TIMEOUT_SECS: u64 = 60 * 3;
const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth"; const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth";
const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth"; const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth";
const LOGIN_ACCOUNT_AUTH: &str = "Login account auth"; const LOGIN_ACCOUNT_AUTH: &str = "Login account auth";

View File

@@ -1584,6 +1584,6 @@ mod test {
#[test] #[test]
fn verify_ffi_enum_data_size() { fn verify_ffi_enum_data_size() {
println!("{}", std::mem::size_of::<Data>()); println!("{}", std::mem::size_of::<Data>());
assert!(std::mem::size_of::<Data>() <= 120); assert!(std::mem::size_of::<Data>() <= 96);
} }
} }

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

@@ -245,20 +245,39 @@ pub fn get_builtin_option(key: &str) -> String {
#[inline] #[inline]
pub fn set_local_option(key: String, value: String) { pub fn set_local_option(key: String, value: String) {
let value = normalize_local_option_value(&key, value);
LocalConfig::set_option(key.clone(), value); LocalConfig::set_option(key.clone(), value);
} }
/// Resolve relative avatar path (e.g. "/avatar/xxx") to absolute URL fn normalize_local_option_value(key: &str, value: String) -> String {
/// by prepending the API server address. if key != "user_info" || value.is_empty() {
pub fn resolve_avatar_url(avatar: String) -> String { return value;
let avatar = avatar.trim().to_owned();
if avatar.starts_with('/') {
let api_server = get_api_server();
if !api_server.is_empty() {
return format!("{}{}", api_server.trim_end_matches('/'), avatar);
}
} }
avatar let Ok(mut v) = serde_json::from_str::<serde_json::Value>(&value) else {
return value;
};
let Some(obj) = v.as_object_mut() else {
return value;
};
let Some(avatar) = obj
.get("avatar")
.and_then(|x| x.as_str())
.map(|x| x.trim().to_owned())
else {
return value;
};
if !avatar.starts_with('/') {
return value;
}
let api_server = get_api_server();
if api_server.is_empty() {
return value;
}
obj.insert(
"avatar".to_owned(),
serde_json::Value::String(format!("{}{}", api_server.trim_end_matches('/'), avatar)),
);
serde_json::to_string(&v).unwrap_or(value)
} }
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]