From 4bf3764b5d1d4c1f729bdb8e032866fd0dadc6b4 Mon Sep 17 00:00:00 2001 From: Sahil Yeole <73148455+sahilyeole@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:29:17 +0530 Subject: [PATCH] Feat: Windows connect to a specific user session (#6825) * feat windows connect to specific user session Signed-off-by: Sahil Yeole * fix import Signed-off-by: Sahil Yeole * fix multiple user session fields Signed-off-by: Sahil Yeole * fix build Signed-off-by: Sahil Yeole * fix build Signed-off-by: Sahil Yeole * fix file transfer Signed-off-by: Sahil Yeole * fix text color on light theme Signed-off-by: Sahil Yeole * feat windows connect to specific user session code changes and sciter support Signed-off-by: Sahil Yeole * update texts Signed-off-by: Sahil Yeole * fix sciter selected user session Signed-off-by: Sahil Yeole * add translations Signed-off-by: Sahil Yeole * Use Y,N options * feat windows specific user code changes Signed-off-by: Sahil Yeole * Update dialog.dart * Update connection.rs * Update connection.rs * feat windows specific user code changes Signed-off-by: Sahil Yeole * fix sciter Signed-off-by: Sahil Yeole * use lr.union Signed-off-by: Sahil Yeole * remove unused peer options Signed-off-by: Sahil Yeole * select user only when authorised and no existing connection Signed-off-by: Sahil Yeole * check for multiple users only once Signed-off-by: Sahil Yeole * optimise and add check for client version Signed-off-by: Sahil Yeole * use misc option message Signed-off-by: Sahil Yeole * update rdp user session proto Signed-off-by: Sahil Yeole * fix show cm on user session Signed-off-by: Sahil Yeole * Update pl.rs * update on_message Signed-off-by: Sahil Yeole * fix cm Signed-off-by: Sahil Yeole * remove user_session_id Signed-off-by: Sahil Yeole * fix cm Signed-off-by: Sahil Yeole * fix multiple connections Signed-off-by: Sahil Yeole --------- Signed-off-by: Sahil Yeole --- flutter/lib/common/widgets/dialog.dart | 103 +++++++++++++++ .../lib/mobile/pages/file_manager_page.dart | 1 + flutter/lib/models/model.dart | 18 ++- libs/hbb_common/protos/message.proto | 11 ++ src/client.rs | 15 ++- src/client/io_loop.rs | 7 ++ src/flutter.rs | 12 ++ src/flutter_ffi.rs | 4 +- src/ipc.rs | 8 ++ src/lang/ar.rs | 2 + src/lang/ca.rs | 2 + src/lang/cn.rs | 2 + src/lang/cs.rs | 2 + src/lang/da.rs | 2 + src/lang/de.rs | 2 + src/lang/el.rs | 2 + src/lang/eo.rs | 2 + src/lang/es.rs | 2 + src/lang/et.rs | 2 + src/lang/fa.rs | 2 + src/lang/fr.rs | 2 + src/lang/hu.rs | 2 + src/lang/id.rs | 2 + src/lang/it.rs | 2 + src/lang/ja.rs | 2 + src/lang/ko.rs | 2 + src/lang/kz.rs | 2 + src/lang/lt.rs | 2 + src/lang/lv.rs | 2 + src/lang/nb.rs | 2 + src/lang/nl.rs | 2 + src/lang/pl.rs | 2 + src/lang/pt_PT.rs | 2 + src/lang/ptbr.rs | 2 + src/lang/ro.rs | 2 + src/lang/ru.rs | 2 + src/lang/sk.rs | 2 + src/lang/sl.rs | 2 + src/lang/sq.rs | 2 + src/lang/sr.rs | 2 + src/lang/sv.rs | 2 + src/lang/template.rs | 2 + src/lang/th.rs | 2 + src/lang/tr.rs | 2 + src/lang/tw.rs | 2 + src/lang/ua.rs | 2 + src/lang/vn.rs | 2 + src/platform/windows.cc | 69 +++++++++++ src/platform/windows.rs | 117 +++++++++++++++++- src/server/connection.rs | 88 ++++++++++++- src/ui/common.css | 15 +++ src/ui/common.tis | 52 +++++++- src/ui/header.tis | 4 + src/ui/remote.rs | 19 ++- src/ui_session_interface.rs | 10 +- 55 files changed, 607 insertions(+), 22 deletions(-) diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 78487eb86..e7a720c8a 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1861,3 +1861,106 @@ void enter2FaDialog( onCancel: cancel); }); } + +void showWindowsSessionsDialog( + String type, + String title, + String text, + OverlayDialogManager dialogManager, + SessionID sessionId, + String peerId, + String sessions) { + List sessionsList = sessions.split(','); + Map sessionMap = {}; + for (var session in sessionsList) { + var sessionInfo = session.split('-'); + if (sessionInfo.isNotEmpty) { + sessionMap[sessionInfo[0]] = sessionInfo[1]; + } + } + String selectedUserValue = sessionMap.keys.first; + dialogManager.dismissAll(); + dialogManager.show((setState, close, context) { + onConnect() { + bind.sessionReconnect( + sessionId: sessionId, + forceRelay: false, + userSessionId: selectedUserValue); + dialogManager.dismissAll(); + dialogManager.showLoading(translate('Connecting...'), + onCancel: closeConnection); + } + + return CustomAlertDialog( + title: null, + content: msgboxContent(type, title, text), + actions: [ + SessionsDropdown(peerId, sessionId, sessionMap, (value) { + setState(() { + selectedUserValue = value; + }); + }), + dialogButton('Connect', onPressed: onConnect, isOutline: false), + ], + ); + }); +} + +class SessionsDropdown extends StatefulWidget { + final String peerId; + final SessionID sessionId; + final Map sessions; + final Function(String) onValueChanged; + + SessionsDropdown( + this.peerId, this.sessionId, this.sessions, this.onValueChanged); + + @override + _SessionsDropdownState createState() => _SessionsDropdownState(); +} + +class _SessionsDropdownState extends State { + late String selectedValue; + @override + void initState() { + super.initState(); + selectedValue = widget.sessions.keys.first; + } + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + child: DropdownButton( + value: selectedValue, + isExpanded: true, + borderRadius: BorderRadius.circular(8), + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), + items: widget.sessions.entries.map((entry) { + return DropdownMenuItem( + value: entry.key, + child: Text( + entry.value, + style: TextStyle( + color: MyTheme.currentThemeMode() == ThemeMode.dark + ? Colors.white + : MyTheme.dark, + ), + ), + ); + }).toList(), + onChanged: (value) { + if (value != null) { + setState(() { + selectedValue = value; + }); + widget.onValueChanged(value); + } + }, + style: TextStyle( + fontSize: 16.0, + ), + ), + ); + } +} diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index e9d0816ad..1e9a070fe 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_breadcrumb/flutter_breadcrumb.dart'; import 'package:flutter_hbb/models/file_model.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import 'package:get/get.dart'; import 'package:toggle_switch/toggle_switch.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 5817e187a..6a0c8d6d3 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -245,6 +245,8 @@ class FfiModel with ChangeNotifier { var name = evt['name']; if (name == 'msgbox') { handleMsgBox(evt, sessionId, peerId); + } else if (name == 'set_multiple_user_session') { + handleMultipleUserSession(evt, sessionId, peerId); } else if (name == 'peer_info') { handlePeerInfo(evt, peerId, false); } else if (name == 'sync_peer_info') { @@ -488,6 +490,19 @@ class FfiModel with ChangeNotifier { dialogManager.dismissByTag(tag); } + handleMultipleUserSession( + Map evt, SessionID sessionId, String peerId) { + if (parent.target == null) return; + final dialogManager = parent.target!.dialogManager; + final sessions = evt['user_sessions']; + final title = translate('Multiple active user sessions found'); + final text = translate('Please select the user you want to connect to'); + final type = ""; + + showWindowsSessionsDialog( + type, title, text, dialogManager, sessionId, peerId, sessions); + } + /// Handle the message box event based on [evt] and [id]. handleMsgBox(Map evt, SessionID sessionId, String peerId) { if (parent.target == null) return; @@ -549,7 +564,8 @@ class FfiModel with ChangeNotifier { void reconnect(OverlayDialogManager dialogManager, SessionID sessionId, bool forceRelay) { - bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay); + bind.sessionReconnect( + sessionId: sessionId, forceRelay: forceRelay, userSessionId: ""); clearPermissions(); dialogManager.dismissAll(); dialogManager.showLoading(translate('Connecting...'), diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 22750c1ec..66df3a656 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -124,6 +124,11 @@ message PeerInfo { string platform_additions = 12; } +message RdpUserSession { + string user_session_id = 1; + string user_name = 2; +} + message LoginResponse { oneof union { string error = 1; @@ -589,6 +594,7 @@ message OptionMessage { BoolOption disable_keyboard = 12; // Position 13 is used for Resolution. Remove later. // Resolution custom_resolution = 13; + string user_session = 14; } message TestDelay { @@ -703,6 +709,10 @@ message PluginFailure { string msg = 3; } +message RdpUserSessions { + repeated RdpUserSession rdp_user_sessions = 1; +} + message Misc { oneof union { ChatMessage chat_message = 4; @@ -734,6 +744,7 @@ message Misc { ToggleVirtualDisplay toggle_virtual_display = 32; TogglePrivacyMode toggle_privacy_mode = 33; SupportedEncoding supported_encoding = 34; + RdpUserSessions rdp_user_sessions = 35; } } diff --git a/src/client.rs b/src/client.rs index b0045e736..28c262ed0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1149,6 +1149,7 @@ pub struct LoginConfigHandler { pub custom_fps: Arc>>, pub adapter_luid: Option, pub mark_unsupported: Vec, + pub selected_user_session_id: String, } impl Deref for LoginConfigHandler { @@ -1235,6 +1236,7 @@ impl LoginConfigHandler { self.received = false; self.switch_uuid = switch_uuid; self.adapter_luid = adapter_luid; + self.selected_user_session_id = "".to_owned(); } /// Check if the client should auto login. @@ -1511,14 +1513,17 @@ impl LoginConfigHandler { /// /// * `ignore_default` - If `true`, ignore the default value of the option. fn get_option_message(&self, ignore_default: bool) -> Option { - if self.conn_type.eq(&ConnType::FILE_TRANSFER) - || self.conn_type.eq(&ConnType::PORT_FORWARD) - || self.conn_type.eq(&ConnType::RDP) - { + if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) { return None; } let mut n = 0; let mut msg = OptionMessage::new(); + msg.user_session = self.selected_user_session_id.clone(); + n += 1; + + if self.conn_type.eq(&ConnType::FILE_TRANSFER) { + return Some(msg); + } let q = self.image_quality.clone(); if let Some(q) = self.get_image_quality_enum(&q, ignore_default) { msg.image_quality = q.into(); @@ -1581,7 +1586,6 @@ impl LoginConfigHandler { &self.mark_unsupported, )); n += 1; - if n > 0 { Some(msg) } else { @@ -2740,6 +2744,7 @@ pub trait Interface: Send + Clone + 'static + Sized { fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str); fn handle_login_error(&self, err: &str) -> bool; fn handle_peer_info(&self, pi: PeerInfo); + fn set_multiple_user_sessions(&self, sessions: Vec); fn on_error(&self, err: &str) { self.msgbox("error", "Error", err, ""); } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 7496a4bd2..2dbf4f7dc 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1314,6 +1314,13 @@ impl Remote { } } Some(message::Union::Misc(misc)) => match misc.union { + Some(misc::Union::RdpUserSessions(sessions)) => { + if !sessions.rdp_user_sessions.is_empty() { + self.handler + .set_multiple_user_session(sessions.rdp_user_sessions); + return false; + } + } Some(misc::Union::AudioFormat(f)) => { self.audio_sender.send(MediaData::AudioFormat(f)).ok(); } diff --git a/src/flutter.rs b/src/flutter.rs index c4e81e342..160e40c3b 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -826,6 +826,18 @@ impl InvokeUiSession for FlutterHandler { ) } + fn set_multiple_user_session(&self, sessions: Vec) { + let formatted_sessions: Vec = sessions + .iter() + .map(|session| format!("{}-{}", session.user_session_id, session.user_name)) + .collect(); + let sessions = formatted_sessions.join(","); + self.push_event( + "set_multiple_user_session", + vec![("user_sessions", &sessions)], + ); + } + fn on_connected(&self, _conn_type: ConnType) {} fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index e8e9b953a..71c65cffe 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -217,9 +217,9 @@ pub fn session_record_status(session_id: SessionID, status: bool) { } } -pub fn session_reconnect(session_id: SessionID, force_relay: bool) { +pub fn session_reconnect(session_id: SessionID, force_relay: bool, user_session_id: String) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.reconnect(force_relay); + session.reconnect(force_relay, user_session_id); } session_on_waiting_for_image_dialog_show(session_id); } diff --git a/src/ipc.rs b/src/ipc.rs index 8b496dd9b..83533c447 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -187,6 +187,7 @@ pub enum Data { Authorize, Close, SAS, + UserSid(Option), OnlineStatus(Option<(i64, bool)>), Config((String, Option)), Options(Option>), @@ -917,6 +918,13 @@ pub fn close_all_instances() -> ResultType { } } +#[tokio::main(flavor = "current_thread")] +pub async fn connect_to_user_session(usid: Option) -> ResultType<()> { + let mut stream = crate::ipc::connect(1000, crate::POSTFIX_SERVICE).await?; + timeout(1000, stream.send(&crate::ipc::Data::UserSid(usid))).await??; + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/lang/ar.rs b/src/lang/ar.rs index cd57ef865..b797ade45 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 246825d18..367ea0368 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 7103de5bd..3f7b85bd4 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "双重认证"), ("Email verification code must be 6 characters.", "Email 验证码必须是 6 个字符。"), ("2FA code must be 6 digits.", "双重认证代码必须是 6 位数字。"), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 5cc8c00c0..3d639edf0 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Dvoufaktorová autentizace"), ("Email verification code must be 6 characters.", "E-mailový ověřovací kód musí mít 6 znaků."), ("2FA code must be 6 digits.", "Kód 2FA musí mít 6 číslic."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 52f423b24..abbfb7ef8 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index eaf326d4f..22e70ac42 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Zwei-Faktor-Authentifizierung"), ("Email verification code must be 6 characters.", "Der E-Mail-Verifizierungscode muss aus 6 Zeichen bestehen."), ("2FA code must be 6 digits.", "Der 2FA-Code muss 6 Ziffern haben."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 7070eb2aa..6bdfe3ff5 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index ba73d48bd..b61e88c1b 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 6af061323..2798a1a1d 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Autenticación en dos pasos"), ("Email verification code must be 6 characters.", "El código de verificación por mail debe tener 6 caracteres"), ("2FA code must be 6 digits.", "El cóidigo 2FA debe tener 6 dígitos"), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 651189aa6..b12b48dbe 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 59a9df4fb..6c6851e83 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "احراز هویت دو مرحله ای"), ("Email verification code must be 6 characters.", "کد تأیید ایمیل باید 6 کاراکتر باشد"), ("2FA code must be 6 digits.", "کد احراز هویت دو مرحله ای باید 6 رقم باشد"), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 757f0a399..333049225 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 623ab273f..e1a207cb7 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 31e1018e2..f628fa97c 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index e2fed1085..9eb1c28a1 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Autenticazione a due fattori"), ("Email verification code must be 6 characters.", "Il codice di verifica email deve contenere 6 caratteri."), ("2FA code must be 6 digits.", "Il codice 2FA deve essere composto da 6 cifre."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index f2328a8b2..3b2bc1cb2 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index b8174a44c..bdf3b694c 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 61f3ba5ee..45dbb9b39 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 7588830f8..ca9e289fb 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index e2177b964..3dcdc8677 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Divu faktoru autentifikācija"), ("Email verification code must be 6 characters.", "E-pasta verifikācijas kodam jābūt ar 6 rakstzīmēm."), ("2FA code must be 6 digits.", "2FA kodam ir jābūt ar 6 cipariem."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index 6c091665b..03bc346a6 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 2487cb39c..6dc613096 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -585,5 +585,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "geef-2fa-titel in"), ("Email verification code must be 6 characters.", "E-mailverificatiecode moet 6 tekens lang zijn."), ("2FA code must be 6 digits.", "2FA-code moet 6 cijfers lang zijn."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index b4567c9ab..93a18aeb9 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Autoryzacja dwuskładnikowa"), ("Email verification code must be 6 characters.", "Kod weryfikacyjny wysłany e-mailem musi mieć 6 znaków."), ("2FA code must be 6 digits.", "Kod 2FA musi zawierać 6 cyfr."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index d3775aef7..32fa059a6 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index e52612bc1..a8462305e 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index a7cf40490..5ed9a4a5d 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3048e0304..1c793ea11 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Двухфакторная аутентификация"), ("Email verification code must be 6 characters.", "Код подтверждения электронной почты должен состоять из 6 символов."), ("2FA code must be 6 digits.", "Код двухфакторной аутентификации должен состоять из 6 цифр."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index dbfea23d3..b73d0c0ee 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "Dvojfaktorové overenie"), ("Email verification code must be 6 characters.", "Overovací kód e-mailu musí mať 6 znakov."), ("2FA code must be 6 digits.", "Kód 2FA musí obsahovať 6 číslic."), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index c29a8884e..49c94b720 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 34d45fe26..9c13940a4 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 80712b544..76c1a1150 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 33340d423..26acc87ef 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 762f98ec7..4702ac2ed 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 547fb096a..613ff24a8 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index a6fd869d8..95143e982 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 350a2a138..7e3b7437e 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", "二步驟驗證"), ("Email verification code must be 6 characters.", "Email 驗證碼必須是 6 個字元。"), ("2FA code must be 6 digits.", "二步驟驗證碼必須是 6 位數字。"), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 3755207cb..53ff61144 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index c6e71d2e7..c403456ba 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -586,5 +586,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("enter-2fa-title", ""), ("Email verification code must be 6 characters.", ""), ("2FA code must be 6 digits.", ""), + ("Multiple active user sessions found", ""), + ("Please select the user you want to connect to", ""), ].iter().cloned().collect(); } diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 8e2f73fbe..e674a2230 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -9,6 +9,7 @@ #include // NOLINT(build/include_order) #include #include +#include void flog(char const *fmt, ...) { @@ -433,6 +434,74 @@ extern "C" return nout; } + uint32_t get_current_process_session_id() + { + DWORD sessionId = 0; + HANDLE hProcess = GetCurrentProcess(); + if (hProcess) { + ProcessIdToSessionId(GetCurrentProcessId(), &sessionId); + CloseHandle(hProcess); + } + return sessionId; + } + + uint32_t get_session_user_info(PWSTR bufin, uint32_t nin, BOOL rdp, uint32_t id) + { + uint32_t nout = 0; + PWSTR buf = NULL; + DWORD n = 0; + if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, id, WTSUserName, &buf, &n)) + { + if (buf) + { + nout = min(nin, n); + memcpy(bufin, buf, nout); + WTSFreeMemory(buf); + } + } + return nout; + } + + void get_available_session_ids(PWSTR buf, uint32_t bufSize, BOOL include_rdp) { + std::vector sessionIds; + PWTS_SESSION_INFOA pInfos; + DWORD count; + + if (WTSEnumerateSessionsA(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pInfos, &count)) { + for (DWORD i = 0; i < count; i++) { + auto info = pInfos[i]; + auto rdp = "rdp"; + auto nrdp = strlen(rdp); + if (info.State == WTSActive) { + if (info.pWinStationName == NULL) + continue; + if (info.SessionId == 65536 || info.SessionId == 655) + continue; + + if (!stricmp(info.pWinStationName, "console")){ + sessionIds.push_back(std::wstring(L"Console:") + std::to_wstring(info.SessionId)); + } + else if (include_rdp && !strnicmp(info.pWinStationName, rdp, nrdp)) { + sessionIds.push_back(std::wstring(L"RDP:") + std::to_wstring(info.SessionId)); + } + } + } + WTSFreeMemory(pInfos); + } + + std::wstring tmpStr; + for (size_t i = 0; i < sessionIds.size(); i++) { + if (i > 0) { + tmpStr += L","; + } + tmpStr += sessionIds[i]; + } + + if (buf && !tmpStr.empty() && tmpStr.size() < bufSize) { + memcpy(buf, tmpStr.c_str(), (tmpStr.size() + 1) * sizeof(wchar_t)); + } + } + BOOL has_rdp_service() { PWTS_SESSION_INFOA pInfos; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 84ca39e1e..59e155dbc 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -5,6 +5,7 @@ use crate::{ license::*, privacy_mode::win_topmost_window::{self, WIN_TOPMOST_INJECTED_PROCESS_EXE}, }; +use hbb_common::libc::{c_int, wchar_t}; use hbb_common::{ allow_err, anyhow::anyhow, @@ -508,7 +509,16 @@ async fn run_service(_arguments: Vec) -> ResultType<()> { log::info!("session id {}", session_id); let mut h_process = launch_server(session_id, true).await.unwrap_or(NULL); let mut incoming = ipc::new_listener(crate::POSTFIX_SERVICE).await?; + let mut stored_usid = None; loop { + let sids = get_all_active_session_ids(); + if !sids.contains(&format!("{}", session_id)) || !is_share_rdp() { + let current_active_session = unsafe { get_current_session(share_rdp()) }; + if session_id != current_active_session { + session_id = current_active_session; + h_process = launch_server(session_id, true).await.unwrap_or(NULL); + } + } let res = timeout(super::SERVICE_INTERVAL, incoming.next()).await; match res { Ok(res) => match res { @@ -523,6 +533,21 @@ async fn run_service(_arguments: Vec) -> ResultType<()> { ipc::Data::SAS => { send_sas(); } + ipc::Data::UserSid(usid) => { + if let Some(usid) = usid { + if session_id != usid { + log::info!( + "session changed from {} to {}", + session_id, + usid + ); + session_id = usid; + stored_usid = Some(session_id); + h_process = + launch_server(session_id, true).await.unwrap_or(NULL); + } + } + } _ => {} } } @@ -537,7 +562,7 @@ async fn run_service(_arguments: Vec) -> ResultType<()> { continue; } let mut close_sent = false; - if tmp != session_id { + if tmp != session_id && stored_usid != Some(session_id) { log::info!("session changed from {} to {}", session_id, tmp); session_id = tmp; send_close_async("").await.ok(); @@ -603,13 +628,16 @@ async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType) -> ResultType> { +pub fn run_as_user(arg: Vec<&str>, usid: Option) -> ResultType> { let cmd = format!( "\"{}\" {}", std::env::current_exe()?.to_str().unwrap_or(""), arg.join(" "), ); - let session_id = unsafe { get_current_session(share_rdp()) }; + let mut session_id = get_current_process_session_id(); + if let Some(usid) = usid { + session_id = usid; + } use std::os::windows::ffi::OsStrExt; let wstr: Vec = std::ffi::OsStr::new(&cmd) .encode_wide() @@ -684,10 +712,10 @@ pub fn try_change_desktop() -> bool { } fn share_rdp() -> BOOL { - if get_reg("share_rdp") != "true" { - FALSE - } else { + if get_reg("share_rdp") != "false" { TRUE + } else { + FALSE } } @@ -705,6 +733,13 @@ pub fn set_share_rdp(enable: bool) { run_cmds(cmd, false, "share_rdp").ok(); } +pub fn get_current_process_session_id() -> u32 { + extern "C" { + fn get_current_process_session_id() -> u32; + } + unsafe { get_current_process_session_id() } +} + pub fn get_active_username() -> String { if !is_root() { return crate::username(); @@ -727,6 +762,76 @@ pub fn get_active_username() -> String { .to_owned() } +pub fn get_all_active_sessions() -> Vec> { + let sids = get_all_active_session_ids_with_station(); + let mut out = Vec::new(); + for sid in sids.split(',') { + let username = get_session_username(sid.to_owned()); + if !username.is_empty() { + let sid_split = sid.split(':').collect::>()[1]; + let v = vec![sid_split.to_owned(), username]; + out.push(v); + } + } + out +} + +pub fn get_session_username(session_id_with_station_name: String) -> String { + let mut session_id = session_id_with_station_name.split(':'); + let station = session_id.next().unwrap_or(""); + let session_id = session_id.next().unwrap_or(""); + if session_id == "" { + return "".to_owned(); + } + + extern "C" { + fn get_session_user_info(path: *mut u16, n: u32, rdp: bool, session_id: u32) -> u32; + } + let buff_size = 256; + let mut buff: Vec = Vec::with_capacity(buff_size); + buff.resize(buff_size, 0); + let n = unsafe { + get_session_user_info( + buff.as_mut_ptr(), + buff_size as _, + true, + session_id.parse::().unwrap(), + ) + }; + if n == 0 { + return "".to_owned(); + } + let sl = unsafe { std::slice::from_raw_parts(buff.as_ptr(), n as _) }; + let out = String::from_utf16(sl) + .unwrap_or("".to_owned()) + .trim_end_matches('\0') + .to_owned(); + station.to_owned() + ": " + &out +} + +pub fn get_all_active_session_ids_with_station() -> String { + extern "C" { + fn get_available_session_ids(buf: *mut wchar_t, buf_size: c_int, include_rdp: bool); + } + const BUF_SIZE: c_int = 1024; + let mut buf: Vec = vec![0; BUF_SIZE as usize]; + + unsafe { + get_available_session_ids(buf.as_mut_ptr(), BUF_SIZE, true); + let session_ids = String::from_utf16_lossy(&buf); + session_ids.trim_matches(char::from(0)).trim().to_string() + } +} + +pub fn get_all_active_session_ids() -> String { + let out = get_all_active_session_ids_with_station() + .split(',') + .map(|x| x.split(':').nth(1).unwrap_or("")) + .collect::>() + .join(","); + out.trim_matches(char::from(0)).trim().to_string() +} + pub fn get_active_user_home() -> Option { let username = get_active_username(); if !username.is_empty() { diff --git a/src/server/connection.rs b/src/server/connection.rs index e05559f9c..03c760872 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -237,6 +237,8 @@ pub struct Connection { file_remove_log_control: FileRemoveLogControl, #[cfg(feature = "gpucodec")] supported_encoding_flag: (bool, Option), + user_session_id: Option, + checked_multiple_session: bool, } impl ConnInner { @@ -384,6 +386,8 @@ impl Connection { file_remove_log_control: FileRemoveLogControl::new(id), #[cfg(feature = "gpucodec")] supported_encoding_flag: (false, None), + user_session_id: None, + checked_multiple_session: false, }; let addr = hbb_common::try_into_v4(addr); if !conn.on_open(addr).await { @@ -1491,8 +1495,50 @@ impl Connection { self.video_ack_required = lr.video_ack_required; } + #[cfg(target_os = "windows")] + async fn handle_multiple_user_sessions(&mut self, usid: Option) -> bool { + if self.port_forward_socket.is_some() { + return true; + } else { + let active_sessions = crate::platform::get_all_active_sessions(); + if active_sessions.len() <= 1 { + return true; + } + let current_process_usid = crate::platform::get_current_process_session_id(); + if usid.is_none() { + let mut res = Misc::new(); + let mut rdp = Vec::new(); + for session in active_sessions { + let u_sid = &session[0]; + let u_name = &session[1]; + let mut rdp_session = RdpUserSession::new(); + rdp_session.user_session_id = u_sid.clone(); + rdp_session.user_name = u_name.clone(); + rdp.push(rdp_session); + } + res.set_rdp_user_sessions(RdpUserSessions { + rdp_user_sessions: rdp, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(res); + self.send(msg_out).await; + return true; + } + if usid != Some(current_process_usid) { + self.on_close("Reconnecting...", false).await; + std::thread::spawn(move || { + let _ = ipc::connect_to_user_session(usid); + }); + return false; + } + true + } + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] fn try_start_cm_ipc(&mut self) { + let usid = self.user_session_id; if let Some(p) = self.start_cm_ipc_para.take() { tokio::spawn(async move { #[cfg(windows)] @@ -1502,6 +1548,7 @@ impl Connection { p.tx_from_cm, p.rx_desktop_ready, p.tx_cm_stream_ready, + usid.clone(), ) .await { @@ -1513,9 +1560,9 @@ impl Connection { } }); #[cfg(all(windows, feature = "flutter"))] - std::thread::spawn(|| { + std::thread::spawn(move || { if crate::is_server() && !crate::check_process("--tray", false) { - crate::platform::run_as_user(vec!["--tray"]).ok(); + crate::platform::run_as_user(vec!["--tray"], usid).ok(); } }); } @@ -1523,6 +1570,19 @@ impl Connection { async fn on_message(&mut self, msg: Message) -> bool { if let Some(message::Union::LoginRequest(lr)) = msg.union { + #[cfg(target_os = "windows")] + { + if !self.checked_multiple_session { + let usid; + match lr.option.user_session.parse::() { + Ok(n) => usid = Some(n), + Err(..) => usid = None, + } + if usid.is_some() { + self.user_session_id = usid; + } + } + } self.handle_login_request_without_validation(&lr).await; if self.authorized { return true; @@ -1761,6 +1821,22 @@ impl Connection { } } } else if self.authorized { + #[cfg(target_os = "windows")] + if !self.checked_multiple_session { + self.checked_multiple_session = true; + if crate::platform::is_installed() + && crate::platform::is_share_rdp() + && !(*CONN_COUNT.lock().unwrap() > 1) + && get_version_number(&self.lr.version) >= get_version_number("1.2.4") + { + if !self + .handle_multiple_user_sessions(self.user_session_id) + .await + { + return false; + } + } + } match msg.union { Some(message::Union::MouseEvent(me)) => { #[cfg(any(target_os = "android", target_os = "ios"))] @@ -3010,6 +3086,7 @@ async fn start_ipc( tx_from_cm: mpsc::UnboundedSender, mut _rx_desktop_ready: mpsc::Receiver<()>, tx_stream_ready: mpsc::Sender<()>, + user_session_id: Option, ) -> ResultType<()> { use hbb_common::anyhow::anyhow; @@ -3057,7 +3134,7 @@ async fn start_ipc( if crate::platform::is_root() { let mut res = Ok(None); for _ in 0..10 { - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "linux", target_os = "windows")))] { log::debug!("Start cm"); res = crate::platform::run_as_user(args.clone()); @@ -3071,6 +3148,11 @@ async fn start_ipc( None::<(&str, &str)>, ); } + #[cfg(target_os = "windows")] + { + log::debug!("Start cm"); + res = crate::platform::run_as_user(args.clone(), user_session_id); + } if res.is_ok() { break; } diff --git a/src/ui/common.css b/src/ui/common.css index 7ee744463..ff2f83883 100644 --- a/src/ui/common.css +++ b/src/ui/common.css @@ -461,3 +461,18 @@ div#msgbox div.set-password input { div#msgbox #error { color: red; } + +div.user-session .title { + font-size: 1.2em; + margin-bottom: 2em; +} + +div.user-session select { + width: 98%; + height: 2em; + border-radius: 0.5em; + border: color(border) solid 1px; + background: color(bg); + color: color(text); + padding-left: 0.5em; +} \ No newline at end of file diff --git a/src/ui/common.tis b/src/ui/common.tis index 6ecfb4342..e3ed83a6e 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -304,7 +304,21 @@ function msgbox(type, title, content, link="", callback=null, height=180, width= return; } }; - } + } else if (type === "multiple-sessions") { + var parts = content.split("-"); + var ids = parts[0].split(","); + var names = parts[1].split(","); + var sessionData = []; + for (var i = 0; i < ids.length; i++) { + sessionData.push({ id: ids[i], name: names[i] }); + } + content = ; + callback = function () { + retryConnect(); + return; + }; + height += 50; + } last_msgbox_tag = type + "-" + title + "-" + content + "-" + link; $(#msgbox).content(); } @@ -339,7 +353,7 @@ handler.msgbox_retry = function(type, title, text, link, hasRetry) { function retryConnect(cancelTimer=false) { if (cancelTimer) self.timer(0, retryConnect); if (!is_port_forward) connecting(); - handler.reconnect(false); + handler.reconnect(false, ""); } /******************** end of msgbox ****************************************/ @@ -458,3 +472,37 @@ function awake() { view.focus = self; } +class MultipleSessionComponent extends Reactor.Component { + this var sessions = []; + this var selectedSessionId = null; + this var sessionlength = 0; + this var messageText = translate("Please select the user you want to connect to"); + + function this(params) { + if (params && params.sessions) { + this.sessions = params.sessions; + this.selectedSessionId = params.sessions[0].id; + this.sessions.map(session => { + this.sessionlength += session.name.length; + }); + } + handler.set_selected_user_session_id(this.selectedSessionId); + } + + function render() { + return
+
{this.messageText}
+ +
; + } + + event change { + var selectedSessionName = this.value.substr(this.messageText.length + this.sessionlength); + this.selectedSessionId = this.sessions.find(session => session.name == selectedSessionName).id; + handler.set_selected_user_session_id(this.selectedSessionId); + } +} \ No newline at end of file diff --git a/src/ui/header.tis b/src/ui/header.tis index 76f82be2b..69be084b6 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -527,6 +527,10 @@ handler.updateDisplays = function(v) { } } +handler.setMultipleUserSession = function(usid,uname) { + msgbox("multiple-sessions", translate("Multiple active user sessions found"), usid+"-"+uname, "", function(res) {}); +} + function updatePrivacyMode() { var el = $(li#privacy-mode); if (el) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index edd58ef61..cf3c95183 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -259,6 +259,15 @@ impl InvokeUiSession for SciterHandler { // Ignore for sciter version. } + fn set_multiple_user_session(&self, sessions: Vec) { + let formatted_sessions: Vec = sessions.iter() + .map(|session| format!("{}-{}", session.user_session_id, session.user_name)) + .collect(); + let u_sids: String = formatted_sessions.iter().map(|s| s.split("-").next().unwrap().to_string()).collect::>().join(","); + let u_names:String = formatted_sessions.iter().map(|s| s.split("-").nth(1).unwrap().to_string()).collect::>().join(","); + self.call("setMultipleUserSession", &make_args!(u_sids, u_names)); + } + fn on_connected(&self, conn_type: ConnType) { match conn_type { ConnType::RDP => {} @@ -346,6 +355,7 @@ impl sciter::EventHandler for SciterSession { } fn detached(&mut self, _root: HELEMENT) { + self.set_selected_user_session_id("".to_string()); *self.element.lock().unwrap() = None; self.sender.write().unwrap().take().map(|sender| { sender.send(Data::Close).ok(); @@ -376,7 +386,7 @@ impl sciter::EventHandler for SciterSession { let site = AssetPtr::adopt(ptr as *mut video_destination); log::debug!("[video] start video"); *VIDEO.lock().unwrap() = Some(site); - self.reconnect(false); + self.reconnect(false, "".to_string()); } } BEHAVIOR_EVENTS::VIDEO_INITIALIZED => { @@ -426,7 +436,7 @@ impl sciter::EventHandler for SciterSession { fn transfer_file(); fn tunnel(); fn lock_screen(); - fn reconnect(bool); + fn reconnect(bool, String); fn get_chatbox(); fn get_icon(); fn get_home_dir(); @@ -477,6 +487,7 @@ impl sciter::EventHandler for SciterSession { fn request_voice_call(); fn close_voice_call(); fn version_cmp(String, String); + fn set_selected_user_session_id(String); } } @@ -580,6 +591,10 @@ impl SciterSession { log::info!("size saved"); } + fn set_selected_user_session_id(&mut self, u_sid: String) { + self.lc.write().unwrap().selected_user_session_id = u_sid; + } + fn get_port_forwards(&mut self) -> Value { let port_forwards = self.lc.read().unwrap().port_forwards.clone(); let mut v = Value::array(0); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 069fd319e..32fd26a38 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -1003,7 +1003,7 @@ impl Session { } } - pub fn reconnect(&self, force_relay: bool) { + pub fn reconnect(&self, force_relay: bool, user_session_id: String) { // 1. If current session is connecting, do not reconnect. // 2. If the connection is established, send `Data::Close`. // 3. If the connection is disconnected, do nothing. @@ -1023,6 +1023,9 @@ impl Session { if true == force_relay { self.lc.write().unwrap().force_relay = true; } + if !user_session_id.is_empty() { + self.lc.write().unwrap().selected_user_session_id = user_session_id; + } let mut lock = self.thread.lock().unwrap(); // No need to join the previous thread, because it will exit automatically. // And the previous thread will not change important states. @@ -1310,6 +1313,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn next_rgba(&self, display: usize); #[cfg(all(feature = "gpucodec", feature = "flutter"))] fn on_texture(&self, display: usize, texture: *mut c_void); + fn set_multiple_user_session(&self, sessions: Vec); } impl Deref for Session { @@ -1351,6 +1355,10 @@ impl Interface for Session { handle_login_error(self.lc.clone(), err, self) } + fn set_multiple_user_sessions(&self, sessions: Vec) { + self.ui_handler.set_multiple_user_session(sessions); + } + fn handle_peer_info(&self, mut pi: PeerInfo) { log::debug!("handle_peer_info :{:?}", pi); pi.username = self.lc.read().unwrap().get_username(&pi);