diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 466b4b74a..7b61c5b48 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -2,6 +2,7 @@ const double kDesktopRemoteTabBarHeight = 48.0; const String kAppTypeMain = "main"; const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; +const String kAppTypeConnectionManager = "connection manager"; const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart new file mode 100644 index 000000000..bf80bfbe7 --- /dev/null +++ b/flutter/lib/desktop/pages/server_page.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:provider/provider.dart'; + +import '../../common.dart'; +import '../../mobile/pages/home_page.dart'; +import '../../models/platform_model.dart'; +import '../../models/server_model.dart'; + +class DesktopServerPage extends StatefulWidget implements PageShape { + @override + final title = translate("Share Screen"); + + @override + final icon = Icon(Icons.mobile_screen_share); + + @override + final appBarActions = [ + PopupMenuButton( + icon: Icon(Icons.more_vert), + itemBuilder: (context) { + return [ + PopupMenuItem( + child: Text(translate("Change ID")), + padding: EdgeInsets.symmetric(horizontal: 16.0), + value: "changeID", + enabled: false, + ), + PopupMenuItem( + child: Text(translate("Set permanent password")), + padding: EdgeInsets.symmetric(horizontal: 16.0), + value: "setPermanentPassword", + enabled: + gFFI.serverModel.verificationMethod != kUseTemporaryPassword, + ), + PopupMenuItem( + child: Text(translate("Set temporary password length")), + padding: EdgeInsets.symmetric(horizontal: 16.0), + value: "setTemporaryPasswordLength", + enabled: + gFFI.serverModel.verificationMethod != kUsePermanentPassword, + ), + const PopupMenuDivider(), + PopupMenuItem( + padding: EdgeInsets.symmetric(horizontal: 0.0), + value: kUseTemporaryPassword, + child: Container( + child: ListTile( + title: Text(translate("Use temporary password")), + trailing: Icon( + Icons.check, + color: gFFI.serverModel.verificationMethod == + kUseTemporaryPassword + ? null + : Color(0xFFFFFFFF), + ))), + ), + PopupMenuItem( + padding: EdgeInsets.symmetric(horizontal: 0.0), + value: kUsePermanentPassword, + child: ListTile( + title: Text(translate("Use permanent password")), + trailing: Icon( + Icons.check, + color: gFFI.serverModel.verificationMethod == + kUsePermanentPassword + ? null + : Color(0xFFFFFFFF), + )), + ), + PopupMenuItem( + padding: EdgeInsets.symmetric(horizontal: 0.0), + value: kUseBothPasswords, + child: ListTile( + title: Text(translate("Use both passwords")), + trailing: Icon( + Icons.check, + color: gFFI.serverModel.verificationMethod != + kUseTemporaryPassword && + gFFI.serverModel.verificationMethod != + kUsePermanentPassword + ? null + : Color(0xFFFFFFFF), + )), + ), + ]; + }, + onSelected: (value) { + if (value == "changeID") { + // TODO + } else if (value == "setPermanentPassword") { + // setPermanentPasswordDialog(); + } else if (value == "setTemporaryPasswordLength") { + // setTemporaryPasswordLengthDialog(); + } else if (value == kUsePermanentPassword || + value == kUseTemporaryPassword || + value == kUseBothPasswords) { + bind.mainSetOption(key: "verification-method", value: value); + gFFI.serverModel.updatePasswordModel(); + } + }) + ]; + + @override + State createState() => _DesktopServerPageState(); +} + +class _DesktopServerPageState extends State { + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: gFFI.serverModel, + child: Consumer( + builder: (context, serverModel, child) => SingleChildScrollView( + controller: gFFI.serverModel.controller, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + ConnectionManager(), + SizedBox.fromSize(size: Size(0, 15.0)), + ], + ), + ), + ))); + } +} + +class ConnectionManager extends StatelessWidget { + @override + Widget build(BuildContext context) { + final serverModel = Provider.of(context); + return Column( + children: serverModel.clients.entries + .map((entry) => PaddingCard( + title: translate(entry.value.isFileTransfer + ? "File Connection" + : "Screen Connection"), + titleIcon: entry.value.isFileTransfer + ? Icons.folder_outlined + : Icons.mobile_screen_share, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: clientInfo(entry.value)), + Expanded( + flex: -1, + child: entry.value.isFileTransfer || + !entry.value.authorized + ? SizedBox.shrink() + : IconButton( + onPressed: () { + gFFI.chatModel + .changeCurrentID(entry.value.id); + final bar = + navigationBarKey.currentWidget; + if (bar != null) { + bar as BottomNavigationBar; + bar.onTap!(1); + } + }, + icon: Icon( + Icons.chat, + color: MyTheme.accent80, + ))) + ], + ), + entry.value.authorized + ? SizedBox.shrink() + : Text( + translate("android_new_connection_tip"), + style: TextStyle(color: Colors.black54), + ), + entry.value.authorized + ? ElevatedButton.icon( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(Colors.red)), + icon: Icon(Icons.close), + onPressed: () { + bind.serverCloseConnection(connId: entry.key); + gFFI.invokeMethod( + "cancel_notification", entry.key); + }, + label: Text(translate("Close"))) + : Row(children: [ + TextButton( + child: Text(translate("Dismiss")), + onPressed: () { + serverModel.sendLoginResponse( + entry.value, false); + }), + SizedBox(width: 20), + ElevatedButton( + child: Text(translate("Accept")), + onPressed: () { + serverModel.sendLoginResponse( + entry.value, true); + }), + ]), + ], + ))) + .toList()); + } +} + +class PaddingCard extends StatelessWidget { + PaddingCard({required this.child, this.title, this.titleIcon}); + + final String? title; + final IconData? titleIcon; + final Widget child; + + @override + Widget build(BuildContext context) { + final children = [child]; + if (title != null) { + children.insert( + 0, + Padding( + padding: EdgeInsets.symmetric(vertical: 5.0), + child: Row( + children: [ + titleIcon != null + ? Padding( + padding: EdgeInsets.only(right: 10), + child: Icon(titleIcon, + color: MyTheme.accent80, size: 30)) + : SizedBox.shrink(), + Text( + title!, + style: TextStyle( + fontFamily: 'WorkSans', + fontWeight: FontWeight.bold, + fontSize: 20, + color: MyTheme.accent80, + ), + ) + ], + ))); + } + return Container( + width: double.maxFinite, + child: Card( + margin: EdgeInsets.fromLTRB(15.0, 15.0, 15.0, 0), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ), + ), + )); + } +} + +Widget clientInfo(Client client) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + children: [ + Expanded( + flex: -1, + child: Padding( + padding: EdgeInsets.only(right: 12), + child: CircleAvatar( + child: Text(client.name[0]), + backgroundColor: MyTheme.border))), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(client.name, + style: TextStyle(color: MyTheme.idColor, fontSize: 18)), + SizedBox(width: 8), + Text(client.peerId, + style: TextStyle(color: MyTheme.idColor, fontSize: 10)) + ])) + ], + ), + ])); +} diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index dd6ccd31d..d8586baad 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; +import 'package:flutter_hbb/desktop/pages/server_page.dart'; import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -import 'package:get/route_manager.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -23,6 +23,7 @@ int? windowId; Future main(List args) async { WidgetsFlutterBinding.ensureInitialized(); + print("launch args: $args"); if (!isDesktop) { runMainApp(false); @@ -47,6 +48,9 @@ Future main(List args) async { default: break; } + } else if (args.isNotEmpty && args.first == '--cm') { + await windowManager.ensureInitialized(); + runConnectionManagerScreen(); } else { await windowManager.ensureInitialized(); windowManager.setPreventClose(true); @@ -111,6 +115,14 @@ void runFileTransferScreen(Map argument) async { ])); } +void runConnectionManagerScreen() async { + await initEnv(kAppTypeConnectionManager); + await windowManager.setAlwaysOnTop(true); + await windowManager.setSize(Size(400, 600)); + await windowManager.setAlignment(Alignment.topRight); + runApp(GetMaterialApp(theme: getCurrentTheme(), home: DesktopServerPage())); +} + class App extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index fcefcca82..695443f21 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -243,8 +243,8 @@ packages: dependency: "direct main" description: path: "." - ref: c53879e9ce4ed038af393a02bf2c7084ad4b53aa - resolved-ref: c53879e9ce4ed038af393a02bf2c7084ad4b53aa + ref: "2b1176d53f195cc55e8d37151bb3d9f6bd52fad3" + resolved-ref: "2b1176d53f195cc55e8d37151bb3d9f6bd52fad3" url: "https://github.com/Kingtous/rustdesk_desktop_multi_window" source: git version: "0.1.0" diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index b8b9580fb..a911903f8 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: desktop_multi_window: git: url: https://github.com/Kingtous/rustdesk_desktop_multi_window - ref: c53879e9ce4ed038af393a02bf2c7084ad4b53aa + ref: 2b1176d53f195cc55e8d37151bb3d9f6bd52fad3 # bitsdojo_window: ^0.1.2 freezed_annotation: ^2.0.3 tray_manager: 0.1.7 diff --git a/src/core_main.rs b/src/core_main.rs index c50bb0835..4e95f70ae 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -1,3 +1,7 @@ +use hbb_common::log; + +use crate::start_os_service; + /// Main entry of the RustDesk Core. /// Return true if the app should continue running with UI(possibly Flutter), false if the app should exit. pub fn core_main() -> bool { @@ -5,7 +9,13 @@ pub fn core_main() -> bool { // TODO: implement core_main() if args.len() > 1 { if args[1] == "--cm" { - // For test purpose only, this should stop any new window from popping up when a new connection is established. + // call connection manager to establish connections + // meanwhile, return true to call flutter window to show control panel + return true; + } + if args[1] == "--service" { + log::info!("start --service"); + start_os_service(); return false; } }