Feat: Windows connect to a specific user session (#6825)

* feat windows connect to specific user session

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix import

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix multiple user session fields

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix build

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix build

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix file transfer

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix text color on light theme

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* feat windows connect to specific user session code changes and sciter support

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* update texts

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix sciter selected user session

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* add translations

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* Use Y,N options

* feat windows specific user code changes

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* Update dialog.dart

* Update connection.rs

* Update connection.rs

* feat windows specific user code changes

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix sciter

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use lr.union

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove unused peer options

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* select user only when authorised and no existing connection

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* check for multiple users only once

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* optimise and add check for client version

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use misc option message

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* update rdp user session proto

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix show cm on user session

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* Update pl.rs

* update on_message

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix cm

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove user_session_id

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix cm

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix multiple connections

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

---------

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>
This commit is contained in:
Sahil Yeole 2024-02-14 21:29:17 +05:30 committed by GitHub
parent 236687ae53
commit 4bf3764b5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 607 additions and 22 deletions

View File

@ -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<String> sessionsList = sessions.split(',');
Map<String, String> 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<String, String> sessions;
final Function(String) onValueChanged;
SessionsDropdown(
this.peerId, this.sessionId, this.sessions, this.onValueChanged);
@override
_SessionsDropdownState createState() => _SessionsDropdownState();
}
class _SessionsDropdownState extends State<SessionsDropdown> {
late String selectedValue;
@override
void initState() {
super.initState();
selectedValue = widget.sessions.keys.first;
}
@override
Widget build(BuildContext context) {
return Container(
width: 300,
child: DropdownButton<String>(
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,
),
),
);
}
}

View File

@ -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';

View File

@ -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<String, dynamic> 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<String, dynamic> 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...'),

View File

@ -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;
}
}

View File

@ -1149,6 +1149,7 @@ pub struct LoginConfigHandler {
pub custom_fps: Arc<Mutex<Option<usize>>>,
pub adapter_luid: Option<i64>,
pub mark_unsupported: Vec<CodecFormat>,
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<OptionMessage> {
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<hbb_common::message_proto::RdpUserSession>);
fn on_error(&self, err: &str) {
self.msgbox("error", "Error", err, "");
}

View File

@ -1314,6 +1314,13 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
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();
}

View File

@ -826,6 +826,18 @@ impl InvokeUiSession for FlutterHandler {
)
}
fn set_multiple_user_session(&self, sessions: Vec<hbb_common::message_proto::RdpUserSession>) {
let formatted_sessions: Vec<String> = 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) {

View File

@ -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);
}

View File

@ -187,6 +187,7 @@ pub enum Data {
Authorize,
Close,
SAS,
UserSid(Option<u32>),
OnlineStatus(Option<(i64, bool)>),
Config((String, Option<String>)),
Options(Option<HashMap<String, String>>),
@ -917,6 +918,13 @@ pub fn close_all_instances() -> ResultType<bool> {
}
}
#[tokio::main(flavor = "current_thread")]
pub async fn connect_to_user_session(usid: Option<u32>) -> 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::*;

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -9,6 +9,7 @@
#include <shlobj.h> // NOLINT(build/include_order)
#include <userenv.h>
#include <versionhelpers.h>
#include <vector>
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<std::wstring> 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;

View File

@ -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<OsString>) -> 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<OsString>) -> 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<OsString>) -> 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<HANDL
Ok(h)
}
pub fn run_as_user(arg: Vec<&str>) -> ResultType<Option<std::process::Child>> {
pub fn run_as_user(arg: Vec<&str>, usid: Option<u32>) -> ResultType<Option<std::process::Child>> {
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<u16> = 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<Vec<String>> {
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::<Vec<_>>()[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<u16> = 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::<u32>().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<wchar_t> = 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::<Vec<_>>()
.join(",");
out.trim_matches(char::from(0)).trim().to_string()
}
pub fn get_active_user_home() -> Option<PathBuf> {
let username = get_active_username();
if !username.is_empty() {

View File

@ -237,6 +237,8 @@ pub struct Connection {
file_remove_log_control: FileRemoveLogControl,
#[cfg(feature = "gpucodec")]
supported_encoding_flag: (bool, Option<bool>),
user_session_id: Option<u32>,
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<u32>) -> 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::<u32>() {
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<ipc::Data>,
mut _rx_desktop_ready: mpsc::Receiver<()>,
tx_stream_ready: mpsc::Sender<()>,
user_session_id: Option<u32>,
) -> 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;
}

View File

@ -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;
}

View File

@ -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 = <MultipleSessionComponent sessions={sessionData} />;
callback = function () {
retryConnect();
return;
};
height += 50;
}
last_msgbox_tag = type + "-" + title + "-" + content + "-" + link;
$(#msgbox).content(<MsgboxComponent width={width} height={height} autoLogin={autoLogin} type={type} title={title} content={content} link={link} remember={remember} callback={callback} contentStyle={contentStyle} hasRetry={hasRetry} />);
}
@ -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 <div .user-session>
<div .title>{this.messageText}</div>
<select>
{this.sessions.map(session =>
<option value={session.id}>{session.name}</option>
)}
</select>
</div>;
}
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);
}
}

View File

@ -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) {

View File

@ -259,6 +259,15 @@ impl InvokeUiSession for SciterHandler {
// Ignore for sciter version.
}
fn set_multiple_user_session(&self, sessions: Vec<hbb_common::message_proto::RdpUserSession>) {
let formatted_sessions: Vec<String> = 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::<Vec<String>>().join(",");
let u_names:String = formatted_sessions.iter().map(|s| s.split("-").nth(1).unwrap().to_string()).collect::<Vec<String>>().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);

View File

@ -1003,7 +1003,7 @@ impl<T: InvokeUiSession> Session<T> {
}
}
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<T: InvokeUiSession> Session<T> {
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<hbb_common::message_proto::RdpUserSession>);
}
impl<T: InvokeUiSession> Deref for Session<T> {
@ -1351,6 +1355,10 @@ impl<T: InvokeUiSession> Interface for Session<T> {
handle_login_error(self.lc.clone(), err, self)
}
fn set_multiple_user_sessions(&self, sessions: Vec<hbb_common::message_proto::RdpUserSession>) {
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);