Merge pull request #1149 from fufesou/flutter_desktop_connection_2
flutter_desktop_connection_2: debug lan
This commit is contained in:
commit
627a940317
624
Cargo.lock
generated
624
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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 => {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user