mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-12 02:05:14 +03:00
fix: auto-close terminal tab/window when shell exits (#15448)
This commit is contained in:
@@ -95,6 +95,13 @@ class _TerminalPageState extends State<TerminalPage>
|
|||||||
// Register this terminal model with FFI for event routing
|
// Register this terminal model with FFI for event routing
|
||||||
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
|
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
|
||||||
|
|
||||||
|
// Auto-close tab when shell exits
|
||||||
|
_terminalModel.onClosed = () {
|
||||||
|
if (mounted) {
|
||||||
|
widget.tabController.closeBy(widget.tabKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize terminal connection
|
// Initialize terminal connection
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.tabController.onSelected?.call(widget.id);
|
widget.tabController.onSelected?.call(widget.id);
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ class _TerminalPageState extends State<TerminalPage>
|
|||||||
// Register this terminal model with FFI for event routing
|
// Register this terminal model with FFI for event routing
|
||||||
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
|
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
|
||||||
|
|
||||||
|
// Auto-close connection when shell exits
|
||||||
|
_terminalModel.onClosed = () {
|
||||||
|
if (mounted) {
|
||||||
|
closeConnection(id: widget.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Web desktop users have full hardware keyboard access, so the on-screen
|
// Web desktop users have full hardware keyboard access, so the on-screen
|
||||||
// terminal extra keys bar is unnecessary and disabled.
|
// terminal extra keys bar is unnecessary and disabled.
|
||||||
_showTerminalExtraKeys = !isWebDesktop &&
|
_showTerminalExtraKeys = !isWebDesktop &&
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class TerminalModel with ChangeNotifier {
|
|||||||
|
|
||||||
void Function(int w, int h, int pw, int ph)? onResizeExternal;
|
void Function(int w, int h, int pw, int ph)? onResizeExternal;
|
||||||
|
|
||||||
|
/// Called when the terminal session ends (shell exits).
|
||||||
|
/// The listener (typically TerminalPage) can use this to auto-close the tab/page.
|
||||||
|
VoidCallback? onClosed;
|
||||||
|
|
||||||
Future<void> _handleInput(String data) async {
|
Future<void> _handleInput(String data) async {
|
||||||
// Soft keyboards (notably iOS) emit '\n' when Enter is pressed, while a
|
// Soft keyboards (notably iOS) emit '\n' when Enter is pressed, while a
|
||||||
// real keyboard's Enter sends '\r'. Some Android keyboards also emit '\n'.
|
// real keyboard's Enter sends '\r'. Some Android keyboards also emit '\n'.
|
||||||
@@ -473,6 +477,8 @@ class TerminalModel with ChangeNotifier {
|
|||||||
_writeToTerminal('\r\nTerminal closed with exit code: $exitCode\r\n');
|
_writeToTerminal('\r\nTerminal closed with exit code: $exitCode\r\n');
|
||||||
_terminalOpened = false;
|
_terminalOpened = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
// Auto-close the tab/page
|
||||||
|
onClosed?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTerminalError(Map<String, dynamic> evt) {
|
void _handleTerminalError(Map<String, dynamic> evt) {
|
||||||
|
|||||||
@@ -1857,15 +1857,33 @@ impl TerminalServiceProxy {
|
|||||||
// Process each session with its own lock
|
// Process each session with its own lock
|
||||||
for (terminal_id, session_arc) in sessions {
|
for (terminal_id, session_arc) in sessions {
|
||||||
if let Ok(mut session) = session_arc.try_lock() {
|
if let Ok(mut session) = session_arc.try_lock() {
|
||||||
// Check if reader thread is still alive and we haven't sent closed message yet
|
// Check if the session has ended (reader thread finished or child exited).
|
||||||
|
// On Linux, the PTY reader thread may not return EOF when the shell exits
|
||||||
|
// (the cloned master fd keeps the read side open), so we also poll the child
|
||||||
|
// process via try_wait() as a fallback detection mechanism.
|
||||||
let mut should_send_closed = false;
|
let mut should_send_closed = false;
|
||||||
if !session.closed_message_sent {
|
if !session.closed_message_sent {
|
||||||
if let Some(thread) = &session.reader_thread {
|
if let Some(thread) = &session.reader_thread {
|
||||||
if thread.is_finished() {
|
if thread.is_finished() {
|
||||||
should_send_closed = true;
|
should_send_closed = true;
|
||||||
session.closed_message_sent = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !should_send_closed {
|
||||||
|
if let Some(child) = &mut session.child {
|
||||||
|
match child.try_wait() {
|
||||||
|
Ok(Some(_)) => {
|
||||||
|
should_send_closed = true;
|
||||||
|
}
|
||||||
|
Ok(None) => {} // still running
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Terminal {} child wait error: {}", terminal_id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if should_send_closed {
|
||||||
|
session.closed_message_sent = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// It's Ok to put the closed message here.
|
// It's Ok to put the closed message here.
|
||||||
// Because the `reader_thread` is joined in `stop()`,
|
// Because the `reader_thread` is joined in `stop()`,
|
||||||
@@ -2018,7 +2036,8 @@ impl TerminalServiceProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For persistent sessions, just clear the child reference
|
// For persistent sessions, clear the child reference and remove the session
|
||||||
|
// if the closed message has been sent (shell has exited).
|
||||||
if let Some(session_arc) = sessions.get(&terminal_id) {
|
if let Some(session_arc) = sessions.get(&terminal_id) {
|
||||||
let mut session = session_arc.lock().unwrap();
|
let mut session = session_arc.lock().unwrap();
|
||||||
if let Some(mut child) = session.child.take() {
|
if let Some(mut child) = session.child.take() {
|
||||||
@@ -2028,6 +2047,12 @@ impl TerminalServiceProxy {
|
|||||||
}
|
}
|
||||||
add_to_reaper(child);
|
add_to_reaper(child);
|
||||||
}
|
}
|
||||||
|
if session.closed_message_sent {
|
||||||
|
// Shell has exited, remove the dead session
|
||||||
|
drop(session);
|
||||||
|
sessions.remove(&terminal_id);
|
||||||
|
service.lock().unwrap().sessions.remove(&terminal_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user