Files
flutter_server_box/lib/view/page/ssh/page/init.dart
lollipopkit🏳️‍⚧️ 9b01da5a23 feat: term session mgr (#846)
2025-08-12 23:43:42 +08:00

163 lines
4.4 KiB
Dart

part of 'page.dart';
extension _Init on SSHPageState {
void _initStoredCfg() {
final fontFamilly = Stores.setting.fontPath.fetch().getFileName();
final textSize = Stores.setting.termFontSize.fetch();
final textStyle = TextStyle(fontFamily: fontFamilly, fontSize: textSize);
_terminalStyle = TerminalStyle.fromTextStyle(textStyle);
}
Future<void> _showHelp() async {
if (Stores.setting.sshTermHelpShown.fetch()) return;
return await context.showRoundDialog(
title: libL10n.doc,
child: Text(l10n.sshTermHelp),
actions: [
TextButton(
onPressed: () {
Stores.setting.sshTermHelpShown.put(true);
context.pop();
},
child: Text(l10n.noPromptAgain),
),
],
);
}
Future<void> _initTerminal() async {
_writeLn(l10n.waitConnection);
_client ??= await genClient(
widget.args.spi,
onStatus: (p0) {
_writeLn(p0.toString());
},
onKeyboardInteractive: (_) => KeybordInteractive.defaultHandle(widget.args.spi, ctx: context),
);
_writeLn('${libL10n.execute}: Shell');
final session = await _client?.shell(
pty: SSHPtyConfig(width: _terminal.viewWidth, height: _terminal.viewHeight),
environment: widget.args.spi.envs,
);
if (session == null) {
_writeLn(libL10n.fail);
return;
}
_terminal.buffer.clear();
_terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) {
session.write(utf8.encode(data));
};
_terminal.onResize = (width, height, pixelWidth, pixelHeight) {
session.resizeTerminal(width, height);
};
_listen(session.stdout);
_listen(session.stderr);
// Hold the session for external control (disconnect)
_session = session;
// Mark status connected for notifications / live activities
TermSessionManager.updateStatus(_sessionId, TermSessionStatus.connected);
for (final snippet in SnippetProvider.snippets.value) {
if (snippet.autoRunOn?.contains(widget.args.spi.id) == true) {
snippet.runInTerm(_terminal, widget.args.spi);
}
}
final initCmd = widget.args.initCmd;
if (initCmd != null) {
_terminal.textInput(initCmd);
_terminal.keyInput(TerminalKey.enter);
}
final initSnippet = widget.args.initSnippet;
if (initSnippet != null) {
initSnippet.runInTerm(_terminal, widget.args.spi);
}
widget.args.focusNode?.requestFocus();
await session.done;
if (mounted && widget.args.notFromTab) {
context.pop();
}
widget.args.onSessionEnd?.call();
TermSessionManager.remove(_sessionId);
}
void _listen(Stream<Uint8List>? stream) {
if (stream == null) {
return;
}
stream
.cast<List<int>>()
.transform(const Utf8Decoder())
.listen(
_terminal.write,
onError: (Object error, StackTrace stack) {
// _terminal.write('Stream error: $error\n');
Loggers.root.warning('Error in SSH stream', error, stack);
},
cancelOnError: false,
);
}
void _setupDiscontinuityTimer() {
_discontinuityTimer = Timer.periodic(const Duration(seconds: 5), (_) async {
var throwTimeout = true;
Future.delayed(const Duration(seconds: 3), () {
if (throwTimeout) {
_catchTimeout();
}
});
await _client?.ping();
throwTimeout = false;
});
}
void _catchTimeout() {
_discontinuityTimer?.cancel();
if (!mounted) return;
_writeLn('\n\nConnection lost\r\n');
TermSessionManager.updateStatus(_sessionId, TermSessionStatus.disconnected);
context.showRoundDialog(
title: libL10n.attention,
child: Text('${l10n.disconnected}\n${l10n.goBackQ}'),
barrierDismiss: false,
actions: Btn.ok(
onTap: () {
contextSafe?.pop(); // Can't use tear-drop here
contextSafe?.pop(); // Pop the SSHPage
},
).toList,
);
}
void _writeLn(String p0) {
_terminal.write('$p0\r\n');
}
}
extension on SSHPageState {
void _disconnectFromNotification() {
// Mark as disconnected in session manager for immediate UI/notification feedback
TermSessionManager.updateStatus(_sessionId, TermSessionStatus.disconnected);
// Try to close the running SSH session, if any
try {
_session?.close();
} catch (e, stackTrace) {
Loggers.app.warning('Error closing SSH session: $e\n$stackTrace');
}
}
}