| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | import 'dart:convert'; | 
					
						
							| 
									
										
										
										
											2023-09-07 21:50:03 +08:00
										 |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | import 'dart:ui' as ui; | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 16:37:11 +08:00
										 |  |  | import 'package:desktop_multi_window/desktop_multi_window.dart'; | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-28 22:04:10 +08:00
										 |  |  | import 'package:flutter_hbb/common.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-29 18:48:12 +08:00
										 |  |  | import 'package:flutter_hbb/common/shared_state.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-03 15:31:19 +08:00
										 |  |  | import 'package:flutter_hbb/consts.dart'; | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  | import 'package:flutter_hbb/models/input_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-01 18:16:52 +08:00
										 |  |  | import 'package:flutter_hbb/models/state_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/pages/remote_page.dart'; | 
					
						
							| 
									
										
										
										
											2023-03-15 18:31:53 +01:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart'; | 
					
						
							| 
									
										
										
										
											2022-08-06 17:08:48 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' | 
					
						
							|  |  |  |     as mod_menu; | 
					
						
							|  |  |  | import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | import 'package:flutter_hbb/utils/multi_window_manager.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-27 19:42:05 +08:00
										 |  |  | import 'package:flutter_svg/flutter_svg.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-28 22:04:10 +08:00
										 |  |  | import 'package:get/get.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | import 'package:bot_toast/bot_toast.dart'; | 
					
						
							| 
									
										
										
										
											2022-06-28 22:04:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  | import '../../common/widgets/dialog.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-13 21:19:05 +09:00
										 |  |  | import '../../models/platform_model.dart'; | 
					
						
							| 
									
										
										
										
											2022-09-08 19:26:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | class _MenuTheme { | 
					
						
							| 
									
										
										
										
											2023-02-15 11:40:17 +01:00
										 |  |  |   static const Color blueColor = MyTheme.button; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   // kMinInteractiveDimension
 | 
					
						
							|  |  |  |   static const double height = 20.0; | 
					
						
							|  |  |  |   static const double dividerHeight = 12.0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | class ConnectionTabPage extends StatefulWidget { | 
					
						
							|  |  |  |   final Map<String, dynamic> params; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const ConnectionTabPage({Key? key, required this.params}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<ConnectionTabPage> createState() => _ConnectionTabPageState(params); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 10:54:09 +08:00
										 |  |  | class _ConnectionTabPageState extends State<ConnectionTabPage> { | 
					
						
							| 
									
										
										
										
											2023-01-30 21:42:58 +08:00
										 |  |  |   final tabController = | 
					
						
							|  |  |  |       Get.put(DesktopTabController(tabType: DesktopTabType.remoteScreen)); | 
					
						
							|  |  |  |   final contentKey = UniqueKey(); | 
					
						
							| 
									
										
										
										
											2022-08-29 18:48:12 +08:00
										 |  |  |   static const IconData selectedIcon = Icons.desktop_windows_sharp; | 
					
						
							|  |  |  |   static const IconData unselectedIcon = Icons.desktop_windows_outlined; | 
					
						
							| 
									
										
										
										
											2022-08-05 10:27:06 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |   String? peerId; | 
					
						
							| 
									
										
										
										
											2023-10-19 07:50:59 +08:00
										 |  |  |   bool _isScreenRectSet = false; | 
					
						
							|  |  |  |   int? _display; | 
					
						
							| 
									
										
										
										
											2022-11-10 14:32:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 10:27:06 +08:00
										 |  |  |   var connectionMap = RxList<Widget>.empty(growable: true); | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   _ConnectionTabPageState(Map<String, dynamic> params) { | 
					
						
							| 
									
										
										
										
											2022-10-08 17:27:30 +08:00
										 |  |  |     RemoteCountState.init(); | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |     peerId = params['id']; | 
					
						
							| 
									
										
										
										
											2023-08-03 23:14:40 +08:00
										 |  |  |     final sessionId = params['session_id']; | 
					
						
							| 
									
										
										
										
											2023-08-14 20:40:58 +08:00
										 |  |  |     final tabWindowId = params['tab_window_id']; | 
					
						
							| 
									
										
										
										
											2023-10-08 21:44:54 +08:00
										 |  |  |     final display = params['display']; | 
					
						
							|  |  |  |     final displays = params['displays']; | 
					
						
							| 
									
										
										
										
											2023-10-17 09:36:08 +08:00
										 |  |  |     final screenRect = parseParamScreenRect(params); | 
					
						
							| 
									
										
										
										
											2023-10-19 07:50:59 +08:00
										 |  |  |     _isScreenRectSet = screenRect != null; | 
					
						
							|  |  |  |     _display = display as int?; | 
					
						
							| 
									
										
										
										
											2023-10-17 00:30:34 +08:00
										 |  |  |     tryMoveToScreenAndSetFullscreen(screenRect); | 
					
						
							| 
									
										
										
										
											2022-08-29 22:46:19 +08:00
										 |  |  |     if (peerId != null) { | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |       ConnectionTypeState.init(peerId!); | 
					
						
							| 
									
										
										
										
											2023-06-07 20:31:54 +08:00
										 |  |  |       tabController.onSelected = (id) { | 
					
						
							| 
									
										
										
										
											2023-07-06 09:40:03 +08:00
										 |  |  |         final remotePage = tabController.widget(id); | 
					
						
							| 
									
										
										
										
											2023-06-07 20:31:54 +08:00
										 |  |  |         if (remotePage is RemotePage) { | 
					
						
							|  |  |  |           final ffi = remotePage.ffi; | 
					
						
							|  |  |  |           bind.setCurSessionId(sessionId: ffi.sessionId); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-07-25 10:45:51 +08:00
										 |  |  |         WindowController.fromWindowId(params['windowId']) | 
					
						
							| 
									
										
										
										
											2023-01-23 22:07:50 +08:00
										 |  |  |             .setTitle(getWindowNameWithId(id)); | 
					
						
							| 
									
										
										
										
											2023-07-06 09:40:03 +08:00
										 |  |  |         UnreadChatCountState.find(id).value = 0; | 
					
						
							| 
									
										
										
										
											2023-01-23 22:07:50 +08:00
										 |  |  |       }; | 
					
						
							| 
									
										
										
										
											2022-08-26 12:14:14 +08:00
										 |  |  |       tabController.add(TabInfo( | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |         key: peerId!, | 
					
						
							|  |  |  |         label: peerId!, | 
					
						
							| 
									
										
										
										
											2022-11-01 17:01:43 +08:00
										 |  |  |         selectedIcon: selectedIcon, | 
					
						
							|  |  |  |         unselectedIcon: unselectedIcon, | 
					
						
							|  |  |  |         onTabCloseButton: () => tabController.closeBy(peerId), | 
					
						
							|  |  |  |         page: RemotePage( | 
					
						
							|  |  |  |           key: ValueKey(peerId), | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |           id: peerId!, | 
					
						
							| 
									
										
										
										
											2023-08-03 23:14:40 +08:00
										 |  |  |           sessionId: sessionId == null ? null : SessionID(sessionId), | 
					
						
							| 
									
										
										
										
											2023-08-14 20:40:58 +08:00
										 |  |  |           tabWindowId: tabWindowId, | 
					
						
							| 
									
										
										
										
											2023-10-08 21:44:54 +08:00
										 |  |  |           display: display, | 
					
						
							|  |  |  |           displays: displays?.cast<int>(), | 
					
						
							| 
									
										
										
										
											2023-03-20 00:16:06 +08:00
										 |  |  |           password: params['password'], | 
					
						
							| 
									
										
										
										
											2024-06-14 00:28:59 +08:00
										 |  |  |           toolbarState: ToolbarState(), | 
					
						
							| 
									
										
										
										
											2023-06-07 20:31:54 +08:00
										 |  |  |           tabController: tabController, | 
					
						
							| 
									
										
										
										
											2023-01-17 13:28:33 +08:00
										 |  |  |           switchUuid: params['switch_uuid'], | 
					
						
							| 
									
										
										
										
											2023-02-13 16:40:24 +08:00
										 |  |  |           forceRelay: params['forceRelay'], | 
					
						
							| 
									
										
										
										
											2024-03-20 15:05:54 +08:00
										 |  |  |           isSharedPassword: params['isSharedPassword'], | 
					
						
							| 
									
										
										
										
											2022-11-01 17:01:43 +08:00
										 |  |  |         ), | 
					
						
							|  |  |  |       )); | 
					
						
							| 
									
										
										
										
											2022-10-08 17:27:30 +08:00
										 |  |  |       _update_remote_count(); | 
					
						
							| 
									
										
										
										
											2022-05-31 16:27:54 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-07-25 10:45:51 +08:00
										 |  |  |     tabController.onRemoved = (_, id) => onRemoveId(id); | 
					
						
							|  |  |  |     rustDeskWinManager.setMethodHandler(_remoteMethodHandler); | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							| 
									
										
										
										
											2022-08-24 20:56:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-19 07:50:59 +08:00
										 |  |  |     if (!_isScreenRectSet) { | 
					
						
							| 
									
										
										
										
											2023-10-17 00:30:34 +08:00
										 |  |  |       Future.delayed(Duration.zero, () { | 
					
						
							|  |  |  |         restoreWindowPosition( | 
					
						
							|  |  |  |           WindowType.RemoteDesktop, | 
					
						
							|  |  |  |           windowId: windowId(), | 
					
						
							|  |  |  |           peerId: tabController.state.value.tabs.isEmpty | 
					
						
							|  |  |  |               ? null | 
					
						
							|  |  |  |               : tabController.state.value.tabs[0].key, | 
					
						
							| 
									
										
										
										
											2023-10-19 07:50:59 +08:00
										 |  |  |           display: _display, | 
					
						
							| 
									
										
										
										
											2023-10-17 00:30:34 +08:00
										 |  |  |         ); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2024-05-08 09:59:05 +08:00
										 |  |  |     final child = Scaffold( | 
					
						
							|  |  |  |       backgroundColor: Theme.of(context).colorScheme.background, | 
					
						
							|  |  |  |       body: DesktopTab( | 
					
						
							|  |  |  |         controller: tabController, | 
					
						
							|  |  |  |         onWindowCloseButton: handleWindowCloseButton, | 
					
						
							|  |  |  |         tail: const AddButton(), | 
					
						
							| 
									
										
										
										
											2024-07-26 11:20:16 +08:00
										 |  |  |         selectedBorderColor: MyTheme.accent, | 
					
						
							| 
									
										
										
										
											2024-05-08 09:59:05 +08:00
										 |  |  |         pageViewBuilder: (pageView) => pageView, | 
					
						
							|  |  |  |         labelGetter: DesktopTab.tablabelGetter, | 
					
						
							|  |  |  |         tabBuilder: (key, icon, label, themeConf) => Obx(() { | 
					
						
							|  |  |  |           final connectionType = ConnectionTypeState.find(key); | 
					
						
							|  |  |  |           if (!connectionType.isValid()) { | 
					
						
							|  |  |  |             return Row( | 
					
						
							|  |  |  |               mainAxisAlignment: MainAxisAlignment.center, | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 icon, | 
					
						
							|  |  |  |                 label, | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             bool secure = | 
					
						
							|  |  |  |                 connectionType.secure.value == ConnectionType.strSecure; | 
					
						
							|  |  |  |             bool direct = | 
					
						
							|  |  |  |                 connectionType.direct.value == ConnectionType.strDirect; | 
					
						
							|  |  |  |             String msgConn; | 
					
						
							|  |  |  |             if (secure && direct) { | 
					
						
							|  |  |  |               msgConn = translate("Direct and encrypted connection"); | 
					
						
							|  |  |  |             } else if (secure && !direct) { | 
					
						
							|  |  |  |               msgConn = translate("Relayed and encrypted connection"); | 
					
						
							|  |  |  |             } else if (!secure && direct) { | 
					
						
							|  |  |  |               msgConn = translate("Direct and unencrypted connection"); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               msgConn = translate("Relayed and unencrypted connection"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             var msgFingerprint = '${translate('Fingerprint')}:\n'; | 
					
						
							|  |  |  |             var fingerprint = FingerprintState.find(key).value; | 
					
						
							|  |  |  |             if (fingerprint.isEmpty) { | 
					
						
							|  |  |  |               fingerprint = 'N/A'; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (fingerprint.length > 5 * 8) { | 
					
						
							|  |  |  |               var first = fingerprint.substring(0, 39); | 
					
						
							|  |  |  |               var second = fingerprint.substring(40); | 
					
						
							|  |  |  |               msgFingerprint += '$first\n$second'; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               msgFingerprint += fingerprint; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             final tab = Row( | 
					
						
							|  |  |  |               mainAxisAlignment: MainAxisAlignment.center, | 
					
						
							|  |  |  |               children: [ | 
					
						
							|  |  |  |                 icon, | 
					
						
							|  |  |  |                 Tooltip( | 
					
						
							|  |  |  |                   message: '$msgConn\n$msgFingerprint', | 
					
						
							|  |  |  |                   child: SvgPicture.asset( | 
					
						
							|  |  |  |                     'assets/${connectionType.secure.value}${connectionType.direct.value}.svg', | 
					
						
							|  |  |  |                     width: themeConf.iconSize, | 
					
						
							|  |  |  |                     height: themeConf.iconSize, | 
					
						
							|  |  |  |                   ).paddingOnly(right: 5), | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |                 label, | 
					
						
							|  |  |  |                 unreadMessageCountBuilder(UnreadChatCountState.find(key)) | 
					
						
							|  |  |  |                     .marginOnly(left: 4), | 
					
						
							|  |  |  |               ], | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return Listener( | 
					
						
							|  |  |  |               onPointerDown: (e) { | 
					
						
							|  |  |  |                 if (e.kind != ui.PointerDeviceKind.mouse) { | 
					
						
							|  |  |  |                   return; | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-05-08 09:59:05 +08:00
										 |  |  |                 final remotePage = tabController.state.value.tabs | 
					
						
							|  |  |  |                     .firstWhere((tab) => tab.key == key) | 
					
						
							|  |  |  |                     .page as RemotePage; | 
					
						
							|  |  |  |                 if (remotePage.ffi.ffiModel.pi.isSet.isTrue && e.buttons == 2) { | 
					
						
							|  |  |  |                   showRightMenu( | 
					
						
							|  |  |  |                     (CancelFunc cancelFunc) { | 
					
						
							|  |  |  |                       return _tabMenuBuilder(key, cancelFunc); | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     target: e.position, | 
					
						
							|  |  |  |                   ); | 
					
						
							| 
									
										
										
										
											2023-04-19 14:39:22 +08:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-05-08 09:59:05 +08:00
										 |  |  |               }, | 
					
						
							|  |  |  |               child: tab, | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }), | 
					
						
							| 
									
										
										
										
											2022-11-16 19:49:52 +08:00
										 |  |  |       ), | 
					
						
							| 
									
										
										
										
											2022-09-19 10:14:14 +08:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2024-05-08 09:59:05 +08:00
										 |  |  |     final tabWidget = isLinux | 
					
						
							|  |  |  |         ? buildVirtualWindowFrame(context, child) | 
					
						
							|  |  |  |         : Obx(() => Container( | 
					
						
							|  |  |  |               decoration: BoxDecoration( | 
					
						
							|  |  |  |                 border: Border.all( | 
					
						
							|  |  |  |                     color: MyTheme.color(context).border!, | 
					
						
							|  |  |  |                     width: stateGlobal.windowBorderWidth.value), | 
					
						
							|  |  |  |               ), | 
					
						
							|  |  |  |               child: child, | 
					
						
							|  |  |  |             )); | 
					
						
							| 
									
										
										
										
											2024-03-24 11:23:06 +08:00
										 |  |  |     return isMacOS || kUseCompatibleUiMode | 
					
						
							| 
									
										
										
										
											2022-09-19 10:14:14 +08:00
										 |  |  |         ? tabWidget | 
					
						
							| 
									
										
										
										
											2023-01-30 21:42:58 +08:00
										 |  |  |         : Obx(() => SubWindowDragToResizeArea( | 
					
						
							|  |  |  |               key: contentKey, | 
					
						
							|  |  |  |               child: tabWidget, | 
					
						
							| 
									
										
										
										
											2023-02-27 12:01:22 +08:00
										 |  |  |               // Specially configured for a better resize area and remote control.
 | 
					
						
							|  |  |  |               childPadding: kDragToResizeAreaPadding, | 
					
						
							| 
									
										
										
										
											2023-01-30 21:42:58 +08:00
										 |  |  |               resizeEdgeSize: stateGlobal.resizeEdgeSize.value, | 
					
						
							|  |  |  |               windowId: stateGlobal.windowId, | 
					
						
							|  |  |  |             )); | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-05-31 16:27:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-11 16:32:22 +08:00
										 |  |  |   // Note: Some dup code to ../widgets/remote_toolbar
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   Widget _tabMenuBuilder(String key, CancelFunc cancelFunc) { | 
					
						
							|  |  |  |     final List<MenuEntryBase<String>> menu = []; | 
					
						
							|  |  |  |     const EdgeInsets padding = EdgeInsets.only(left: 8.0, right: 5.0); | 
					
						
							|  |  |  |     final remotePage = tabController.state.value.tabs | 
					
						
							|  |  |  |         .firstWhere((tab) => tab.key == key) | 
					
						
							|  |  |  |         .page as RemotePage; | 
					
						
							|  |  |  |     final ffi = remotePage.ffi; | 
					
						
							|  |  |  |     final pi = ffi.ffiModel.pi; | 
					
						
							|  |  |  |     final perms = ffi.ffiModel.permissions; | 
					
						
							| 
									
										
										
										
											2023-06-06 07:39:44 +08:00
										 |  |  |     final sessionId = ffi.sessionId; | 
					
						
							| 
									
										
										
										
											2024-06-14 00:28:59 +08:00
										 |  |  |     final toolbarState = remotePage.toolbarState; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     menu.addAll([ | 
					
						
							|  |  |  |       MenuEntryButton<String>( | 
					
						
							|  |  |  |         childBuilder: (TextStyle? style) => Obx(() => Text( | 
					
						
							| 
									
										
										
										
											2022-11-10 14:32:22 +08:00
										 |  |  |               translate( | 
					
						
							| 
									
										
										
										
											2024-06-14 00:28:59 +08:00
										 |  |  |                   toolbarState.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |               style: style, | 
					
						
							|  |  |  |             )), | 
					
						
							|  |  |  |         proc: () { | 
					
						
							| 
									
										
										
										
											2024-06-14 00:28:59 +08:00
										 |  |  |           toolbarState.switchShow(sessionId); | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |           cancelFunc(); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         padding: padding, | 
					
						
							|  |  |  |       ), | 
					
						
							| 
									
										
										
										
											2022-11-29 16:36:35 +08:00
										 |  |  |     ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-06 10:19:30 +08:00
										 |  |  |     if (tabController.state.value.tabs.length > 1) { | 
					
						
							|  |  |  |       final splitAction = MenuEntryButton<String>( | 
					
						
							|  |  |  |         childBuilder: (TextStyle? style) => Text( | 
					
						
							| 
									
										
										
										
											2023-08-06 11:21:07 +08:00
										 |  |  |           translate('Move tab to new window'), | 
					
						
							| 
									
										
										
										
											2023-08-06 10:19:30 +08:00
										 |  |  |           style: style, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         proc: () async { | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |           await DesktopMultiWindow.invokeMethod(kMainWindowId, | 
					
						
							|  |  |  |               kWindowEventMoveTabToNewWindow, '${windowId()},$key,$sessionId'); | 
					
						
							| 
									
										
										
										
											2023-08-06 10:19:30 +08:00
										 |  |  |           cancelFunc(); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         padding: padding, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       menu.insert(1, splitAction); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |     if (perms['restart'] != false && | 
					
						
							|  |  |  |         (pi.platform == kPeerPlatformLinux || | 
					
						
							|  |  |  |             pi.platform == kPeerPlatformWindows || | 
					
						
							|  |  |  |             pi.platform == kPeerPlatformMacOS)) { | 
					
						
							|  |  |  |       menu.add(MenuEntryButton<String>( | 
					
						
							|  |  |  |         childBuilder: (TextStyle? style) => Text( | 
					
						
							| 
									
										
										
										
											2023-11-06 20:12:01 +08:00
										 |  |  |           translate('Restart remote device'), | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |           style: style, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         proc: () => showRestartRemoteDevice( | 
					
						
							|  |  |  |             pi, peerId ?? '', sessionId, ffi.dialogManager), | 
					
						
							|  |  |  |         padding: padding, | 
					
						
							|  |  |  |         dismissOnClicked: true, | 
					
						
							|  |  |  |         dismissCallback: cancelFunc, | 
					
						
							|  |  |  |       )); | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-23 21:59:22 +08:00
										 |  |  |     if (perms['keyboard'] != false && !ffi.ffiModel.viewOnly) { | 
					
						
							|  |  |  |       menu.add(RemoteMenuEntry.insertLock(sessionId, padding, | 
					
						
							|  |  |  |           dismissFunc: cancelFunc)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) { | 
					
						
							|  |  |  |         menu.add(RemoteMenuEntry.insertCtrlAltDel(sessionId, padding, | 
					
						
							|  |  |  |             dismissFunc: cancelFunc)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     menu.addAll([ | 
					
						
							|  |  |  |       MenuEntryDivider<String>(), | 
					
						
							|  |  |  |       MenuEntryButton<String>( | 
					
						
							|  |  |  |         childBuilder: (TextStyle? style) => Text( | 
					
						
							|  |  |  |           translate('Copy Fingerprint'), | 
					
						
							|  |  |  |           style: style, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         proc: () => onCopyFingerprint(FingerprintState.find(key).value), | 
					
						
							|  |  |  |         padding: padding, | 
					
						
							|  |  |  |         dismissOnClicked: true, | 
					
						
							|  |  |  |         dismissCallback: cancelFunc, | 
					
						
							| 
									
										
										
										
											2023-04-19 14:39:22 +08:00
										 |  |  |       ), | 
					
						
							| 
									
										
										
										
											2023-08-06 16:42:13 +08:00
										 |  |  |       MenuEntryButton<String>( | 
					
						
							|  |  |  |         childBuilder: (TextStyle? style) => Text( | 
					
						
							|  |  |  |           translate('Close'), | 
					
						
							|  |  |  |           style: style, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         proc: () { | 
					
						
							|  |  |  |           tabController.closeBy(key); | 
					
						
							|  |  |  |           cancelFunc(); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         padding: padding, | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |     ]); | 
					
						
							| 
									
										
										
										
											2023-04-19 14:39:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     return mod_menu.PopupMenu<String>( | 
					
						
							|  |  |  |       items: menu | 
					
						
							|  |  |  |           .map((entry) => entry.build( | 
					
						
							|  |  |  |               context, | 
					
						
							|  |  |  |               const MenuConfig( | 
					
						
							| 
									
										
										
										
											2023-02-15 11:40:17 +01:00
										 |  |  |                 commonColor: _MenuTheme.blueColor, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |                 height: _MenuTheme.height, | 
					
						
							|  |  |  |                 dividerHeight: _MenuTheme.dividerHeight, | 
					
						
							|  |  |  |               ))) | 
					
						
							|  |  |  |           .expand((i) => i) | 
					
						
							|  |  |  |           .toList(), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-09 15:14:11 +08:00
										 |  |  |   void onRemoveId(String id) async { | 
					
						
							| 
									
										
										
										
											2022-08-30 16:45:47 +08:00
										 |  |  |     if (tabController.state.value.tabs.isEmpty) { | 
					
						
							| 
									
										
										
										
											2023-09-29 21:42:49 +08:00
										 |  |  |       // Keep calling until the window status is hidden.
 | 
					
						
							|  |  |  |       //
 | 
					
						
							|  |  |  |       // Workaround for Windows:
 | 
					
						
							|  |  |  |       // If you click other buttons and close in msgbox within a very short period of time, the close may fail.
 | 
					
						
							|  |  |  |       // `await WindowController.fromWindowId(windowId()).close();`.
 | 
					
						
							|  |  |  |       Future<void> loopCloseWindow() async { | 
					
						
							|  |  |  |         int c = 0; | 
					
						
							|  |  |  |         final windowController = WindowController.fromWindowId(windowId()); | 
					
						
							| 
									
										
										
										
											2023-09-29 22:53:58 +08:00
										 |  |  |         while (c < 20 && | 
					
						
							| 
									
										
										
										
											2023-09-29 21:42:49 +08:00
										 |  |  |             tabController.state.value.tabs.isEmpty && | 
					
						
							|  |  |  |             (!await windowController.isHidden())) { | 
					
						
							|  |  |  |           await windowController.close(); | 
					
						
							| 
									
										
										
										
											2023-09-29 22:53:58 +08:00
										 |  |  |           await Future.delayed(Duration(milliseconds: 100)); | 
					
						
							| 
									
										
										
										
											2023-09-29 21:42:49 +08:00
										 |  |  |           c++; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-10-17 00:30:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-29 21:42:49 +08:00
										 |  |  |       loopCloseWindow(); | 
					
						
							| 
									
										
										
										
											2022-08-09 09:01:06 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-29 22:46:19 +08:00
										 |  |  |     ConnectionTypeState.delete(id); | 
					
						
							| 
									
										
										
										
											2022-10-08 17:27:30 +08:00
										 |  |  |     _update_remote_count(); | 
					
						
							| 
									
										
										
										
											2022-05-31 16:27:54 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-09 16:37:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   int windowId() { | 
					
						
							|  |  |  |     return widget.params["windowId"]; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:03:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 19:29:19 +08:00
										 |  |  |   Future<bool> handleWindowCloseButton() async { | 
					
						
							| 
									
										
										
										
											2022-10-08 17:27:30 +08:00
										 |  |  |     final connLength = tabController.length; | 
					
						
							| 
									
										
										
										
											2022-10-13 21:19:05 +09:00
										 |  |  |     if (connLength <= 1) { | 
					
						
							|  |  |  |       tabController.clear(); | 
					
						
							| 
									
										
										
										
											2022-09-09 19:29:19 +08:00
										 |  |  |       return true; | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2022-10-13 21:19:05 +09:00
										 |  |  |       final bool res; | 
					
						
							| 
									
										
										
										
											2024-05-19 14:07:42 +08:00
										 |  |  |       if (!option2bool(kOptionEnableConfirmClosingTabs, | 
					
						
							|  |  |  |           bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) { | 
					
						
							| 
									
										
										
										
											2022-10-13 21:19:05 +09:00
										 |  |  |         res = true; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         res = await closeConfirmDialog(); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-09-09 19:29:19 +08:00
										 |  |  |       if (res) { | 
					
						
							|  |  |  |         tabController.clear(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return res; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-10-08 17:27:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   _update_remote_count() => | 
					
						
							|  |  |  |       RemoteCountState.find().value = tabController.length; | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   Future<dynamic> _remoteMethodHandler(call, fromWindowId) async { | 
					
						
							|  |  |  |     print( | 
					
						
							|  |  |  |         "[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dynamic returnValue; | 
					
						
							|  |  |  |     // for simplify, just replace connectionId
 | 
					
						
							|  |  |  |     if (call.method == kWindowEventNewRemoteDesktop) { | 
					
						
							|  |  |  |       final args = jsonDecode(call.arguments); | 
					
						
							|  |  |  |       final id = args['id']; | 
					
						
							|  |  |  |       final switchUuid = args['switch_uuid']; | 
					
						
							|  |  |  |       final sessionId = args['session_id']; | 
					
						
							|  |  |  |       final tabWindowId = args['tab_window_id']; | 
					
						
							|  |  |  |       final display = args['display']; | 
					
						
							|  |  |  |       final displays = args['displays']; | 
					
						
							|  |  |  |       final screenRect = parseParamScreenRect(args); | 
					
						
							| 
									
										
										
										
											2024-07-25 21:52:57 +08:00
										 |  |  |       final prePeerCount = tabController.length; | 
					
						
							| 
									
										
										
										
											2024-05-23 22:11:40 +08:00
										 |  |  |       Future.delayed(Duration.zero, () async { | 
					
						
							|  |  |  |         if (stateGlobal.fullscreen.isTrue) { | 
					
						
							|  |  |  |           await WindowController.fromWindowId(windowId()).setFullscreen(false); | 
					
						
							|  |  |  |           stateGlobal.setFullscreen(false, procWnd: false); | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-07-25 21:52:57 +08:00
										 |  |  |         await setNewConnectWindowFrame( | 
					
						
							|  |  |  |             windowId(), id!, prePeerCount, display, screenRect); | 
					
						
							| 
									
										
										
										
											2024-05-23 22:11:40 +08:00
										 |  |  |         Future.delayed(Duration(milliseconds: isWindows ? 100 : 0), () async { | 
					
						
							|  |  |  |           await windowOnTop(windowId()); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  |       ConnectionTypeState.init(id); | 
					
						
							|  |  |  |       tabController.add(TabInfo( | 
					
						
							|  |  |  |         key: id, | 
					
						
							|  |  |  |         label: id, | 
					
						
							|  |  |  |         selectedIcon: selectedIcon, | 
					
						
							|  |  |  |         unselectedIcon: unselectedIcon, | 
					
						
							|  |  |  |         onTabCloseButton: () => tabController.closeBy(id), | 
					
						
							|  |  |  |         page: RemotePage( | 
					
						
							|  |  |  |           key: ValueKey(id), | 
					
						
							|  |  |  |           id: id, | 
					
						
							|  |  |  |           sessionId: sessionId == null ? null : SessionID(sessionId), | 
					
						
							|  |  |  |           tabWindowId: tabWindowId, | 
					
						
							|  |  |  |           display: display, | 
					
						
							|  |  |  |           displays: displays?.cast<int>(), | 
					
						
							|  |  |  |           password: args['password'], | 
					
						
							| 
									
										
										
										
											2024-06-14 00:28:59 +08:00
										 |  |  |           toolbarState: ToolbarState(), | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  |           tabController: tabController, | 
					
						
							|  |  |  |           switchUuid: switchUuid, | 
					
						
							|  |  |  |           forceRelay: args['forceRelay'], | 
					
						
							|  |  |  |           isSharedPassword: args['isSharedPassword'], | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       )); | 
					
						
							|  |  |  |     } else if (call.method == kWindowDisableGrabKeyboard) { | 
					
						
							|  |  |  |       // ???
 | 
					
						
							|  |  |  |     } else if (call.method == "onDestroy") { | 
					
						
							|  |  |  |       tabController.clear(); | 
					
						
							|  |  |  |     } else if (call.method == kWindowActionRebuild) { | 
					
						
							|  |  |  |       reloadCurrentWindow(); | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventActiveSession) { | 
					
						
							|  |  |  |       final jumpOk = tabController.jumpToByKey(call.arguments); | 
					
						
							|  |  |  |       if (jumpOk) { | 
					
						
							|  |  |  |         windowOnTop(windowId()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return jumpOk; | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventActiveDisplaySession) { | 
					
						
							|  |  |  |       final args = jsonDecode(call.arguments); | 
					
						
							|  |  |  |       final id = args['id']; | 
					
						
							|  |  |  |       final display = args['display']; | 
					
						
							|  |  |  |       final jumpOk = tabController.jumpToByKeyAndDisplay(id, display); | 
					
						
							|  |  |  |       if (jumpOk) { | 
					
						
							|  |  |  |         windowOnTop(windowId()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return jumpOk; | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventGetRemoteList) { | 
					
						
							|  |  |  |       return tabController.state.value.tabs | 
					
						
							|  |  |  |           .map((e) => e.key) | 
					
						
							|  |  |  |           .toList() | 
					
						
							|  |  |  |           .join(','); | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventGetSessionIdList) { | 
					
						
							|  |  |  |       return tabController.state.value.tabs | 
					
						
							|  |  |  |           .map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}') | 
					
						
							|  |  |  |           .toList() | 
					
						
							|  |  |  |           .join(';'); | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventGetCachedSessionData) { | 
					
						
							|  |  |  |       // Ready to show new window and close old tab.
 | 
					
						
							|  |  |  |       final args = jsonDecode(call.arguments); | 
					
						
							|  |  |  |       final id = args['id']; | 
					
						
							|  |  |  |       final close = args['close']; | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         final remotePage = tabController.state.value.tabs | 
					
						
							|  |  |  |             .firstWhere((tab) => tab.key == id) | 
					
						
							|  |  |  |             .page as RemotePage; | 
					
						
							|  |  |  |         returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString(); | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							|  |  |  |         debugPrint('Failed to get cached session data: $e'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (close && returnValue != null) { | 
					
						
							|  |  |  |         closeSessionOnDispose[id] = false; | 
					
						
							|  |  |  |         tabController.closeBy(id); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else if (call.method == kWindowEventRemoteWindowCoords) { | 
					
						
							|  |  |  |       final remotePage = | 
					
						
							|  |  |  |           tabController.state.value.selectedTabInfo.page as RemotePage; | 
					
						
							|  |  |  |       final ffi = remotePage.ffi; | 
					
						
							|  |  |  |       final displayRect = ffi.ffiModel.displaysRect(); | 
					
						
							|  |  |  |       if (displayRect != null) { | 
					
						
							|  |  |  |         final wc = WindowController.fromWindowId(windowId()); | 
					
						
							|  |  |  |         Rect? frame; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           frame = await wc.getFrame(); | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |           debugPrint( | 
					
						
							|  |  |  |               "Failed to get frame of window $windowId, it may be hidden"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (frame != null) { | 
					
						
							|  |  |  |           ffi.cursorModel.moveLocal(0, 0); | 
					
						
							|  |  |  |           final coords = RemoteWindowCoords( | 
					
						
							|  |  |  |               frame, | 
					
						
							|  |  |  |               CanvasCoords.fromCanvasModel(ffi.canvasModel), | 
					
						
							|  |  |  |               CursorCoords.fromCursorModel(ffi.cursorModel), | 
					
						
							|  |  |  |               displayRect); | 
					
						
							|  |  |  |           returnValue = jsonEncode(coords.toJson()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2024-05-23 09:51:19 +08:00
										 |  |  |     } else if (call.method == kWindowEventSetFullscreen) { | 
					
						
							|  |  |  |       stateGlobal.setFullscreen(call.arguments == 'true'); | 
					
						
							| 
									
										
										
										
											2024-04-27 13:45:44 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |     _update_remote_count(); | 
					
						
							|  |  |  |     return returnValue; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-05-29 17:19:50 +08:00
										 |  |  | } |