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 '../../mobile/pages/settings_page.dart';
import '../../models/model.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. /// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget implements PageShape { class ConnectionPage extends StatefulWidget implements PageShape {
@ -76,74 +76,57 @@ class _ConnectionPageState extends State<ConnectionPage> {
thickness: 1, thickness: 1,
), ),
Expanded( Expanded(
child: DefaultTabController( // TODO: move all tab info into _PeerTabbedPage
length: 4, child: _PeerTabbedPage(
child: Column( tabs: [
crossAxisAlignment: CrossAxisAlignment.start, translate('Recent Sessions'),
children: [ translate('Favorites'),
TabBar( translate('Discovered'),
isScrollable: true, translate('Address Book')
indicatorSize: TabBarIndicatorSize.label, ],
tabs: [ children: [
Tab( RecentPeerWidget(),
child: Text(translate("Recent Sessions")), FavoritePeerWidget(),
), DiscoveredPeerWidget(),
Tab( // AddressBookPeerWidget(),
child: Text(translate("Favorites")), // FutureBuilder<Widget>(
), // future: getPeers(rType: RemoteType.recently),
Tab( // builder: (context, snapshot) {
child: Text(translate("Discovered")), // if (snapshot.hasData) {
), // return snapshot.data!;
Tab( // } else {
child: Text(translate("Address Book")), // return Offstage();
), // }
]), // }),
Expanded( // FutureBuilder<Widget>(
child: TabBarView(children: [ // future: getPeers(rType: RemoteType.favorite),
RecentPeerWidget(), // builder: (context, snapshot) {
FavoritePeerWidget(), // if (snapshot.hasData) {
DiscoveredPeerWidget(), // return snapshot.data!;
// AddressBookPeerWidget(), // } else {
// FutureBuilder<Widget>( // return Offstage();
// future: getPeers(rType: RemoteType.recently), // }
// builder: (context, snapshot) { // }),
// if (snapshot.hasData) { // FutureBuilder<Widget>(
// return snapshot.data!; // future: getPeers(rType: RemoteType.discovered),
// } else { // builder: (context, snapshot) {
// return Offstage(); // if (snapshot.hasData) {
// } // return snapshot.data!;
// }), // } else {
// FutureBuilder<Widget>( // return Offstage();
// future: getPeers(rType: RemoteType.favorite), // }
// builder: (context, snapshot) { // }),
// if (snapshot.hasData) { FutureBuilder<Widget>(
// return snapshot.data!; future: buildAddressBook(context),
// } else { builder: (context, snapshot) {
// return Offstage(); if (snapshot.hasData) {
// } return snapshot.data!;
// }), } else {
// FutureBuilder<Widget>( return Offstage();
// 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))
],
)),
),
Divider(), Divider(),
SizedBox(height: 50, child: Obx(() => buildStatus())) SizedBox(height: 50, child: Obx(() => buildStatus()))
.paddingSymmetric(horizontal: 12.0) .paddingSymmetric(horizontal: 12.0)
@ -329,61 +312,61 @@ class _ConnectionPageState extends State<ConnectionPage> {
return true; return true;
} }
/// Show the peer menu and handle user's choice. // /// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer. // /// User might remove the peer or send a file to the peer.
void showPeerMenu(BuildContext context, String id, RemoteType rType) async { // void showPeerMenu(BuildContext context, String id, RemoteType rType) async {
var items = [ // var items = [
PopupMenuItem<String>( // PopupMenuItem<String>(
child: Text(translate('Connect')), value: 'connect'), // child: Text(translate('Connect')), value: 'connect'),
PopupMenuItem<String>( // PopupMenuItem<String>(
child: Text(translate('Transfer File')), value: 'file'), // child: Text(translate('Transfer File')), value: 'file'),
PopupMenuItem<String>( // PopupMenuItem<String>(
child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'), // child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'),
PopupMenuItem<String>(child: Text(translate('Rename')), value: 'rename'), // PopupMenuItem<String>(child: Text(translate('Rename')), value: 'rename'),
rType == RemoteType.addressBook // rType == RemoteType.addressBook
? PopupMenuItem<String>( // ? PopupMenuItem<String>(
child: Text(translate('Remove')), value: 'ab-delete') // child: Text(translate('Remove')), value: 'ab-delete')
: PopupMenuItem<String>( // : PopupMenuItem<String>(
child: Text(translate('Remove')), value: 'remove'), // child: Text(translate('Remove')), value: 'remove'),
PopupMenuItem<String>( // PopupMenuItem<String>(
child: Text(translate('Unremember Password')), // child: Text(translate('Unremember Password')),
value: 'unremember-password'), // value: 'unremember-password'),
]; // ];
if (rType == RemoteType.favorite) { // if (rType == RemoteType.favorite) {
items.add(PopupMenuItem<String>( // items.add(PopupMenuItem<String>(
child: Text(translate('Remove from Favorites')), // child: Text(translate('Remove from Favorites')),
value: 'remove-fav')); // value: 'remove-fav'));
} else if (rType != RemoteType.addressBook) { // } else if (rType != RemoteType.addressBook) {
items.add(PopupMenuItem<String>( // items.add(PopupMenuItem<String>(
child: Text(translate('Add to Favorites')), value: 'add-fav')); // child: Text(translate('Add to Favorites')), value: 'add-fav'));
} else { // } else {
items.add(PopupMenuItem<String>( // items.add(PopupMenuItem<String>(
child: Text(translate('Edit Tag')), value: 'ab-edit-tag')); // child: Text(translate('Edit Tag')), value: 'ab-edit-tag'));
} // }
var value = await showMenu( // var value = await showMenu(
context: context, // context: context,
position: this._menuPos, // position: this._menuPos,
items: items, // items: items,
elevation: 8, // elevation: 8,
); // );
if (value == 'remove') { // if (value == 'remove') {
setState(() => gFFI.setByName('remove', '$id')); // setState(() => gFFI.setByName('remove', '$id'));
() async { // () async {
removePreference(id); // removePreference(id);
}(); // }();
} else if (value == 'file') { // } else if (value == 'file') {
connect(id, isFileTransfer: true); // connect(id, isFileTransfer: true);
} else if (value == 'add-fav') { // } else if (value == 'add-fav') {
} else if (value == 'connect') { // } else if (value == 'connect') {
connect(id, isFileTransfer: false); // connect(id, isFileTransfer: false);
} else if (value == 'ab-delete') { // } else if (value == 'ab-delete') {
gFFI.abModel.deletePeer(id); // gFFI.abModel.deletePeer(id);
await gFFI.abModel.updateAb(); // await gFFI.abModel.updateAb();
setState(() {}); // setState(() {});
} else if (value == 'ab-edit-tag') { // } else if (value == 'ab-edit-tag') {
abEditTag(id); // abEditTag(id);
} // }
} // }
var svcStopped = false.obs; var svcStopped = false.obs;
var svcStatusCode = 0.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) { Widget getRawPointerAndKeyBody(bool keyboard, Widget child) {
return Listener( return Listener(
onPointerHover: (e) { onPointerHover: (e) {
debugPrint("onPointerHover ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) { if (!_isPhysicalMouse) {
setState(() { setState(() {
@ -307,6 +308,7 @@ class _RemotePageState extends State<RemotePage>
} }
}, },
onPointerDown: (e) { onPointerDown: (e) {
debugPrint("onPointerDown ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) { if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) { if (_isPhysicalMouse) {
setState(() { setState(() {
@ -319,18 +321,21 @@ class _RemotePageState extends State<RemotePage>
} }
}, },
onPointerUp: (e) { onPointerUp: (e) {
debugPrint("onPointerUp ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mouseup')); _ffi.handleMouse(getEvent(e, 'mouseup'));
} }
}, },
onPointerMove: (e) { onPointerMove: (e) {
debugPrint("onPointerMove ${e}");
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove')); _ffi.handleMouse(getEvent(e, 'mousemove'));
} }
}, },
onPointerSignal: (e) { onPointerSignal: (e) {
debugPrint("onPointerSignal ${e}");
if (e is PointerScrollEvent) { if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx; var dx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy; var dy = e.scrollDelta.dy;

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import '../../common.dart'; import '../../common.dart';
@ -35,23 +36,29 @@ class Peer {
class Peers extends ChangeNotifier { class Peers extends ChangeNotifier {
late String _name; late String _name;
late var _peers; late List<Peer> _peers;
static const cbQueryOnlines = 'callback_query_onlines'; 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; _name = name;
_peers = peers; _loadEvent = loadEvent;
gFFI.ffiModel.platformFFI.registerEventHandler(cbQueryOnlines, _name, _peers = _initPeers;
gFFI.ffiModel.platformFFI.registerEventHandler(_cbQueryOnlines, _name,
(evt) { (evt) {
_updateOnlineState(evt); _updateOnlineState(evt);
}); });
gFFI.ffiModel.platformFFI.registerEventHandler(_loadEvent, _name, (evt) {
_updatePeers(evt);
});
} }
List<Peer> get peers => _peers; List<Peer> get peers => _peers;
@override @override
void dispose() { void dispose() {
gFFI.ffiModel.platformFFI.unregisterEventHandler(cbQueryOnlines, _name); gFFI.ffiModel.platformFFI.unregisterEventHandler(_cbQueryOnlines, _name);
gFFI.ffiModel.platformFFI.unregisterEventHandler(_loadEvent, _name);
super.dispose(); super.dispose();
} }
@ -86,4 +93,37 @@ class Peers extends ChangeNotifier {
notifyListeners(); 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 flutter_rust_bridge::{StreamSink, SyncReturn, ZeroCopyBuffer};
use serde_json::{json, Number, Value}; use serde_json::{json, Number, Value};
use hbb_common::{ResultType, password_security};
use hbb_common::{ use hbb_common::{
config::{self, Config, LocalConfig, PeerConfig, ONLINE}, config::{self, Config, LocalConfig, PeerConfig, ONLINE},
fs, log, fs, log,
}; };
use hbb_common::{password_security, ResultType};
use crate::client::file_trait::FileManager; use crate::client::file_trait::FileManager;
use crate::common::make_fd_to_json; use crate::common::make_fd_to_json;
@ -20,7 +20,7 @@ use crate::flutter::{self, Session, SESSIONS};
use crate::start_server; use crate::start_server;
use crate::ui_interface; use crate::ui_interface;
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_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_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, 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() using_public_server()
} }
pub fn main_discover() {
discover();
}
pub fn main_has_rendezvous_service() -> bool { pub fn main_has_rendezvous_service() -> bool {
has_rendezvous_service() has_rendezvous_service()
} }
@ -509,6 +513,61 @@ pub fn main_forget_password(id: String) {
forget_password(id) 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. /// FFI for **get** commands which are idempotent.
/// Return result in c string. /// 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 { if last_write_time.elapsed().as_millis() > 300 {
config::LanPeers::store(&peers); config::LanPeers::store(&peers);
last_write_time = Instant::now(); last_write_time = Instant::now();
#[cfg(feature = "flutter")]
crate::flutter_ffi::main_load_lan_peers();
} }
} }
None => { 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")] #[tokio::main(flavor = "current_thread")]
pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<String>, f: F) { pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<String>, f: F) {
let test = false; let test = false;

View File

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

View File

@ -9,12 +9,12 @@ use hbb_common::{
allow_err, allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all, futures::future::join_all,
log, log, password_security,
protobuf::Message as _, protobuf::Message as _,
rendezvous_proto::*, rendezvous_proto::*,
sleep, sleep,
tcp::FramedStream, tcp::FramedStream,
tokio::{self, sync::mpsc, time}, password_security, tokio::{self, sync::mpsc, time},
}; };
use crate::common::SOFTWARE_UPDATE_URL; use crate::common::SOFTWARE_UPDATE_URL;
@ -538,12 +538,26 @@ pub fn create_shortcut(_id: String) {
pub fn discover() { pub fn discover() {
std::thread::spawn(move || { std::thread::spawn(move || {
allow_err!(crate::rendezvous_mediator::discover()); allow_err!(crate::lan::discover());
}); });
} }
pub fn get_lan_peers() -> String { 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 { pub fn get_uuid() -> String {