diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index 4b52e6c46..db9f7af00 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -551,7 +551,7 @@ abstract class BasePeerCard extends StatelessWidget { MenuEntryBase _terminalAction(BuildContext context) { return _connectCommonAction( context, - translate('Terminal'), + '${translate('Terminal')} (beta)', isTerminal: true, ); } @@ -560,7 +560,7 @@ abstract class BasePeerCard extends StatelessWidget { MenuEntryBase _terminalRunAsAdminAction(BuildContext context) { return _connectCommonAction( context, - translate('Terminal (Run as administrator)'), + '${translate('Terminal (Run as administrator)')} (beta)', isTerminalRunAsAdmin: true, ); } diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index ee05e52a3..cf5ed5c97 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -183,7 +183,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { ); v.add( TTextMenu( - child: Text(translate('Terminal')), + child: Text('${translate('Terminal')} (beta)'), onPressed: () => connectWithToken(isTerminal: true)), ); v.add( diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 41553b8db..6f672a759 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -563,7 +563,7 @@ class _ConnectionPageState extends State () => onConnect(isViewCamera: true) ), ( - 'Terminal', + '${translate('Terminal')} (beta)', () => onConnect(isTerminal: true) ), ] diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 505b0ff04..5c9d28383 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -378,7 +378,7 @@ class _SettingsState extends State with WidgetsBindingObserver { }, ), SettingsTile.switchTile( - title: Text('${translate('Adaptive bitrate')} (beta)'), + title: Text(translate('Adaptive bitrate')), initialValue: _enableAbr, onToggle: isOptionFixed(kOptionEnableAbr) ? null @@ -540,7 +540,7 @@ class _SettingsState extends State with WidgetsBindingObserver { enhancementsTiles.add(SettingsTile.switchTile( initialValue: _enableStartOnBoot, title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("${translate('Start on boot')} (beta)"), + Text(translate('Start on boot')), Text( '* ${translate('Start the screen sharing service on boot, requires special permissions')}', style: Theme.of(context).textTheme.bodySmall), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 017f2c9d1..c6118efa1 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -3214,7 +3214,7 @@ class FFI { } void routeTerminalResponse(Map evt) { - final int terminalId = evt['terminal_id'] ?? 0; + final int terminalId = TerminalModel.getTerminalIdFromEvt(evt); // Route to specific terminal model if it exists final model = _terminalModels[terminalId]; diff --git a/flutter/lib/models/terminal_model.dart b/flutter/lib/models/terminal_model.dart index 8f059c486..ae64e8183 100644 --- a/flutter/lib/models/terminal_model.dart +++ b/flutter/lib/models/terminal_model.dart @@ -165,9 +165,58 @@ class TerminalModel with ChangeNotifier { } } + static int getTerminalIdFromEvt(Map evt) { + if (evt.containsKey('terminal_id')) { + final v = evt['terminal_id']; + if (v is int) { + // Desktop and mobile send terminal_id as an int + return v; + } else if (v is String) { + // Web sends terminal_id as a string + final parsed = int.tryParse(v); + if (parsed != null) { + return parsed; + } else { + debugPrint( + '[TerminalModel] Failed to parse terminal_id as integer: $v. Expected a numeric string.'); + return 0; + } + } else { + // Unexpected type, log and handle gracefully + debugPrint( + '[TerminalModel] Unexpected terminal_id type: ${v.runtimeType}, value: $v. Expected int or String.'); + return 0; + } + } else { + debugPrint('[TerminalModel] Event does not contain terminal_id'); + return 0; + } + } + + static bool getSuccessFromEvt(Map evt) { + if (evt.containsKey('success')) { + final v = evt['success']; + if (v is bool) { + // Desktop and mobile + return v; + } else if (v is String) { + // Web + return v.toLowerCase() == 'true'; + } else { + // Unexpected type, log and handle gracefully + debugPrint( + '[TerminalModel] Unexpected success type: ${v.runtimeType}, value: $v. Expected bool or String.'); + return false; + } + } else { + debugPrint('[TerminalModel] Event does not contain success'); + return false; + } + } + void handleTerminalResponse(Map evt) { final String? type = evt['type']; - final int evtTerminalId = evt['terminal_id'] ?? 0; + final int evtTerminalId = getTerminalIdFromEvt(evt); // Only handle events for this terminal if (evtTerminalId != terminalId) { @@ -193,7 +242,7 @@ class TerminalModel with ChangeNotifier { } void _handleTerminalOpened(Map evt) { - final bool success = evt['success'] ?? false; + final bool success = getSuccessFromEvt(evt); final String message = evt['message'] ?? ''; final String? serviceId = evt['service_id']; diff --git a/flutter/lib/web/bridge.dart b/flutter/lib/web/bridge.dart index f1839c630..388fba5da 100644 --- a/flutter/lib/web/bridge.dart +++ b/flutter/lib/web/bridge.dart @@ -908,8 +908,18 @@ class RustdeskImpl { return js.context.callMethod('getByName', ['option:local', key]); } + // Do not return the real environment variables. + // Use the global variable as the environment variable in web. String mainGetEnv({required String key, dynamic hint}) { - throw UnimplementedError("mainGetEnv"); + return js.context.callMethod('getByName', ['envvar', key]); + } + + // Use the global variable as the environment variable in web. + void mainSetEnv({required String key, String? value, dynamic hint}) { + js.context.callMethod('setByName', [ + 'envvar', + jsonEncode({'name': key, 'value': value}) + ]); } Future mainSetLocalOption( @@ -1960,9 +1970,7 @@ class RustdeskImpl { } Future sessionCloseTerminal( - {required UuidValue sessionId, - required int terminalId, - dynamic hint}) { + {required UuidValue sessionId, required int terminalId, dynamic hint}) { return Future(() => js.context.callMethod('setByName', [ 'close_terminal', jsonEncode({ diff --git a/src/server/terminal_service.rs b/src/server/terminal_service.rs index 8bcd4c246..3a3bcdb87 100644 --- a/src/server/terminal_service.rs +++ b/src/server/terminal_service.rs @@ -131,7 +131,7 @@ fn get_or_create_service( // Ensure cleanup task is running ensure_cleanup_task(); - service.lock().unwrap().reset_status(); + service.lock().unwrap().reset_status(is_persistent); Ok(service) } @@ -600,7 +600,8 @@ impl PersistentTerminalService { !self.sessions.is_empty() } - fn reset_status(&mut self) { + fn reset_status(&mut self, is_persistent: bool) { + self.is_persistent = is_persistent; self.needs_session_sync = true; for session in self.sessions.values() { let mut session = session.lock().unwrap();