port forward ui

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages 2022-08-26 11:35:28 +08:00
parent 7b4a655eaf
commit 6ea16e4cdb
10 changed files with 769 additions and 29 deletions

View File

@ -4,6 +4,7 @@ const double kDesktopRemoteTabBarHeight = 28.0;
const String kAppTypeMain = "main";
const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";
const String kAppTypeDesktopPortForward = "port forward";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";

View File

@ -0,0 +1,348 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
import 'package:wakelock/wakelock.dart';
const double _kColumn1Width = 30;
const double _kColumn4Width = 100;
const double _kRowHeight = 50;
const double _kTextLeftMargin = 20;
class _PortForward {
int localPort;
String remoteHost;
int remotePort;
_PortForward.fromJson(List<dynamic> json)
: localPort = json[0] as int,
remoteHost = json[1] as String,
remotePort = json[2] as int;
}
class PortForwardPage extends StatefulWidget {
const PortForwardPage({Key? key, required this.id, required this.isRDP})
: super(key: key);
final String id;
final bool isRDP;
@override
State<PortForwardPage> createState() => _PortForwardPageState();
}
class _PortForwardPageState extends State<PortForwardPage>
with AutomaticKeepAliveClientMixin {
final bool isRdp = false;
final TextEditingController localPortController = TextEditingController();
final TextEditingController remoteHostController = TextEditingController();
final TextEditingController remotePortController = TextEditingController();
RxList<_PortForward> pfs = RxList.empty(growable: true);
late FFI _ffi;
@override
void initState() {
super.initState();
_ffi = FFI();
// _ffi.connect(widget.id, isPortForward: true);
Get.put(_ffi, tag: 'pf_${widget.id}');
if (!Platform.isLinux) {
Wakelock.enable();
}
print("init success with id ${widget.id}");
}
@override
void dispose() {
_ffi.close();
_ffi.dialogManager.dismissAll();
if (!Platform.isLinux) {
Wakelock.disable();
}
Get.delete<FFI>(tag: 'pf_${widget.id}');
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).grayBg,
body: FutureBuilder(future: () async {
if (!isRdp) {
refreshTunnelConfig();
}
}(), builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 20, color: MyTheme.color(context).grayBg!)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildPrompt(context),
Flexible(
child: Container(
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
border: Border.all(width: 1, color: MyTheme.border)),
child:
widget.isRDP ? buildRdp(context) : buildTunnel(context),
),
),
],
),
);
}
return const Offstage();
}),
);
}
buildPrompt(BuildContext context) {
return Obx(() => Offstage(
offstage: pfs.isEmpty && !widget.isRDP,
child: Container(
height: 45,
color: const Color(0xFF007F00),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
translate('Listening ...'),
style: const TextStyle(fontSize: 16, color: Colors.white),
),
Text(
translate('not_close_tcp_tip'),
style: const TextStyle(
fontSize: 10, color: Color(0xFFDDDDDD), height: 1.2),
)
])).marginOnly(bottom: 8),
));
}
buildTunnel(BuildContext context) {
text(String lable) => Expanded(
child: Text(translate(lable)).marginOnly(left: _kTextLeftMargin));
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
child: Obx(() => ListView.builder(
itemCount: pfs.length + 2,
itemBuilder: ((context, index) {
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
child: Row(children: [
text('Local Port'),
const SizedBox(width: _kColumn1Width),
text('Remote Host'),
text('Remote Port'),
SizedBox(
width: _kColumn4Width, child: Text(translate('Action')))
]),
);
} else if (index == 1) {
return buildTunnelAddRow(context);
} else {
return buildTunnelDataRow(context, pfs[index - 2], index - 2);
}
}))),
);
}
buildTunnelAddRow(BuildContext context) {
var portInputFormatter = [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$'))
];
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
child: Row(children: [
buildTunnelInputCell(context,
controller: localPortController,
inputFormatters: portInputFormatter),
const SizedBox(
width: _kColumn1Width, child: Icon(Icons.arrow_forward_sharp)),
buildTunnelInputCell(context,
controller: remoteHostController, hint: 'localhost'),
buildTunnelInputCell(context,
controller: remotePortController,
inputFormatters: portInputFormatter),
SizedBox(
width: _kColumn4Width,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0, side: const BorderSide(color: MyTheme.border)),
onPressed: () async {
int? localPort = int.tryParse(localPortController.text);
int? remotePort = int.tryParse(remotePortController.text);
if (localPort != null &&
remotePort != null &&
(remoteHostController.text.isEmpty ||
remoteHostController.text.trim().isNotEmpty)) {
await bind.mainAddPortForward(
id: widget.id,
localPort: localPort,
remoteHost: remoteHostController.text.trim().isEmpty
? 'localhost'
: remoteHostController.text.trim(),
remotePort: remotePort);
localPortController.clear();
remoteHostController.clear();
remotePortController.clear();
refreshTunnelConfig();
}
},
child: Text(
translate('Add'),
),
).marginAll(10),
),
]),
);
}
buildTunnelInputCell(BuildContext context,
{required TextEditingController controller,
List<TextInputFormatter>? inputFormatters,
String? hint}) {
return Expanded(
child: TextField(
controller: controller,
inputFormatters: inputFormatters,
cursorColor: MyTheme.color(context).text,
cursorHeight: 20,
cursorWidth: 1,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: MyTheme.color(context).border!)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: MyTheme.color(context).border!)),
fillColor: MyTheme.color(context).bg,
contentPadding: const EdgeInsets.all(10),
hintText: hint,
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder, fontSize: 16)),
style: TextStyle(color: MyTheme.color(context).text, fontSize: 16),
).marginAll(10),
);
}
Widget buildTunnelDataRow(BuildContext context, _PortForward pf, int index) {
text(String lable) => Expanded(
child: Text(lable, style: const TextStyle(fontSize: 20))
.marginOnly(left: _kTextLeftMargin));
return Container(
height: _kRowHeight,
decoration: BoxDecoration(
color: index % 2 == 0
? isDarkTheme()
? const Color(0xFF202020)
: const Color(0xFFF4F5F6)
: MyTheme.color(context).bg),
child: Row(children: [
text(pf.localPort.toString()),
const SizedBox(width: _kColumn1Width),
text(pf.remoteHost),
text(pf.remotePort.toString()),
SizedBox(
width: _kColumn4Width,
child: IconButton(
icon: const Icon(Icons.close),
onPressed: () async {
await bind.mainRemovePortForward(
id: widget.id, localPort: pf.localPort);
refreshTunnelConfig();
},
),
),
]),
);
}
void refreshTunnelConfig() async {
String peer = await bind.mainGetPeer(id: widget.id);
Map<String, dynamic> config = jsonDecode(peer);
List<dynamic> infos = config['port_forwards'] as List;
List<_PortForward> result = List.empty(growable: true);
for (var e in infos) {
result.add(_PortForward.fromJson(e));
}
pfs.value = result;
}
buildRdp(BuildContext context) {
text1(String lable) =>
Expanded(child: Text(lable).marginOnly(left: _kTextLeftMargin));
text2(String lable) => Expanded(
child: Text(
lable,
style: TextStyle(fontSize: 20),
).marginOnly(left: _kTextLeftMargin));
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
child: ListView.builder(
itemCount: 2,
itemBuilder: ((context, index) {
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
child: Row(children: [
text1('Local Port'),
const SizedBox(width: _kColumn1Width),
text1('Remote Host'),
text1('Remote Port'),
]),
);
} else {
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
child: Row(children: [
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: 120,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
elevation: 0,
side: const BorderSide(color: MyTheme.border)),
onPressed: () {},
child: Text(
translate('New RDP'),
style: TextStyle(
fontWeight: FontWeight.w300, fontSize: 14),
),
).marginSymmetric(vertical: 10),
).marginOnly(left: 20),
),
),
const SizedBox(
width: _kColumn1Width,
child: Icon(Icons.arrow_forward_sharp)),
text2('localhost'),
text2('RDP'),
]),
);
}
})),
);
}
@override
bool get wantKeepAlive => true;
}

View File

@ -0,0 +1,106 @@
import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/port_forward_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart';
class PortForwardTabPage extends StatefulWidget {
final Map<String, dynamic> params;
const PortForwardTabPage({Key? key, required this.params}) : super(key: key);
@override
State<PortForwardTabPage> createState() => _PortForwardTabPageState(params);
}
class _PortForwardTabPageState extends State<PortForwardTabPage> {
final tabController = Get.put(DesktopTabController());
static final IconData selectedIcon = Icons.forward_sharp;
static final IconData unselectedIcon = Icons.forward_outlined;
_PortForwardTabPageState(Map<String, dynamic> params) {
tabController.add(TabInfo(
key: params['id'] + params['isRDP'].toString(),
label: params['id'],
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
page: PortForwardPage(
key: ValueKey(params['id']),
id: params['id'],
isRDP: params['isRDP'],
)));
}
@override
void initState() {
super.initState();
tabController.onRemove = (_, id) => onRemoveId(id);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
print(
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
// for simplify, just replace connectionId
if (call.method == "new_port_forward") {
final args = jsonDecode(call.arguments);
final id = args['id'];
final isRDP = args['isRDP'];
window_on_top(windowId());
tabController.add(TabInfo(
key: id,
label: id,
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
page: PortForwardPage(id: id, isRDP: isRDP)));
} else if (call.method == "onDestroy") {
tabController.state.value.tabs.forEach((tab) {
print("executing onDestroy hook, closing ${tab.label}}");
final tag = tab.label;
ffi(tag).close().then((_) {
Get.delete<FFI>(tag: tag);
});
});
Get.back();
}
});
}
@override
Widget build(BuildContext context) {
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
return SubWindowDragToResizeArea(
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
theme: theme,
isMainWindow: false,
tail: AddButton(
theme: theme,
).paddingOnly(left: 10),
)),
),
);
}
void onRemoveId(String id) {
ffi("pf_$id").close();
if (tabController.state.value.tabs.length == 0) {
WindowController.fromWindowId(windowId()).close();
}
}
int windowId() {
return widget.params["windowId"];
}
}

View File

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/port_forward_tab_page.dart';
import 'package:provider/provider.dart';
/// multi-tab file port forward screen
class DesktopPortForwardScreen extends StatelessWidget {
final Map<String, dynamic> params;
const DesktopPortForwardScreen({Key? key, required this.params})
: super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: gFFI.ffiModel),
],
child: Scaffold(
body: PortForwardTabPage(
params: params,
),
),
);
}
}

View File

@ -1,5 +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';
@ -284,11 +285,20 @@ class _PeerCardState extends State<_PeerCard>
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
void _connect(String id, {bool isFileTransfer = false}) async {
/// If [isTcpTunneling], starts a session only for tcp tunneling.
/// If [isRDP], starts a session only for rdp.
void _connect(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.new_file_transfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.new_port_forward(id, isRDP);
} else {
await rustDeskWinManager.new_remote_desktop(id);
}
@ -307,12 +317,18 @@ class _PeerCardState extends State<_PeerCard>
items: await super.widget.popupMenuItemsFunc(),
elevation: 8,
);
if (value == 'remove') {
if (value == 'connect') {
_connect(id);
} else if (value == 'file') {
_connect(id, isFileTransfer: true);
} else if (value == 'tcp-tunnel') {
_connect(id, isTcpTunneling: true);
} else if (value == 'RDP') {
_connect(id, isRDP: true);
} else if (value == 'remove') {
await bind.mainRemovePeer(id: id);
removePreference(id);
Get.forceAppUpdate(); // TODO use inner model / state
} else if (value == 'file') {
_connect(id, isFileTransfer: true);
} else if (value == 'add-fav') {
final favs = (await bind.mainGetFav()).toList();
if (favs.indexOf(id) < 0) {
@ -325,8 +341,6 @@ class _PeerCardState extends State<_PeerCard>
bind.mainStoreFav(favs: favs);
Get.forceAppUpdate(); // TODO use inner model / state
}
} else if (value == 'connect') {
_connect(id, isFileTransfer: false);
} else if (value == 'ab-delete') {
gFFI.abModel.deletePeer(id);
await gFFI.abModel.updateAb();
@ -554,7 +568,7 @@ class RecentPeerCard extends BasePeerCard {
: super(peer: peer, key: key, type: PeerType.recent);
Future<List<PopupMenuItem<String>>> _getPopupMenuItems() async {
return [
var items = [
PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>(
@ -570,6 +584,10 @@ class RecentPeerCard extends BasePeerCard {
PopupMenuItem<String>(
child: Text(translate('Add to Favorites')), value: 'add-fav'),
];
if (peer.platform == 'Windows') {
items.insert(3, _rdpMenuItem(peer.id));
}
return items;
}
}
@ -578,7 +596,7 @@ class FavoritePeerCard extends BasePeerCard {
: super(peer: peer, key: key, type: PeerType.fav);
Future<List<PopupMenuItem<String>>> _getPopupMenuItems() async {
return [
var items = [
PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>(
@ -594,6 +612,10 @@ class FavoritePeerCard extends BasePeerCard {
PopupMenuItem<String>(
child: Text(translate('Remove from Favorites')), value: 'remove-fav'),
];
if (peer.platform == 'Windows') {
items.insert(3, _rdpMenuItem(peer.id));
}
return items;
}
}
@ -602,7 +624,7 @@ class DiscoveredPeerCard extends BasePeerCard {
: super(peer: peer, key: key, type: PeerType.discovered);
Future<List<PopupMenuItem<String>>> _getPopupMenuItems() async {
return [
var items = [
PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>(
@ -618,6 +640,10 @@ class DiscoveredPeerCard extends BasePeerCard {
PopupMenuItem<String>(
child: Text(translate('Add to Favorites')), value: 'add-fav'),
];
if (peer.platform == 'Windows') {
items.insert(3, _rdpMenuItem(peer.id));
}
return items;
}
}
@ -626,7 +652,7 @@ class AddressBookPeerCard extends BasePeerCard {
: super(peer: peer, key: key, type: PeerType.ab);
Future<List<PopupMenuItem<String>>> _getPopupMenuItems() async {
return [
var items = [
PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>(
@ -645,6 +671,10 @@ class AddressBookPeerCard extends BasePeerCard {
PopupMenuItem<String>(
child: Text(translate('Edit Tag')), value: 'ab-edit-tag'),
];
if (peer.platform == 'Windows') {
items.insert(3, _rdpMenuItem(peer.id));
}
return items;
}
}
@ -664,3 +694,136 @@ Future<PopupMenuItem<String>> _forceAlwaysRelayMenuItem(String id) async {
),
value: 'force-always-relay');
}
PopupMenuItem<String> _rdpMenuItem(String id) {
return PopupMenuItem<String>(
child: Row(
children: [
Text('RDP'),
SizedBox(width: 20),
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _rdpDialog(id),
)
],
),
value: 'RDP');
}
void _rdpDialog(String id) async {
final portController = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_port'));
final userController = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_username'));
final passwordContorller = TextEditingController(
text: await bind.mainGetPeerOption(id: id, key: 'rdp_password'));
RxBool secure = true.obs;
gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text('RDP ' + translate('Settings')),
content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text(
"${translate('Port')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$'))
],
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: '3389'),
controller: portController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text(
"${translate('Username')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(border: OutlineInputBorder()),
controller: userController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: Obx(() => TextField(
obscureText: secure.value,
decoration: InputDecoration(
border: OutlineInputBorder(),
suffixIcon: IconButton(
onPressed: () => secure.value = !secure.value,
icon: Icon(secure.value
? Icons.visibility_off
: Icons.visibility))),
controller: passwordContorller,
)),
),
],
),
],
),
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetPeerOption(
id: id, key: 'rdp_port', value: portController.text.trim());
await bind.mainSetPeerOption(
id: id, key: 'rdp_username', value: userController.text);
await bind.mainSetPeerOption(
id: id, key: 'rdp_password', value: passwordContorller.text);
close();
},
child: Text(translate("OK"))),
],
);
});
}

View File

@ -5,6 +5,7 @@ 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_port_forward_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';
@ -47,6 +48,9 @@ Future<Null> main(List<String> args) async {
case WindowType.FileTransfer:
runFileTransferScreen(argument);
break;
case WindowType.PortForward:
runPortForwardScreen(argument);
break;
default:
break;
}
@ -133,6 +137,23 @@ void runFileTransferScreen(Map<String, dynamic> argument) async {
);
}
void runPortForwardScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopPortForward);
runApp(
GetMaterialApp(
navigatorKey: globalKey,
debugShowCheckedModeBanner: false,
title: 'RustDesk - Port Forward',
theme: getCurrentTheme(),
home: DesktopPortForwardScreen(params: argument),
navigatorObservers: [
// FirebaseAnalyticsObserver(analytics: analytics),
],
builder: _keepScaleBuilder(),
),
);
}
void runConnectionManagerScreen() async {
// initialize window
WindowOptions windowOptions = getHiddenTitleBarWindowOptions(Size(300, 400));

View File

@ -1034,17 +1034,24 @@ class FFI {
return [];
}
/// Connect with the given [id]. Only transfer file if [isFileTransfer].
/// Connect with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward].
void connect(String id,
{bool isFileTransfer = false, double tabBarHeight = 0.0}) {
if (!isFileTransfer) {
{bool isFileTransfer = false,
bool isPortForward = false,
double tabBarHeight = 0.0}) {
assert(!(isFileTransfer && isPortForward), "more than one connect type");
if (isFileTransfer) {
id = 'ft_${id}';
} else if (isPortForward) {
id = 'pf_${id}';
} else {
chatModel.resetClientMode();
canvasModel.id = id;
imageModel._id = id;
cursorModel.id = id;
}
id = isFileTransfer ? 'ft_${id}' : id;
final stream = bind.sessionConnect(id: id, isFileTransfer: isFileTransfer);
final stream = bind.sessionConnect(
id: id, isFileTransfer: isFileTransfer, isPortForward: isPortForward);
final cb = ffiModel.startEventListener(id);
() async {
await for (final message in stream) {

View File

@ -35,6 +35,7 @@ class RustDeskMultiWindowManager {
int? _remoteDesktopWindowId;
int? _fileTransferWindowId;
int? _portForwardWindowId;
Future<dynamic> new_remote_desktop(String remote_id) async {
final msg =
@ -87,6 +88,34 @@ class RustDeskMultiWindowManager {
}
}
Future<dynamic> new_port_forward(String remote_id, bool isRDP) async {
final msg = jsonEncode({
"type": WindowType.PortForward.index,
"id": remote_id,
"isRDP": isRDP
});
try {
final ids = await DesktopMultiWindow.getAllSubWindowIds();
if (!ids.contains(_portForwardWindowId)) {
_portForwardWindowId = null;
}
} on Error {
_portForwardWindowId = null;
}
if (_portForwardWindowId == null) {
final portForwardController = await DesktopMultiWindow.createWindow(msg);
portForwardController
..setFrame(const Offset(0, 0) & const Size(1280, 720))
..center()
..setTitle("rustdesk - port forward")
..show();
_portForwardWindowId = portForwardController.windowId;
} else {
return call(WindowType.PortForward, "new_port_forward", msg);
}
}
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
int? windowId = findWindowByType(type);
if (windowId == null) {
@ -104,7 +133,7 @@ class RustDeskMultiWindowManager {
case WindowType.FileTransfer:
return _fileTransferWindowId;
case WindowType.PortForward:
break;
return _portForwardWindowId;
case WindowType.Unknown:
break;
}
@ -120,7 +149,7 @@ class RustDeskMultiWindowManager {
await Future.wait(WindowType.values.map((e) => closeWindows(e)));
}
Future<void> closeWindows(WindowType type) async {
Future<void> closeWindows(WindowType type) async {
if (type == WindowType.Main) {
// skip main window, use window manager instead
return;

View File

@ -70,7 +70,13 @@ impl Session {
///
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
/// * `is_file_transfer` - If the session is used for file transfer.
pub fn start(identifier: &str, is_file_transfer: bool, events2ui: StreamSink<EventToUI>) {
/// * `is_port_forward` - If the session is used for port forward.
pub fn start(
identifier: &str,
is_file_transfer: bool,
is_port_forward: bool,
events2ui: StreamSink<EventToUI>,
) {
// TODO check same id
let session_id = get_session_id(identifier.to_owned());
LocalConfig::set_remote_id(&session_id);
@ -83,17 +89,17 @@ impl Session {
lc: Default::default(),
events2ui,
};
session
.lc
.write()
.unwrap()
.initialize(session_id.clone(), is_file_transfer, false);
session.lc.write().unwrap().initialize(
session_id.clone(),
is_file_transfer,
is_port_forward,
);
SESSIONS
.write()
.unwrap()
.insert(identifier.to_owned(), session.clone());
std::thread::spawn(move || {
Connection::start(session, is_file_transfer);
Connection::start(session, is_file_transfer, is_port_forward);
});
}
@ -201,7 +207,7 @@ impl Session {
self.send(Data::Close);
let session = self.clone();
std::thread::spawn(move || {
Connection::start(session, false);
Connection::start(session, false, false);
});
}
@ -719,18 +725,21 @@ impl Connection {
///
/// * `session` - The session to create a new connection for.
/// * `is_file_transfer` - Whether the connection is for file transfer.
/// * `is_port_forward` - Whether the connection is for port forward.
#[tokio::main(flavor = "current_thread")]
async fn start(session: Session, is_file_transfer: bool) {
async fn start(session: Session, is_file_transfer: bool, is_port_forward: bool) {
let mut last_recv_time = Instant::now();
let (sender, mut receiver) = mpsc::unbounded_channel::<Data>();
let mut stop_clipboard = None;
if !is_file_transfer {
if !is_file_transfer && !is_port_forward {
stop_clipboard = Self::start_clipboard(sender.clone(), session.lc.clone());
}
*session.sender.write().unwrap() = Some(sender);
let conn_type = if is_file_transfer {
session.lc.write().unwrap().is_file_transfer = true;
ConnType::FILE_TRANSFER
} else if is_port_forward {
ConnType::PORT_FORWARD // TODO: RDP
} else {
ConnType::DEFAULT_CONN
};

View File

@ -111,8 +111,9 @@ pub fn session_connect(
events2ui: StreamSink<EventToUI>,
id: String,
is_file_transfer: bool,
is_port_forward: bool,
) -> ResultType<()> {
Session::start(&id, is_file_transfer, events2ui);
Session::start(&id, is_file_transfer, is_port_forward, events2ui);
Ok(())
}
@ -592,12 +593,41 @@ pub fn main_load_lan_peers() {
{
let data = HashMap::from([
("name", "load_lan_peers".to_owned()),
("peers", serde_json::to_string(&get_lan_peers()).unwrap_or_default()),
(
"peers",
serde_json::to_string(&get_lan_peers()).unwrap_or_default(),
),
]);
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
};
}
pub fn main_add_port_forward(id: String, local_port: i32, remote_host: String, remote_port: i32) {
let mut config = get_peer(id.clone());
if config
.port_forwards
.iter()
.filter(|x| x.0 == local_port)
.next()
.is_some()
{
return;
}
let pf = (local_port, remote_host, remote_port);
config.port_forwards.push(pf);
config.store(&id);
}
pub fn main_remove_port_forward(id: String, local_port: i32) {
let mut config = get_peer(id.clone());
config.port_forwards = config
.port_forwards
.drain(..)
.filter(|x| x.0 != local_port)
.collect();
config.store(&id);
}
pub fn main_get_last_remote_id() -> String {
// if !config::APP_DIR.read().unwrap().is_empty() {
// res = LocalConfig::get_remote_id();