From 7fce02e68822ed1293c70591d4039bf2b08ac1fa Mon Sep 17 00:00:00 2001
From: 21pages <pages21@163.com>
Date: Mon, 29 Aug 2022 19:28:00 +0800
Subject: [PATCH 1/3] fix: not use fixed button width

Signed-off-by: 21pages <pages21@163.com>
---
 flutter/lib/desktop/pages/connection_page.dart | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart
index 29219df2a..d366d06ec 100644
--- a/flutter/lib/desktop/pages/connection_page.dart
+++ b/flutter/lib/desktop/pages/connection_page.dart
@@ -233,7 +233,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
                         },
                         child: Container(
                           height: 24,
-                          width: 72,
                           alignment: Alignment.center,
                           decoration: BoxDecoration(
                             color: ftPressed.value
@@ -257,7 +256,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
                                 color: ftPressed.value
                                     ? MyTheme.color(context).bg
                                     : MyTheme.color(context).text),
-                          ),
+                          ).marginSymmetric(horizontal: 12),
                         ),
                       )),
                   SizedBox(
@@ -272,7 +271,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
                       onTap: onConnect,
                       child: Container(
                         height: 24,
-                        width: 65,
                         decoration: BoxDecoration(
                           color: connPressed.value
                               ? MyTheme.accent
@@ -289,12 +287,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
                         child: Center(
                           child: Text(
                             translate(
-                              "Connection",
+                              "Connect",
                             ),
                             style: TextStyle(
                                 fontSize: 12, color: MyTheme.color(context).bg),
                           ),
-                        ),
+                        ).marginSymmetric(horizontal: 12),
                       ),
                     ),
                   ),

From 839be76b8f890919be69c36a8ae56f5672508df8 Mon Sep 17 00:00:00 2001
From: 21pages <pages21@163.com>
Date: Tue, 30 Aug 2022 14:43:57 +0800
Subject: [PATCH 2/3] tabbar: check before scroll

Signed-off-by: 21pages <pages21@163.com>
---
 flutter/lib/desktop/widgets/tabbar_widget.dart | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart
index 6e0ce747d..aea90c868 100644
--- a/flutter/lib/desktop/widgets/tabbar_widget.dart
+++ b/flutter/lib/desktop/widgets/tabbar_widget.dart
@@ -64,6 +64,7 @@ class DesktopTabController {
       state.update((val) {
         val!.tabs.add(tab);
       });
+      state.value.scrollController.itemCount = state.value.tabs.length;
       toIndex = state.value.tabs.length - 1;
       assert(toIndex >= 0);
     }
@@ -96,8 +97,16 @@ class DesktopTabController {
   void jumpTo(int index) {
     state.update((val) {
       val!.selected = index;
-      val.pageController.jumpToPage(index);
-      val.scrollController.scrollToItem(index, center: true, animate: true);
+      Future.delayed(Duration.zero, (() {
+        if (val.pageController.hasClients) {
+          val.pageController.jumpToPage(index);
+        }
+        if (val.scrollController.hasClients &&
+            val.scrollController.canScroll &&
+            val.scrollController.itemCount >= index) {
+          val.scrollController.scrollToItem(index, center: true, animate: true);
+        }
+      }));
     });
     onSelected?.call(index);
   }

From 38abd273840723bd7caee72b201c8444283f9a18 Mon Sep 17 00:00:00 2001
From: 21pages <pages21@163.com>
Date: Tue, 30 Aug 2022 16:50:25 +0800
Subject: [PATCH 3/3] impl option remote modification

Signed-off-by: 21pages <pages21@163.com>
---
 flutter/lib/common.dart                       |  2 -
 .../desktop/pages/connection_tab_page.dart    |  2 +-
 .../lib/desktop/pages/desktop_tab_page.dart   |  2 +-
 .../desktop/pages/file_manager_tab_page.dart  |  6 +-
 .../desktop/pages/port_forward_tab_page.dart  | 10 +--
 flutter/lib/desktop/pages/server_page.dart    | 26 ++++++--
 .../lib/desktop/widgets/tabbar_widget.dart    | 62 +++++++++++++++++--
 flutter/lib/main.dart                         |  9 ++-
 src/flutter_ffi.rs                            | 23 ++++---
 9 files changed, 109 insertions(+), 33 deletions(-)

diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart
index b991c7a96..6ba917bf3 100644
--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -664,8 +664,6 @@ Future<void> initGlobalFFI() async {
   debugPrint("_globalFFI init end");
   // after `put`, can also be globally found by Get.find<FFI>();
   Get.put(_globalFFI, permanent: true);
-  // trigger connection status updater
-  await bind.mainCheckConnectStatus();
   // global shared preference
   await Get.putAsync(() => SharedPreferences.getInstance());
 }
diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart
index 1b9e04ebf..4175bd11b 100644
--- a/flutter/lib/desktop/pages/connection_tab_page.dart
+++ b/flutter/lib/desktop/pages/connection_tab_page.dart
@@ -93,7 +93,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
                 body: Obx(() => DesktopTab(
                       controller: tabController,
                       theme: theme,
-                      isMainWindow: false,
+                      tabType: DesktopTabType.remoteScreen,
                       showTabBar: fullscreen.isFalse,
                       onClose: () {
                         tabController.clear();
diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart
index a7a93d7ad..57ee43e14 100644
--- a/flutter/lib/desktop/pages/desktop_tab_page.dart
+++ b/flutter/lib/desktop/pages/desktop_tab_page.dart
@@ -46,7 +46,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
                 body: DesktopTab(
                   controller: tabController,
                   theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
-                  isMainWindow: true,
+                  tabType: DesktopTabType.main,
                   tail: ActionIcon(
                     message: 'Settings',
                     icon: IconFont.menu,
diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart
index e7f08a516..6c8b58a30 100644
--- a/flutter/lib/desktop/pages/file_manager_tab_page.dart
+++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart
@@ -37,7 +37,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
   @override
   void initState() {
     super.initState();
-    
+
     tabController.onRemove = (_, id) => onRemoveId(id);
 
     rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
@@ -74,9 +74,9 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
             body: DesktopTab(
               controller: tabController,
               theme: theme,
-              isMainWindow: false,
+              tabType: DesktopTabType.fileTransfer,
               onClose: () {
-                 tabController.clear();
+                tabController.clear();
               },
               tail: AddButton(
                 theme: theme,
diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart
index 8db4c7f98..1e2c8e2bc 100644
--- a/flutter/lib/desktop/pages/port_forward_tab_page.dart
+++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart
@@ -19,11 +19,13 @@ class PortForwardTabPage extends StatefulWidget {
 
 class _PortForwardTabPageState extends State<PortForwardTabPage> {
   final tabController = Get.put(DesktopTabController());
+  late final bool isRDP;
 
-  static final IconData selectedIcon = Icons.forward_sharp;
-  static final IconData unselectedIcon = Icons.forward_outlined;
+  static const IconData selectedIcon = Icons.forward_sharp;
+  static const IconData unselectedIcon = Icons.forward_outlined;
 
   _PortForwardTabPageState(Map<String, dynamic> params) {
+    isRDP = params['isRDP'];
     tabController.add(TabInfo(
         key: params['id'],
         label: params['id'],
@@ -32,7 +34,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
         page: PortForwardPage(
           key: ValueKey(params['id']),
           id: params['id'],
-          isRDP: params['isRDP'],
+          isRDP: isRDP,
         )));
   }
 
@@ -76,7 +78,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
             body: DesktopTab(
               controller: tabController,
               theme: theme,
-              isMainWindow: false,
+              tabType: isRDP ? DesktopTabType.rdp : DesktopTabType.portForward,
               onClose: () {
                 tabController.clear();
               },
diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart
index d96efc710..e7922403b 100644
--- a/flutter/lib/desktop/pages/server_page.dart
+++ b/flutter/lib/desktop/pages/server_page.dart
@@ -111,7 +111,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
             showMaximize: false,
             showMinimize: false,
             controller: serverModel.tabController,
-            isMainWindow: true,
+            tabType: DesktopTabType.cm,
             pageViewBuilder: (pageView) => Row(children: [
                   Expanded(child: pageView),
                   Consumer<ChatModel>(
@@ -294,7 +294,8 @@ class _CmHeaderState extends State<_CmHeader>
         Offstage(
           offstage: client.isFileTransfer,
           child: IconButton(
-            onPressed: () => gFFI.chatModel.toggleCMChatPage(client.id),
+            onPressed: () => checkClickTime(
+                client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)),
             icon: Icon(Icons.message_outlined),
           ),
         )
@@ -326,7 +327,8 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
             BoxDecoration(color: enabled ? MyTheme.accent80 : Colors.grey),
         padding: EdgeInsets.all(4.0),
         child: InkWell(
-          onTap: () => onTap?.call(!enabled),
+          onTap: () =>
+              checkClickTime(widget.client.id, () => onTap?.call(!enabled)),
           child: Image(
             image: icon,
             width: 50,
@@ -422,7 +424,8 @@ class _CmControlPanel extends StatelessWidget {
           decoration: BoxDecoration(
               color: Colors.redAccent, borderRadius: BorderRadius.circular(10)),
           child: InkWell(
-              onTap: () => handleDisconnect(context),
+              onTap: () =>
+                  checkClickTime(client.id, () => handleDisconnect(context)),
               child: Row(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
@@ -447,7 +450,8 @@ class _CmControlPanel extends StatelessWidget {
           decoration: BoxDecoration(
               color: MyTheme.accent, borderRadius: BorderRadius.circular(10)),
           child: InkWell(
-              onTap: () => handleAccept(context),
+              onTap: () =>
+                  checkClickTime(client.id, () => handleAccept(context)),
               child: Row(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
@@ -469,7 +473,8 @@ class _CmControlPanel extends StatelessWidget {
               borderRadius: BorderRadius.circular(10),
               border: Border.all(color: Colors.grey)),
           child: InkWell(
-              onTap: () => handleDisconnect(context),
+              onTap: () =>
+                  checkClickTime(client.id, () => handleDisconnect(context)),
               child: Row(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
@@ -572,3 +577,12 @@ Widget clientInfo(Client client) {
         ),
       ]));
 }
+
+void checkClickTime(int id, Function() callback) async {
+  var clickCallbackTime = DateTime.now().millisecondsSinceEpoch;
+  await bind.cmCheckClickTime(connId: id);
+  Timer(const Duration(milliseconds: 120), () async {
+    var d = clickCallbackTime - await bind.cmGetClickTime();
+    if (d > 120) callback();
+  });
+}
diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart
index aea90c868..a89d0da38 100644
--- a/flutter/lib/desktop/widgets/tabbar_widget.dart
+++ b/flutter/lib/desktop/widgets/tabbar_widget.dart
@@ -1,4 +1,5 @@
 import 'dart:io';
+import 'dart:async';
 import 'dart:math';
 
 import 'package:desktop_multi_window/desktop_multi_window.dart';
@@ -6,6 +7,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hbb/common.dart';
 import 'package:flutter_hbb/consts.dart';
 import 'package:flutter_hbb/main.dart';
+import 'package:flutter_hbb/models/platform_model.dart';
 import 'package:get/get.dart';
 import 'package:scroll_pos/scroll_pos.dart';
 import 'package:window_manager/window_manager.dart';
@@ -34,6 +36,15 @@ class TabInfo {
       required this.page});
 }
 
+enum DesktopTabType {
+  main,
+  cm,
+  remoteScreen,
+  fileTransfer,
+  portForward,
+  rdp,
+}
+
 class DesktopTabState {
   final List<TabInfo> tabs = [];
   final ScrollPosController scrollController =
@@ -143,6 +154,7 @@ typedef LabelGetter = Rx<String> Function(String key);
 class DesktopTab extends StatelessWidget {
   final Function(String)? onTabClose;
   final TarBarTheme theme;
+  final DesktopTabType tabType;
   final bool isMainWindow;
   final bool showTabBar;
   final bool showLogo;
@@ -161,7 +173,7 @@ class DesktopTab extends StatelessWidget {
 
   const DesktopTab({
     required this.controller,
-    required this.isMainWindow,
+    required this.tabType,
     this.theme = const TarBarTheme.light(),
     this.onTabClose,
     this.showTabBar = true,
@@ -175,7 +187,8 @@ class DesktopTab extends StatelessWidget {
     this.onClose,
     this.tabBuilder,
     this.labelGetter,
-  });
+  }) : isMainWindow =
+            tabType == DesktopTabType.main || tabType == DesktopTabType.cm;
 
   @override
   Widget build(BuildContext context) {
@@ -204,11 +217,48 @@ class DesktopTab extends StatelessWidget {
     ]);
   }
 
+  Widget _buildBlock({required Widget child}) {
+    if (tabType != DesktopTabType.main) {
+      return child;
+    }
+    var block = false.obs;
+    return Obx(() => MouseRegion(
+          onEnter: (_) async {
+            if (!option2bool(
+                'allow-remote-config-modification',
+                await bind.mainGetOption(
+                    key: 'allow-remote-config-modification'))) {
+              var time0 = DateTime.now().millisecondsSinceEpoch;
+              await bind.mainCheckMouseTime();
+              Timer(const Duration(milliseconds: 120), () async {
+                var d = time0 - await bind.mainGetMouseTime();
+                if (d < 120) {
+                  block.value = true;
+                }
+              });
+            }
+          },
+          onExit: (_) => block.value = false,
+          child: Stack(
+            children: [
+              child,
+              Offstage(
+                  offstage: !block.value,
+                  child: Container(
+                    color: Colors.black.withOpacity(0.5),
+                  )),
+            ],
+          ),
+        ));
+  }
+
   Widget _buildPageView() {
-    return Obx(() => PageView(
-        controller: state.value.pageController,
-        children:
-            state.value.tabs.map((tab) => tab.page).toList(growable: false)));
+    return _buildBlock(
+        child: Obx(() => PageView(
+            controller: state.value.pageController,
+            children: state.value.tabs
+                .map((tab) => tab.page)
+                .toList(growable: false))));
   }
 
   Widget _buildBar() {
diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart
index efca88180..e1c254942 100644
--- a/flutter/lib/main.dart
+++ b/flutter/lib/main.dart
@@ -81,6 +81,8 @@ Future<void> initEnv(String appType) async {
 
 void runMainApp(bool startService) async {
   await initEnv(kAppTypeMain);
+  // trigger connection status updater
+  await bind.mainCheckConnectStatus();
   if (startService) {
     // await windowManager.ensureInitialized();
     // disable tray
@@ -89,10 +91,11 @@ void runMainApp(bool startService) async {
   }
   runApp(App());
   // set window option
-  WindowOptions windowOptions = getHiddenTitleBarWindowOptions(const Size(1280, 720));
+  WindowOptions windowOptions =
+      getHiddenTitleBarWindowOptions(const Size(1280, 720));
   windowManager.waitUntilReadyToShow(windowOptions, () async {
-      await windowManager.show();
-      await windowManager.focus();
+    await windowManager.show();
+    await windowManager.focus();
   });
 }
 
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index db8030782..a17b3d695 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -22,12 +22,13 @@ use crate::ui_interface;
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
 use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
 use crate::ui_interface::{
-    check_super_user_permission, discover, forget_password, get_api_server, get_app_name,
-    get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_langs,
-    get_license, get_local_option, get_option, get_options, get_peer, get_peer_option, get_socks,
-    get_sound_inputs, get_uuid, get_version, has_hwcodec, has_rendezvous_service, post_request,
-    set_local_option, set_option, set_options, set_peer_option, set_permanent_password, set_socks,
-    store_fav, test_if_valid_server, update_temporary_password, using_public_server,
+    check_mouse_time, check_super_user_permission, discover, forget_password, get_api_server,
+    get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers,
+    get_langs, get_license, get_local_option, get_mouse_time, get_option, get_options, get_peer,
+    get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, has_hwcodec,
+    has_rendezvous_service, post_request, set_local_option, set_option, set_options,
+    set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server,
+    update_temporary_password, using_public_server,
 };
 
 fn initialize(app_dir: &str) {
@@ -472,7 +473,7 @@ pub fn main_get_connect_status() -> String {
 
 pub fn main_check_connect_status() {
     #[cfg(not(any(target_os = "android", target_os = "ios")))]
-    check_connect_status(true);
+    check_mouse_time(); // avoid multi calls
 }
 
 pub fn main_is_using_public_server() -> bool {
@@ -764,6 +765,14 @@ pub fn main_check_super_user_permission() -> bool {
     check_super_user_permission()
 }
 
+pub fn main_check_mouse_time() {
+    check_mouse_time();
+}
+
+pub fn main_get_mouse_time() -> f64 {
+    get_mouse_time()
+}
+
 pub fn cm_send_chat(conn_id: i32, msg: String) {
     connection_manager::send_chat(conn_id, msg);
 }