mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-14 03:05:11 +03:00
Compare commits
1 Commits
88ae00ba73
...
screenlock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3bd9fa933 |
1
flutter/assets/auth-azure.svg
Normal file
1
flutter/assets/auth-azure.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="199"><path fill="#0089d6" d="M118.432 187.698c32.89-5.81 60.055-10.618 60.367-10.684l.568-.12-31.052-36.935c-17.078-20.314-31.051-37.014-31.051-37.11 0-.182 32.063-88.477 32.243-88.792.06-.105 21.88 37.567 52.893 91.32 29.035 50.323 52.973 91.815 53.195 92.203l.405.707-98.684-.012-98.684-.013 59.8-10.564zM0 176.435c0-.052 14.631-25.451 32.514-56.442l32.514-56.347 37.891-31.799C123.76 14.358 140.867.027 140.935.001c.069-.026-.205.664-.609 1.534s-18.919 40.582-41.145 88.25l-40.41 86.67-29.386.037c-16.162.02-29.385-.005-29.385-.057z"/></svg>
|
||||
|
After Width: | Height: | Size: 604 B |
@@ -24,35 +24,6 @@ const kOpSvgList = [
|
||||
'microsoft'
|
||||
];
|
||||
|
||||
class _OidcProviderBranding {
|
||||
final String label;
|
||||
final String iconKey;
|
||||
|
||||
const _OidcProviderBranding({
|
||||
required this.label,
|
||||
required this.iconKey,
|
||||
});
|
||||
}
|
||||
|
||||
_OidcProviderBranding _oidcProviderBranding(String op) {
|
||||
switch (op.toLowerCase()) {
|
||||
case 'azure':
|
||||
return _OidcProviderBranding(
|
||||
label: 'Microsoft',
|
||||
iconKey: 'microsoft',
|
||||
);
|
||||
default:
|
||||
return _OidcProviderBranding(
|
||||
label: {
|
||||
'github': 'GitHub',
|
||||
'gitlab': 'GitLab',
|
||||
}[op.toLowerCase()] ??
|
||||
toCapitalized(op),
|
||||
iconKey: op.toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _IconOP extends StatelessWidget {
|
||||
final String op;
|
||||
final String? icon;
|
||||
@@ -103,8 +74,11 @@ class ButtonOP extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final branding = _oidcProviderBranding(op);
|
||||
final buttonLabel = translate("Continue with {${branding.label}}");
|
||||
final opLabel = {
|
||||
'github': 'GitHub',
|
||||
'gitlab': 'GitLab'
|
||||
}[op.toLowerCase()] ??
|
||||
toCapitalized(op);
|
||||
return Row(children: [
|
||||
Container(
|
||||
height: height,
|
||||
@@ -121,7 +95,7 @@ class ButtonOP extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: _IconOP(
|
||||
op: branding.iconKey,
|
||||
op: op,
|
||||
icon: icon,
|
||||
margin: EdgeInsets.only(right: 5),
|
||||
),
|
||||
@@ -129,7 +103,8 @@ class ButtonOP extends StatelessWidget {
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Center(child: Text(buttonLabel)),
|
||||
child: Center(
|
||||
child: Text(translate("Continue with {$opLabel}"))),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -55,8 +55,6 @@ import 'package:flutter_hbb/native/custom_cursor.dart'
|
||||
typedef HandleMsgBox = Function(Map<String, dynamic> evt, String id);
|
||||
typedef ReconnectHandle = Function(OverlayDialogManager, SessionID, bool);
|
||||
final _constSessionId = Uuid().v4obj();
|
||||
// Empirical restart reconnect cadence: keep the last frame briefly and retry quickly.
|
||||
const _restartReconnectSilentDelaySecs = 5;
|
||||
|
||||
class CachedPeerData {
|
||||
Map<String, dynamic> updatePrivacyMode = {};
|
||||
@@ -121,7 +119,6 @@ class FfiModel with ChangeNotifier {
|
||||
bool _touchMode = false;
|
||||
late VirtualMouseMode virtualMouseMode;
|
||||
Timer? _timer;
|
||||
Timer? _restartReconnectDelayTimer;
|
||||
var _reconnects = 1;
|
||||
DateTime? _offlineReconnectStartTime;
|
||||
bool _viewOnly = false;
|
||||
@@ -253,7 +250,6 @@ class FfiModel with ChangeNotifier {
|
||||
_inputBlocked = false;
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
resetRestartReconnectState();
|
||||
clearPermissions();
|
||||
waitForImageTimer?.cancel();
|
||||
timerScreenshot?.cancel();
|
||||
@@ -345,7 +341,6 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'connection_ready') {
|
||||
setConnectionType(peerId, evt['secure'] == 'true',
|
||||
evt['direct'] == 'true', evt['stream_type'] ?? '');
|
||||
resetRestartReconnectState();
|
||||
} else if (name == 'switch_display') {
|
||||
// switch display is kept for backward compatibility
|
||||
handleSwitchDisplay(evt, sessionId, peerId);
|
||||
@@ -927,28 +922,8 @@ class FfiModel with ChangeNotifier {
|
||||
enterUserLoginAndPasswordDialog(
|
||||
sessionId, dialogManager, 'terminal-admin-login-tip', false);
|
||||
} else if (type == 'restarting') {
|
||||
// Treat restart messages as reconnect control events. Rust still sends
|
||||
// title/text for legacy UI and translation reuse; Flutter keeps the last
|
||||
// frame briefly, then shows the Connecting overlay.
|
||||
if (_restartReconnectDelayTimer == null) {
|
||||
parent.target?.inputModel.setRelativeMouseMode(false);
|
||||
bind.sessionReconnect(sessionId: sessionId, forceRelay: false);
|
||||
clearPermissions();
|
||||
// Retry once more after the silent window so restart reconnect attempts
|
||||
// are spaced by the empirical short cadence instead of only updating UI.
|
||||
_restartReconnectDelayTimer =
|
||||
Timer(Duration(seconds: _restartReconnectSilentDelaySecs), () {
|
||||
_restartReconnectDelayTimer = null;
|
||||
if (parent.target?.closed == true) {
|
||||
return;
|
||||
}
|
||||
reconnect(dialogManager, sessionId, false);
|
||||
});
|
||||
}
|
||||
} else if (type == 'restarting-show') {
|
||||
_restartReconnectDelayTimer?.cancel();
|
||||
_restartReconnectDelayTimer = null;
|
||||
reconnect(dialogManager, sessionId, false);
|
||||
showMsgBox(sessionId, type, title, text, link, false, dialogManager,
|
||||
hasCancel: false);
|
||||
} else if (type == 'wait-remote-accept-nook') {
|
||||
showWaitAcceptDialog(sessionId, type, title, text, dialogManager);
|
||||
} else if (type == 'on-uac' || type == 'on-foreground-elevated') {
|
||||
@@ -974,11 +949,6 @@ class FfiModel with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void resetRestartReconnectState() {
|
||||
_restartReconnectDelayTimer?.cancel();
|
||||
_restartReconnectDelayTimer = null;
|
||||
}
|
||||
|
||||
/// Auto-retry check for "Remote desktop is offline" error.
|
||||
/// returns true to auto-retry, false otherwise.
|
||||
bool shouldAutoRetryOnOffline(
|
||||
@@ -1404,7 +1374,6 @@ class FfiModel with ChangeNotifier {
|
||||
if (displays.isNotEmpty) {
|
||||
_reconnects = 1;
|
||||
_offlineReconnectStartTime = null;
|
||||
resetRestartReconnectState();
|
||||
waitForFirstImage.value = true;
|
||||
isRefreshing = false;
|
||||
}
|
||||
@@ -3697,7 +3666,6 @@ class FFI {
|
||||
|
||||
/// Mobile reuse FFI
|
||||
void mobileReset() {
|
||||
ffiModel.resetRestartReconnectState();
|
||||
ffiModel.waitForFirstImage.value = true;
|
||||
ffiModel.isRefreshing = false;
|
||||
ffiModel.waitForImageDialogShow.value = true;
|
||||
@@ -3911,7 +3879,6 @@ class FFI {
|
||||
}
|
||||
if (ffiModel.waitForFirstImage.value == true) {
|
||||
ffiModel.waitForFirstImage.value = false;
|
||||
ffiModel.resetRestartReconnectState();
|
||||
dialogManager.dismissAll();
|
||||
await canvasModel.updateViewStyle();
|
||||
await canvasModel.updateScrollStyle();
|
||||
|
||||
114
src/client.rs
114
src/client.rs
@@ -30,6 +30,7 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
check_port,
|
||||
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
|
||||
common::PLATFORM_ADDITION_IS_LOGIN_SCREEN,
|
||||
create_symmetric_key_msg, decode_id_pk, get_rs_pk, is_keyboard_mode_supported,
|
||||
kcp_stream::KcpStream,
|
||||
secure_tcp,
|
||||
@@ -96,8 +97,6 @@ pub mod screenshot;
|
||||
|
||||
pub const MILLI1: Duration = Duration::from_millis(1);
|
||||
pub const SEC30: Duration = Duration::from_secs(30);
|
||||
// Empirical restart reconnect grace window.
|
||||
const RESTART_REMOTE_DEVICE_GRACE: Duration = Duration::from_secs(5 * 60);
|
||||
pub const VIDEO_QUEUE_SIZE: usize = 120;
|
||||
const MAX_DECODE_FAIL_COUNTER: usize = 3;
|
||||
|
||||
@@ -1742,10 +1741,7 @@ pub struct LoginConfigHandler {
|
||||
features: Option<Features>,
|
||||
pub session_id: u64, // used for local <-> server communication
|
||||
pub supported_encoding: SupportedEncoding,
|
||||
restarting_remote_device: bool,
|
||||
// Start time of the restart grace window. On Windows the peer may briefly
|
||||
// reconnect before the real reboot disconnect.
|
||||
restart_remote_device_at: Option<Instant>,
|
||||
pub restarting_remote_device: bool,
|
||||
pub force_relay: bool,
|
||||
pub direct: Option<bool>,
|
||||
pub received: bool,
|
||||
@@ -1854,7 +1850,7 @@ impl LoginConfigHandler {
|
||||
}
|
||||
self.session_id = sid;
|
||||
self.supported_encoding = Default::default();
|
||||
self.clear_restarting_remote_device();
|
||||
self.restarting_remote_device = false;
|
||||
self.force_relay =
|
||||
config::option2bool("force-always-relay", &self.get_option("force-always-relay"))
|
||||
|| force_relay
|
||||
@@ -1906,11 +1902,11 @@ impl LoginConfigHandler {
|
||||
|
||||
/// Check if the client should auto login.
|
||||
/// Return password if the client should auto login, otherwise return empty string.
|
||||
pub fn should_auto_login(&self) -> String {
|
||||
pub fn should_auto_login(&self, pi: &PeerInfo) -> String {
|
||||
let l = self.lock_after_session_end.v;
|
||||
let a = !self.get_option("auto-login").is_empty();
|
||||
let p = self.get_option("os-password");
|
||||
if !p.is_empty() && l && a {
|
||||
if !p.is_empty() && l && a && !peer_reports_unlocked_desktop(pi) {
|
||||
p
|
||||
} else {
|
||||
"".to_owned()
|
||||
@@ -2784,30 +2780,6 @@ impl LoginConfigHandler {
|
||||
msg_out
|
||||
}
|
||||
|
||||
pub fn mark_restarting_remote_device(&mut self) {
|
||||
self.restarting_remote_device = true;
|
||||
self.restart_remote_device_at = Some(Instant::now());
|
||||
}
|
||||
|
||||
pub fn clear_restarting_remote_device(&mut self) {
|
||||
self.restarting_remote_device = false;
|
||||
self.restart_remote_device_at = None;
|
||||
}
|
||||
|
||||
pub fn is_restarting_remote_device(&self) -> bool {
|
||||
if !self.restarting_remote_device {
|
||||
return false;
|
||||
}
|
||||
// Keep this flag alive for a short grace window instead of clearing it on
|
||||
// connection_ready or the first peer bytes. During OS restart the peer can
|
||||
// briefly reconnect before the real reboot disconnect, and clearing it too
|
||||
// early would let the next disconnect escape the restart flow and fall back
|
||||
// to the normal error dialog / manual reconnect path.
|
||||
self.restart_remote_device_at
|
||||
.map(|started_at| started_at.elapsed() < RESTART_REMOTE_DEVICE_GRACE)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn get_conn_token(&self) -> Option<String> {
|
||||
if self.password.is_empty() {
|
||||
return None;
|
||||
@@ -2833,6 +2805,67 @@ impl LoginConfigHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_reports_unlocked_desktop(pi: &PeerInfo) -> bool {
|
||||
serde_json::from_str::<HashMap<String, serde_json::Value>>(&pi.platform_additions)
|
||||
.ok()
|
||||
.and_then(|platform_additions| {
|
||||
platform_additions
|
||||
.get(PLATFORM_ADDITION_IS_LOGIN_SCREEN)
|
||||
.and_then(|value| value.as_bool())
|
||||
})
|
||||
== Some(false)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hbb_common::message_proto::PeerInfo;
|
||||
|
||||
fn login_config_handler() -> super::LoginConfigHandler {
|
||||
let mut handler = super::LoginConfigHandler::default();
|
||||
handler.config.lock_after_session_end.v = true;
|
||||
handler
|
||||
.config
|
||||
.options
|
||||
.insert("auto-login".to_owned(), "Y".to_owned());
|
||||
handler
|
||||
.config
|
||||
.options
|
||||
.insert("os-password".to_owned(), "secret".to_owned());
|
||||
handler
|
||||
}
|
||||
|
||||
fn peer_info(platform_additions: &str) -> PeerInfo {
|
||||
PeerInfo {
|
||||
platform_additions: platform_additions.to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_auto_login_skips_unlocked_peer() {
|
||||
let handler = login_config_handler();
|
||||
let pi = peer_info(r#"{"is_login_screen":false}"#);
|
||||
|
||||
assert_eq!("", handler.should_auto_login(&pi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_auto_login_keeps_peer_on_login_screen() {
|
||||
let handler = login_config_handler();
|
||||
let pi = peer_info(r#"{"is_login_screen":true}"#);
|
||||
|
||||
assert_eq!("secret", handler.should_auto_login(&pi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_auto_login_keeps_legacy_peer_without_login_screen_state() {
|
||||
let handler = login_config_handler();
|
||||
let pi = peer_info("");
|
||||
|
||||
assert_eq!("secret", handler.should_auto_login(&pi));
|
||||
}
|
||||
}
|
||||
|
||||
/// Media data.
|
||||
pub enum MediaData {
|
||||
VideoQueue,
|
||||
@@ -3747,18 +3780,9 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
||||
fn on_establish_connection_error(&self, err: String) {
|
||||
let title = "Connection Error";
|
||||
let text = err.to_string();
|
||||
let lch = self.get_lch();
|
||||
let (is_restarting, direct, received) = {
|
||||
let lc = lch.read().unwrap();
|
||||
(lc.is_restarting_remote_device(), lc.direct, lc.received)
|
||||
};
|
||||
if is_restarting {
|
||||
log::info!("Restart remote device, suppress connection error: {err}");
|
||||
// Flutter treats this as a reconnect control event. The text is kept
|
||||
// for legacy UI and existing translation reuse.
|
||||
self.msgbox("restarting", "Restarting remote device", "Connection in progress. Please wait.", "");
|
||||
return;
|
||||
}
|
||||
let lc = self.get_lch();
|
||||
let direct = lc.read().unwrap().direct;
|
||||
let received = lc.read().unwrap().received;
|
||||
|
||||
let mut relay_hint = false;
|
||||
let mut relay_hint_type = "relay-hint";
|
||||
|
||||
@@ -10,10 +10,6 @@ use crate::{
|
||||
common::get_default_sound_input,
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
|
||||
// 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);
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
use crate::{clipboard::try_empty_clipboard_files, clipboard_file::unix_file_clip};
|
||||
#[cfg(any(
|
||||
@@ -157,6 +153,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
};
|
||||
|
||||
let mut last_recv_time = Instant::now();
|
||||
let mut received = false;
|
||||
let conn_type = if self.handler.is_file_transfer() {
|
||||
ConnType::FILE_TRANSFER
|
||||
@@ -222,7 +219,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
let mut fps_instant = Instant::now();
|
||||
|
||||
let _keep_it = client::hc_connection(feedback, rendezvous_server, token).await;
|
||||
let mut last_recv_time = Instant::now();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -248,7 +244,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
} else {
|
||||
if self.handler.is_restarting_remote_device() {
|
||||
log::info!("Restart remote device");
|
||||
self.handler.msgbox("restarting", "Restarting remote device", "Connection in progress. Please wait.", "");
|
||||
self.handler.msgbox("restarting", "Restarting remote device", "remote_restarting_tip", "");
|
||||
} else {
|
||||
log::info!("Reset by the peer");
|
||||
self.handler.msgbox("error", "Connection Error", "Reset by the peer", "");
|
||||
@@ -283,12 +279,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
if self.handler.is_restarting_remote_device()
|
||||
&& last_recv_time.elapsed() >= RESTART_REMOTE_DEVICE_NO_DATA_TIMEOUT
|
||||
{
|
||||
self.handler.msgbox("restarting-show", "Restarting remote device", "Connection in progress. Please wait.", "");
|
||||
break;
|
||||
}
|
||||
let elapsed = fps_instant.elapsed().as_millis();
|
||||
if elapsed < 1000 {
|
||||
continue;
|
||||
|
||||
@@ -59,6 +59,7 @@ pub const PLATFORM_WINDOWS: &str = "Windows";
|
||||
pub const PLATFORM_LINUX: &str = "Linux";
|
||||
pub const PLATFORM_MACOS: &str = "Mac OS";
|
||||
pub const PLATFORM_ANDROID: &str = "Android";
|
||||
pub const PLATFORM_ADDITION_IS_LOGIN_SCREEN: &str = "is_login_screen";
|
||||
|
||||
pub const TIMER_OUT: Duration = Duration::from_secs(1);
|
||||
pub const DEFAULT_KEEP_ALIVE: i32 = 60_000;
|
||||
|
||||
@@ -430,7 +430,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Strong", "Forte"),
|
||||
("Switch Sides", "Trocar de lado"),
|
||||
("Please confirm if you want to share your desktop?", "Por favor, confirme se você deseja compartilhar sua área de trabalho?"),
|
||||
("Display", "Exibição"),
|
||||
("Display", "Display"),
|
||||
("Default View Style", "Estilo de Visualização Padrão"),
|
||||
("Default Scroll Style", "Estilo de Rolagem Padrão"),
|
||||
("Default Image Quality", "Qualidade de Imagem Padrão"),
|
||||
@@ -744,7 +744,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("password-hidden-tip", "A senha permanente está definida como (oculta)."),
|
||||
("preset-password-in-use-tip", "A senha predefinida está sendo usada."),
|
||||
("Enable privacy mode", "Habilitar modo de privacidade"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Fixar a barra de ferramentas remota em qualquer borda da janela"),
|
||||
("allow-remote-toolbar-docking-any-edge", "Permitir fixar a barra de ferramentas remota em qualquer borda da janela"),
|
||||
("API Token", "Token de API"),
|
||||
("Deploy", "Implantar"),
|
||||
("Custom ID (optional)", "ID personalizado (opcional)"),
|
||||
|
||||
@@ -1623,6 +1623,10 @@ impl Connection {
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
platform_additions.insert(
|
||||
crate::common::PLATFORM_ADDITION_IS_LOGIN_SCREEN.into(),
|
||||
json!(crate::platform::is_prelogin() || crate::platform::is_locked()),
|
||||
);
|
||||
platform_additions.insert(
|
||||
"supported_privacy_mode_impl".into(),
|
||||
json!(privacy_mode::get_supported_privacy_mode_impl()),
|
||||
|
||||
@@ -560,7 +560,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
|
||||
pub fn restart_remote_device(&self) {
|
||||
let mut lc = self.lc.write().unwrap();
|
||||
lc.mark_restarting_remote_device();
|
||||
lc.restarting_remote_device = true;
|
||||
let msg = lc.restart_remote_device();
|
||||
self.send(Data::Message(msg));
|
||||
}
|
||||
@@ -656,7 +656,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
|
||||
pub fn is_restarting_remote_device(&self) -> bool {
|
||||
self.lc.read().unwrap().is_restarting_remote_device()
|
||||
self.lc.read().unwrap().restarting_remote_device
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1806,7 +1806,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
||||
return;
|
||||
}
|
||||
self.try_change_init_resolution(pi.current_display);
|
||||
let p = self.lc.read().unwrap().should_auto_login();
|
||||
let p = self.lc.read().unwrap().should_auto_login(&pi);
|
||||
if !p.is_empty() {
|
||||
input_os_password(p, true, self.clone());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user