From 0f44de7dc351cec422f7f5d87dd931745ffb1928 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 18 Feb 2024 22:08:25 +0800 Subject: [PATCH] refactor windows specific session (#7170) 1. Modify the process to have the control side lead the session switching: After the control side sends a `LoginRequest`, the controlled side will add all session information and the current session ID in the `LoginResponse`. Upon receiving the `LoginResponse`, the control side will check if the current session ID matches the ID in the `LoginConfigHandler`. If they match, the control side will send the current session ID. If they don't match, a session selection dialog will pop up, the selected session id will be sent. Upon receiving this message, the controlled side will restart if different or sub service if same . 2. Always show physical console session on the top 3. Show running session and distinguish sessions with the same name 4. Not sub service until correct session id is ensured 5. Fix switch sides not work for multisession session 6. Remove all session string join/split except get_available_sessions in windows.rs 7. Fix prelogin, when share rdp is enabled and there is a rdp session, the console is in login screen, get_active_username will be the rdp's username and prelogin will be false, cm can't be created an that causes disconnection in a loop 8. Rename all user session to windows session Known issue: 1. Use current process session id for `run_as_user`, sahil says it can be wrong but I didn't reproduce. 2. Have not change tray process to current session 3. File transfer doesn't update home directory when session changed 4. When it's in login screen, remote file directory is empty, because cm have not start up Signed-off-by: 21pages --- flutter/lib/common.dart | 80 +++++++++ flutter/lib/common/widgets/dialog.dart | 105 +++--------- .../desktop/pages/desktop_setting_page.dart | 106 ++---------- flutter/lib/models/model.dart | 9 +- libs/hbb_common/protos/message.proto | 16 +- src/client.rs | 10 +- src/client/io_loop.rs | 7 - src/flutter.rs | 22 ++- src/flutter_ffi.rs | 10 +- src/platform/windows.cc | 11 -- src/platform/windows.rs | 142 +++++++++------- src/server/connection.rs | 154 ++++++++---------- src/server/video_service.rs | 4 +- src/ui/common.tis | 33 +--- src/ui/header.tis | 11 +- src/ui/msgbox.tis | 3 + src/ui/remote.rs | 30 ++-- src/ui_session_interface.rs | 37 ++++- 18 files changed, 375 insertions(+), 415 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 44d197e763eb..2758ce5b2878 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2990,3 +2990,83 @@ ColorFilter? svgColor(Color? color) { return ColorFilter.mode(color, BlendMode.srcIn); } } + +// ignore: must_be_immutable +class ComboBox extends StatelessWidget { + late final List keys; + late final List values; + late final String initialKey; + late final Function(String key) onChanged; + late final bool enabled; + late String current; + + ComboBox({ + Key? key, + required this.keys, + required this.values, + required this.initialKey, + required this.onChanged, + this.enabled = true, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var index = keys.indexOf(initialKey); + if (index < 0) { + index = 0; + } + var ref = values[index].obs; + current = keys[index]; + return Container( + decoration: BoxDecoration( + border: Border.all( + color: enabled + ? MyTheme.color(context).border2 ?? MyTheme.border + : MyTheme.border, + ), + borderRadius: + BorderRadius.circular(8), //border raiuds of dropdown button + ), + height: 42, // should be the height of a TextField + child: Obx(() => DropdownButton( + isExpanded: true, + value: ref.value, + elevation: 16, + underline: Container(), + style: TextStyle( + color: enabled + ? Theme.of(context).textTheme.titleMedium?.color + : disabledTextColor(context, enabled)), + icon: const Icon( + Icons.expand_more_sharp, + size: 20, + ).marginOnly(right: 15), + onChanged: enabled + ? (String? newValue) { + if (newValue != null && newValue != ref.value) { + ref.value = newValue; + current = newValue; + onChanged(keys[values.indexOf(newValue)]); + } + } + : null, + items: values.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text( + value, + style: const TextStyle(fontSize: 15), + overflow: TextOverflow.ellipsis, + ).marginOnly(left: 15), + ); + }).toList(), + )), + ).marginOnly(bottom: 5); + } +} + +Color? disabledTextColor(BuildContext context, bool enabled) { + return enabled + ? null + : Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6); +} diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index e7a720c8a052..6cfa31af9f77 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -1862,6 +1863,7 @@ void enter2FaDialog( }); } +// This dialog should not be dismissed, otherwise it will be black screen, have not reproduced this. void showWindowsSessionsDialog( String type, String title, @@ -1870,97 +1872,40 @@ void showWindowsSessionsDialog( SessionID sessionId, String peerId, String sessions) { - List sessionsList = sessions.split(','); - Map sessionMap = {}; + List sessionsList = []; + try { + sessionsList = json.decode(sessions); + } catch (e) { + print(e); + } + List sids = []; + List names = []; for (var session in sessionsList) { - var sessionInfo = session.split('-'); - if (sessionInfo.isNotEmpty) { - sessionMap[sessionInfo[0]] = sessionInfo[1]; - } + sids.add(session['sid']); + names.add(session['name']); } - String selectedUserValue = sessionMap.keys.first; + String selectedUserValue = sids.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); + submit() { + bind.sessionSendSelectedSessionId( + sessionId: sessionId, sid: selectedUserValue); + close(); } return CustomAlertDialog( title: null, content: msgboxContent(type, title, text), actions: [ - SessionsDropdown(peerId, sessionId, sessionMap, (value) { - setState(() { - selectedUserValue = value; - }); - }), - dialogButton('Connect', onPressed: onConnect, isOutline: false), + ComboBox( + keys: sids, + values: names, + initialKey: selectedUserValue, + onChanged: (value) { + selectedUserValue = value; + }), + dialogButton('Connect', onPressed: submit, 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/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 7e82dd359efe..1abea9f5e2fd 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -514,7 +514,7 @@ class _GeneralState extends State<_General> { if (!keys.contains(currentKey)) { currentKey = ''; } - return _ComboBox( + return ComboBox( keys: keys, values: values, initialKey: currentKey, @@ -600,7 +600,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { child: Text( translate('enable-2fa-title'), style: - TextStyle(color: _disabledTextColor(context, enabled)), + TextStyle(color: disabledTextColor(context, enabled)), )) ], )), @@ -654,7 +654,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { } return _Card(title: 'Permissions', children: [ - _ComboBox( + ComboBox( keys: [ '', 'full', @@ -761,7 +761,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { Text( value, style: TextStyle( - color: _disabledTextColor( + color: disabledTextColor( context, onChanged != null)), ), ], @@ -781,7 +781,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { final usePassword = model.approveMode != 'click'; return _Card(title: 'Password', children: [ - _ComboBox( + ComboBox( enabled: !locked, keys: modeKeys, values: modeValues, @@ -841,7 +841,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { Expanded( child: Text(translate('Enable RDP session sharing'), style: - TextStyle(color: _disabledTextColor(context, enabled))), + TextStyle(color: disabledTextColor(context, enabled))), ) ], ).marginOnly(left: _kCheckBoxLeftMargin), @@ -944,7 +944,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { child: Text( translate('Use IP Whitelisting'), style: - TextStyle(color: _disabledTextColor(context, enabled)), + TextStyle(color: disabledTextColor(context, enabled)), )) ], )), @@ -988,7 +988,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { child: Text( translate('Hide connection management window'), style: TextStyle( - color: _disabledTextColor( + color: disabledTextColor( context, enabled && enableHideCm)), ), ), @@ -1686,12 +1686,6 @@ Widget _Card( ); } -Color? _disabledTextColor(BuildContext context, bool enabled) { - return enabled - ? null - : Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6); -} - // ignore: non_constant_identifier_names Widget _OptionCheckBox(BuildContext context, String label, String key, {Function()? update, @@ -1740,7 +1734,7 @@ Widget _OptionCheckBox(BuildContext context, String label, String key, Expanded( child: Text( translate(label), - style: TextStyle(color: _disabledTextColor(context, enabled)), + style: TextStyle(color: disabledTextColor(context, enabled)), )) ], ), @@ -1777,7 +1771,7 @@ Widget _Radio(BuildContext context, overflow: autoNewLine ? null : TextOverflow.ellipsis, style: TextStyle( fontSize: _kContentFontSize, - color: _disabledTextColor(context, enabled))) + color: disabledTextColor(context, enabled))) .marginOnly(left: 5), ), ], @@ -1827,7 +1821,7 @@ Widget _SubLabeledWidget(BuildContext context, String label, Widget child, children: [ Text( '${translate(label)}: ', - style: TextStyle(color: _disabledTextColor(context, enabled)), + style: TextStyle(color: disabledTextColor(context, enabled)), ), SizedBox( width: 10, @@ -1891,7 +1885,7 @@ _LabeledTextField( '${translate(label)}:', textAlign: TextAlign.right, style: TextStyle( - fontSize: 16, color: _disabledTextColor(context, enabled)), + fontSize: 16, color: disabledTextColor(context, enabled)), ).marginOnly(right: 10)), Expanded( child: TextField( @@ -1901,87 +1895,13 @@ _LabeledTextField( decoration: InputDecoration( errorText: errorText.isNotEmpty ? errorText : null), style: TextStyle( - color: _disabledTextColor(context, enabled), + color: disabledTextColor(context, enabled), )), ), ], ).marginOnly(bottom: 8); } -// ignore: must_be_immutable -class _ComboBox extends StatelessWidget { - late final List keys; - late final List values; - late final String initialKey; - late final Function(String key) onChanged; - late final bool enabled; - late String current; - - _ComboBox({ - Key? key, - required this.keys, - required this.values, - required this.initialKey, - required this.onChanged, - this.enabled = true, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - var index = keys.indexOf(initialKey); - if (index < 0) { - index = 0; - } - var ref = values[index].obs; - current = keys[index]; - return Container( - decoration: BoxDecoration( - border: Border.all( - color: enabled - ? MyTheme.color(context).border2 ?? MyTheme.border - : MyTheme.border, - ), - borderRadius: - BorderRadius.circular(8), //border raiuds of dropdown button - ), - height: 42, // should be the height of a TextField - child: Obx(() => DropdownButton( - isExpanded: true, - value: ref.value, - elevation: 16, - underline: Container(), - style: TextStyle( - color: enabled - ? Theme.of(context).textTheme.titleMedium?.color - : _disabledTextColor(context, enabled)), - icon: const Icon( - Icons.expand_more_sharp, - size: 20, - ).marginOnly(right: 15), - onChanged: enabled - ? (String? newValue) { - if (newValue != null && newValue != ref.value) { - ref.value = newValue; - current = newValue; - onChanged(keys[values.indexOf(newValue)]); - } - } - : null, - items: values.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text( - value, - style: const TextStyle(fontSize: _kContentFontSize), - overflow: TextOverflow.ellipsis, - ).marginOnly(left: 15), - ); - }).toList(), - )), - ).marginOnly(bottom: 5); - } -} - class _CountDownButton extends StatefulWidget { _CountDownButton({ Key? key, diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 6a0c8d6d31e0..80fc0677a2bc 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -245,8 +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 == 'set_multiple_windows_session') { + handleMultipleWindowsSession(evt, sessionId, peerId); } else if (name == 'peer_info') { handlePeerInfo(evt, peerId, false); } else if (name == 'sync_peer_info') { @@ -490,7 +490,7 @@ class FfiModel with ChangeNotifier { dialogManager.dismissByTag(tag); } - handleMultipleUserSession( + handleMultipleWindowsSession( Map evt, SessionID sessionId, String peerId) { if (parent.target == null) return; final dialogManager = parent.target!.dialogManager; @@ -564,8 +564,7 @@ class FfiModel with ChangeNotifier { void reconnect(OverlayDialogManager dialogManager, SessionID sessionId, bool forceRelay) { - bind.sessionReconnect( - sessionId: sessionId, forceRelay: forceRelay, userSessionId: ""); + bind.sessionReconnect(sessionId: sessionId, forceRelay: forceRelay); clearPermissions(); dialogManager.dismissAll(); dialogManager.showLoading(translate('Connecting...'), diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index 66df3a656da3..f816e3d6260b 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -122,11 +122,12 @@ message PeerInfo { // Use JSON's key-value format which is friendly for peer to handle. // NOTE: Only support one-level dictionaries (for peer to update), and the key is of type string. string platform_additions = 12; + WindowsSessions windows_sessions = 13; } -message RdpUserSession { - string user_session_id = 1; - string user_name = 2; +message WindowsSession { + uint32 sid = 1; + string name = 2; } message LoginResponse { @@ -594,7 +595,7 @@ message OptionMessage { BoolOption disable_keyboard = 12; // Position 13 is used for Resolution. Remove later. // Resolution custom_resolution = 13; - string user_session = 14; + BoolOption support_windows_specific_session = 14; } message TestDelay { @@ -709,8 +710,9 @@ message PluginFailure { string msg = 3; } -message RdpUserSessions { - repeated RdpUserSession rdp_user_sessions = 1; +message WindowsSessions { + repeated WindowsSession sessions = 1; + uint32 current_sid = 2; } message Misc { @@ -744,7 +746,7 @@ message Misc { ToggleVirtualDisplay toggle_virtual_display = 32; TogglePrivacyMode toggle_privacy_mode = 33; SupportedEncoding supported_encoding = 34; - RdpUserSessions rdp_user_sessions = 35; + uint32 selected_sid = 35; } } diff --git a/src/client.rs b/src/client.rs index 45eac5abd715..c3f240fb58fd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1149,7 +1149,7 @@ pub struct LoginConfigHandler { pub custom_fps: Arc>>, pub adapter_luid: Option, pub mark_unsupported: Vec, - pub selected_user_session_id: String, + pub selected_windows_session_id: Option, } impl Deref for LoginConfigHandler { @@ -1236,7 +1236,7 @@ impl LoginConfigHandler { self.received = false; self.switch_uuid = switch_uuid; self.adapter_luid = adapter_luid; - self.selected_user_session_id = "".to_owned(); + self.selected_windows_session_id = None; } /// Check if the client should auto login. @@ -1518,7 +1518,7 @@ impl LoginConfigHandler { } let mut n = 0; let mut msg = OptionMessage::new(); - msg.user_session = self.selected_user_session_id.clone(); + msg.support_windows_specific_session = BoolOption::Yes.into(); n += 1; if self.conn_type.eq(&ConnType::FILE_TRANSFER) { @@ -2595,7 +2595,7 @@ pub async fn handle_hash( peer: &mut Stream, ) { lc.write().unwrap().hash = hash.clone(); - let uuid = lc.read().unwrap().switch_uuid.clone(); + let uuid = lc.write().unwrap().switch_uuid.take(); if let Some(uuid) = uuid { if let Ok(uuid) = uuid::Uuid::from_str(&uuid) { send_switch_login_request(lc.clone(), peer, uuid).await; @@ -2744,7 +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 set_multiple_windows_session(&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 82b87a690c0a..ec2fdd3e7422 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1314,13 +1314,6 @@ 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 160e40c3b27b..b6b0b70ddbc2 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -826,15 +826,21 @@ 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(","); + fn set_multiple_windows_session(&self, sessions: Vec) { + let mut msg_vec = Vec::new(); + let mut sessions = sessions; + for d in sessions.drain(..) { + let mut h: HashMap<&str, String> = Default::default(); + h.insert("sid", d.sid.to_string()); + h.insert("name", d.name); + msg_vec.push(h); + } self.push_event( - "set_multiple_user_session", - vec![("user_sessions", &sessions)], + "set_multiple_windows_session", + vec![( + "user_sessions", + &serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()), + )], ); } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index cb68a8480e5c..0a9b6183c6ef 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, user_session_id: String) { +pub fn session_reconnect(session_id: SessionID, force_relay: bool) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.reconnect(force_relay, user_session_id); + session.reconnect(force_relay); } session_on_waiting_for_image_dialog_show(session_id); } @@ -701,6 +701,12 @@ pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, super::flutter::session_set_size(_session_id, _display, _width, _height) } +pub fn session_send_selected_session_id(session_id: SessionID, sid: String) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.send_selected_session_id(sid); + } +} + pub fn main_get_sound_inputs() -> Vec { #[cfg(not(any(target_os = "android", target_os = "ios")))] return get_sound_inputs(); diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 68d194c5028e..e6f3c4fc0378 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -434,17 +434,6 @@ 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; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 59e155dbc0d5..fac8ffa62db3 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -12,9 +12,10 @@ use hbb_common::{ bail, config::{self, Config}, log, - message_proto::Resolution, + message_proto::{Resolution, WindowsSession}, sleep, timeout, tokio, }; +use sha2::digest::generic_array::functional::FunctionalSequence; use std::process::{Command, Stdio}; use std::{ collections::HashMap, @@ -38,7 +39,7 @@ use winapi::{ minwinbase::STILL_ACTIVE, processthreadsapi::{ GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess, - OpenProcessToken, PROCESS_INFORMATION, STARTUPINFOW, + OpenProcessToken, ProcessIdToSessionId, PROCESS_INFORMATION, STARTUPINFOW, }, securitybaseapi::GetTokenInformation, shellapi::ShellExecuteW, @@ -511,8 +512,11 @@ async fn run_service(_arguments: Vec) -> ResultType<()> { 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 sids: Vec<_> = get_available_sessions(false) + .iter() + .map(|e| e.sid) + .collect(); + if !sids.contains(&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; @@ -628,16 +632,15 @@ async fn launch_server(session_id: DWORD, close_first: bool) -> ResultType, usid: Option) -> ResultType> { +pub fn run_as_user(arg: Vec<&str>) -> ResultType> { let cmd = format!( "\"{}\" {}", std::env::current_exe()?.to_str().unwrap_or(""), arg.join(" "), ); - let mut session_id = get_current_process_session_id(); - if let Some(usid) = usid { - session_id = usid; - } + let Some(session_id) = get_current_process_session_id() else { + bail!("Failed to get current process session id"); + }; use std::os::windows::ffi::OsStrExt; let wstr: Vec = std::ffi::OsStr::new(&cmd) .encode_wide() @@ -733,11 +736,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; +pub fn get_current_process_session_id() -> Option { + let mut sid = 0; + if unsafe { ProcessIdToSessionId(GetCurrentProcessId(), &mut sid) == TRUE } { + Some(sid) + } else { + None } - unsafe { get_current_process_session_id() } } pub fn get_active_username() -> String { @@ -762,74 +767,91 @@ 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(); - } - +fn get_session_username(session_id: u32) -> String { 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(), - ) - }; + let n = unsafe { get_session_user_info(buff.as_mut_ptr(), buff_size as _, true, session_id) }; 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) + String::from_utf16(sl) .unwrap_or("".to_owned()) .trim_end_matches('\0') - .to_owned(); - station.to_owned() + ": " + &out + .to_owned() } -pub fn get_all_active_session_ids_with_station() -> String { +pub fn get_available_sessions(name: bool) -> Vec { 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 { + let station_session_id_array = 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() + }; + let mut v: Vec = vec![]; + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-wtsgetactiveconsolesessionid + let physical_console_session_id = unsafe { get_current_session(FALSE) }; + let physical_console_username = get_session_username(physical_console_session_id); + let physical_console_name = if name { + if physical_console_username.is_empty() { + "Console".to_owned() + } else { + format!("Console:{physical_console_username}") + } + } else { + "".to_owned() + }; + v.push(WindowsSession { + sid: physical_console_session_id, + name: physical_console_name, + ..Default::default() + }); + // https://learn.microsoft.com/en-us/previous-versions//cc722458(v=technet.10)?redirectedfrom=MSDN + for type_session_id in station_session_id_array.split(",") { + let split: Vec<_> = type_session_id.split(":").collect(); + if split.len() == 2 { + if let Ok(sid) = split[1].parse::() { + if !v.iter().any(|e| (*e).sid == sid) { + let name = if name { + format!("{}:{}", split[0], get_session_username(sid)) + } else { + "".to_owned() + }; + v.push(WindowsSession { + sid, + name, + ..Default::default() + }); + } + } + } } -} - -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() + if name { + let mut name_count: HashMap = HashMap::new(); + for session in &v { + *name_count.entry(session.name.clone()).or_insert(0) += 1; + } + let current_sid = get_current_process_session_id().unwrap_or_default(); + for e in v.iter_mut() { + let running = e.sid == current_sid && current_sid != 0; + if name_count.get(&e.name).map(|v| *v).unwrap_or_default() > 1 { + e.name = format!("{} (sid = {})", e.name, e.sid); + } + if running { + e.name = format!("{} (running)", e.name); + } + } + } + v } pub fn get_active_user_home() -> Option { @@ -845,7 +867,11 @@ pub fn get_active_user_home() -> Option { } pub fn is_prelogin() -> bool { - let username = get_active_username(); + let Some(sid) = get_current_process_session_id() else { + log::error!("get_current_process_session_id failed"); + return false; + }; + let username = get_session_username(sid); username.is_empty() || username == "SYSTEM" } diff --git a/src/server/connection.rs b/src/server/connection.rs index 8d0f1caf7b64..8433b7c3fe44 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -237,8 +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, + need_sub_remote_service: bool, + remote_service_subed: bool, } impl ConnInner { @@ -386,8 +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, + need_sub_remote_service: false, + remote_service_subed: false, }; let addr = hbb_common::try_into_v4(addr); if !conn.on_open(addr).await { @@ -1194,6 +1194,9 @@ impl Connection { .into(); let mut sub_service = false; + let mut delay_sub_service = false; + #[cfg(windows)] + self.handle_windows_specific_session(&mut pi, &mut delay_sub_service); if self.file_transfer.is_some() { res.set_peer_info(pi); } else { @@ -1255,6 +1258,16 @@ impl Connection { }; self.read_dir(dir, show_hidden); } else if sub_service { + self.need_sub_remote_service = true; + if !delay_sub_service { + self.check_sub_remote_services(); + } + } + } + + fn check_sub_remote_services(&mut self) { + if self.need_sub_remote_service && !self.remote_service_subed { + self.remote_service_subed = true; if let Some(s) = self.server.upgrade() { let mut noperms = Vec::new(); if !self.peer_keyboard_enabled() && !self.show_remote_cursor { @@ -1279,6 +1292,27 @@ impl Connection { } } + #[cfg(windows)] + fn handle_windows_specific_session(&mut self, pi: &mut PeerInfo, delay_sub_service: &mut bool) { + let sessions = crate::platform::get_available_sessions(true); + let current_sid = crate::platform::get_current_process_session_id().unwrap_or_default(); + if crate::platform::is_installed() + && crate::platform::is_share_rdp() + && raii::AuthedConnID::remote_and_file_conn_count() == 1 + && sessions.len() > 1 + && current_sid != 0 + && self.lr.option.support_windows_specific_session == BoolOption::Yes.into() + { + pi.windows_sessions = Some(WindowsSessions { + sessions, + current_sid, + ..Default::default() + }) + .into(); + *delay_sub_service = true; + } + } + fn on_remote_authorized(&self) { self.update_codec_on_login(); #[cfg(any(target_os = "windows", target_os = "linux"))] @@ -1495,50 +1529,8 @@ 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)] @@ -1548,7 +1540,6 @@ impl Connection { p.tx_from_cm, p.rx_desktop_ready, p.tx_cm_stream_ready, - usid.clone(), ) .await { @@ -1562,7 +1553,7 @@ impl Connection { #[cfg(all(windows, feature = "flutter"))] std::thread::spawn(move || { if crate::is_server() && !crate::check_process("--tray", false) { - crate::platform::run_as_user(vec!["--tray"], usid).ok(); + crate::platform::run_as_user(vec!["--tray"]).ok(); } }); } @@ -1570,19 +1561,6 @@ 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; @@ -1821,22 +1799,6 @@ 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() - && Self::alive_conns().len() == 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"))] @@ -2304,6 +2266,26 @@ impl Connection { .lock() .unwrap() .user_record(self.inner.id(), status), + #[cfg(windows)] + Some(misc::Union::SelectedSid(sid)) => { + let current_process_usid = + crate::platform::get_current_process_session_id().unwrap_or_default(); + let sessions = crate::platform::get_available_sessions(false); + if crate::platform::is_installed() + && crate::platform::is_share_rdp() + && raii::AuthedConnID::remote_and_file_conn_count() == 1 + && sessions.len() > 1 + && current_process_usid != 0 + && current_process_usid != sid + && sessions.iter().any(|e| e.sid == sid) + { + std::thread::spawn(move || { + let _ = ipc::connect_to_user_session(Some(sid)); + }); + return false; + } + self.check_sub_remote_services(); + } _ => {} }, Some(message::Union::AudioFrame(frame)) => { @@ -3087,7 +3069,6 @@ 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; @@ -3135,7 +3116,7 @@ async fn start_ipc( if crate::platform::is_root() { let mut res = Ok(None); for _ in 0..10 { - #[cfg(not(any(target_os = "linux", target_os = "windows")))] + #[cfg(not(any(target_os = "linux")))] { log::debug!("Start cm"); res = crate::platform::run_as_user(args.clone()); @@ -3149,14 +3130,10 @@ 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; } + log::error!("Failed to run cm: {res:?}"); sleep(1.).await; } if let Some(task) = res? { @@ -3540,6 +3517,15 @@ mod raii { .unwrap() .send((conn_count, remote_count))); } + + pub fn remote_and_file_conn_count() -> usize { + AUTHED_CONNS + .lock() + .unwrap() + .iter() + .filter(|c| c.1 == AuthConnType::Remote || c.1 == AuthConnType::FileTransfer) + .count() + } } impl Drop for AuthedConnID { diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 567d57c012b6..f4ba3a4e6b63 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -50,7 +50,7 @@ use scrap::hwcodec::{HwEncoder, HwEncoderConfig}; use scrap::Capturer; use scrap::{ aom::AomEncoderConfig, - codec::{Encoder, EncoderCfg, EncodingUpdate, Quality}, + codec::{Encoder, EncoderCfg, Quality}, record::{Recorder, RecorderContext}, vpxcodec::{VpxEncoderConfig, VpxVideoCodecId}, CodecName, Display, Frame, TraitCapturer, @@ -643,7 +643,7 @@ fn get_encoder_config( GpuEncoder::set_not_use(_display_idx, true); } #[cfg(feature = "gpucodec")] - Encoder::update(EncodingUpdate::Check); + Encoder::update(scrap::codec::EncodingUpdate::Check); // https://www.wowza.com/community/t/the-correct-keyframe-interval-in-obs-studio/95162 let keyframe_interval = if record { Some(240) } else { None }; let negotiated_codec = Encoder::negotiated_codec(); diff --git a/src/ui/common.tis b/src/ui/common.tis index e3ed83a6ee2f..d48b8babedf8 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -304,21 +304,7 @@ 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(); } @@ -353,7 +339,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 ****************************************/ @@ -474,19 +460,12 @@ function awake() { 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() { @@ -494,15 +473,9 @@ class MultipleSessionComponent extends Reactor.Component {
{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 69be084b67a9..b859ecb36648 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -527,8 +527,15 @@ handler.updateDisplays = function(v) { } } -handler.setMultipleUserSession = function(usid,uname) { - msgbox("multiple-sessions", translate("Multiple active user sessions found"), usid+"-"+uname, "", function(res) {}); +handler.setMultipleWindowsSession = function(sessions) { + // It will be covered by other message box if the timer is not used, + self.timer(1000ms, function() { + msgbox("multiple-sessions-nocancel", translate("Multiple active user sessions found"), , "", function(res) { + if (res && res.sid) { + handler.set_selected_windows_session_id("" + res.sid); + } + }, 230); + }); } function updatePrivacyMode() { diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis index 391a5e7ff8d3..a8fa79ad4a5c 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.tis @@ -312,6 +312,9 @@ class MsgboxComponent: Reactor.Component { return; } } + if (this.type == "multiple-sessions-nocancel") { + values.sid = (this.$$(select))[0].value; + } return values; } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index cf3c951839d4..f7b10d2a0d42 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -259,14 +259,17 @@ 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 set_multiple_windows_session(&self, sessions: Vec) { + let mut v = Value::array(0); + let mut sessions = sessions; + for s in sessions.drain(..) { + let mut obj = Value::map(); + obj.set_item("sid", s.sid.to_string()); + obj.set_item("name", s.name); + v.push(obj); + } + self.call("setMultipleWindowsSession", &make_args!(v)); + } fn on_connected(&self, conn_type: ConnType) { match conn_type { @@ -355,7 +358,6 @@ 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(); @@ -386,7 +388,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, "".to_string()); + self.reconnect(false); } } BEHAVIOR_EVENTS::VIDEO_INITIALIZED => { @@ -436,7 +438,7 @@ impl sciter::EventHandler for SciterSession { fn transfer_file(); fn tunnel(); fn lock_screen(); - fn reconnect(bool, String); + fn reconnect(bool); fn get_chatbox(); fn get_icon(); fn get_home_dir(); @@ -487,7 +489,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); + fn set_selected_windows_session_id(String); } } @@ -591,8 +593,8 @@ 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 set_selected_windows_session_id(&mut self, u_sid: String) { + self.send_selected_session_id(u_sid); } fn get_port_forwards(&mut self) -> Value { diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 32fd26a38b81..1ca932e20e91 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, user_session_id: String) { + pub fn reconnect(&self, force_relay: bool) { // 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,9 +1023,6 @@ 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. @@ -1254,6 +1251,19 @@ impl Session { pub fn close_voice_call(&self) { self.send(Data::CloseVoiceCall); } + + pub fn send_selected_session_id(&self, sid: String) { + if let Ok(sid) = sid.parse::() { + self.lc.write().unwrap().selected_windows_session_id = Some(sid); + let mut misc = Misc::new(); + misc.set_selected_sid(sid); + let mut msg = Message::new(); + msg.set_misc(misc); + self.send(Data::Message(msg)); + } else { + log::error!("selected invalid sid: {}", sid); + } + } } pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { @@ -1313,7 +1323,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); + fn set_multiple_windows_session(&self, sessions: Vec); } impl Deref for Session { @@ -1355,8 +1365,8 @@ 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 set_multiple_windows_session(&self, sessions: Vec) { + self.ui_handler.set_multiple_windows_session(sessions); } fn handle_peer_info(&self, mut pi: PeerInfo) { @@ -1419,6 +1429,19 @@ impl Interface for Session { crate::platform::windows::add_recent_document(&path); } } + if !pi.windows_sessions.sessions.is_empty() { + let selected = self + .lc + .read() + .unwrap() + .selected_windows_session_id + .to_owned(); + if selected == Some(pi.windows_sessions.current_sid) { + self.send_selected_session_id(pi.windows_sessions.current_sid.to_string()); + } else { + self.set_multiple_windows_session(pi.windows_sessions.sessions.clone()); + } + } } async fn handle_hash(&self, pass: &str, hash: Hash, peer: &mut Stream) {