From a50482af5caced4fde8d2db269d123534bef8eab Mon Sep 17 00:00:00 2001 From: fufesou Date: Tue, 6 Sep 2022 02:08:59 -0700 Subject: [PATCH] flutter_desktop: WOL & menu, mid commit Signed-off-by: fufesou --- .../lib/desktop/pages/connection_page.dart | 6 +- .../lib/desktop/pages/desktop_home_page.dart | 273 +++++++++++++----- .../desktop/pages/desktop_setting_page.dart | 1 - .../lib/desktop/pages/file_manager_page.dart | 2 +- .../desktop/pages/file_manager_tab_page.dart | 4 +- flutter/lib/desktop/pages/remote_page.dart | 19 +- flutter/lib/desktop/pages/server_page.dart | 4 +- .../lib/desktop/widgets/peercard_widget.dart | 49 +++- flutter/lib/desktop/widgets/popup_menu.dart | 41 ++- .../lib/desktop/widgets/remote_menubar.dart | 28 +- flutter/pubspec.yaml | 4 + src/flutter_ffi.rs | 4 + 12 files changed, 311 insertions(+), 124 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index e4f6527ca..b113d8a56 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -22,10 +22,10 @@ import '../../models/platform_model.dart'; /// Connection page for connecting to a remote peer. class ConnectionPage extends StatefulWidget { - ConnectionPage({Key? key}) : super(key: key); + const ConnectionPage({Key? key}) : super(key: key); @override - _ConnectionPageState createState() => _ConnectionPageState(); + State createState() => _ConnectionPageState(); } /// State for the connection page. @@ -101,7 +101,7 @@ class _ConnectionPageState extends State { ], ).marginSymmetric(horizontal: 22), ), - Divider(), + const Divider(), SizedBox(height: 50, child: Obx(() => buildStatus())) .paddingSymmetric(horizontal: 12.0) ]), diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 3ce956c23..545e9165c 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -19,8 +19,15 @@ import 'package:tray_manager/tray_manager.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:window_manager/window_manager.dart'; +class _PopupMenuTheme { + static const Color commonColor = MyTheme.accent; + // kMinInteractiveDimension + static const double height = 25.0; + static const double dividerHeight = 3.0; +} + class DesktopHomePage extends StatefulWidget { - DesktopHomePage({Key? key}) : super(key: key); + const DesktopHomePage({Key? key}) : super(key: key); @override State createState() => _DesktopHomePageState(); @@ -86,7 +93,7 @@ class _DesktopHomePageState extends State buildServerBoard(BuildContext context) { return Container( color: MyTheme.color(context).grayBg, - child: ConnectionPage(), + child: const ConnectionPage(), ); } @@ -154,8 +161,190 @@ class _DesktopHomePageState extends State ); } + // Future> _genSwitchEntry( + // String label, String key) async { + + // final v = await bind.mainGetOption(key: key); + // bool enable; + // if (key == "stop-service") { + // enable = v != "Y"; + // } else if (key.startsWith("allow-")) { + // enable = v == "Y"; + // } else { + // enable = v != "N"; + // } + + // return PopupMenuItem( + // child: Row( + // children: [ + // Icon(Icons.check, + // color: enable ? null : MyTheme.accent.withAlpha(00)), + // Text( + // label, + // style: genTextStyle(enable), + // ), + // ], + // ), + // value: key, + // ); + // } + + _popupMenu(BuildContext context, RelativeRect position) async { + TextStyle styleEnabled = const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal); + TextStyle styleDisabled = const TextStyle( + color: Colors.redAccent, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal, + decoration: TextDecoration.lineThrough); + + enabledEntry(String label, String key) { + Rx textStyle = styleEnabled.obs; + return MenuEntrySwitch( + text: translate(label), + textStyle: textStyle, + getter: () async { + final opt = await bind.mainGetOption(key: key); + bool enabled; + if (key == 'stop-service') { + enabled = opt != 'Y'; + } else if (key.startsWith("allow-")) { + enabled = opt == 'Y'; + } else { + enabled = opt != 'N'; + } + textStyle.value = enabled ? styleEnabled : styleDisabled; + return enabled; + }, + setter: (bool v) async { + String opt; + if (key == 'stop-service') { + opt = v ? 'Y' : ''; + } else if (key.startsWith("allow-")) { + opt = v ? 'Y' : ''; + } else { + opt = v ? '' : 'N'; + } + await bind.mainSetOption(key: key, value: opt); + if (key == 'allow-darktheme') { + changeTheme(opt); + } + }, + dismissOnClicked: false, + ); + } + + final userName = await gFFI.userModel.getUserName(); + final enabledInput = await bind.mainGetOption(key: 'enable-audio'); + final defaultInput = await gFFI.getDefaultAudioInput(); + + final List> menu = >[ + enabledEntry('Enable Keyboard/Mouse', 'enable-keyboard'), + enabledEntry('Enable Clipboard', 'enable-clipboard'), + enabledEntry('Enable File Transfer', 'enable-file-transfer'), + enabledEntry('Enable TCP Tunneling', 'enable-tunnel'), + // TODO: audio sub menu? + // genAudioInputPopupMenuItem(enabledInput != "N", defaultInput), + MenuEntryDivider(), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('ID/Relay Server'), + style: style, + ), + proc: () { + changeServer(); + }, + dismissOnClicked: true, + ), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('IP Whitelisting'), + style: style, + ), + proc: () { + changeWhiteList(); + }, + dismissOnClicked: true, + ), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Socks5 Proxy'), + style: style, + ), + proc: () { + changeSocks5Proxy(); + }, + dismissOnClicked: true, + ), + MenuEntryDivider(), + enabledEntry('Enable Service', 'stop-service'), + enabledEntry('Always connected via relay', 'allow-always-relay'), + // FIXME: is this option correct? + enabledEntry('Start ID/relay service', 'stop-rendezvous-service'), + MenuEntryDivider(), + userName.isEmpty + ? MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Login'), + style: style, + ), + proc: () { + login(); + }, + dismissOnClicked: true, + ) + : MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Logout'), + style: style, + ), + proc: () { + logOut(); + }, + dismissOnClicked: true, + ), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Change ID'), + style: style, + ), + proc: () { + changeId(); + }, + dismissOnClicked: true, + ), + MenuEntryDivider(), + enabledEntry('Dark Theme', 'allow-darktheme'), + MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('About'), + style: style, + ), + proc: () { + about(); + }, + dismissOnClicked: true, + ), + ]; + + await mod_menu.showMenu( + context: context, + position: position, + items: menu + .map((e) => e.build( + context, + const MenuConfig( + commonColor: _PopupMenuTheme.commonColor, + height: _PopupMenuTheme.height, + dividerHeight: _PopupMenuTheme.dividerHeight))) + .expand((i) => i) + .toList()); + } + Widget buildPopupMenu(BuildContext context) { - var position; + RelativeRect position = const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0); RxBool hover = false.obs; return InkWell( onTapDown: (detail) { @@ -164,83 +353,7 @@ class _DesktopHomePageState extends State position = RelativeRect.fromLTRB(x, y, x, y); }, onTap: () async { - final userName = await gFFI.userModel.getUserName(); - final enabledInput = await bind.mainGetOption(key: 'enable-audio'); - final defaultInput = await gFFI.getDefaultAudioInput(); - var menu = [ - await genEnablePopupMenuItem( - translate("Enable Keyboard/Mouse"), - 'enable-keyboard', - ), - await genEnablePopupMenuItem( - translate("Enable Clipboard"), - 'enable-clipboard', - ), - await genEnablePopupMenuItem( - translate("Enable File Transfer"), - 'enable-file-transfer', - ), - await genEnablePopupMenuItem( - translate("Enable TCP Tunneling"), - 'enable-tunnel', - ), - genAudioInputPopupMenuItem(enabledInput != "N", defaultInput), - PopupMenuDivider(), - PopupMenuItem( - child: Text(translate("ID/Relay Server")), - value: 'custom-server', - ), - PopupMenuItem( - child: Text(translate("IP Whitelisting")), - value: 'whitelist', - ), - PopupMenuItem( - child: Text(translate("Socks5 Proxy")), - value: 'socks5-proxy', - ), - PopupMenuDivider(), - await genEnablePopupMenuItem( - translate("Enable Service"), - 'stop-service', - ), - // TODO: direct server - await genEnablePopupMenuItem( - translate("Always connected via relay"), - 'allow-always-relay', - ), - await genEnablePopupMenuItem( - translate("Start ID/relay service"), - 'stop-rendezvous-service', - ), - PopupMenuDivider(), - userName.isEmpty - ? PopupMenuItem( - child: Text(translate("Login")), - value: 'login', - ) - : PopupMenuItem( - child: Text("${translate("Logout")} $userName"), - value: 'logout', - ), - PopupMenuItem( - child: Text(translate("Change ID")), - value: 'change-id', - ), - PopupMenuDivider(), - await genEnablePopupMenuItem( - translate("Dark Theme"), - 'allow-darktheme', - ), - PopupMenuItem( - child: Text(translate("About")), - value: 'about', - ), - ]; - final v = - await showMenu(context: context, position: position, items: menu); - if (v != null) { - onSelectMenu(v); - } + await _popupMenu(context, position); }, child: Obx( () => CircleAvatar( diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index b4bb00ab8..35383a7e0 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index be0fedc5c..bd6e4cb63 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -17,7 +17,7 @@ import '../../models/platform_model.dart'; enum LocationStatus { bread, textField } class FileManagerPage extends StatefulWidget { - FileManagerPage({Key? key, required this.id}) : super(key: key); + const FileManagerPage({Key? key, required this.id}) : super(key: key); final String id; @override diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index 18ea039a7..d6f01e55f 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -21,8 +21,8 @@ class FileManagerTabPage extends StatefulWidget { class _FileManagerTabPageState extends State { DesktopTabController get tabController => Get.find(); - static final IconData selectedIcon = Icons.file_copy_sharp; - static final IconData unselectedIcon = Icons.file_copy_outlined; + static const IconData selectedIcon = Icons.file_copy_sharp; + static const IconData unselectedIcon = Icons.file_copy_outlined; _FileManagerTabPageState(Map params) { Get.put(DesktopTabController(tabType: DesktopTabType.fileTransfer)); diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 23e4e4900..9b0ce66c2 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; +import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; // import 'package:window_manager/window_manager.dart'; @@ -22,7 +23,7 @@ import '../../common/shared_state.dart'; final initText = '\1' * 1024; class RemotePage extends StatefulWidget { - RemotePage({ + const RemotePage({ Key? key, required this.id, required this.tabBarHeight, @@ -32,7 +33,7 @@ class RemotePage extends StatefulWidget { final double tabBarHeight; @override - _RemotePageState createState() => _RemotePageState(); + State createState() => _RemotePageState(); } class _RemotePageState extends State @@ -483,6 +484,8 @@ class ImagePaint extends StatelessWidget { child: CustomPaint( painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), )); + + Rx pos = Rx(Offset(0.0, 0.0)); return Center( child: NotificationListener( onNotification: (notification) { @@ -498,9 +501,15 @@ class ImagePaint extends StatelessWidget { return false; }, child: Obx(() => MouseRegion( - cursor: (keyboardEnabled.isTrue && cursorOverImage.isTrue) - ? SystemMouseCursors.none - : MouseCursor.defer, + // cursor: (keyboardEnabled.isTrue && cursorOverImage.isTrue) + // ? SystemMouseCursors.none + // : MouseCursor.defer, + /// cursor: MouseCursor.defer, + cursor: FlutterCustomCursor( + path: "assets/pencil.png", x: 1.0, y: 8.0), + onHover: (evt) { + pos.value = evt.position; + }, child: _buildCrossScrollbar(_buildListener(imageWidget)))), ), ); diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index ac2fb7caa..08f3e5836 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -13,8 +13,10 @@ import '../../models/platform_model.dart'; import '../../models/server_model.dart'; class DesktopServerPage extends StatefulWidget { + const DesktopServerPage({Key? key}) : super(key: key); + @override - State createState() => _DesktopServerPageState(); + State createState() => _DesktopServerPageState(); } class _DesktopServerPageState extends State diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 13ab92ffe..fb1c59891 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -427,7 +427,7 @@ abstract class BasePeerCard extends StatelessWidget { alignment: Alignment.centerRight, child: IconButton( padding: EdgeInsets.zero, - icon: Icon(Icons.edit), + icon: const Icon(Icons.edit), onPressed: () => _rdpDialog(id), ), )) @@ -440,6 +440,20 @@ abstract class BasePeerCard extends StatelessWidget { ); } + @protected + MenuEntryBase _wolAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('WOL'), + style: style, + ), + proc: () { + bind.mainWol(id: id); + }, + dismissOnClicked: true, + ); + } + @protected Future> _forceAlwaysRelayAction(String id) async { const option = 'force-always-relay'; @@ -620,11 +634,16 @@ class RecentPeerCard extends BasePeerCard { _transferFileAction(context, peer.id), _tcpTunnelingAction(context, peer.id), ]; + MenuEntryBase? rdpAction; if (peer.platform == 'Windows') { - menuItems.add(_rdpAction(context, peer.id)); + rdpAction = _rdpAction(context, peer.id); } - menuItems.add(MenuEntryDivider()); menuItems.add(await _forceAlwaysRelayAction(peer.id)); + if (rdpAction != null) { + menuItems.add(rdpAction); + } + menuItems.add(_wolAction(peer.id)); + menuItems.add(MenuEntryDivider()); menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadRecentPeers(); @@ -647,10 +666,16 @@ class FavoritePeerCard extends BasePeerCard { _transferFileAction(context, peer.id), _tcpTunnelingAction(context, peer.id), ]; + MenuEntryBase? rdpAction; if (peer.platform == 'Windows') { - menuItems.add(_rdpAction(context, peer.id)); + rdpAction = _rdpAction(context, peer.id); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); + if (rdpAction != null) { + menuItems.add(rdpAction); + } + menuItems.add(_wolAction(peer.id)); + menuItems.add(MenuEntryDivider()); menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadFavPeers(); @@ -673,10 +698,16 @@ class DiscoveredPeerCard extends BasePeerCard { _transferFileAction(context, peer.id), _tcpTunnelingAction(context, peer.id), ]; + MenuEntryBase? rdpAction; if (peer.platform == 'Windows') { - menuItems.add(_rdpAction(context, peer.id)); + rdpAction = _rdpAction(context, peer.id); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); + if (rdpAction != null) { + menuItems.add(rdpAction); + } + menuItems.add(_wolAction(peer.id)); + menuItems.add(MenuEntryDivider()); menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async { await bind.mainLoadLanPeers(); @@ -698,10 +729,16 @@ class AddressBookPeerCard extends BasePeerCard { _transferFileAction(context, peer.id), _tcpTunnelingAction(context, peer.id), ]; + MenuEntryBase? rdpAction; if (peer.platform == 'Windows') { - menuItems.add(_rdpAction(context, peer.id)); + rdpAction = _rdpAction(context, peer.id); } menuItems.add(await _forceAlwaysRelayAction(peer.id)); + if (rdpAction != null) { + menuItems.add(rdpAction); + } + menuItems.add(_wolAction(peer.id)); + menuItems.add(MenuEntryDivider()); menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async {})); menuItems.add(_unrememberPasswordAction(peer.id)); diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index ea678673a..02e3512df 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -325,8 +325,10 @@ typedef SwitchSetter = Future Function(bool); abstract class MenuEntrySwitchBase extends MenuEntryBase { final String text; + final Rx? textStyle; - MenuEntrySwitchBase({required this.text, required dismissOnClicked}) + MenuEntrySwitchBase( + {required this.text, required dismissOnClicked, this.textStyle}) : super(dismissOnClicked: dismissOnClicked); RxBool get curOption; @@ -344,14 +346,23 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { alignment: AlignmentDirectional.centerStart, height: conf.height, child: Row(children: [ - // const SizedBox(width: MenuConfig.midPadding), - Text( - text, - style: TextStyle( - color: MyTheme.color(context).text, - fontSize: MenuConfig.fontSize, - fontWeight: FontWeight.normal), - ), + () { + if (textStyle != null) { + final style = textStyle!; + return Obx(() => Text( + text, + style: style.value, + )); + } else { + return Text( + text, + style: const TextStyle( + color: Colors.black, + fontSize: MenuConfig.fontSize, + fontWeight: FontWeight.normal), + ); + } + }(), Expanded( child: Align( alignment: Alignment.centerRight, @@ -388,8 +399,12 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { {required String text, required this.getter, required this.setter, + Rx? textStyle, dismissOnClicked = false}) - : super(text: text, dismissOnClicked: dismissOnClicked) { + : super( + text: text, + textStyle: textStyle, + dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await getter(); }(); @@ -418,8 +433,12 @@ class MenuEntrySwitch2 extends MenuEntrySwitchBase { {required String text, required this.getter, required this.setter, + Rx? textStyle, dismissOnClicked = false}) - : super(text: text, dismissOnClicked: dismissOnClicked); + : super( + text: text, + textStyle: textStyle, + dismissOnClicked: dismissOnClicked); @override RxBool get curOption => getter(); diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index c83f61a17..411c30e73 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -75,20 +75,20 @@ class _RemoteMenubarState extends State { final List menubarItems = []; if (!isWebDesktop) { menubarItems.add(_buildFullscreen(context)); - if (widget.ffi.ffiModel.isPeerAndroid) { - menubarItems.add(IconButton( - tooltip: translate('Mobile Actions'), - color: _MenubarTheme.commonColor, - icon: const Icon(Icons.build), - onPressed: () { - if (mobileActionsOverlayEntry == null) { - showMobileActionsOverlay(); - } else { - hideMobileActionsOverlay(); - } - }, - )); - } + //if (widget.ffi.ffiModel.isPeerAndroid) { + menubarItems.add(IconButton( + tooltip: translate('Mobile Actions'), + color: _MenubarTheme.commonColor, + icon: const Icon(Icons.build), + onPressed: () { + if (mobileActionsOverlayEntry == null) { + showMobileActionsOverlay(); + } else { + hideMobileActionsOverlay(); + } + }, + )); + //} } menubarItems.add(_buildMonitor(context)); menubarItems.add(_buildControl(context)); diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index f86c59755..1f4987e5c 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -68,6 +68,10 @@ dependencies: git: url: https://github.com/Kingtous/rustdesk_tray_manager ref: 3aa37c86e47ea748e7b5507cbe59f2c54ebdb23a + flutter_custom_cursor: + git: + url: https://github.com/Kingtous/rustdesk_flutter_custom_cursor + ref: 7fe78c139c711bafbae52d924e9caf18bd193e28 get: ^4.6.5 visibility_detector: ^0.3.3 contextmenu: ^3.0.0 diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index ef2aaeaa1..04d8619c1 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -789,6 +789,10 @@ pub fn main_get_mouse_time() -> f64 { get_mouse_time() } +pub fn main_wol(id: String) { + crate::lan::send_wol(id) +} + pub fn cm_send_chat(conn_id: i32, msg: String) { crate::ui_cm_interface::send_chat(conn_id, msg); }