From 21b277ea3fb2aabfd835eadfedadf81b50f4af3f Mon Sep 17 00:00:00 2001
From: fufesou <shuanglongchen@yeah.net>
Date: Thu, 8 Sep 2022 00:35:19 -0700
Subject: [PATCH] flutter_desktop: check remote menu, mid commit

Signed-off-by: fufesou <shuanglongchen@yeah.net>
---
 .../lib/desktop/widgets/peercard_widget.dart  |  35 +--
 .../lib/desktop/widgets/remote_menubar.dart   | 234 ++++++++++++++----
 flutter/lib/desktop/widgets/utils.dart        |  28 +++
 src/flutter_ffi.rs                            |  30 ++-
 4 files changed, 237 insertions(+), 90 deletions(-)
 create mode 100644 flutter/lib/desktop/widgets/utils.dart

diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart
index fb1c59891..a809adf9c 100644
--- a/flutter/lib/desktop/widgets/peercard_widget.dart
+++ b/flutter/lib/desktop/widgets/peercard_widget.dart
@@ -1,7 +1,6 @@
 import 'package:contextmenu/contextmenu.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-import 'package:flutter_hbb/utils/multi_window_manager.dart';
 import 'package:get/get.dart';
 
 import '../../common.dart';
@@ -11,6 +10,7 @@ import '../../models/peer_model.dart';
 import '../../models/platform_model.dart';
 import './material_mod_popup_menu.dart' as mod_menu;
 import './popup_menu.dart';
+import './utils.dart';
 
 class _PopupMenuTheme {
   static const Color commonColor = MyTheme.accent;
@@ -32,7 +32,7 @@ class _PeerCard extends StatefulWidget {
   final Function(BuildContext, String) connect;
   final PopupMenuEntryBuilder popupMenuEntryBuilder;
 
-  _PeerCard(
+  const _PeerCard(
       {required this.peer,
       required this.alias,
       required this.connect,
@@ -317,7 +317,7 @@ abstract class BasePeerCard extends StatelessWidget {
     return _PeerCard(
       peer: peer,
       alias: alias,
-      connect: (BuildContext context, String id) => _connect(context, id),
+      connect: (BuildContext context, String id) => connect(context, id),
       popupMenuEntryBuilder: _buildPopupMenuEntry,
     );
   }
@@ -337,31 +337,6 @@ abstract class BasePeerCard extends StatelessWidget {
   @protected
   Future<List<MenuEntryBase<String>>> _buildMenuItems(BuildContext context);
 
-  /// Connect to a peer with [id].
-  /// If [isFileTransfer], starts a session only for file transfer.
-  /// If [isTcpTunneling], starts a session only for tcp tunneling.
-  /// If [isRDP], starts a session only for rdp.
-  void _connect(BuildContext context, String id,
-      {bool isFileTransfer = false,
-      bool isTcpTunneling = false,
-      bool isRDP = false}) async {
-    if (id == '') return;
-    id = id.replaceAll(' ', '');
-    assert(!(isFileTransfer && isTcpTunneling && isRDP),
-        "more than one connect type");
-    if (isFileTransfer) {
-      await rustDeskWinManager.newFileTransfer(id);
-    } else if (isTcpTunneling || isRDP) {
-      await rustDeskWinManager.newPortForward(id, isRDP);
-    } else {
-      await rustDeskWinManager.newRemoteDesktop(id);
-    }
-    FocusScopeNode currentFocus = FocusScope.of(context);
-    if (!currentFocus.hasPrimaryFocus) {
-      currentFocus.unfocus();
-    }
-  }
-
   MenuEntryBase<String> _connectCommonAction(
       BuildContext context, String id, String title,
       {bool isFileTransfer = false,
@@ -373,7 +348,7 @@ abstract class BasePeerCard extends StatelessWidget {
         style: style,
       ),
       proc: () {
-        _connect(
+        connect(
           context,
           peer.id,
           isFileTransfer: isFileTransfer,
@@ -434,7 +409,7 @@ abstract class BasePeerCard extends StatelessWidget {
             ],
           )),
       proc: () {
-        _connect(context, id, isRDP: true);
+        connect(context, id, isRDP: true);
       },
       dismissOnClicked: true,
     );
diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart
index c5e74be12..d7274b7ff 100644
--- a/flutter/lib/desktop/widgets/remote_menubar.dart
+++ b/flutter/lib/desktop/widgets/remote_menubar.dart
@@ -11,6 +11,7 @@ import '../../models/platform_model.dart';
 import '../../common/shared_state.dart';
 import './popup_menu.dart';
 import './material_mod_popup_menu.dart' as mod_menu;
+import './utils.dart';
 
 class _MenubarTheme {
   static const Color commonColor = MyTheme.accent;
@@ -225,7 +226,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
       ),
       tooltip: translate('Control Actions'),
       position: mod_menu.PopupMenuPosition.under,
-      itemBuilder: (BuildContext context) => _getControlMenu()
+      itemBuilder: (BuildContext context) => _getControlMenu(context)
           .map((entry) => entry.build(
               context,
               const MenuConfig(
@@ -297,66 +298,76 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
     );
   }
 
-  List<MenuEntryBase<String>> _getControlMenu() {
+  List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
     final pi = widget.ffi.ffiModel.pi;
     final perms = widget.ffi.ffiModel.permissions;
 
     final List<MenuEntryBase<String>> displayMenu = [];
-
-    if (pi.version.isNotEmpty) {
-      displayMenu.add(MenuEntryButton<String>(
-        childBuilder: (TextStyle? style) => Text(
-          translate('Refresh'),
-          style: style,
+    displayMenu.addAll([
+      MenuEntryButton<String>(
+        childBuilder: (TextStyle? style) => Row(
+          children: [
+            Text(
+              translate('OS Password'),
+              style: style,
+            ),
+            Expanded(
+                child: Align(
+              alignment: Alignment.centerRight,
+              child: IconButton(
+                padding: EdgeInsets.zero,
+                icon: const Icon(Icons.edit),
+                onPressed: () => showSetOSPassword(
+                    widget.id, false, widget.ffi.dialogManager),
+              ),
+            ))
+          ],
         ),
         proc: () {
-          bind.sessionRefresh(id: widget.id);
+          showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
         },
         dismissOnClicked: true,
-      ));
-    }
-    displayMenu.add(MenuEntryButton<String>(
-      childBuilder: (TextStyle? style) => Text(
-        translate('OS Password'),
-        style: style,
       ),
-      proc: () {
-        showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
-      },
-      dismissOnClicked: true,
-    ));
-
-    if (!isWebDesktop) {
-      if (perms['keyboard'] != false && perms['clipboard'] != false) {
-        displayMenu.add(MenuEntryButton<String>(
-          childBuilder: (TextStyle? style) => Text(
-            translate('Paste'),
-            style: style,
-          ),
-          proc: () {
-            () async {
-              ClipboardData? data =
-                  await Clipboard.getData(Clipboard.kTextPlain);
-              if (data != null && data.text != null) {
-                bind.sessionInputString(id: widget.id, value: data.text ?? "");
-              }
-            }();
-          },
-          dismissOnClicked: true,
-        ));
-      }
-
-      displayMenu.add(MenuEntryButton<String>(
+      MenuEntryButton<String>(
         childBuilder: (TextStyle? style) => Text(
-          translate('Reset canvas'),
+          translate('Transfer File'),
           style: style,
         ),
         proc: () {
-          widget.ffi.cursorModel.reset();
+          connect(context, widget.id, isFileTransfer: true);
         },
         dismissOnClicked: true,
-      ));
-    }
+      ),
+      MenuEntryButton<String>(
+        childBuilder: (TextStyle? style) => Text(
+          translate('TCP Tunneling'),
+          style: style,
+        ),
+        proc: () {
+          connect(context, widget.id, isTcpTunneling: true);
+        },
+        dismissOnClicked: true,
+      ),
+    ]);
+
+    // {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
+    final auditServer = bind.sessionGetAuditServerSync(id: widget.id);
+    //if (auditServer.isNotEmpty) {
+    displayMenu.add(
+      MenuEntryButton<String>(
+        childBuilder: (TextStyle? style) => Text(
+          translate('Note'),
+          style: style,
+        ),
+        proc: () {
+          showAuditDialog(widget.id, widget.ffi.dialogManager);
+        },
+        dismissOnClicked: true,
+      ),
+    );
+    //}
+
+    displayMenu.add(MenuEntryDivider());
 
     if (perms['keyboard'] != false) {
       if (pi.platform == 'Linux' || pi.sasEnabled) {
@@ -371,7 +382,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
           dismissOnClicked: true,
         ));
       }
+    }
+    if (gFFI.ffiModel.permissions["restart"] != false &&
+        (pi.platform == "Linux" ||
+            pi.platform == "Windows" ||
+            pi.platform == "Mac OS")) {
+      displayMenu.add(MenuEntryButton<String>(
+        childBuilder: (TextStyle? style) => Text(
+          translate('Restart Remote Device'),
+          style: style,
+        ),
+        proc: () {
+          showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
+        },
+        dismissOnClicked: true,
+      ));
+    }
 
+    if (perms['keyboard'] != false) {
       displayMenu.add(MenuEntryButton<String>(
         childBuilder: (TextStyle? style) => Text(
           translate('Insert Lock'),
@@ -402,17 +430,46 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
       }
     }
 
-    if (gFFI.ffiModel.permissions["restart"] != false &&
-        (pi.platform == "Linux" ||
-            pi.platform == "Windows" ||
-            pi.platform == "Mac OS")) {
+    if (pi.version.isNotEmpty) {
       displayMenu.add(MenuEntryButton<String>(
         childBuilder: (TextStyle? style) => Text(
-          translate('Restart Remote Device'),
+          translate('Refresh'),
           style: style,
         ),
         proc: () {
-          showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
+          bind.sessionRefresh(id: widget.id);
+        },
+        dismissOnClicked: true,
+      ));
+    }
+
+    if (!isWebDesktop) {
+      //   if (perms['keyboard'] != false && perms['clipboard'] != false) {
+      //     displayMenu.add(MenuEntryButton<String>(
+      //       childBuilder: (TextStyle? style) => Text(
+      //         translate('Paste'),
+      //         style: style,
+      //       ),
+      //       proc: () {
+      //         () async {
+      //           ClipboardData? data =
+      //               await Clipboard.getData(Clipboard.kTextPlain);
+      //           if (data != null && data.text != null) {
+      //             bind.sessionInputString(id: widget.id, value: data.text ?? "");
+      //           }
+      //         }();
+      //       },
+      //       dismissOnClicked: true,
+      //     ));
+      //   }
+
+      displayMenu.add(MenuEntryButton<String>(
+        childBuilder: (TextStyle? style) => Text(
+          translate('Reset canvas'),
+          style: style,
+        ),
+        proc: () {
+          widget.ffi.cursorModel.reset();
         },
         dismissOnClicked: true,
       ));
@@ -684,3 +741,76 @@ void showSetOSPassword(
     );
   });
 }
+
+void showAuditDialog(String id, dialogManager) async {
+  final controller = TextEditingController();
+  dialogManager.show((setState, close) {
+    submit() {
+      var text = controller.text.trim();
+      if (text != "") {
+        bind.sessionSendNote(id: id, note: text);
+      }
+      close();
+    }
+
+    late final focusNode = FocusNode(
+      onKey: (FocusNode node, RawKeyEvent evt) {
+        if (evt.logicalKey.keyLabel == 'Enter') {
+          if (evt is RawKeyDownEvent) {
+            int pos = controller.selection.base.offset;
+            controller.text =
+                '${controller.text.substring(0, pos)}\n${controller.text.substring(pos)}';
+            controller.selection =
+                TextSelection.fromPosition(TextPosition(offset: pos + 1));
+          }
+          return KeyEventResult.handled;
+        }
+        if (evt.logicalKey.keyLabel == 'Esc') {
+          if (evt is RawKeyDownEvent) {
+            close();
+          }
+          return KeyEventResult.handled;
+        } else {
+          return KeyEventResult.ignored;
+        }
+      },
+    );
+
+    return CustomAlertDialog(
+      title: Text(translate('Note')),
+      content: SizedBox(
+          width: 250,
+          height: 120,
+          child: TextField(
+            autofocus: true,
+            keyboardType: TextInputType.multiline,
+            textInputAction: TextInputAction.newline,
+            decoration: const InputDecoration.collapsed(
+              hintText: "input note here",
+            ),
+            // inputFormatters: [
+            //   LengthLimitingTextInputFormatter(16),
+            //   // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
+            // ],
+            maxLines: null,
+            maxLength: 256,
+            controller: controller,
+            focusNode: focusNode,
+          )),
+      actions: [
+        TextButton(
+          style: flatButtonStyle,
+          onPressed: close,
+          child: Text(translate('Cancel')),
+        ),
+        TextButton(
+          style: flatButtonStyle,
+          onPressed: submit,
+          child: Text(translate('OK')),
+        ),
+      ],
+      onSubmit: submit,
+      onCancel: close,
+    );
+  });
+}
diff --git a/flutter/lib/desktop/widgets/utils.dart b/flutter/lib/desktop/widgets/utils.dart
new file mode 100644
index 000000000..2f555c239
--- /dev/null
+++ b/flutter/lib/desktop/widgets/utils.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_hbb/utils/multi_window_manager.dart';
+
+/// Connect to a peer with [id].
+/// If [isFileTransfer], starts a session only for file transfer.
+/// If [isTcpTunneling], starts a session only for tcp tunneling.
+/// If [isRDP], starts a session only for rdp.
+void connect(BuildContext context, String id,
+    {bool isFileTransfer = false,
+    bool isTcpTunneling = false,
+    bool isRDP = false}) async {
+  if (id == '') return;
+  id = id.replaceAll(' ', '');
+  assert(!(isFileTransfer && isTcpTunneling && isRDP),
+      "more than one connect type");
+
+  FocusScopeNode currentFocus = FocusScope.of(context);
+  if (isFileTransfer) {
+    await rustDeskWinManager.newFileTransfer(id);
+  } else if (isTcpTunneling || isRDP) {
+    await rustDeskWinManager.newPortForward(id, isRDP);
+  } else {
+    await rustDeskWinManager.newRemoteDesktop(id);
+  }
+  if (!currentFocus.hasPrimaryFocus) {
+    currentFocus.unfocus();
+  }
+}
diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs
index 56d69f4c4..10ab95487 100644
--- a/src/flutter_ffi.rs
+++ b/src/flutter_ffi.rs
@@ -17,15 +17,14 @@ use crate::flutter::{self, SESSIONS};
 use crate::start_server;
 use crate::ui_interface;
 #[cfg(not(any(target_os = "android", target_os = "ios")))]
-use crate::ui_interface::change_id;
 use crate::ui_interface::{
-    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, send_to_cm, 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,
+    change_id, 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, send_to_cm, 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,
 };
 use crate::{
     client::file_trait::FileManager,
@@ -810,6 +809,21 @@ pub fn session_restart_remote_device(id: String) {
     }
 }
 
+pub fn session_get_audit_server_sync(id: String) -> SyncReturn<String> {
+    let res = if let Some(session) = SESSIONS.read().unwrap().get(&id) {
+        session.get_audit_server()
+    } else {
+        "".to_owned()
+    };
+    SyncReturn(res)
+}
+
+pub fn session_send_note(id: String, note: String) {
+    if let Some(session) = SESSIONS.read().unwrap().get(&id) {
+        session.send_note(note)
+    }
+}
+
 pub fn main_set_home_dir(home: String) {
     *config::APP_HOME_DIR.write().unwrap() = home;
 }