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>> _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 _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 { ), 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 { ); } - List> _getControlMenu() { + List> _getControlMenu(BuildContext context) { final pi = widget.ffi.ffiModel.pi; final perms = widget.ffi.ffiModel.permissions; final List> displayMenu = []; - - if (pi.version.isNotEmpty) { - displayMenu.add(MenuEntryButton( - childBuilder: (TextStyle? style) => Text( - translate('Refresh'), - style: style, + displayMenu.addAll([ + MenuEntryButton( + 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( - 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( - 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( + MenuEntryButton( 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( + childBuilder: (TextStyle? style) => Text( + translate('TCP Tunneling'), + style: style, + ), + proc: () { + connect(context, widget.id, isTcpTunneling: true); + }, + dismissOnClicked: true, + ), + ]); + + // {handler.get_audit_server() &&
  • {translate('Note')}
  • } + final auditServer = bind.sessionGetAuditServerSync(id: widget.id); + //if (auditServer.isNotEmpty) { + displayMenu.add( + MenuEntryButton( + 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 { dismissOnClicked: true, )); } + } + if (gFFI.ffiModel.permissions["restart"] != false && + (pi.platform == "Linux" || + pi.platform == "Windows" || + pi.platform == "Mac OS")) { + displayMenu.add(MenuEntryButton( + 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( childBuilder: (TextStyle? style) => Text( translate('Insert Lock'), @@ -402,17 +430,46 @@ class _RemoteMenubarState extends State { } } - if (gFFI.ffiModel.permissions["restart"] != false && - (pi.platform == "Linux" || - pi.platform == "Windows" || - pi.platform == "Mac OS")) { + if (pi.version.isNotEmpty) { displayMenu.add(MenuEntryButton( 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( + // 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( + 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 { + 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; }