Merge pull request #1149 from fufesou/flutter_desktop_connection_2

flutter_desktop_connection_2: debug lan
This commit is contained in:
RustDesk 2022-08-02 13:19:39 +08:00 committed by GitHub
commit 627a940317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 740 additions and 609 deletions

624
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ import '../../mobile/pages/scan_page.dart';
import '../../mobile/pages/settings_page.dart';
import '../../models/model.dart';
enum RemoteType { recently, favorite, discovered, addressBook }
// enum RemoteType { recently, favorite, discovered, addressBook }
/// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget implements PageShape {
@ -76,74 +76,57 @@ class _ConnectionPageState extends State<ConnectionPage> {
thickness: 1,
),
Expanded(
child: DefaultTabController(
length: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TabBar(
isScrollable: true,
indicatorSize: TabBarIndicatorSize.label,
tabs: [
Tab(
child: Text(translate("Recent Sessions")),
),
Tab(
child: Text(translate("Favorites")),
),
Tab(
child: Text(translate("Discovered")),
),
Tab(
child: Text(translate("Address Book")),
),
]),
Expanded(
child: TabBarView(children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
// AddressBookPeerWidget(),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.recently),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.favorite),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.discovered),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
FutureBuilder<Widget>(
future: buildAddressBook(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return Offstage();
}
}),
]).paddingSymmetric(horizontal: 12.0, vertical: 4.0))
],
)),
),
// TODO: move all tab info into _PeerTabbedPage
child: _PeerTabbedPage(
tabs: [
translate('Recent Sessions'),
translate('Favorites'),
translate('Discovered'),
translate('Address Book')
],
children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
// AddressBookPeerWidget(),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.recently),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.favorite),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
// FutureBuilder<Widget>(
// future: getPeers(rType: RemoteType.discovered),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// }),
FutureBuilder<Widget>(
future: buildAddressBook(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return Offstage();
}
}),
],
)),
Divider(),
SizedBox(height: 50, child: Obx(() => buildStatus()))
.paddingSymmetric(horizontal: 12.0)
@ -329,61 +312,61 @@ class _ConnectionPageState extends State<ConnectionPage> {
return true;
}
/// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer.
void showPeerMenu(BuildContext context, String id, RemoteType rType) async {
var items = [
PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>(
child: Text(translate('Transfer File')), value: 'file'),
PopupMenuItem<String>(
child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'),
PopupMenuItem<String>(child: Text(translate('Rename')), value: 'rename'),
rType == RemoteType.addressBook
? PopupMenuItem<String>(
child: Text(translate('Remove')), value: 'ab-delete')
: PopupMenuItem<String>(
child: Text(translate('Remove')), value: 'remove'),
PopupMenuItem<String>(
child: Text(translate('Unremember Password')),
value: 'unremember-password'),
];
if (rType == RemoteType.favorite) {
items.add(PopupMenuItem<String>(
child: Text(translate('Remove from Favorites')),
value: 'remove-fav'));
} else if (rType != RemoteType.addressBook) {
items.add(PopupMenuItem<String>(
child: Text(translate('Add to Favorites')), value: 'add-fav'));
} else {
items.add(PopupMenuItem<String>(
child: Text(translate('Edit Tag')), value: 'ab-edit-tag'));
}
var value = await showMenu(
context: context,
position: this._menuPos,
items: items,
elevation: 8,
);
if (value == 'remove') {
setState(() => gFFI.setByName('remove', '$id'));
() async {
removePreference(id);
}();
} else if (value == 'file') {
connect(id, isFileTransfer: true);
} else if (value == 'add-fav') {
} else if (value == 'connect') {
connect(id, isFileTransfer: false);
} else if (value == 'ab-delete') {
gFFI.abModel.deletePeer(id);
await gFFI.abModel.updateAb();
setState(() {});
} else if (value == 'ab-edit-tag') {
abEditTag(id);
}
}
// /// Show the peer menu and handle user's choice.
// /// User might remove the peer or send a file to the peer.
// void showPeerMenu(BuildContext context, String id, RemoteType rType) async {
// var items = [
// PopupMenuItem<String>(
// child: Text(translate('Connect')), value: 'connect'),
// PopupMenuItem<String>(
// child: Text(translate('Transfer File')), value: 'file'),
// PopupMenuItem<String>(
// child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'),
// PopupMenuItem<String>(child: Text(translate('Rename')), value: 'rename'),
// rType == RemoteType.addressBook
// ? PopupMenuItem<String>(
// child: Text(translate('Remove')), value: 'ab-delete')
// : PopupMenuItem<String>(
// child: Text(translate('Remove')), value: 'remove'),
// PopupMenuItem<String>(
// child: Text(translate('Unremember Password')),
// value: 'unremember-password'),
// ];
// if (rType == RemoteType.favorite) {
// items.add(PopupMenuItem<String>(
// child: Text(translate('Remove from Favorites')),
// value: 'remove-fav'));
// } else if (rType != RemoteType.addressBook) {
// items.add(PopupMenuItem<String>(
// child: Text(translate('Add to Favorites')), value: 'add-fav'));
// } else {
// items.add(PopupMenuItem<String>(
// child: Text(translate('Edit Tag')), value: 'ab-edit-tag'));
// }
// var value = await showMenu(
// context: context,
// position: this._menuPos,
// items: items,
// elevation: 8,
// );
// if (value == 'remove') {
// setState(() => gFFI.setByName('remove', '$id'));
// () async {
// removePreference(id);
// }();
// } else if (value == 'file') {
// connect(id, isFileTransfer: true);
// } else if (value == 'add-fav') {
// } else if (value == 'connect') {
// connect(id, isFileTransfer: false);
// } else if (value == 'ab-delete') {
// gFFI.abModel.deletePeer(id);
// await gFFI.abModel.updateAb();
// setState(() {});
// } else if (value == 'ab-edit-tag') {
// abEditTag(id);
// }
// }
var svcStopped = false.obs;
var svcStatusCode = 0.obs;
@ -896,3 +879,86 @@ class _WebMenuState extends State<WebMenu> {
});
}
}
class _PeerTabbedPage extends StatefulWidget {
final List<String> tabs;
final List<Widget> children;
const _PeerTabbedPage({required this.tabs, required this.children, Key? key})
: super(key: key);
@override
_PeerTabbedPageState createState() => _PeerTabbedPageState();
}
class _PeerTabbedPageState extends State<_PeerTabbedPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController =
TabController(vsync: this, length: super.widget.tabs.length);
_tabController.addListener(_handleTabSelection);
}
// hard code for now
void _handleTabSelection() {
if (_tabController.indexIsChanging) {
switch (_tabController.index) {
case 0:
break;
case 1:
break;
case 2:
gFFI.bind.mainDiscover();
break;
case 3:
break;
}
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// return DefaultTabController(
// length: 4,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// _createTabBar(),
// _createTabBarView(),
// ],
// ));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_createTabBar(),
_createTabBarView(),
],
);
}
Widget _createTabBar() {
return TabBar(
isScrollable: true,
indicatorSize: TabBarIndicatorSize.label,
controller: _tabController,
tabs: super.widget.tabs.map((t) {
return Tab(child: Text(t));
}).toList());
}
Widget _createTabBarView() {
return Expanded(
child: TabBarView(
controller: _tabController, children: super.widget.children)
.paddingSymmetric(horizontal: 12.0, vertical: 4.0));
}
}

View File

@ -296,6 +296,7 @@ class _RemotePageState extends State<RemotePage>
Widget getRawPointerAndKeyBody(bool keyboard, Widget child) {
return Listener(
onPointerHover: (e) {
debugPrint("onPointerHover ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) {
setState(() {
@ -307,6 +308,7 @@ class _RemotePageState extends State<RemotePage>
}
},
onPointerDown: (e) {
debugPrint("onPointerDown ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) {
setState(() {
@ -319,18 +321,21 @@ class _RemotePageState extends State<RemotePage>
}
},
onPointerUp: (e) {
debugPrint("onPointerUp ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mouseup'));
}
},
onPointerMove: (e) {
debugPrint("onPointerMove ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'));
}
},
onPointerSignal: (e) {
debugPrint("onPointerSignal ${e}");
if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy;

View File

@ -15,15 +15,13 @@ typedef OffstageFunc = bool Function(Peer peer);
typedef PeerCardWidgetFunc = Widget Function(Peer peer);
class _PeerWidget extends StatefulWidget {
late final _name;
late final _peers;
late final OffstageFunc _offstageFunc;
late final PeerCardWidgetFunc _peerCardWidgetFunc;
_PeerWidget(String name, List<Peer> peers, OffstageFunc offstageFunc,
_PeerWidget(Peers peers, OffstageFunc offstageFunc,
PeerCardWidgetFunc peerCardWidgetFunc,
{Key? key})
: super(key: key) {
_name = name;
_peers = peers;
_offstageFunc = offstageFunc;
_peerCardWidgetFunc = peerCardWidgetFunc;
@ -70,7 +68,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
Widget build(BuildContext context) {
final space = 8.0;
return ChangeNotifierProvider<Peers>(
create: (context) => Peers(super.widget._name, super.widget._peers),
create: (context) => super.widget._peers,
child: SingleChildScrollView(
child: Consumer<Peers>(
builder: (context, peers, child) => Wrap(
@ -136,83 +134,69 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
abstract class BasePeerWidget extends StatelessWidget {
late final _name;
late final _loadEvent;
late final OffstageFunc _offstageFunc;
late final PeerCardWidgetFunc _peerCardWidgetFunc;
late final List<Peer> _initPeers;
BasePeerWidget({Key? key}) : super(key: key) {}
@override
Widget build(BuildContext context) {
return FutureBuilder<Widget>(future: () async {
return _PeerWidget(
_name, await _loadPeers(), _offstageFunc, _peerCardWidgetFunc);
}(), builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return Offstage();
}
});
return _PeerWidget(Peers(_name, _loadEvent, _initPeers), _offstageFunc,
_peerCardWidgetFunc);
}
@protected
Future<List<Peer>> _loadPeers();
}
class RecentPeerWidget extends BasePeerWidget {
RecentPeerWidget({Key? key}) : super(key: key) {
super._name = "recent peer";
super._loadEvent = "load_recent_peers";
super._offstageFunc = (Peer _peer) => false;
super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard(peer: peer);
super._initPeers = [];
}
Future<List<Peer>> _loadPeers() async {
debugPrint("call RecentPeerWidget _loadPeers");
return gFFI.peers();
@override
Widget build(BuildContext context) {
final widget = super.build(context);
gFFI.bind.mainLoadRecentPeers();
return widget;
}
}
class FavoritePeerWidget extends BasePeerWidget {
FavoritePeerWidget({Key? key}) : super(key: key) {
super._name = "favorite peer";
super._loadEvent = "load_fav_peers";
super._offstageFunc = (Peer _peer) => false;
super._peerCardWidgetFunc = (Peer peer) => FavoritePeerCard(peer: peer);
super._initPeers = [];
}
@override
Future<List<Peer>> _loadPeers() async {
debugPrint("call FavoritePeerWidget _loadPeers");
return await gFFI.bind.mainGetFav().then((peers) async {
final peersEntities = await Future.wait(peers
.map((id) => gFFI.bind.mainGetPeers(id: id))
.toList(growable: false))
.then((peers_str) {
final len = peers_str.length;
final ps = List<Peer>.empty(growable: true);
for (var i = 0; i < len; i++) {
print("${peers[i]}: ${peers_str[i]}");
ps.add(Peer.fromJson(peers[i], jsonDecode(peers_str[i])['info']));
}
return ps;
});
return peersEntities;
});
Widget build(BuildContext context) {
final widget = super.build(context);
gFFI.bind.mainLoadFavPeers();
return widget;
}
}
class DiscoveredPeerWidget extends BasePeerWidget {
DiscoveredPeerWidget({Key? key}) : super(key: key) {
super._name = "discovered peer";
super._loadEvent = "load_lan_peers";
super._offstageFunc = (Peer _peer) => false;
super._peerCardWidgetFunc = (Peer peer) => DiscoveredPeerCard(peer: peer);
super._initPeers = [];
}
Future<List<Peer>> _loadPeers() async {
debugPrint("call DiscoveredPeerWidget _loadPeers");
return await gFFI.bind.mainGetLanPeers().then((peers_string) {
debugPrint(peers_string);
return [];
});
@override
Widget build(BuildContext context) {
debugPrint("DiscoveredPeerWidget build");
final widget = super.build(context);
gFFI.bind.mainLoadLanPeers();
return widget;
}
}
@ -222,10 +206,10 @@ class AddressBookPeerWidget extends BasePeerWidget {
super._offstageFunc =
(Peer peer) => !_hitTag(gFFI.abModel.selectedTags, peer.tags);
super._peerCardWidgetFunc = (Peer peer) => AddressBookPeerCard(peer: peer);
super._initPeers = _loadPeers();
}
Future<List<Peer>> _loadPeers() async {
debugPrint("call AddressBookPeerWidget _loadPeers");
List<Peer> _loadPeers() {
return gFFI.abModel.peers.map((e) {
return Peer.fromJson(e['id'], e);
}).toList();

View File

@ -125,10 +125,10 @@ class _RemotePageState extends State<RemotePage> {
oldValue = oldValue.substring(j + 1);
var common = 0;
for (;
common < oldValue.length &&
common < newValue.length &&
newValue[common] == oldValue[common];
++common) {}
common < oldValue.length &&
common < newValue.length &&
newValue[common] == oldValue[common];
++common) {}
for (i = 0; i < oldValue.length - common; ++i) {
gFFI.inputKey('VK_BACK');
}
@ -235,13 +235,13 @@ class _RemotePageState extends State<RemotePage> {
floatingActionButton: !showActionButton
? null
: FloatingActionButton(
mini: !hideKeyboard,
child: Icon(
hideKeyboard ? Icons.expand_more : Icons.expand_less),
backgroundColor: MyTheme.accent,
onPressed: () {
setState(() {
if (hideKeyboard) {
mini: !hideKeyboard,
child: Icon(
hideKeyboard ? Icons.expand_more : Icons.expand_less),
backgroundColor: MyTheme.accent,
onPressed: () {
setState(() {
if (hideKeyboard) {
_showEdit = false;
gFFI.invokeMethod("enable_soft_keyboard", false);
_mobileFocusNode.unfocus();
@ -250,7 +250,7 @@ class _RemotePageState extends State<RemotePage> {
_showBar = !_showBar;
}
});
}),
}),
bottomNavigationBar: _showBar && pi.displays.length > 0
? getBottomAppBar(keyboard)
: null,
@ -262,7 +262,7 @@ class _RemotePageState extends State<RemotePage> {
child: isWebDesktop
? getBodyForDesktopWithListener(keyboard)
: SafeArea(child:
OrientationBuilder(builder: (ctx, orientation) {
OrientationBuilder(builder: (ctx, orientation) {
if (_currentOrientation != orientation) {
Timer(Duration(milliseconds: 200), () {
resetMobileActionsOverlay();
@ -271,10 +271,10 @@ class _RemotePageState extends State<RemotePage> {
});
}
return Container(
color: MyTheme.canvasColor,
child: _isPhysicalMouse
? getBodyForMobile()
: getBodyForMobileWithGesture());
color: MyTheme.canvasColor,
child: _isPhysicalMouse
? getBodyForMobile()
: getBodyForMobileWithGesture());
})));
})
],
@ -395,14 +395,14 @@ class _RemotePageState extends State<RemotePage> {
children: <Widget>[
Row(
children: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.clear),
onPressed: () {
clientClose();
},
)
] +
IconButton(
color: Colors.white,
icon: Icon(Icons.clear),
onPressed: () {
clientClose();
},
)
] +
<Widget>[
IconButton(
color: Colors.white,
@ -441,20 +441,20 @@ class _RemotePageState extends State<RemotePage> {
: Icons.mouse),
onPressed: changeTouchMode,
),
]) +
]) +
(isWeb
? []
: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.message),
onPressed: () {
IconButton(
color: Colors.white,
icon: Icon(Icons.message),
onPressed: () {
gFFI.chatModel
.changeCurrentID(ChatModel.clientModeID);
toggleChatOverlay();
},
)
]) +
)
]) +
[
IconButton(
color: Colors.white,
@ -602,17 +602,17 @@ class _RemotePageState extends State<RemotePage> {
child: !_showEdit
? Container()
: TextFormField(
textInputAction: TextInputAction.newline,
autocorrect: false,
enableSuggestions: false,
autofocus: true,
focusNode: _mobileFocusNode,
maxLines: null,
initialValue: _value,
// trick way to make backspace work always
keyboardType: TextInputType.multiline,
onChanged: handleInput,
),
textInputAction: TextInputAction.newline,
autocorrect: false,
enableSuggestions: false,
autofocus: true,
focusNode: _mobileFocusNode,
maxLines: null,
initialValue: _value,
// trick way to make backspace work always
keyboardType: TextInputType.multiline,
onChanged: handleInput,
),
),
]));
}
@ -697,7 +697,7 @@ class _RemotePageState extends State<RemotePage> {
value: 'block-input'));
}
}
() async {
() async {
var value = await showMenu(
context: context,
position: RelativeRect.fromLTRB(x, y, x, y),
@ -715,7 +715,7 @@ class _RemotePageState extends State<RemotePage> {
} else if (value == 'refresh') {
gFFI.setByName('refresh');
} else if (value == 'paste') {
() async {
() async {
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && data.text != null) {
gFFI.setByName('input_string', '${data.text}');
@ -803,25 +803,25 @@ class _RemotePageState extends State<RemotePage> {
final keys = <Widget>[
wrap(
' Fn ',
() => setState(
() => setState(
() {
_fn = !_fn;
if (_fn) {
_more = false;
}
},
),
_fn = !_fn;
if (_fn) {
_more = false;
}
},
),
_fn),
wrap(
' ... ',
() => setState(
() => setState(
() {
_more = !_more;
if (_more) {
_fn = false;
}
},
),
_more = !_more;
if (_more) {
_fn = false;
}
},
),
_more),
];
final fn = <Widget>[
@ -952,7 +952,8 @@ class ImagePainter extends CustomPainter {
}
}
CheckboxListTile getToggle(void Function(void Function()) setState, option, name) {
CheckboxListTile getToggle(
void Function(void Function()) setState, option, name) {
return CheckboxListTile(
value: gFFI.getByName('toggle_option', option) == 'true',
onChanged: (v) {

View File

@ -1028,6 +1028,7 @@ class FFI {
RustdeskImpl get bind => ffiModel.platformFFI.ffiBind;
handleMouse(Map<String, dynamic> evt) {
debugPrint("mouse ${evt.toString()}");
var type = '';
var isMove = false;
switch (evt['type']) {
@ -1045,7 +1046,7 @@ class FFI {
}
evt['type'] = type;
var x = evt['x'];
var y = evt['y'];
var y = max(0.0, (evt['y'] as double) - 50.0);
if (isMove) {
canvasModel.moveDesktopMouse(x, y);
}

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../../common.dart';
@ -35,23 +36,29 @@ class Peer {
class Peers extends ChangeNotifier {
late String _name;
late var _peers;
static const cbQueryOnlines = 'callback_query_onlines';
late List<Peer> _peers;
late final _loadEvent;
static const _cbQueryOnlines = 'callback_query_onlines';
Peers(String name, List<Peer> peers) {
Peers(String name, String loadEvent, List<Peer> _initPeers) {
_name = name;
_peers = peers;
gFFI.ffiModel.platformFFI.registerEventHandler(cbQueryOnlines, _name,
_loadEvent = loadEvent;
_peers = _initPeers;
gFFI.ffiModel.platformFFI.registerEventHandler(_cbQueryOnlines, _name,
(evt) {
_updateOnlineState(evt);
});
gFFI.ffiModel.platformFFI.registerEventHandler(_loadEvent, _name, (evt) {
_updatePeers(evt);
});
}
List<Peer> get peers => _peers;
@override
void dispose() {
gFFI.ffiModel.platformFFI.unregisterEventHandler(cbQueryOnlines, _name);
gFFI.ffiModel.platformFFI.unregisterEventHandler(_cbQueryOnlines, _name);
gFFI.ffiModel.platformFFI.unregisterEventHandler(_loadEvent, _name);
super.dispose();
}
@ -86,4 +93,37 @@ class Peers extends ChangeNotifier {
notifyListeners();
}
void _updatePeers(Map<String, dynamic> evt) {
final onlineStates = _getOnlineStates();
_peers = _decodePeers(evt['peers']);
_peers.forEach((peer) {
final state = onlineStates[peer.id];
peer.online = state != null && state != false;
});
notifyListeners();
}
Map<String, bool> _getOnlineStates() {
var onlineStates = new Map<String, bool>();
_peers.forEach((peer) {
onlineStates[peer.id] = peer.online;
});
return onlineStates;
}
List<Peer> _decodePeers(String peersStr) {
try {
if (peersStr == "") return [];
List<dynamic> peers = json.decode(peersStr);
return peers
.map((s) => s as List<dynamic>)
.map((s) =>
Peer.fromJson(s[0] as String, s[1] as Map<String, dynamic>))
.toList();
} catch (e) {
print('peers(): $e');
}
return [];
}
}

View File

@ -7,11 +7,11 @@ use std::{
use flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::{json, Number, Value};
use hbb_common::{ResultType, password_security};
use hbb_common::{
config::{self, Config, LocalConfig, PeerConfig, ONLINE},
fs, log,
};
use hbb_common::{password_security, ResultType};
use crate::client::file_trait::FileManager;
use crate::common::make_fd_to_json;
@ -20,7 +20,7 @@ use crate::flutter::{self, Session, SESSIONS};
use crate::start_server;
use crate::ui_interface;
use crate::ui_interface::{
change_id, check_connect_status, forget_password, get_api_server, get_app_name,
change_id, check_connect_status, discover, forget_password, get_api_server, get_app_name,
get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_license,
get_local_option, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs,
get_uuid, get_version, has_rendezvous_service, is_ok_change_id, post_request, set_local_option,
@ -469,6 +469,10 @@ pub fn main_is_using_public_server() -> bool {
using_public_server()
}
pub fn main_discover() {
discover();
}
pub fn main_has_rendezvous_service() -> bool {
has_rendezvous_service()
}
@ -509,6 +513,61 @@ pub fn main_forget_password(id: String) {
forget_password(id)
}
pub fn main_load_recent_peers() {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
.drain(..)
.map(|(id, _, p)| (id, p.info))
.collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() {
let data = HashMap::from([
("name", "load_recent_peers".to_owned()),
(
"peers",
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
),
]);
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
};
}
}
pub fn main_load_fav_peers() {
if !config::APP_DIR.read().unwrap().is_empty() {
let favs = get_fav();
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
.into_iter()
.filter_map(|(id, _, peer)| {
if favs.contains(&id) {
Some((id, peer.info))
} else {
None
}
})
.collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() {
let data = HashMap::from([
("name", "load_fav_peers".to_owned()),
(
"peers",
serde_json::ser::to_string(&peers).unwrap_or("".to_owned()),
),
]);
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
};
}
}
pub fn main_load_lan_peers() {
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().as_ref() {
let data = HashMap::from([
("name", "load_lan_peers".to_owned()),
("peers", get_lan_peers()),
]);
s.add(serde_json::ser::to_string(&data).unwrap_or("".to_owned()));
};
}
/// FFI for **get** commands which are idempotent.
/// Return result in c string.
///

View File

@ -277,6 +277,9 @@ async fn handle_received_peers(mut rx: UnboundedReceiver<config::DiscoveryPeer>)
if last_write_time.elapsed().as_millis() > 300 {
config::LanPeers::store(&peers);
last_write_time = Instant::now();
#[cfg(feature = "flutter")]
crate::flutter_ffi::main_load_lan_peers();
}
}
None => {

View File

@ -604,68 +604,6 @@ fn lan_discovery() -> ResultType<()> {
}
}
pub fn discover() -> ResultType<()> {
let addr = SocketAddr::from(([0, 0, 0, 0], 0));
let socket = std::net::UdpSocket::bind(addr)?;
socket.set_broadcast(true)?;
let mut msg_out = Message::new();
let peer = PeerDiscovery {
cmd: "ping".to_owned(),
..Default::default()
};
msg_out.set_peer_discovery(peer);
let maddr = SocketAddr::from(([255, 255, 255, 255], get_broadcast_port()));
socket.send_to(&msg_out.write_to_bytes()?, maddr)?;
log::info!("discover ping sent");
let mut last_recv_time = Instant::now();
let mut last_write_time = Instant::now();
let mut last_write_n = 0;
// to-do: load saved peers, and update incrementally (then we can see offline)
let mut peers = Vec::new();
let mac = get_mac();
socket.set_read_timeout(Some(std::time::Duration::from_millis(10)))?;
loop {
let mut buf = [0; 2048];
if let Ok((len, _)) = socket.recv_from(&mut buf) {
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
match msg_in.union {
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
last_recv_time = Instant::now();
if p.cmd == "pong" {
if p.mac != mac {
let dp = DiscoveryPeer {
id: "".to_string(),
ip_mac: HashMap::from([
// TODO: addr ip
(addr.ip().to_string(), p.mac.clone()),
]),
username: p.username,
hostname: p.hostname,
platform: p.platform,
online: true,
};
peers.push(dp);
}
}
}
_ => {}
}
}
}
if last_write_time.elapsed().as_millis() > 300 && last_write_n != peers.len() {
config::LanPeers::store(&peers);
last_write_time = Instant::now();
last_write_n = peers.len();
}
if last_recv_time.elapsed().as_millis() > 3_000 {
break;
}
}
log::info!("discover ping done");
config::LanPeers::store(&peers);
Ok(())
}
#[tokio::main(flavor = "current_thread")]
pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<String>, f: F) {
let test = false;

View File

@ -20,6 +20,7 @@ use hbb_common::{
};
use crate::common::{get_app_name, SOFTWARE_UPDATE_URL};
use crate::ipc;
use crate::ui_interface::{
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
forget_password, get_api_server, get_async_job_status, get_connect_status, get_error, get_fav,
@ -35,7 +36,6 @@ use crate::ui_interface::{
show_run_without_install, store_fav, t, temporary_password, test_if_valid_server, update_me,
update_temporary_password, using_public_server,
};
use crate::{discover, ipc};
mod cm;
#[cfg(feature = "inline")]
@ -493,7 +493,9 @@ impl UI {
}
fn discover(&self) {
discover();
std::thread::spawn(move || {
allow_err!(crate::lan::discover());
});
}
fn get_lan_peers(&self) -> String {

View File

@ -9,12 +9,12 @@ use hbb_common::{
allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
log, password_security,
protobuf::Message as _,
rendezvous_proto::*,
sleep,
tcp::FramedStream,
tokio::{self, sync::mpsc, time}, password_security,
tokio::{self, sync::mpsc, time},
};
use crate::common::SOFTWARE_UPDATE_URL;
@ -538,12 +538,26 @@ pub fn create_shortcut(_id: String) {
pub fn discover() {
std::thread::spawn(move || {
allow_err!(crate::rendezvous_mediator::discover());
allow_err!(crate::lan::discover());
});
}
pub fn get_lan_peers() -> String {
serde_json::to_string(&config::LanPeers::load().peers).unwrap_or_default()
let peers: Vec<(String, config::PeerInfoSerde)> = config::LanPeers::load()
.peers
.iter()
.map(|peer| {
(
peer.id.clone(),
config::PeerInfoSerde {
username: peer.username.clone(),
hostname: peer.hostname.clone(),
platform: peer.platform.clone(),
},
)
})
.collect();
serde_json::to_string(&peers).unwrap_or_default()
}
pub fn get_uuid() -> String {