Merge pull request #1218 from Heap-Hop/flutter_desktop

refactor flutter_desktop
This commit is contained in:
RustDesk 2022-08-09 08:23:32 +08:00 committed by GitHub
commit 927991c9de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 783 additions and 1087 deletions

View File

@ -352,6 +352,23 @@ RadioListTile<T> getRadio<T>(
); );
} }
CheckboxListTile getToggle(
String id, void Function(void Function()) setState, option, name) {
final opt = bind.getSessionToggleOptionSync(id: id, arg: option);
return CheckboxListTile(
value: opt,
onChanged: (v) {
setState(() {
bind.sessionToggleOption(id: id, value: option);
});
if (option == "show-quality-monitor") {
gFFI.qualityMonitorModel.checkShowQualityMonitor(id);
}
},
dense: true,
title: Text(translate(name)));
}
/// find ffi, tag is Remote ID /// find ffi, tag is Remote ID
/// for session specific usage /// for session specific usage
FFI ffi(String? tag) { FFI ffi(String? tag) {
@ -374,3 +391,10 @@ Future<void> initGlobalFFI() async {
// global shared preference // global shared preference
await Get.putAsync(() => SharedPreferences.getInstance()); await Get.putAsync(() => SharedPreferences.getInstance());
} }
String translate(String name) {
if (name.startsWith('Failed to') && name.contains(': ')) {
return name.split(': ').map((x) => translate(x)).join(': ');
}
return platformFFI.translate(name, localeName);
}

View File

@ -44,13 +44,22 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// Update url. If it's not null, means an update is available. /// Update url. If it's not null, means an update is available.
var _updateUrl = ''; var _updateUrl = '';
var _menuPos;
Timer? _updateTimer; Timer? _updateTimer;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (_idController.text.isEmpty) {
() async {
final lastRemoteId = await bind.mainGetLastRemoteId();
if (lastRemoteId != _idController.text) {
setState(() {
_idController.text = lastRemoteId;
});
}
}();
}
_updateTimer = Timer.periodic(Duration(seconds: 1), (timer) { _updateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
updateStatus(); updateStatus();
}); });
@ -58,9 +67,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context);
if (_idController.text.isEmpty) _idController.text = gFFI.getId();
return Container( return Container(
decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg), decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg),
child: Column( child: Column(
@ -430,7 +436,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
} }
updateStatus() async { updateStatus() async {
svcStopped.value = gFFI.getOption("stop-service") == "Y"; svcStopped.value = bind.mainGetOption(key: "stop-service") == "Y";
final status = final status =
jsonDecode(await bind.mainGetConnectStatus()) as Map<String, dynamic>; jsonDecode(await bind.mainGetConnectStatus()) as Map<String, dynamic>;
svcStatusCode.value = status["status_num"]; svcStatusCode.value = status["status_num"];
@ -446,7 +452,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
} }
Future<Widget> buildAddressBook(BuildContext context) async { Future<Widget> buildAddressBook(BuildContext context) async {
final token = await gFFI.getLocalOption('access_token'); final token = await bind.mainGetLocalOption(key: 'access_token');
if (token.trim().isEmpty) { if (token.trim().isEmpty) {
return Center( return Center(
child: InkWell( child: InkWell(
@ -819,10 +825,34 @@ class WebMenu extends StatefulWidget {
} }
class _WebMenuState extends State<WebMenu> { class _WebMenuState extends State<WebMenu> {
String? username;
String url = "";
@override
void initState() {
super.initState();
() async {
final usernameRes = await getUsername();
final urlRes = await getUrl();
var update = false;
if (usernameRes != username) {
username = usernameRes;
update = true;
}
if (urlRes != url) {
url = urlRes;
update = true;
}
if (update) {
setState(() {});
}
}();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
final username = getUsername();
return PopupMenuButton<String>( return PopupMenuButton<String>(
icon: Icon(Icons.more_vert), icon: Icon(Icons.more_vert),
itemBuilder: (context) { itemBuilder: (context) {
@ -840,7 +870,7 @@ class _WebMenuState extends State<WebMenu> {
value: "server", value: "server",
) )
] + ] +
(getUrl().contains('admin.rustdesk.com') (url.contains('admin.rustdesk.com')
? <PopupMenuItem<String>>[] ? <PopupMenuItem<String>>[]
: [ : [
PopupMenuItem( PopupMenuItem(

View File

@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/connection_page.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart';
import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -156,6 +155,8 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
}, },
onTap: () async { onTap: () async {
final userName = await gFFI.userModel.getUserName(); final userName = await gFFI.userModel.getUserName();
final enabledInput = await bind.mainGetOption(key: 'enable-audio');
final defaultInput = await gFFI.getDefaultAudioInput();
var menu = <PopupMenuEntry>[ var menu = <PopupMenuEntry>[
genEnablePopupMenuItem( genEnablePopupMenuItem(
translate("Enable Keyboard/Mouse"), translate("Enable Keyboard/Mouse"),
@ -173,7 +174,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
translate("Enable TCP Tunneling"), translate("Enable TCP Tunneling"),
'enable-tunnel', 'enable-tunnel',
), ),
genAudioInputPopupMenuItem(), genAudioInputPopupMenuItem(enabledInput != "N", defaultInput),
PopupMenuDivider(), PopupMenuDivider(),
PopupMenuItem( PopupMenuItem(
child: Text(translate("ID/Relay Server")), child: Text(translate("ID/Relay Server")),
@ -274,9 +275,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
), ),
IconButton( IconButton(
icon: Icon(Icons.refresh), icon: Icon(Icons.refresh),
onPressed: () { onPressed: () => bind.mainUpdateTemporaryPassword(),
gFFI.setByName("temporary_password");
},
), ),
FutureBuilder<Widget>( FutureBuilder<Widget>(
future: buildPasswordPopupMenu(context), future: buildPasswordPopupMenu(context),
@ -359,7 +358,7 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
if (gFFI.serverModel.temporaryPasswordLength != if (gFFI.serverModel.temporaryPasswordLength !=
e) { e) {
gFFI.serverModel.temporaryPasswordLength = e; gFFI.serverModel.temporaryPasswordLength = e;
gFFI.setByName("temporary_password"); bind.mainUpdateTemporaryPassword();
} }
}, },
)) ))
@ -465,49 +464,60 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
Get.find<SharedPreferences>().setString("darkTheme", choice); Get.find<SharedPreferences>().setString("darkTheme", choice);
} }
void onSelectMenu(String value) { void onSelectMenu(String key) async {
if (value.startsWith('enable-')) { if (key.startsWith('enable-')) {
final option = gFFI.getOption(value); final option = await bind.mainGetOption(key: key);
gFFI.setOption(value, option == "N" ? "" : "N"); bind.mainSetOption(key: key, value: option == "N" ? "" : "N");
} else if (value.startsWith('allow-')) { } else if (key.startsWith('allow-')) {
final option = gFFI.getOption(value); final option = await bind.mainGetOption(key: key);
final choice = option == "Y" ? "" : "Y"; final choice = option == "Y" ? "" : "Y";
gFFI.setOption(value, choice); bind.mainSetOption(key: key, value: choice);
changeTheme(choice); changeTheme(choice);
} else if (value == "stop-service") { } else if (key == "stop-service") {
final option = gFFI.getOption(value); final option = await bind.mainGetOption(key: key);
gFFI.setOption(value, option == "Y" ? "" : "Y"); bind.mainSetOption(key: key, value: option == "Y" ? "" : "Y");
} else if (value == "change-id") { } else if (key == "change-id") {
changeId(); changeId();
} else if (value == "custom-server") { } else if (key == "custom-server") {
changeServer(); changeServer();
} else if (value == "whitelist") { } else if (key == "whitelist") {
changeWhiteList(); changeWhiteList();
} else if (value == "socks5-proxy") { } else if (key == "socks5-proxy") {
changeSocks5Proxy(); changeSocks5Proxy();
} else if (value == "about") { } else if (key == "about") {
about(); about();
} else if (value == "logout") { } else if (key == "logout") {
logOut(); logOut();
} else if (value == "login") { } else if (key == "login") {
login(); login();
} }
} }
PopupMenuItem<String> genEnablePopupMenuItem(String label, String value) { PopupMenuItem<String> genEnablePopupMenuItem(String label, String key) {
final v = gFFI.getOption(value); Future<bool> getOptionEnable(String key) async {
final isEnable = value.startsWith('enable-') ? v != "N" : v == "Y"; final v = await bind.mainGetOption(key: key);
return key.startsWith('enable-') ? v != "N" : v == "Y";
}
return PopupMenuItem( return PopupMenuItem(
child: Row( child: FutureBuilder<bool>(
children: [ future: getOptionEnable(key),
Offstage(offstage: !isEnable, child: Icon(Icons.check)), builder: (context, snapshot) {
Text( var enable = false;
label, if (snapshot.hasData && snapshot.data!) {
style: genTextStyle(isEnable), enable = true;
), }
], return Row(
), children: [
value: value, Offstage(offstage: !enable, child: Icon(Icons.check)),
Text(
label,
style: genTextStyle(enable),
),
],
);
}),
value: key,
); );
} }
@ -518,10 +528,11 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
color: Colors.redAccent, decoration: TextDecoration.lineThrough); color: Colors.redAccent, decoration: TextDecoration.lineThrough);
} }
PopupMenuItem<String> genAudioInputPopupMenuItem() { PopupMenuItem<String> genAudioInputPopupMenuItem(
final _enabledInput = gFFI.getOption('enable-audio'); bool enableInput, String defaultAudioInput) {
var defaultInput = gFFI.getDefaultAudioInput().obs; final defaultInput = defaultAudioInput.obs;
var enabled = (_enabledInput != "N").obs; final enabled = enableInput.obs;
return PopupMenuItem( return PopupMenuItem(
child: FutureBuilder<List<String>>( child: FutureBuilder<List<String>>(
future: gFFI.getAudioInputs(), future: gFFI.getAudioInputs(),
@ -569,12 +580,13 @@ class _DesktopHomePageState extends State<DesktopHomePage> with TrayListener {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(translate("Audio Input"))), child: Text(translate("Audio Input"))),
itemBuilder: (context) => inputList, itemBuilder: (context) => inputList,
onSelected: (dev) { onSelected: (dev) async {
if (dev == "Mute") { if (dev == "Mute") {
gFFI.setOption( await bind.mainSetOption(
'enable-audio', _enabledInput == 'N' ? '' : 'N'); key: 'enable-audio', value: enabled.value ? '' : 'N');
enabled.value = gFFI.getOption('enable-audio') != 'N'; enabled.value =
} else if (dev != gFFI.getDefaultAudioInput()) { await bind.mainGetOption(key: 'enable-audio') != 'N';
} else if (dev != await gFFI.getDefaultAudioInput()) {
gFFI.setDefaultAudioInput(dev); gFFI.setDefaultAudioInput(dev);
defaultInput.value = dev; defaultInput.value = dev;
} }
@ -1322,8 +1334,8 @@ Future<bool> loginDialog() async {
return completer.future; return completer.future;
} }
void setPasswordDialog() { void setPasswordDialog() async {
final pw = gFFI.getByName("permanent_password"); final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw); final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw); final p1 = TextEditingController(text: pw);
var errMsg0 = ""; var errMsg0 = "";
@ -1413,7 +1425,7 @@ void setPasswordDialog() {
}); });
return; return;
} }
gFFI.setByName("permanent_password", pass); bind.mainSetPermanentPassword(password: pass);
close(); close();
}, },
child: Text(translate("OK"))), child: Text(translate("OK"))),

View File

@ -346,8 +346,9 @@ class _RemotePageState extends State<RemotePage>
if (dy > 0) if (dy > 0)
dy = -1; dy = -1;
else if (dy < 0) dy = 1; else if (dy < 0) dy = 1;
_ffi.setByName('send_mouse', bind.sessionSendMouse(
'{"id": "${widget.id}", "type": "wheel", "x": "$dx", "y": "$dy"}'); id: widget.id,
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
} }
}, },
child: Consumer<FfiModel>( child: Consumer<FfiModel>(
@ -896,32 +897,6 @@ class ImagePainter extends CustomPainter {
} }
} }
CheckboxListTile getToggle(
String id, void Function(void Function()) setState, option, name) {
final opt = bind.getSessionToggleOptionSync(id: id, arg: option);
return CheckboxListTile(
value: opt,
onChanged: (v) {
setState(() {
bind.sessionToggleOption(id: id, value: option);
});
},
dense: true,
title: Text(translate(name)));
}
RadioListTile<String> getRadio(String name, String toValue, String curValue,
void Function(String?) onChange) {
return RadioListTile<String>(
controlAffinity: ListTileControlAffinity.trailing,
title: Text(translate(name)),
value: toValue,
groupValue: curValue,
onChanged: onChange,
dense: true,
);
}
void showOptions(String id) async { void showOptions(String id) async {
String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced';
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';

View File

@ -89,7 +89,8 @@ class _PeerCardState extends State<_PeerCard>
children: [ children: [
Expanded( Expanded(
child: FutureBuilder<String>( child: FutureBuilder<String>(
future: gFFI.getPeerOption(peer.id, 'alias'), future: bind.mainGetPeerOption(
id: peer.id, key: 'alias'),
builder: (_, snapshot) { builder: (_, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
final name = snapshot.data!.isEmpty final name = snapshot.data!.isEmpty
@ -186,7 +187,7 @@ class _PeerCardState extends State<_PeerCard>
elevation: 8, elevation: 8,
); );
if (value == 'remove') { if (value == 'remove') {
setState(() => gFFI.setByName('remove', '$id')); setState(() => bind.mainRemovePeer(id: id));
() async { () async {
removePreference(id); removePreference(id);
}(); }();
@ -304,7 +305,7 @@ class _PeerCardState extends State<_PeerCard>
void _rename(String id) async { void _rename(String id) async {
var isInProgress = false; var isInProgress = false;
var name = await gFFI.getPeerOption(id, 'alias'); var name = await bind.mainGetPeerOption(id: id, key: 'alias');
if (widget.type == PeerType.ab) { if (widget.type == PeerType.ab) {
final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']); final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']);
if (peer == null) { if (peer == null) {
@ -359,7 +360,8 @@ class _PeerCardState extends State<_PeerCard>
if (k.currentState != null) { if (k.currentState != null) {
if (k.currentState!.validate()) { if (k.currentState!.validate()) {
k.currentState!.save(); k.currentState!.save();
await gFFI.setPeerOption(id, 'alias', name); await bind.mainSetPeerOption(
id: id, key: 'alias', value: name);
if (widget.type == PeerType.ab) { if (widget.type == PeerType.ab) {
gFFI.abModel.setPeerOption(id, 'alias', name); gFFI.abModel.setPeerOption(id, 'alias', name);
await gFFI.abModel.updateAb(); await gFFI.abModel.updateAb();

View File

@ -7,6 +7,8 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import 'home_page.dart'; import 'home_page.dart';
import 'remote_page.dart'; import 'remote_page.dart';
import 'scan_page.dart'; import 'scan_page.dart';
@ -41,9 +43,20 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (_idController.text.isEmpty) {
() async {
final lastRemoteId = await bind.mainGetLastRemoteId();
if (lastRemoteId != _idController.text) {
setState(() {
_idController.text = lastRemoteId;
});
}
}();
}
if (isAndroid) { if (isAndroid) {
Timer(Duration(seconds: 5), () { Timer(Duration(seconds: 5), () async {
_updateUrl = gFFI.getByName('software_update_url'); _updateUrl = await bind.mainGetSoftwareUpdateUrl();
;
if (_updateUrl.isNotEmpty) setState(() {}); if (_updateUrl.isNotEmpty) setState(() {});
}); });
} }
@ -52,7 +65,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
if (_idController.text.isEmpty) _idController.text = gFFI.getId();
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -221,44 +233,52 @@ class _ConnectionPageState extends State<ConnectionPage> {
final n = (windowWidth / (minWidth + 2 * space)).floor(); final n = (windowWidth / (minWidth + 2 * space)).floor();
width = windowWidth / n - 2 * space; width = windowWidth / n - 2 * space;
} }
final cards = <Widget>[]; return FutureBuilder<List<Peer>>(
var peers = gFFI.peers(); future: gFFI.peers(),
peers.forEach((p) { builder: (context, snapshot) {
cards.add(Container( final cards = <Widget>[];
width: width, if (snapshot.hasData) {
child: Card( final peers = snapshot.data!;
child: GestureDetector( peers.forEach((p) {
onTap: !isWebDesktop ? () => connect('${p.id}') : null, cards.add(Container(
onDoubleTap: isWebDesktop ? () => connect('${p.id}') : null, width: width,
onLongPressStart: (details) { child: Card(
final x = details.globalPosition.dx; child: GestureDetector(
final y = details.globalPosition.dy; onTap:
_menuPos = RelativeRect.fromLTRB(x, y, x, y); !isWebDesktop ? () => connect('${p.id}') : null,
showPeerMenu(context, p.id); onDoubleTap:
}, isWebDesktop ? () => connect('${p.id}') : null,
child: ListTile( onLongPressStart: (details) {
contentPadding: const EdgeInsets.only(left: 12), final x = details.globalPosition.dx;
subtitle: Text('${p.username}@${p.hostname}'), final y = details.globalPosition.dy;
title: Text('${p.id}'), _menuPos = RelativeRect.fromLTRB(x, y, x, y);
leading: Container( showPeerMenu(context, p.id);
padding: const EdgeInsets.all(6), },
child: getPlatformImage('${p.platform}'), child: ListTile(
color: str2color('${p.id}${p.platform}', 0x7f)), contentPadding: const EdgeInsets.only(left: 12),
trailing: InkWell( subtitle: Text('${p.username}@${p.hostname}'),
child: Padding( title: Text('${p.id}'),
padding: const EdgeInsets.all(12), leading: Container(
child: Icon(Icons.more_vert)), padding: const EdgeInsets.all(6),
onTapDown: (e) { child: getPlatformImage('${p.platform}'),
final x = e.globalPosition.dx; color: str2color('${p.id}${p.platform}', 0x7f)),
final y = e.globalPosition.dy; trailing: InkWell(
_menuPos = RelativeRect.fromLTRB(x, y, x, y); child: Padding(
}, padding: const EdgeInsets.all(12),
onTap: () { child: Icon(Icons.more_vert)),
showPeerMenu(context, p.id); onTapDown: (e) {
}), final x = e.globalPosition.dx;
))))); final y = e.globalPosition.dy;
}); _menuPos = RelativeRect.fromLTRB(x, y, x, y);
return Wrap(children: cards, spacing: space, runSpacing: space); },
onTap: () {
showPeerMenu(context, p.id);
}),
)))));
});
}
return Wrap(children: cards, spacing: space, runSpacing: space);
});
} }
/// Show the peer menu and handle user's choice. /// Show the peer menu and handle user's choice.
@ -280,7 +300,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
elevation: 8, elevation: 8,
); );
if (value == 'remove') { if (value == 'remove') {
setState(() => gFFI.setByName('remove', '$id')); setState(() => bind.mainRemovePeer(id: id));
() async { () async {
removePreference(id); removePreference(id);
}(); }();
@ -296,10 +316,34 @@ class WebMenu extends StatefulWidget {
} }
class _WebMenuState extends State<WebMenu> { class _WebMenuState extends State<WebMenu> {
String? username;
String url = "";
@override
void initState() {
super.initState();
() async {
final usernameRes = await getUsername();
final urlRes = await getUrl();
var update = false;
if (usernameRes != username) {
username = usernameRes;
update = true;
}
if (urlRes != url) {
url = urlRes;
update = true;
}
if (update) {
setState(() {});
}
}();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
final username = getUsername();
return PopupMenuButton<String>( return PopupMenuButton<String>(
icon: Icon(Icons.more_vert), icon: Icon(Icons.more_vert),
itemBuilder: (context) { itemBuilder: (context) {
@ -317,7 +361,7 @@ class _WebMenuState extends State<WebMenu> {
value: "server", value: "server",
) )
] + ] +
(getUrl().contains('admin.rustdesk.com') (url.contains('admin.rustdesk.com')
? <PopupMenuItem<String>>[] ? <PopupMenuItem<String>>[]
: [ : [
PopupMenuItem( PopupMenuItem(

View File

@ -12,10 +12,10 @@ abstract class PageShape extends Widget {
final List<Widget> appBarActions = []; final List<Widget> appBarActions = [];
} }
final homeKey = GlobalKey<_HomePageState>();
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key); static final homeKey = GlobalKey<_HomePageState>();
HomePage() : super(key: homeKey);
@override @override
_HomePageState createState() => _HomePageState(); _HomePageState createState() => _HomePageState();

View File

@ -12,6 +12,7 @@ import 'package:wakelock/wakelock.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart'; import '../widgets/dialog.dart';
import '../widgets/gestures.dart'; import '../widgets/gestures.dart';
import '../widgets/overlay.dart'; import '../widgets/overlay.dart';
@ -135,7 +136,7 @@ class _RemotePageState extends State<RemotePage> {
if (newValue.length > common) { if (newValue.length > common) {
var s = newValue.substring(common); var s = newValue.substring(common);
if (s.length > 1) { if (s.length > 1) {
gFFI.setByName('input_string', s); bind.sessionInputString(id: widget.id, value: s);
} else { } else {
inputChar(s); inputChar(s);
} }
@ -169,11 +170,11 @@ class _RemotePageState extends State<RemotePage> {
content == '' || content == '' ||
content == '【】')) { content == '【】')) {
// can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input // can not only input content[0], because when input ], [ are also auo insert, which cause ] never be input
gFFI.setByName('input_string', content); bind.sessionInputString(id: widget.id, value: content);
openKeyboard(); openKeyboard();
return; return;
} }
gFFI.setByName('input_string', content); bind.sessionInputString(id: widget.id, value: content);
} else { } else {
inputChar(content); inputChar(content);
} }
@ -329,8 +330,9 @@ class _RemotePageState extends State<RemotePage> {
if (dy > 0) if (dy > 0)
dy = -1; dy = -1;
else if (dy < 0) dy = 1; else if (dy < 0) dy = 1;
gFFI.setByName( bind.sessionSendMouse(
'send_mouse', '{"type": "wheel", "x": "$dx", "y": "$dy"}'); id: widget.id,
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
} }
}, },
child: MouseRegion( child: MouseRegion(
@ -409,7 +411,7 @@ class _RemotePageState extends State<RemotePage> {
icon: Icon(Icons.tv), icon: Icon(Icons.tv),
onPressed: () { onPressed: () {
setState(() => _showEdit = false); setState(() => _showEdit = false);
showOptions(); showOptions(widget.id);
}, },
) )
] + ] +
@ -461,7 +463,7 @@ class _RemotePageState extends State<RemotePage> {
icon: Icon(Icons.more_vert), icon: Icon(Icons.more_vert),
onPressed: () { onPressed: () {
setState(() => _showEdit = false); setState(() => _showEdit = false);
showActions(); showActions(widget.id);
}, },
), ),
]), ]),
@ -573,7 +575,7 @@ class _RemotePageState extends State<RemotePage> {
}, },
onTwoFingerScaleEnd: (d) { onTwoFingerScaleEnd: (d) {
_scale = 1; _scale = 1;
gFFI.setByName('peer_option', '{"name": "view-style", "value": ""}'); bind.sessionPeerOption(id: widget.id, name: "view-style", value: "");
}, },
onThreeFingerVerticalDragUpdate: gFFI.ffiModel.isPeerAndroid onThreeFingerVerticalDragUpdate: gFFI.ffiModel.isPeerAndroid
? null ? null
@ -620,8 +622,9 @@ class _RemotePageState extends State<RemotePage> {
Widget getBodyForDesktopWithListener(bool keyboard) { Widget getBodyForDesktopWithListener(bool keyboard) {
var paints = <Widget>[ImagePaint()]; var paints = <Widget>[ImagePaint()];
if (keyboard || final cursor = bind.getSessionToggleOptionSync(
gFFI.getByName('toggle_option', 'show-remote-cursor') == 'true') { id: widget.id, arg: 'show-remote-cursor');
if (keyboard || cursor) {
paints.add(CursorPaint()); paints.add(CursorPaint());
} }
return Container( return Container(
@ -649,7 +652,7 @@ class _RemotePageState extends State<RemotePage> {
return out; return out;
} }
void showActions() { void showActions(String id) async {
final size = MediaQuery.of(context).size; final size = MediaQuery.of(context).size;
final x = 120.0; final x = 120.0;
final y = size.height; final y = size.height;
@ -668,7 +671,7 @@ class _RemotePageState extends State<RemotePage> {
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
showSetOSPassword(false); showSetOSPassword(id, false);
}, },
child: Icon(Icons.edit, color: MyTheme.accent), child: Icon(Icons.edit, color: MyTheme.accent),
) )
@ -691,7 +694,8 @@ class _RemotePageState extends State<RemotePage> {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate('Insert Lock')), value: 'lock')); child: Text(translate('Insert Lock')), value: 'lock'));
if (pi.platform == 'Windows' && if (pi.platform == 'Windows' &&
gFFI.getByName('toggle_option', 'privacy-mode') != 'true') { await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') !=
true) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') +
'lock user input')), 'lock user input')),
@ -713,28 +717,29 @@ class _RemotePageState extends State<RemotePage> {
elevation: 8, elevation: 8,
); );
if (value == 'cad') { if (value == 'cad') {
gFFI.setByName('ctrl_alt_del'); bind.sessionCtrlAltDel(id: widget.id);
} else if (value == 'lock') { } else if (value == 'lock') {
gFFI.setByName('lock_screen'); bind.sessionLockScreen(id: widget.id);
} else if (value == 'block-input') { } else if (value == 'block-input') {
gFFI.setByName('toggle_option', bind.sessionToggleOption(
(gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); id: widget.id,
value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input');
gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked;
} else if (value == 'refresh') { } else if (value == 'refresh') {
gFFI.setByName('refresh'); bind.sessionRefresh(id: widget.id);
} 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}'); bind.sessionInputString(id: widget.id, value: data.text ?? "");
} }
}(); }();
} else if (value == 'enter_os_password') { } else if (value == 'enter_os_password') {
var password = gFFI.getByName('peer_option', "os-password"); var password = await bind.getSessionOption(id: id, arg: "os-password");
if (password != "") { if (password != null) {
gFFI.setByName('input_os_password', password); bind.sessionInputOsPassword(id: widget.id, value: password);
} else { } else {
showSetOSPassword(true); showSetOSPassword(id, true);
} }
} else if (value == 'reset_canvas') { } else if (value == 'reset_canvas') {
gFFI.cursorModel.reset(); gFFI.cursorModel.reset();
@ -762,8 +767,8 @@ class _RemotePageState extends State<RemotePage> {
onTouchModeChange: (t) { onTouchModeChange: (t) {
gFFI.ffiModel.toggleTouchMode(); gFFI.ffiModel.toggleTouchMode();
final v = gFFI.ffiModel.touchMode ? 'Y' : ''; final v = gFFI.ffiModel.touchMode ? 'Y' : '';
gFFI.setByName('peer_option', bind.sessionPeerOption(
'{"name": "touch-mode", "value": "$v"}'); id: widget.id, name: "touch", value: v);
})); }));
})); }));
} }
@ -978,23 +983,23 @@ class QualityMonitor extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Speed: ${qualityMonitorModel.data.speed}", "Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: MyTheme.grayBg),
), ),
Text( Text(
"FPS: ${qualityMonitorModel.data.fps}", "FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: MyTheme.grayBg),
), ),
Text( Text(
"Delay: ${qualityMonitorModel.data.delay} ms", "Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: MyTheme.grayBg),
), ),
Text( Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate}kb", "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: MyTheme.grayBg),
), ),
Text( Text(
"Codec: ${qualityMonitorModel.data.codecFormat}", "Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: MyTheme.grayBg),
), ),
], ],
@ -1003,26 +1008,11 @@ class QualityMonitor extends StatelessWidget {
: SizedBox.shrink()))); : SizedBox.shrink())));
} }
CheckboxListTile getToggle( void showOptions(String id) async {
void Function(void Function()) setState, option, name) { String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced';
return CheckboxListTile(
value: gFFI.getByName('toggle_option', option) == 'true',
onChanged: (v) {
setState(() {
gFFI.setByName('toggle_option', option);
});
if (option == "show-quality-monitor") {
gFFI.qualityMonitorModel.checkShowQualityMonitor();
}
},
dense: true,
title: Text(translate(name)));
}
void showOptions() {
String quality = gFFI.getByName('image_quality');
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';
String viewStyle = gFFI.getByName('peer_option', 'view-style'); String viewStyle =
await bind.getSessionOption(id: id, arg: 'view-style') ?? '';
var displays = <Widget>[]; var displays = <Widget>[];
final pi = gFFI.ffiModel.pi; final pi = gFFI.ffiModel.pi;
final image = gFFI.ffiModel.getConnectionImage(); final image = gFFI.ffiModel.getConnectionImage();
@ -1035,7 +1025,7 @@ void showOptions() {
children.add(InkWell( children.add(InkWell(
onTap: () { onTap: () {
if (i == cur) return; if (i == cur) return;
gFFI.setByName('switch_display', i.toString()); bind.sessionSwitchDisplay(id: id, value: i);
SmartDialog.dismiss(); SmartDialog.dismiss();
}, },
child: Ink( child: Ink(
@ -1064,30 +1054,30 @@ void showOptions() {
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
final more = <Widget>[]; final more = <Widget>[];
if (perms['audio'] != false) { if (perms['audio'] != false) {
more.add(getToggle(setState, 'disable-audio', 'Mute')); more.add(getToggle(id, setState, 'disable-audio', 'Mute'));
} }
if (perms['keyboard'] != false) { if (perms['keyboard'] != false) {
if (perms['clipboard'] != false) if (perms['clipboard'] != false)
more.add(getToggle(setState, 'disable-clipboard', 'Disable clipboard')); more.add(
getToggle(id, setState, 'disable-clipboard', 'Disable clipboard'));
more.add(getToggle( more.add(getToggle(
setState, 'lock-after-session-end', 'Lock after session end')); id, setState, 'lock-after-session-end', 'Lock after session end'));
if (pi.platform == 'Windows') { if (pi.platform == 'Windows') {
more.add(getToggle(setState, 'privacy-mode', 'Privacy mode')); more.add(getToggle(id, setState, 'privacy-mode', 'Privacy mode'));
} }
} }
var setQuality = (String? value) { var setQuality = (String? value) {
if (value == null) return; if (value == null) return;
setState(() { setState(() {
quality = value; quality = value;
gFFI.setByName('image_quality', value); bind.sessionSetImageQuality(id: id, value: value);
}); });
}; };
var setViewStyle = (String? value) { var setViewStyle = (String? value) {
if (value == null) return; if (value == null) return;
setState(() { setState(() {
viewStyle = value; viewStyle = value;
gFFI.setByName( bind.sessionPeerOption(id: id, name: "view-style", value: value);
'peer_option', '{"name": "view-style", "value": "$value"}');
gFFI.canvasModel.updateViewStyle(); gFFI.canvasModel.updateViewStyle();
}); });
}; };
@ -1105,9 +1095,10 @@ void showOptions() {
getRadio('Balanced', 'balanced', quality, setQuality), getRadio('Balanced', 'balanced', quality, setQuality),
getRadio('Optimize reaction time', 'low', quality, setQuality), getRadio('Optimize reaction time', 'low', quality, setQuality),
Divider(color: MyTheme.border), Divider(color: MyTheme.border),
getToggle(setState, 'show-remote-cursor', 'Show remote cursor'),
getToggle( getToggle(
setState, 'show-quality-monitor', 'Show quality monitor'), id, setState, 'show-remote-cursor', 'Show remote cursor'),
getToggle(id, setState, 'show-quality-monitor',
'Show quality monitor'),
] + ] +
more), more),
actions: [], actions: [],
@ -1134,13 +1125,13 @@ void showRestartRemoteDevice(PeerInfo pi, String id) async {
onPressed: () => close(true), child: Text(translate("OK"))), onPressed: () => close(true), child: Text(translate("OK"))),
], ],
)); ));
if (res == true) gFFI.setByName('restart_remote_device'); if (res == true) bind.sessionRestartRemoteDevice(id: id);
} }
void showSetOSPassword(bool login) { void showSetOSPassword(String id, bool login) async {
final controller = TextEditingController(); final controller = TextEditingController();
var password = gFFI.getByName('peer_option', "os-password"); var password = await bind.getSessionOption(id: id, arg: "os-password") ?? "";
var autoLogin = gFFI.getByName('peer_option', "auto-login") != ""; var autoLogin = await bind.getSessionOption(id: id, arg: "auto-login") != "";
controller.text = password; controller.text = password;
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(
@ -1173,12 +1164,11 @@ void showSetOSPassword(bool login) {
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: () {
var text = controller.text.trim(); var text = controller.text.trim();
gFFI.setByName( bind.sessionPeerOption(id: id, name: "os-password", value: text);
'peer_option', '{"name": "os-password", "value": "$text"}'); bind.sessionPeerOption(
gFFI.setByName('peer_option', id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
'{"name": "auto-login", "value": "${autoLogin ? 'Y' : ''}"}');
if (text != "" && login) { if (text != "" && login) {
gFFI.setByName('input_os_password', text); bind.sessionInputOsPassword(id: id, value: text);
} }
close(); close();
}, },

View File

@ -9,7 +9,7 @@ import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:zxing2/qrcode.dart'; import 'package:zxing2/qrcode.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/platform_model.dart';
class ScanPage extends StatefulWidget { class ScanPage extends StatefulWidget {
@override @override
@ -153,54 +153,80 @@ class _ScanPageState extends State<ScanPage> {
} }
void showServerSettingsWithValue( void showServerSettingsWithValue(
String id, String relay, String key, String api) { String id, String relay, String key, String api) async {
final formKey = GlobalKey<FormState>(); Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
final id0 = gFFI.getByName('option', 'custom-rendezvous-server'); String id0 = oldOptions['custom-rendezvous-server'] ?? "";
final relay0 = gFFI.getByName('option', 'relay-server'); String relay0 = oldOptions['relay-server'] ?? "";
final api0 = gFFI.getByName('option', 'api-server'); String api0 = oldOptions['api-server'] ?? "";
final key0 = gFFI.getByName('option', 'key'); String key0 = oldOptions['key'] ?? "";
var isInProgress = false;
final idController = TextEditingController(text: id);
final relayController = TextEditingController(text: relay);
final apiController = TextEditingController(text: api);
String? idServerMsg;
String? relayServerMsg;
String? apiServerMsg;
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
Future<bool> validate() async {
if (idController.text != id) {
final res = await validateAsync(idController.text);
setState(() => idServerMsg = res);
if (idServerMsg != null) return false;
id = idController.text;
}
if (relayController.text != relay) {
relayServerMsg = await validateAsync(relayController.text);
if (relayServerMsg != null) return false;
relay = relayController.text;
}
if (apiController.text != relay) {
apiServerMsg = await validateAsync(apiController.text);
if (apiServerMsg != null) return false;
api = apiController.text;
}
return true;
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('ID/Relay Server')), title: Text(translate('ID/Relay Server')),
content: Form( content: Form(
key: formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
TextFormField( TextFormField(
initialValue: id, controller: idController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: translate('ID Server'), labelText: translate('ID Server'),
), errorText: idServerMsg),
validator: validate,
onSaved: (String? value) {
if (value != null) id = value.trim();
},
) )
] + ] +
(isAndroid (isAndroid
? [ ? [
TextFormField( TextFormField(
initialValue: relay, controller: relayController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: translate('Relay Server'), labelText: translate('Relay Server'),
), errorText: relayServerMsg),
validator: validate,
onSaved: (String? value) {
if (value != null) relay = value.trim();
},
) )
] ]
: []) + : []) +
[ [
TextFormField( TextFormField(
initialValue: api, controller: apiController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: translate('API Server'), labelText: translate('API Server'),
), ),
validator: validate, autovalidateMode: AutovalidateMode.onUserInteraction,
onSaved: (String? value) { validator: (v) {
if (value != null) api = value.trim(); if (v != null && v.length > 0) {
if (!(v.startsWith('http://') ||
v.startsWith("https://"))) {
return translate("invalid_http");
}
}
return apiServerMsg;
}, },
), ),
TextFormField( TextFormField(
@ -208,11 +234,13 @@ void showServerSettingsWithValue(
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Key', labelText: 'Key',
), ),
validator: null, onChanged: (String? value) {
onSaved: (String? value) {
if (value != null) key = value.trim(); if (value != null) key = value.trim();
}, },
), ),
Offstage(
offstage: !isInProgress,
child: LinearProgressIndicator())
])), ])),
actions: [ actions: [
TextButton( TextButton(
@ -224,24 +252,28 @@ void showServerSettingsWithValue(
), ),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: () async {
if (formKey.currentState != null && setState(() {
formKey.currentState!.validate()) { idServerMsg = null;
formKey.currentState!.save(); relayServerMsg = null;
if (id != id0) apiServerMsg = null;
gFFI.setByName('option', isInProgress = true;
'{"name": "custom-rendezvous-server", "value": "$id"}'); });
if (await validate()) {
if (id != id0) {
bind.mainSetOption(key: "custom-rendezvous-server", value: id);
}
if (relay != relay0) if (relay != relay0)
gFFI.setByName( bind.mainSetOption(key: "relay-server", value: relay);
'option', '{"name": "relay-server", "value": "$relay"}'); if (key != key0) bind.mainSetOption(key: "key", value: key);
if (key != key0)
gFFI.setByName('option', '{"name": "key", "value": "$key"}');
if (api != api0) if (api != api0)
gFFI.setByName( bind.mainSetOption(key: "api-server", value: api);
'option', '{"name": "api-server", "value": "$api"}');
gFFI.ffiModel.updateUser(); gFFI.ffiModel.updateUser();
close(); close();
} }
setState(() {
isInProgress = false;
});
}, },
child: Text(translate('OK')), child: Text(translate('OK')),
), ),
@ -250,11 +282,11 @@ void showServerSettingsWithValue(
}); });
} }
String? validate(value) { Future<String?> validateAsync(String value) async {
value = value.trim(); value = value.trim();
if (value.isEmpty) { if (value.isEmpty) {
return null; return null;
} }
final res = gFFI.getByName('test_if_valid_server', value); final res = await bind.mainTestIfValidServer(server: value);
return res.isEmpty ? null : res; return res.isEmpty ? null : res;
} }

View File

@ -1,13 +1,10 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart'; import 'package:flutter_hbb/mobile/widgets/dialog.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/platform_model.dart';
import '../../models/server_model.dart'; import '../../models/server_model.dart';
import 'home_page.dart'; import 'home_page.dart';
@ -99,10 +96,7 @@ class ServerPage extends StatelessWidget implements PageShape {
} else if (value == kUsePermanentPassword || } else if (value == kUsePermanentPassword ||
value == kUseTemporaryPassword || value == kUseTemporaryPassword ||
value == kUseBothPasswords) { value == kUseBothPasswords) {
Map<String, String> msg = Map() bind.mainSetOption(key: "verification-method", value: value);
..["name"] = "verification-method"
..["value"] = value;
gFFI.setByName('option', jsonEncode(msg));
gFFI.serverModel.updatePasswordModel(); gFFI.serverModel.updatePasswordModel();
} }
}) })
@ -183,9 +177,8 @@ class ServerInfo extends StatelessWidget {
? null ? null
: IconButton( : IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: () { onPressed: () =>
gFFI.setByName("temporary_password"); bind.mainUpdateTemporaryPassword())),
})),
onSaved: (String? value) {}, onSaved: (String? value) {},
), ),
], ],
@ -406,8 +399,7 @@ class ConnectionManager extends StatelessWidget {
MaterialStateProperty.all(Colors.red)), MaterialStateProperty.all(Colors.red)),
icon: Icon(Icons.close), icon: Icon(Icons.close),
onPressed: () { onPressed: () {
gFFI.setByName( bind.serverCloseConnection(connId: entry.key);
"close_conn", entry.key.toString());
gFFI.invokeMethod( gFFI.invokeMethod(
"cancel_notification", entry.key); "cancel_notification", entry.key);
}, },

View File

@ -9,6 +9,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart'; import '../widgets/dialog.dart';
import 'home_page.dart'; import 'home_page.dart';
import 'scan_page.dart'; import 'scan_page.dart';
@ -30,15 +31,38 @@ class SettingsPage extends StatefulWidget implements PageShape {
const url = 'https://rustdesk.com/'; const url = 'https://rustdesk.com/';
final _hasIgnoreBattery = androidVersion >= 26; final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false; var _ignoreBatteryOpt = false;
var _enableAbr = false;
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver { class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
String? username;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
if (_hasIgnoreBattery) {
updateIgnoreBatteryStatus(); () async {
} var update = false;
if (_hasIgnoreBattery) {
update = await updateIgnoreBatteryStatus();
}
final usernameRes = await getUsername();
if (usernameRes != username) {
update = true;
username = usernameRes;
}
final enableAbrRes = await bind.mainGetOption(key: "enable-abr") != "N";
if (enableAbrRes != _enableAbr) {
update = true;
_enableAbr = enableAbrRes;
}
if (update) {
setState(() {});
}
}();
} }
@override @override
@ -50,16 +74,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) { if (state == AppLifecycleState.resumed) {
updateIgnoreBatteryStatus(); () async {
if (await updateIgnoreBatteryStatus()) {
setState(() {});
}
}();
} }
} }
Future<bool> updateIgnoreBatteryStatus() async { Future<bool> updateIgnoreBatteryStatus() async {
final res = await PermissionManager.check("ignore_battery_optimizations"); final res = await PermissionManager.check("ignore_battery_optimizations");
if (_ignoreBatteryOpt != res) { if (_ignoreBatteryOpt != res) {
setState(() { _ignoreBatteryOpt = res;
_ignoreBatteryOpt = res;
});
return true; return true;
} else { } else {
return false; return false;
@ -69,21 +95,15 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Provider.of<FfiModel>(context); Provider.of<FfiModel>(context);
final username = getUsername();
final enableAbr = gFFI.getByName("option", "enable-abr") != 'N';
final enhancementsTiles = [ final enhancementsTiles = [
SettingsTile.switchTile( SettingsTile.switchTile(
title: Text(translate('Adaptive Bitrate') + '(beta)'), title: Text(translate('Adaptive Bitrate') + ' (beta)'),
initialValue: enableAbr, initialValue: _enableAbr,
onToggle: (v) { onToggle: (v) {
final msg = Map() bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
..["name"] = "enable-abr" setState(() {
..["value"] = ""; _enableAbr = !_enableAbr;
if (!v) { });
msg["value"] = "N";
}
gFFI.setByName("option", json.encode(msg));
setState(() {});
}, },
) )
]; ];
@ -184,29 +204,27 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
} }
} }
void showServerSettings() { void showServerSettings() async {
final id = gFFI.getByName('option', 'custom-rendezvous-server'); Map<String, dynamic> options = jsonDecode(await bind.mainGetOptions());
final relay = gFFI.getByName('option', 'relay-server'); String id = options['custom-rendezvous-server'] ?? "";
final api = gFFI.getByName('option', 'api-server'); String relay = options['relay-server'] ?? "";
final key = gFFI.getByName('option', 'key'); String api = options['api-server'] ?? "";
String key = options['key'] ?? "";
showServerSettingsWithValue(id, relay, key, api); showServerSettingsWithValue(id, relay, key, api);
} }
void showLanguageSettings() { void showLanguageSettings() async {
try { try {
final langs = json.decode(gFFI.getByName('langs')) as List<dynamic>; final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = gFFI.getByName('local_option', 'lang'); var lang = await bind.mainGetLocalOption(key: "lang");
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
final setLang = (v) { final setLang = (v) {
if (lang != v) { if (lang != v) {
setState(() { setState(() {
lang = v; lang = v;
}); });
final msg = Map() bind.mainSetLocalOption(key: "lang", value: v);
..['name'] = 'lang' HomePage.homeKey.currentState?.refreshPages();
..['value'] = v;
gFFI.setByName('local_option', json.encode(msg));
homeKey.currentState?.refreshPages();
Future.delayed(Duration(milliseconds: 200), close); Future.delayed(Duration(milliseconds: 200), close);
} }
}; };
@ -277,8 +295,8 @@ fetch('http://localhost:21114/api/login', {
final body = { final body = {
'username': name, 'username': name,
'password': pass, 'password': pass,
'id': gFFI.getByName('server_id'), 'id': bind.mainGetMyId(),
'uuid': gFFI.getByName('uuid') 'uuid': bind.mainGetUuid()
}; };
try { try {
final response = await http.post(Uri.parse('$url/api/login'), final response = await http.post(Uri.parse('$url/api/login'),
@ -298,26 +316,22 @@ String parseResp(String body) {
} }
final token = data['access_token']; final token = data['access_token'];
if (token != null) { if (token != null) {
gFFI.setByName('option', '{"name": "access_token", "value": "$token"}'); bind.mainSetOption(key: "access_token", value: token);
} }
final info = data['user']; final info = data['user'];
if (info != null) { if (info != null) {
final value = json.encode(info); final value = json.encode(info);
gFFI.setByName( bind.mainSetOption(key: "user_info", value: value);
'option', json.encode({"name": "user_info", "value": value}));
gFFI.ffiModel.updateUser(); gFFI.ffiModel.updateUser();
} }
return ''; return '';
} }
void refreshCurrentUser() async { void refreshCurrentUser() async {
final token = gFFI.getByName("option", "access_token"); final token = await bind.mainGetOption(key: "access_token");
if (token == '') return; if (token == '') return;
final url = getUrl(); final url = getUrl();
final body = { final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()};
'id': gFFI.getByName('server_id'),
'uuid': gFFI.getByName('uuid')
};
try { try {
final response = await http.post(Uri.parse('$url/api/currentUser'), final response = await http.post(Uri.parse('$url/api/currentUser'),
headers: { headers: {
@ -337,13 +351,10 @@ void refreshCurrentUser() async {
} }
void logout() async { void logout() async {
final token = gFFI.getByName("option", "access_token"); final token = await bind.mainGetOption(key: "access_token");
if (token == '') return; if (token == '') return;
final url = getUrl(); final url = getUrl();
final body = { final body = {'id': bind.mainGetMyId(), 'uuid': bind.mainGetUuid()};
'id': gFFI.getByName('server_id'),
'uuid': gFFI.getByName('uuid')
};
try { try {
await http.post(Uri.parse('$url/api/logout'), await http.post(Uri.parse('$url/api/logout'),
headers: { headers: {
@ -357,16 +368,16 @@ void logout() async {
resetToken(); resetToken();
} }
void resetToken() { void resetToken() async {
gFFI.setByName('option', '{"name": "access_token", "value": ""}'); await bind.mainSetOption(key: "access_token", value: "");
gFFI.setByName('option', '{"name": "user_info", "value": ""}'); await bind.mainSetOption(key: "user_info", value: "");
gFFI.ffiModel.updateUser(); gFFI.ffiModel.updateUser();
} }
String getUrl() { Future<String> getUrl() async {
var url = gFFI.getByName('option', 'api-server'); var url = await bind.mainGetOption(key: "api-server");
if (url == '') { if (url == '') {
url = gFFI.getByName('option', 'custom-rendezvous-server'); url = await bind.mainGetOption(key: "custom-rendezvous-server");
if (url != '') { if (url != '') {
if (url.contains(':')) { if (url.contains(':')) {
final tmp = url.split(':'); final tmp = url.split(':');
@ -455,11 +466,11 @@ void showLogin() {
}); });
} }
String? getUsername() { Future<String?> getUsername() async {
final token = gFFI.getByName("option", "access_token"); final token = await bind.mainGetOption(key: "access_token");
String? username; String? username;
if (token != "") { if (token != "") {
final info = gFFI.getByName("option", "user_info"); final info = await bind.mainGetOption(key: "user_info");
if (info != "") { if (info != "") {
try { try {
Map<String, dynamic> tmp = json.decode(info); Map<String, dynamic> tmp = json.decode(info);

View File

@ -1,11 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../common.dart'; import '../../common.dart';
import '../../models/model.dart'; import '../../models/platform_model.dart';
void clientClose() { void clientClose() {
msgBox('', 'Close', 'Are you sure to close the connection?'); msgBox('', 'Close', 'Are you sure to close the connection?');
@ -22,8 +20,8 @@ void showError({Duration duration = SEC1}) {
showToast(translate("Error"), duration: SEC1); showToast(translate("Error"), duration: SEC1);
} }
void setPermanentPasswordDialog() { void setPermanentPasswordDialog() async {
final pw = gFFI.getByName("permanent_password"); final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw); final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw); final p1 = TextEditingController(text: pw);
var validateLength = false; var validateLength = false;
@ -103,9 +101,9 @@ void setPermanentPasswordDialog() {
}); });
} }
void setTemporaryPasswordLengthDialog() { void setTemporaryPasswordLengthDialog() async {
List<String> lengths = ['6', '8', '10']; List<String> lengths = ['6', '8', '10'];
String length = gFFI.getByName('option', 'temporary-password-length'); String length = await bind.mainGetOption(key: "temporary-password-length");
var index = lengths.indexOf(length); var index = lengths.indexOf(length);
if (index < 0) index = 0; if (index < 0) index = 0;
length = lengths[index]; length = lengths[index];
@ -116,11 +114,8 @@ void setTemporaryPasswordLengthDialog() {
setState(() { setState(() {
length = newValue; length = newValue;
}); });
Map<String, String> msg = Map() bind.mainSetOption(key: "temporary-password-length", value: newValue);
..["name"] = "temporary-password-length" bind.mainUpdateTemporaryPassword();
..["value"] = newValue;
gFFI.setByName("option", jsonEncode(msg));
gFFI.setByName("temporary_password");
Future.delayed(Duration(milliseconds: 200), () { Future.delayed(Duration(milliseconds: 200), () {
close(); close();
showSuccess(); showSuccess();
@ -138,9 +133,9 @@ void setTemporaryPasswordLengthDialog() {
}, backDismiss: true, clickMaskDismiss: true); }, backDismiss: true, clickMaskDismiss: true);
} }
void enterPasswordDialog(String id) { void enterPasswordDialog(String id) async {
final controller = TextEditingController(); final controller = TextEditingController();
var remember = gFFI.getByName('remember', id) == 'true'; var remember = await bind.getSessionRemember(id: id) ?? false;
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('Password Required')), title: Text(translate('Password Required')),

View File

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:dash_chat_2/dash_chat_2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import 'model.dart'; import 'model.dart';
@ -72,7 +71,7 @@ class ChatModel with ChangeNotifier {
} }
} }
receive(int id, String text) { receive(int id, String text) async {
if (text.isEmpty) return; if (text.isEmpty) return;
// first message show overlay icon // first message show overlay icon
if (chatIconOverlayEntry == null) { if (chatIconOverlayEntry == null) {
@ -82,7 +81,7 @@ class ChatModel with ChangeNotifier {
if (id == clientModeID) { if (id == clientModeID) {
chatUser = ChatUser( chatUser = ChatUser(
firstName: _ffi.target?.ffiModel.pi.username, firstName: _ffi.target?.ffiModel.pi.username,
id: _ffi.target?.getId() ?? "", id: await bind.mainGetLastRemoteId(),
); );
} else { } else {
final client = _ffi.target?.serverModel.clients[id]; final client = _ffi.target?.serverModel.clients[id];
@ -105,12 +104,11 @@ class ChatModel with ChangeNotifier {
if (message.text.isNotEmpty) { if (message.text.isNotEmpty) {
_messages[_currentID]?.insert(message); _messages[_currentID]?.insert(message);
if (_currentID == clientModeID) { if (_currentID == clientModeID) {
_ffi.target?.setByName("chat_client_mode", message.text); if (_ffi.target != null) {
bind.sessionSendChat(id: _ffi.target!.id, text: message.text);
}
} else { } else {
final msg = Map() bind.serverSendChat(connId: _currentID, msg: message.text);
..["id"] = _currentID
..["text"] = message.text;
_ffi.target?.setByName("chat_server_mode", jsonEncode(msg));
} }
} }
notifyListeners(); notifyListeners();

View File

@ -290,7 +290,7 @@ class FileModel extends ChangeNotifier {
} }
onReady() async { onReady() async {
_localOption.home = _ffi.target?.getByName("get_home_dir") ?? ""; _localOption.home = await bind.mainGetHomeDir();
_localOption.showHidden = (await bind.sessionGetPeerOption( _localOption.showHidden = (await bind.sessionGetPeerOption(
id: _ffi.target?.id ?? "", name: "local_show_hidden")) id: _ffi.target?.id ?? "", name: "local_show_hidden"))
.isNotEmpty; .isNotEmpty;
@ -444,7 +444,7 @@ class FileModel extends ChangeNotifier {
items.items.forEach((from) async { items.items.forEach((from) async {
_jobId++; _jobId++;
await bind.sessionSendFiles( await bind.sessionSendFiles(
id: '${_ffi.target?.getId()}', id: await bind.mainGetLastRemoteId(),
actId: _jobId, actId: _jobId,
path: from.path, path: from.path,
to: PathUtil.join(toPath, from.name, isWindows), to: PathUtil.join(toPath, from.name, isWindows),

View File

@ -171,6 +171,8 @@ class FfiModel with ChangeNotifier {
parent.target?.serverModel.onClientAuthorized(evt); parent.target?.serverModel.onClientAuthorized(evt);
} else if (name == 'on_client_remove') { } else if (name == 'on_client_remove') {
parent.target?.serverModel.onClientRemove(evt); parent.target?.serverModel.onClientRemove(evt);
} else if (name == 'update_quality_status') {
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
} }
}; };
} }
@ -807,9 +809,10 @@ class QualityMonitorModel with ChangeNotifier {
bool get show => _show; bool get show => _show;
QualityMonitorData get data => _data; QualityMonitorData get data => _data;
checkShowQualityMonitor() { checkShowQualityMonitor(String id) async {
final show = final show = await bind.getSessionToggleOption(
gFFI.getByName('toggle_option', 'show-quality-monitor') == 'true'; id: id, arg: 'show-quality-monitor') ==
true;
if (_show != show) { if (_show != show) {
_show = show; _show = show;
notifyListeners(); notifyListeners();
@ -878,11 +881,6 @@ class FFI {
this.qualityMonitorModel = QualityMonitorModel(WeakReference(this)); this.qualityMonitorModel = QualityMonitorModel(WeakReference(this));
} }
/// Get the remote id for current client.
String getId() {
return getByName('remote_id'); // TODO
}
/// Send a mouse tap event(down and up). /// Send a mouse tap event(down and up).
void tap(MouseButtons button) { void tap(MouseButtons button) {
sendMouse('down', button); sendMouse('down', button);
@ -891,8 +889,10 @@ class FFI {
/// Send scroll event with scroll distance [y]. /// Send scroll event with scroll distance [y].
void scroll(int y) { void scroll(int y) {
setByName('send_mouse', bind.sessionSendMouse(
json.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()}))); id: id,
msg: json
.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
} }
/// Reconnect to the remote peer. /// Reconnect to the remote peer.
@ -918,8 +918,9 @@ class FFI {
/// Send mouse press event. /// Send mouse press event.
void sendMouse(String type, MouseButtons button) { void sendMouse(String type, MouseButtons button) {
if (!ffiModel.keyboard()) return; if (!ffiModel.keyboard()) return;
setByName('send_mouse', bind.sessionSendMouse(
json.encode(modify({'id': id, 'type': type, 'buttons': button.value}))); id: id,
msg: json.encode(modify({'type': type, 'buttons': button.value})));
} }
/// Send key stroke event. /// Send key stroke event.
@ -955,14 +956,14 @@ class FFI {
if (!ffiModel.keyboard()) return; if (!ffiModel.keyboard()) return;
var x2 = x.toInt(); var x2 = x.toInt();
var y2 = y.toInt(); var y2 = y.toInt();
setByName( bind.sessionSendMouse(
'send_mouse', json.encode(modify({'id': id, 'x': '$x2', 'y': '$y2'}))); id: id, msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
} }
/// List the saved peers. /// List the saved peers.
List<Peer> peers() { Future<List<Peer>> peers() async {
try { try {
var str = getByName('peers'); // TODO var str = await bind.mainGetRecentPeers();
if (str == "") return []; if (str == "") return [];
List<dynamic> peers = json.decode(str); List<dynamic> peers = json.decode(str);
return peers return peers
@ -1034,41 +1035,14 @@ class FFI {
/// Send **get** command to the Rust core based on [name] and [arg]. /// Send **get** command to the Rust core based on [name] and [arg].
/// Return the result as a string. /// Return the result as a string.
String getByName(String name, [String arg = '']) { // String getByName(String name, [String arg = '']) {
return platformFFI.getByName(name, arg); // return platformFFI.getByName(name, arg);
} // }
/// Send **set** command to the Rust core based on [name] and [value]. /// Send **set** command to the Rust core based on [name] and [value].
void setByName(String name, [String value = '']) { // void setByName(String name, [String value = '']) {
platformFFI.setByName(name, value); // platformFFI.setByName(name, value);
} // }
String getOption(String name) {
return platformFFI.getByName("option", name);
}
Future<String> getLocalOption(String name) {
return bind.mainGetLocalOption(key: name);
}
Future<void> setLocalOption(String key, String value) {
return bind.mainSetLocalOption(key: key, value: value);
}
Future<String> getPeerOption(String id, String key) {
return bind.mainGetPeerOption(id: id, key: key);
}
Future<void> setPeerOption(String id, String key, String value) {
return bind.mainSetPeerOption(id: id, key: key, value: value);
}
void setOption(String name, String value) {
Map<String, String> res = Map()
..["name"] = name
..["value"] = value;
return platformFFI.setByName('option', jsonEncode(res));
}
handleMouse(Map<String, dynamic> evt, {double tabBarHeight = 0.0}) { handleMouse(Map<String, dynamic> evt, {double tabBarHeight = 0.0}) {
var type = ''; var type = '';
@ -1121,8 +1095,7 @@ class FFI {
break; break;
} }
evt['buttons'] = buttons; evt['buttons'] = buttons;
evt['id'] = id; bind.sessionSendMouse(id: id, msg: json.encode(evt));
setByName('send_mouse', json.encode(evt));
} }
listenToMouse(bool yesOrNo) { listenToMouse(bool yesOrNo) {
@ -1145,8 +1118,8 @@ class FFI {
return await bind.mainGetSoundInputs(); return await bind.mainGetSoundInputs();
} }
String getDefaultAudioInput() { Future<String> getDefaultAudioInput() async {
final input = getOption('audio-input'); final input = await bind.mainGetOption(key: 'audio-input');
if (input.isEmpty && Platform.isWindows) { if (input.isEmpty && Platform.isWindows) {
return "System Sound"; return "System Sound";
} }
@ -1154,11 +1127,14 @@ class FFI {
} }
void setDefaultAudioInput(String input) { void setDefaultAudioInput(String input) {
setOption('audio-input', input); bind.mainSetOption(key: 'audio-input', value: input);
} }
Future<Map<String, String>> getHttpHeaders() async { Future<Map<String, String>> getHttpHeaders() async {
return {"Authorization": "Bearer " + await getLocalOption("access_token")}; return {
"Authorization":
"Bearer " + await bind.mainGetLocalOption(key: "access_token")
};
} }
} }
@ -1230,11 +1206,12 @@ void initializeCursorAndCanvas(FFI ffi) async {
/// Translate text based on the pre-defined dictionary. /// Translate text based on the pre-defined dictionary.
/// note: params [FFI?] can be used to replace global FFI implementation /// note: params [FFI?] can be used to replace global FFI implementation
/// for example: during global initialization, gFFI not exists yet. /// for example: during global initialization, gFFI not exists yet.
String translate(String name, {FFI? ffi}) { // String translate(String name, {FFI? ffi}) {
if (name.startsWith('Failed to') && name.contains(': ')) { // if (name.startsWith('Failed to') && name.contains(': ')) {
return name.split(': ').map((x) => translate(x)).join(': '); // return name.split(': ').map((x) => translate(x)).join(': ');
} // }
var a = 'translate'; // var a = 'translate';
var b = '{"locale": "$localeName", "text": "$name"}'; // var b = '{"locale": "$localeName", "text": "$name"}';
return (ffi ?? gFFI).getByName(a, b); //
} // return (ffi ?? gFFI).getByName(a, b);
// }

View File

@ -29,8 +29,7 @@ typedef HandleEvent = void Function(Map<String, dynamic> evt);
class PlatformFFI { class PlatformFFI {
String _dir = ''; String _dir = '';
String _homeDir = ''; String _homeDir = '';
F2? _getByName; F2? _translate;
F3? _setByName;
var _eventHandlers = Map<String, Map<String, HandleEvent>>(); var _eventHandlers = Map<String, Map<String, HandleEvent>>();
late RustdeskImpl _ffiBind; late RustdeskImpl _ffiBind;
late String _appType; late String _appType;
@ -75,31 +74,19 @@ class PlatformFFI {
} }
} }
/// Send **get** command to the Rust core based on [name] and [arg]. String translate(String name, String locale) {
/// Return the result as a string. if (_translate == null) return '';
String getByName(String name, [String arg = '']) {
if (_getByName == null) return '';
var a = name.toNativeUtf8(); var a = name.toNativeUtf8();
var b = arg.toNativeUtf8(); var b = locale.toNativeUtf8();
var p = _getByName!(a, b); var p = _translate!(a, b);
assert(p != nullptr); assert(p != nullptr);
var res = p.toDartString(); final res = p.toDartString();
calloc.free(p); calloc.free(p);
calloc.free(a); calloc.free(a);
calloc.free(b); calloc.free(b);
return res; return res;
} }
/// Send **set** command to the Rust core based on [name] and [value].
void setByName(String name, [String value = '']) {
if (_setByName == null) return;
var a = name.toNativeUtf8();
var b = value.toNativeUtf8();
_setByName!(a, b);
calloc.free(a);
calloc.free(b);
}
/// Init the FFI class, loads the native Rust core library. /// Init the FFI class, loads the native Rust core library.
Future<Null> init(String appType) async { Future<Null> init(String appType) async {
_appType = appType; _appType = appType;
@ -118,10 +105,7 @@ class PlatformFFI {
: DynamicLibrary.process(); : DynamicLibrary.process();
debugPrint('initializing FFI ${_appType}'); debugPrint('initializing FFI ${_appType}');
try { try {
_getByName = dylib.lookupFunction<F2, F2>('get_by_name'); _translate = dylib.lookupFunction<F2, F2>('translate');
_setByName =
dylib.lookupFunction<Void Function(Pointer<Utf8>, Pointer<Utf8>), F3>(
'set_by_name');
_dir = (await getApplicationDocumentsDirectory()).path; _dir = (await getApplicationDocumentsDirectory()).path;
_ffiBind = RustdeskImpl(dylib); _ffiBind = RustdeskImpl(dylib);
_startListenEvent(_ffiBind); // global event _startListenEvent(_ffiBind); // global event
@ -162,10 +146,10 @@ class PlatformFFI {
} }
print( print(
"_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); "_appType:$_appType,info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir");
setByName('info1', id); await _ffiBind.mainDeviceId(id: id);
setByName('info2', name); await _ffiBind.mainDeviceName(name: name);
setByName('home_dir', _homeDir); await _ffiBind.mainSetHomeDir(home: _homeDir);
setByName('init', _dir); await _ffiBind.mainInit(appDir: _dir);
} catch (e) { } catch (e) {
print(e); print(e);
} }

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import '../common.dart'; import '../common.dart';
@ -57,7 +58,7 @@ class ServerModel with ChangeNotifier {
set verificationMethod(String method) { set verificationMethod(String method) {
_verificationMethod = method; _verificationMethod = method;
gFFI.setOption("verification-method", method); bind.mainSetOption(key: "verification-method", value: method);
} }
String get temporaryPasswordLength { String get temporaryPasswordLength {
@ -70,7 +71,7 @@ class ServerModel with ChangeNotifier {
set temporaryPasswordLength(String length) { set temporaryPasswordLength(String length) {
_temporaryPasswordLength = length; _temporaryPasswordLength = length;
gFFI.setOption("temporary-password-length", length); bind.mainSetOption(key: "temporary-password-length", value: length);
} }
TextEditingController get serverId => _serverId; TextEditingController get serverId => _serverId;
@ -85,7 +86,7 @@ class ServerModel with ChangeNotifier {
ServerModel(this.parent) { ServerModel(this.parent) {
() async { () async {
_emptyIdShow = translate("Generating ...", ffi: this.parent.target); _emptyIdShow = translate("Generating ...");
_serverId = TextEditingController(text: this._emptyIdShow); _serverId = TextEditingController(text: this._emptyIdShow);
/** /**
* 1. check android permission * 1. check android permission
@ -98,42 +99,29 @@ class ServerModel with ChangeNotifier {
// audio // audio
if (androidVersion < 30 || !await PermissionManager.check("audio")) { if (androidVersion < 30 || !await PermissionManager.check("audio")) {
_audioOk = false; _audioOk = false;
parent.target?.setByName( bind.mainSetOption(key: "enable-audio", value: "N");
'option',
jsonEncode(Map()
..["name"] = "enable-audio"
..["value"] = "N"));
} else { } else {
final audioOption = parent.target?.getByName('option', 'enable-audio'); final audioOption = await bind.mainGetOption(key: 'enable-audio');
_audioOk = audioOption?.isEmpty ?? false; _audioOk = audioOption.isEmpty;
} }
// file // file
if (!await PermissionManager.check("file")) { if (!await PermissionManager.check("file")) {
_fileOk = false; _fileOk = false;
parent.target?.setByName( bind.mainSetOption(key: "enable-file-transfer", value: "N");
'option',
jsonEncode(Map()
..["name"] = "enable-file-transfer"
..["value"] = "N"));
} else { } else {
final fileOption = final fileOption =
parent.target?.getByName('option', 'enable-file-transfer'); await bind.mainGetOption(key: 'enable-file-transfer');
_fileOk = fileOption?.isEmpty ?? false; _fileOk = fileOption.isEmpty;
} }
// input (mouse control) // input (mouse control) false by default
Map<String, String> res = Map() bind.mainSetOption(key: "enable-keyboard", value: "N");
..["name"] = "enable-keyboard"
..["value"] = 'N';
parent.target
?.setByName('option', jsonEncode(res)); // input false by default
notifyListeners(); notifyListeners();
}(); }();
Timer.periodic(Duration(seconds: 1), (timer) { Timer.periodic(Duration(seconds: 1), (timer) async {
var status = var status = await bind.mainGetOnlineStatue();
int.tryParse(parent.target?.getByName('connect_statue') ?? "") ?? 0;
if (status > 0) { if (status > 0) {
status = 1; status = 1;
} }
@ -141,10 +129,8 @@ class ServerModel with ChangeNotifier {
_connectStatus = status; _connectStatus = status;
notifyListeners(); notifyListeners();
} }
final res = parent.target final res = await bind.mainCheckClientsLength(length: _clients.length);
?.getByName('check_clients_length', _clients.length.toString()) ?? if (res != null) {
"";
if (res.isNotEmpty) {
debugPrint("clients not match!"); debugPrint("clients not match!");
updateClientState(res); updateClientState(res);
} }
@ -153,11 +139,13 @@ class ServerModel with ChangeNotifier {
}); });
} }
updatePasswordModel() { updatePasswordModel() async {
var update = false; var update = false;
final temporaryPassword = gFFI.getByName("temporary_password"); final temporaryPassword = await bind.mainGetTemporaryPassword();
final verificationMethod = gFFI.getOption("verification-method"); final verificationMethod =
final temporaryPasswordLength = gFFI.getOption("temporary-password-length"); await bind.mainGetOption(key: "verification-method");
final temporaryPasswordLength =
await bind.mainGetOption(key: "temporary-password-length");
final oldPwdText = _serverPasswd.text; final oldPwdText = _serverPasswd.text;
if (_serverPasswd.text != temporaryPassword) { if (_serverPasswd.text != temporaryPassword) {
_serverPasswd.text = temporaryPassword; _serverPasswd.text = temporaryPassword;
@ -191,10 +179,7 @@ class ServerModel with ChangeNotifier {
} }
_audioOk = !_audioOk; _audioOk = !_audioOk;
Map<String, String> res = Map() bind.mainSetOption(key: "enable-audio", value: _audioOk ? '' : 'N');
..["name"] = "enable-audio"
..["value"] = _audioOk ? '' : 'N';
parent.target?.setByName('option', jsonEncode(res));
notifyListeners(); notifyListeners();
} }
@ -208,10 +193,7 @@ class ServerModel with ChangeNotifier {
} }
_fileOk = !_fileOk; _fileOk = !_fileOk;
Map<String, String> res = Map() bind.mainSetOption(key: "enable-file-transfer", value: _fileOk ? '' : 'N');
..["name"] = "enable-file-transfer"
..["value"] = _fileOk ? '' : 'N';
parent.target?.setByName('option', jsonEncode(res));
notifyListeners(); notifyListeners();
} }
@ -281,7 +263,7 @@ class ServerModel with ChangeNotifier {
// TODO // TODO
parent.target?.ffiModel.updateEventListener(""); parent.target?.ffiModel.updateEventListener("");
await parent.target?.invokeMethod("init_service"); await parent.target?.invokeMethod("init_service");
parent.target?.setByName("start_service"); await bind.mainStartService();
_fetchID(); _fetchID();
updateClientState(); updateClientState();
if (!Platform.isLinux) { if (!Platform.isLinux) {
@ -296,7 +278,7 @@ class ServerModel with ChangeNotifier {
// TODO // TODO
parent.target?.serverModel.closeAll(); parent.target?.serverModel.closeAll();
await parent.target?.invokeMethod("stop_service"); await parent.target?.invokeMethod("stop_service");
parent.target?.setByName("stop_service"); await bind.mainStopService();
notifyListeners(); notifyListeners();
if (!Platform.isLinux) { if (!Platform.isLinux) {
// current linux is not supported // current linux is not supported
@ -309,9 +291,9 @@ class ServerModel with ChangeNotifier {
} }
Future<bool> setPermanentPassword(String newPW) async { Future<bool> setPermanentPassword(String newPW) async {
parent.target?.setByName("permanent_password", newPW); await bind.mainSetPermanentPassword(password: newPW);
await Future.delayed(Duration(milliseconds: 500)); await Future.delayed(Duration(milliseconds: 500));
final pw = parent.target?.getByName("permanent_password"); final pw = await bind.mainGetPermanentPassword();
if (newPW == pw) { if (newPW == pw) {
return true; return true;
} else { } else {
@ -325,7 +307,7 @@ class ServerModel with ChangeNotifier {
const maxCount = 10; const maxCount = 10;
while (count < maxCount) { while (count < maxCount) {
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
final id = parent.target?.getByName("server_id") ?? ""; final id = await bind.mainGetMyId();
if (id.isEmpty) { if (id.isEmpty) {
continue; continue;
} else { } else {
@ -352,10 +334,7 @@ class ServerModel with ChangeNotifier {
break; break;
case "input": case "input":
if (_inputOk != value) { if (_inputOk != value) {
Map<String, String> res = Map() bind.mainSetOption(key: "enable-keyboard", value: value ? '' : 'N');
..["name"] = "enable-keyboard"
..["value"] = value ? '' : 'N';
parent.target?.setByName('option', jsonEncode(res));
} }
_inputOk = value; _inputOk = value;
break; break;
@ -365,8 +344,8 @@ class ServerModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
updateClientState([String? json]) { updateClientState([String? json]) async {
var res = json ?? parent.target?.getByName("clients_state") ?? ""; var res = await bind.mainGetClientsState();
try { try {
final List clientsJson = jsonDecode(res); final List clientsJson = jsonDecode(res);
for (var clientJson in clientsJson) { for (var clientJson in clientsJson) {
@ -448,12 +427,9 @@ class ServerModel with ChangeNotifier {
}); });
} }
void sendLoginResponse(Client client, bool res) { void sendLoginResponse(Client client, bool res) async {
final Map<String, dynamic> response = Map();
response["id"] = client.id;
response["res"] = res;
if (res) { if (res) {
parent.target?.setByName("login_res", jsonEncode(response)); bind.serverLoginRes(connId: client.id, res: res);
if (!client.isFileTransfer) { if (!client.isFileTransfer) {
parent.target?.invokeMethod("start_capture"); parent.target?.invokeMethod("start_capture");
} }
@ -461,7 +437,7 @@ class ServerModel with ChangeNotifier {
_clients[client.id]?.authorized = true; _clients[client.id]?.authorized = true;
notifyListeners(); notifyListeners();
} else { } else {
parent.target?.setByName("login_res", jsonEncode(response)); bind.serverLoginRes(connId: client.id, res: res);
parent.target?.invokeMethod("cancel_notification", client.id); parent.target?.invokeMethod("cancel_notification", client.id);
_clients.remove(client.id); _clients.remove(client.id);
} }
@ -493,7 +469,7 @@ class ServerModel with ChangeNotifier {
closeAll() { closeAll() {
_clients.forEach((id, client) { _clients.forEach((id, client) {
parent.target?.setByName("close_conn", id.toString()); bind.serverCloseConnection(connId: id);
}); });
_clients.clear(); _clients.clear();
} }

View File

@ -1,8 +1,9 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter_hbb/models/model.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
import '../common.dart';
Future<void> initTray({List<MenuItem>? extra_item}) async { Future<void> initTray({List<MenuItem>? extra_item}) async {
List<MenuItem> items = [ List<MenuItem> items = [
MenuItem(key: "show", label: translate("show rustdesk")), MenuItem(key: "show", label: translate("show rustdesk")),

View File

@ -1280,7 +1280,7 @@ impl LoginConfigHandler {
/// Create a [`Message`] for login. /// Create a [`Message`] for login.
fn create_login_msg(&self, password: Vec<u8>) -> Message { fn create_login_msg(&self, password: Vec<u8>) -> Message {
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
let my_id = Config::get_id_or(crate::common::FLUTTER_INFO1.lock().unwrap().clone()); let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone());
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let my_id = Config::get_id(); let my_id = Config::get_id();
let mut lr = LoginRequest { let mut lr = LoginRequest {

View File

@ -28,8 +28,8 @@ lazy_static::lazy_static! {
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref FLUTTER_INFO1: Arc<Mutex<String>> = Default::default(); pub static ref DEVICE_ID: Arc<Mutex<String>> = Default::default();
pub static ref FLUTTER_INFO2: Arc<Mutex<String>> = Default::default(); pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default();
} }
#[inline] #[inline]
@ -441,7 +441,7 @@ pub fn username() -> String {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
return whoami::username().trim_end_matches('\0').to_owned(); return whoami::username().trim_end_matches('\0').to_owned();
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
return FLUTTER_INFO2.lock().unwrap().clone(); return DEVICE_NAME.lock().unwrap().clone();
} }
#[inline] #[inline]

View File

@ -23,10 +23,11 @@ use crate::ui_interface;
use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id}; use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
use crate::ui_interface::{ use crate::ui_interface::{
discover, forget_password, get_api_server, get_app_name, get_async_job_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_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_option,
get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version,
has_rendezvous_service, post_request, set_local_option, set_options, set_peer_option, has_rendezvous_service, post_request, set_local_option, set_option, set_options,
set_socks, store_fav, test_if_valid_server, using_public_server, set_peer_option, set_permanent_password, set_socks, store_fav, test_if_valid_server,
update_temporary_password, using_public_server,
}; };
fn initialize(app_dir: &str) { fn initialize(app_dir: &str) {
@ -81,14 +82,24 @@ pub enum EventToUI {
} }
pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> { pub fn start_global_event_stream(s: StreamSink<String>, app_type: String) -> ResultType<()> {
if let Some(_) = flutter::GLOBAL_EVENT_STREAM.write().unwrap().insert(app_type.clone(), s) { if let Some(_) = flutter::GLOBAL_EVENT_STREAM
log::warn!("Global event stream of type {} is started before, but now removed", app_type); .write()
.unwrap()
.insert(app_type.clone(), s)
{
log::warn!(
"Global event stream of type {} is started before, but now removed",
app_type
);
} }
Ok(()) Ok(())
} }
pub fn stop_global_event_stream(app_type: String) { pub fn stop_global_event_stream(app_type: String) {
let _ = flutter::GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type); let _ = flutter::GLOBAL_EVENT_STREAM
.write()
.unwrap()
.remove(&app_type);
} }
pub fn host_stop_system_key_propagate(stopped: bool) { pub fn host_stop_system_key_propagate(stopped: bool) {
@ -113,7 +124,6 @@ pub fn get_session_remember(id: String) -> Option<bool> {
} }
} }
// TODO sync
pub fn get_session_toggle_option(id: String, arg: String) -> Option<bool> { pub fn get_session_toggle_option(id: String, arg: String) -> Option<bool> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_toggle_option(&arg)) Some(session.get_toggle_option(&arg))
@ -143,7 +153,6 @@ pub fn get_session_option(id: String, arg: String) -> Option<String> {
} }
} }
// void
pub fn session_login(id: String, password: String, remember: bool) { pub fn session_login(id: String, password: String, remember: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.login(&password, remember); session.login(&password, remember);
@ -227,38 +236,6 @@ pub fn session_send_chat(id: String, text: String) {
} }
} }
// if let Some(_type) = m.get("type") {
// mask = match _type.as_str() {
// "down" => 1,
// "up" => 2,
// "wheel" => 3,
// _ => 0,
// };
// }
// if let Some(buttons) = m.get("buttons") {
// mask |= match buttons.as_str() {
// "left" => 1,
// "right" => 2,
// "wheel" => 4,
// _ => 0,
// } << 3;
// }
// TODO
pub fn session_send_mouse(
id: String,
mask: i32,
x: i32,
y: i32,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
}
}
pub fn session_peer_option(id: String, name: String, value: String) { pub fn session_peer_option(id: String, name: String, value: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.set_option(name, value); session.set_option(name, value);
@ -409,6 +386,22 @@ pub fn main_get_async_status() -> String {
get_async_job_status() get_async_job_status()
} }
pub fn main_get_option(key: String) -> String {
get_option(key)
}
pub fn main_set_option(key: String, value: String) {
if key.eq("custom-rendezvous-server") {
set_option(key, value);
#[cfg(target_os = "android")]
crate::rendezvous_mediator::RendezvousMediator::restart();
#[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
crate::common::test_rendezvous_server();
} else {
set_option(key, value);
}
}
pub fn main_get_options() -> String { pub fn main_get_options() -> String {
get_options() get_options()
} }
@ -452,7 +445,7 @@ pub fn main_store_fav(favs: Vec<String>) {
store_fav(favs) store_fav(favs)
} }
pub fn main_get_peers(id: String) -> String { pub fn main_get_peer(id: String) -> String {
let conf = get_peer(id); let conf = get_peer(id);
serde_json::to_string(&conf).unwrap_or("".to_string()) serde_json::to_string(&conf).unwrap_or("".to_string())
} }
@ -525,13 +518,30 @@ pub fn main_forget_password(id: String) {
forget_password(id) forget_password(id)
} }
// TODO APP_DIR & ui_interface
pub fn main_get_recent_peers() -> String {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
.drain(..)
.map(|(id, _, p)| (id, p.info))
.collect();
serde_json::ser::to_string(&peers).unwrap_or("".to_owned())
} else {
String::new()
}
}
pub fn main_load_recent_peers() { pub fn main_load_recent_peers() {
if !config::APP_DIR.read().unwrap().is_empty() { if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers()
.drain(..) .drain(..)
.map(|(id, _, p)| (id, p.info)) .map(|(id, _, p)| (id, p.info))
.collect(); .collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read()
.unwrap()
.get(flutter::APP_TYPE_MAIN)
{
let data = HashMap::from([ let data = HashMap::from([
("name", "load_recent_peers".to_owned()), ("name", "load_recent_peers".to_owned()),
( (
@ -557,7 +567,11 @@ pub fn main_load_fav_peers() {
} }
}) })
.collect(); .collect();
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read()
.unwrap()
.get(flutter::APP_TYPE_MAIN)
{
let data = HashMap::from([ let data = HashMap::from([
("name", "load_fav_peers".to_owned()), ("name", "load_fav_peers".to_owned()),
( (
@ -571,7 +585,11 @@ pub fn main_load_fav_peers() {
} }
pub fn main_load_lan_peers() { pub fn main_load_lan_peers() {
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read()
.unwrap()
.get(flutter::APP_TYPE_MAIN)
{
let data = HashMap::from([ let data = HashMap::from([
("name", "load_lan_peers".to_owned()), ("name", "load_lan_peers".to_owned()),
("peers", get_lan_peers()), ("peers", get_lan_peers()),
@ -580,532 +598,168 @@ pub fn main_load_lan_peers() {
}; };
} }
/// FFI for **get** commands which are idempotent. pub fn main_get_last_remote_id() -> String {
/// Return result in c string. // if !config::APP_DIR.read().unwrap().is_empty() {
/// // res = LocalConfig::get_remote_id();
/// # Arguments // }
/// LocalConfig::get_remote_id()
/// * `name` - name of the command }
/// * `arg` - argument of the command
#[no_mangle] pub fn main_get_software_update_url() -> String {
unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *const c_char { crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone()
let mut res = "".to_owned(); }
let arg: &CStr = CStr::from_ptr(arg);
let name: &CStr = CStr::from_ptr(name); pub fn main_get_home_dir() -> String {
if let Ok(name) = name.to_str() { fs::get_home_as_string()
match name { }
"peers" => {
if !config::APP_DIR.read().unwrap().is_empty() { pub fn main_get_langs() -> String {
let peers: Vec<(String, config::PeerInfoSerde)> = PeerConfig::peers() crate::lang::LANGS.to_string()
.drain(..) }
.map(|(id, _, p)| (id, p.info))
.collect(); pub fn main_get_temporary_password() -> String {
res = serde_json::ser::to_string(&peers).unwrap_or("".to_owned()); ui_interface::temporary_password()
} }
}
"remote_id" => { pub fn main_get_permanent_password() -> String {
if !config::APP_DIR.read().unwrap().is_empty() { ui_interface::permanent_password()
res = LocalConfig::get_remote_id(); }
}
} pub fn main_get_online_statue() -> i64 {
// "remember" => { ONLINE.lock().unwrap().values().max().unwrap_or(&0).clone()
// res = Session::get_remember().to_string(); }
// }
// "toggle_option" => { pub fn main_get_clients_state() -> String {
// if let Ok(arg) = arg.to_str() { get_clients_state()
// if let Some(v) = Session::get_toggle_option(arg) { }
// res = v.to_string();
// } pub fn main_check_clients_length(length: usize) -> Option<String> {
// } if length != get_clients_length() {
// } Some(get_clients_state())
"test_if_valid_server" => { } else {
if let Ok(arg) = arg.to_str() { None
res = hbb_common::socket_client::test_if_valid_server(arg); }
} }
}
"option" => { pub fn main_init(app_dir: String) {
if let Ok(arg) = arg.to_str() { initialize(&app_dir);
res = ui_interface::get_option(arg.to_owned()); }
}
} pub fn main_device_id(id: String) {
// "image_quality" => { *crate::common::DEVICE_ID.lock().unwrap() = id;
// res = Session::get_image_quality(); }
// }
"software_update_url" => { pub fn main_device_name(name: String) {
res = crate::common::SOFTWARE_UPDATE_URL.lock().unwrap().clone() *crate::common::DEVICE_NAME.lock().unwrap() = name;
} }
"translate" => {
if let Ok(arg) = arg.to_str() { pub fn main_remove_peer(id: String) {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(arg) { PeerConfig::remove(&id);
if let Some(locale) = m.get("locale") { }
if let Some(text) = m.get("text") {
res = crate::client::translate_locale(text.to_owned(), locale); // TODO
} pub fn session_send_mouse(id: String, msg: String) {
} if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(&msg) {
} let alt = m.get("alt").is_some();
} let ctrl = m.get("ctrl").is_some();
} let shift = m.get("shift").is_some();
// "peer_option" => { let command = m.get("command").is_some();
// if let Ok(arg) = arg.to_str() { let x = m
// res = Session::get_option(arg); .get("x")
// } .map(|x| x.parse::<i32>().unwrap_or(0))
// } .unwrap_or(0);
// File Action let y = m
"get_home_dir" => { .get("y")
res = fs::get_home_as_string(); .map(|x| x.parse::<i32>().unwrap_or(0))
} .unwrap_or(0);
// "read_local_dir_sync" => { let mut mask = 0;
// if let Ok(value) = arg.to_str() { if let Some(_type) = m.get("type") {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) { mask = match _type.as_str() {
// if let (Some(path), Some(show_hidden)) = "down" => 1,
// (m.get("path"), m.get("show_hidden")) "up" => 2,
// { "wheel" => 3,
// if let Ok(fd) = _ => 0,
// fs::read_dir(&fs::get_path(path), show_hidden.eq("true")) };
// { }
// res = make_fd_to_json(fd); if let Some(buttons) = m.get("buttons") {
// } mask |= match buttons.as_str() {
// } "left" => 1,
// } "right" => 2,
// } "wheel" => 4,
// } _ => 0,
// Server Side } << 3;
"local_option" => { }
if let Ok(arg) = arg.to_str() { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
res = LocalConfig::get_option(arg); session.send_mouse(mask, x, y, alt, ctrl, shift, command);
}
}
"langs" => {
res = crate::lang::LANGS.to_string();
}
"server_id" => {
res = ui_interface::get_id();
}
"temporary_password" => {
res = ui_interface::temporary_password();
}
"permanent_password" => {
res = ui_interface::permanent_password();
}
"connect_statue" => {
res = ONLINE
.lock()
.unwrap()
.values()
.max()
.unwrap_or(&0)
.clone()
.to_string();
}
#[cfg(not(any(target_os = "ios")))]
"clients_state" => {
res = get_clients_state();
}
#[cfg(not(any(target_os = "ios")))]
"check_clients_length" => {
if let Ok(value) = arg.to_str() {
if value.parse::<usize>().unwrap_or(usize::MAX) != get_clients_length() {
res = get_clients_state()
}
}
}
"uuid" => {
res = base64::encode(get_uuid());
}
_ => {
log::error!("Unknown name of get_by_name: {}", name);
}
} }
} }
}
pub fn session_restart_remote_device(id: String) {
// TODO
// Session::restart_remote_device();
}
pub fn main_set_home_dir(home: String) {
*config::APP_HOME_DIR.write().unwrap() = home;
}
pub fn main_stop_service() {
#[cfg(target_os = "android")]
{
Config::set_option("stop-service".into(), "Y".into());
crate::rendezvous_mediator::RendezvousMediator::restart();
}
}
pub fn main_start_service() {
#[cfg(target_os = "android")]
{
Config::set_option("stop-service".into(), "".into());
crate::rendezvous_mediator::RendezvousMediator::restart();
}
#[cfg(not(target_os = "android"))]
std::thread::spawn(move || start_server(true));
}
pub fn main_update_temporary_password() {
update_temporary_password();
}
pub fn main_set_permanent_password(password: String) {
set_permanent_password(password);
}
pub fn server_send_chat(conn_id: i32, msg: String) {
connection_manager::send_chat(conn_id, msg);
}
pub fn server_login_res(conn_id: i32, res: bool) {
connection_manager::on_login_res(conn_id, res);
}
pub fn server_close_connection(conn_id: i32) {
connection_manager::close_conn(conn_id);
}
#[no_mangle]
unsafe extern "C" fn translate(name: *const c_char, locale: *const c_char) -> *const c_char {
let name = CStr::from_ptr(name);
let locale = CStr::from_ptr(locale);
let res = if let (Ok(name), Ok(locale)) = (name.to_str(), locale.to_str()) {
crate::client::translate_locale(name.to_owned(), locale)
} else {
String::new()
};
CString::from_vec_unchecked(res.into_bytes()).into_raw() CString::from_vec_unchecked(res.into_bytes()).into_raw()
} }
/// FFI for **set** commands which are not idempotent.
///
/// # Arguments
///
/// * `name` - name of the command
/// * `arg` - argument of the command
#[no_mangle]
unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
let value: &CStr = CStr::from_ptr(value);
if let Ok(value) = value.to_str() {
let name: &CStr = CStr::from_ptr(name);
if let Ok(name) = name.to_str() {
match name {
"init" => {
initialize(value);
}
"info1" => {
*crate::common::FLUTTER_INFO1.lock().unwrap() = value.to_owned();
}
"info2" => {
*crate::common::FLUTTER_INFO2.lock().unwrap() = value.to_owned();
}
// "connect" => {
// Session::start(value, false);
// }
// "connect_file_transfer" => {
// Session::start(value, true);
// }
// "login" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let Some(password) = m.get("password") {
// if let Some(remember) = m.get("remember") {
// Session::login(password, remember == "true");
// }
// }
// }
// }
// "close" => {
// Session::close();
// }
// "refresh" => {
// Session::refresh();
// }
// "reconnect" => {
// Session::reconnect();
// }
// "toggle_option" => {
// Session::toggle_option(value);
// }
// "image_quality" => {
// Session::set_image_quality(value);
// }
// "lock_screen" => {
// Session::lock_screen();
// }
// "ctrl_alt_del" => {
// Session::ctrl_alt_del();
// }
// "switch_display" => {
// if let Ok(v) = value.parse::<i32>() {
// Session::switch_display(v);
// }
// }
"remove" => {
PeerConfig::remove(value);
}
// "input_key" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// let alt = m.get("alt").is_some();
// let ctrl = m.get("ctrl").is_some();
// let shift = m.get("shift").is_some();
// let command = m.get("command").is_some();
// let down = m.get("down").is_some();
// let press = m.get("press").is_some();
// if let Some(name) = m.get("name") {
// Session::input_key(name, down, press, alt, ctrl, shift, command);
// }
// }
// }
// "input_string" => {
// Session::input_string(value);
// }
// "chat_client_mode" => {
// Session::send_chat(value.to_owned());
// }
// TODO
"send_mouse" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
let id = m.get("id");
if id.is_none() {
return;
}
let id = id.unwrap();
let alt = m.get("alt").is_some();
let ctrl = m.get("ctrl").is_some();
let shift = m.get("shift").is_some();
let command = m.get("command").is_some();
let x = m
.get("x")
.map(|x| x.parse::<i32>().unwrap_or(0))
.unwrap_or(0);
let y = m
.get("y")
.map(|x| x.parse::<i32>().unwrap_or(0))
.unwrap_or(0);
let mut mask = 0;
if let Some(_type) = m.get("type") {
mask = match _type.as_str() {
"down" => 1,
"up" => 2,
"wheel" => 3,
_ => 0,
};
}
if let Some(buttons) = m.get("buttons") {
mask |= match buttons.as_str() {
"left" => 1,
"right" => 2,
"wheel" => 4,
_ => 0,
} << 3;
}
if let Some(session) = SESSIONS.read().unwrap().get(id) {
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
}
}
}
"option" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
if let Some(name) = m.get("name") {
if let Some(value) = m.get("value") {
ui_interface::set_option(name.to_owned(), value.to_owned());
if name == "custom-rendezvous-server" {
#[cfg(target_os = "android")]
crate::rendezvous_mediator::RendezvousMediator::restart();
#[cfg(any(
target_os = "android",
target_os = "ios",
feature = "cli"
))]
crate::common::test_rendezvous_server();
}
}
}
}
}
// "peer_option" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let Some(name) = m.get("name") {
// if let Some(value) = m.get("value") {
// Session::set_option(name.to_owned(), value.to_owned());
// }
// }
// }
// }
"local_option" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
if let Some(name) = m.get("name") {
if let Some(value) = m.get("value") {
LocalConfig::set_option(name.to_owned(), value.to_owned());
}
}
}
}
// "input_os_password" => {
// Session::input_os_password(value.to_owned(), true);
// }
"restart_remote_device" => {
// TODO
// Session::restart_remote_device();
}
// // File Action
// "read_remote_dir" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (Some(path), Some(show_hidden), Some(session)) = (
// m.get("path"),
// m.get("show_hidden"),
// Session::get().read().unwrap().as_ref(),
// ) {
// session.read_remote_dir(path.to_owned(), show_hidden.eq("true"));
// }
// }
// }
// "send_files" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (
// Some(id),
// Some(path),
// Some(to),
// Some(file_num),
// Some(show_hidden),
// Some(is_remote),
// ) = (
// m.get("id"),
// m.get("path"),
// m.get("to"),
// m.get("file_num"),
// m.get("show_hidden"),
// m.get("is_remote"),
// ) {
// Session::send_files(
// id.parse().unwrap_or(0),
// path.to_owned(),
// to.to_owned(),
// file_num.parse().unwrap_or(0),
// show_hidden.eq("true"),
// is_remote.eq("true"),
// );
// }
// }
// }
// "set_confirm_override_file" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (
// Some(id),
// Some(file_num),
// Some(need_override),
// Some(remember),
// Some(is_upload),
// ) = (
// m.get("id"),
// m.get("file_num"),
// m.get("need_override"),
// m.get("remember"),
// m.get("is_upload"),
// ) {
// Session::set_confirm_override_file(
// id.parse().unwrap_or(0),
// file_num.parse().unwrap_or(0),
// need_override.eq("true"),
// remember.eq("true"),
// is_upload.eq("true"),
// );
// }
// }
// }
// ** TODO ** continue
// "remove_file" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (
// Some(id),
// Some(path),
// Some(file_num),
// Some(is_remote),
// Some(session),
// ) = (
// m.get("id"),
// m.get("path"),
// m.get("file_num"),
// m.get("is_remote"),
// Session::get().write().unwrap().as_mut(),
// ) {
// session.remove_file(
// id.parse().unwrap_or(0),
// path.to_owned(),
// file_num.parse().unwrap_or(0),
// is_remote.eq("true"),
// );
// }
// }
// }
// "read_dir_recursive" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
// m.get("id"),
// m.get("path"),
// m.get("is_remote"),
// Session::get().write().unwrap().as_mut(),
// ) {
// session.remove_dir_all(
// id.parse().unwrap_or(0),
// path.to_owned(),
// is_remote.eq("true"),
// );
// }
// }
// }
// "remove_all_empty_dirs" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
// m.get("id"),
// m.get("path"),
// m.get("is_remote"),
// Session::get().write().unwrap().as_mut(),
// ) {
// session.remove_dir(
// id.parse().unwrap_or(0),
// path.to_owned(),
// is_remote.eq("true"),
// );
// }
// }
// }
// "cancel_job" => {
// if let (Ok(id), Some(session)) =
// (value.parse(), Session::get().write().unwrap().as_mut())
// {
// session.cancel_job(id);
// }
// }
// "create_dir" => {
// if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {
// if let (Some(id), Some(path), Some(is_remote), Some(session)) = (
// m.get("id"),
// m.get("path"),
// m.get("is_remote"),
// Session::get().write().unwrap().as_mut(),
// ) {
// session.create_dir(
// id.parse().unwrap_or(0),
// path.to_owned(),
// is_remote.eq("true"),
// );
// }
// }
// }
// Server Side
// "update_password" => {
// if value.is_empty() {
// Config::set_password(&Config::get_auto_password());
// } else {
// Config::set_password(value);
// }
// }
#[cfg(target_os = "android")]
"chat_server_mode" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, Value>>(value) {
if let (Some(Value::Number(id)), Some(Value::String(text))) =
(m.get("id"), m.get("text"))
{
let id = id.as_i64().unwrap_or(0);
connection_manager::send_chat(id as i32, text.to_owned());
}
}
}
"home_dir" => {
*config::APP_HOME_DIR.write().unwrap() = value.to_owned();
}
#[cfg(target_os = "android")]
"login_res" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, Value>>(value) {
if let (Some(Value::Number(id)), Some(Value::Bool(res))) =
(m.get("id"), m.get("res"))
{
let id = id.as_i64().unwrap_or(0);
connection_manager::on_login_res(id as i32, *res);
}
}
}
#[cfg(target_os = "android")]
"stop_service" => {
Config::set_option("stop-service".into(), "Y".into());
crate::rendezvous_mediator::RendezvousMediator::restart();
}
"start_service" => {
#[cfg(target_os = "android")]
{
Config::set_option("stop-service".into(), "".into());
crate::rendezvous_mediator::RendezvousMediator::restart();
}
#[cfg(not(target_os = "android"))]
std::thread::spawn(move || start_server(true));
}
#[cfg(target_os = "android")]
"close_conn" => {
if let Ok(id) = value.parse::<i32>() {
connection_manager::close_conn(id);
};
}
"temporary_password" => {
ui_interface::update_temporary_password();
}
"permanent_password" => {
ui_interface::set_permanent_password(value.to_owned());
}
_ => {
log::error!("Unknown name of set_by_name: {}", name);
}
}
}
}
}
fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) { fn handle_query_onlines(onlines: Vec<String>, offlines: Vec<String>) {
if let Some(s) = flutter::GLOBAL_EVENT_STREAM.read().unwrap().get(flutter::APP_TYPE_MAIN) { if let Some(s) = flutter::GLOBAL_EVENT_STREAM
.read()
.unwrap()
.get(flutter::APP_TYPE_MAIN)
{
let data = HashMap::from([ let data = HashMap::from([
("name", "callback_query_onlines".to_owned()), ("name", "callback_query_onlines".to_owned()),
("onlines", onlines.join(",")), ("onlines", onlines.join(",")),

View File

@ -5,7 +5,7 @@ use crate::clipboard_file::*;
use crate::common::update_clipboard; use crate::common::update_clipboard;
use crate::video_service; use crate::video_service;
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
use crate::{common::FLUTTER_INFO2, flutter::connection_manager::start_channel}; use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
use crate::{ipc, VERSION}; use crate::{ipc, VERSION};
use hbb_common::{ use hbb_common::{
config::Config, config::Config,
@ -643,7 +643,7 @@ impl Connection {
} }
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
pi.hostname = FLUTTER_INFO2.lock().unwrap().clone(); pi.hostname = DEVICE_NAME.lock().unwrap().clone();
pi.platform = "Android".into(); pi.platform = "Android".into();
} }
#[cfg(feature = "hwcodec")] #[cfg(feature = "hwcodec")]

View File

@ -138,10 +138,11 @@ pub fn get_license() -> String {
} }
pub fn get_option(key: String) -> String { pub fn get_option(key: String) -> String {
#[cfg(any(target_os = "android", target_os = "ios"))] get_option_(&key)
return Config::get_option(&key); // #[cfg(any(target_os = "android", target_os = "ios"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))] // return Config::get_option(&key);
return get_option_(&key); // #[cfg(not(any(target_os = "android", target_os = "ios")))]
// return get_option_(&key);
} }
fn get_option_(key: &str) -> String { fn get_option_(key: &str) -> String {
@ -250,33 +251,31 @@ pub fn get_sound_inputs() -> Vec<String> {
} }
pub fn set_options(m: HashMap<String, String>) { pub fn set_options(m: HashMap<String, String>) {
*OPTIONS.lock().unwrap() = m.clone();
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ ipc::set_options(m).ok();
*OPTIONS.lock().unwrap() = m.clone(); #[cfg(any(target_os = "android", target_os = "ios"))]
ipc::set_options(m).ok(); Config::set_options(m);
}
} }
pub fn set_option(key: String, value: String) { pub fn set_option(key: String, value: String) {
let mut options = OPTIONS.lock().unwrap();
#[cfg(target_os = "macos")]
if &key == "stop-service" {
let is_stop = value == "Y";
if is_stop && crate::platform::macos::uninstall() {
return;
}
}
if value.is_empty() {
options.remove(&key);
} else {
options.insert(key.clone(), value.clone());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
ipc::set_options(options.clone()).ok();
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(any(target_os = "android", target_os = "ios"))]
Config::set_option(key, value); Config::set_option(key, value);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
let mut options = OPTIONS.lock().unwrap();
#[cfg(target_os = "macos")]
if &key == "stop-service" {
let is_stop = value == "Y";
if is_stop && crate::platform::macos::uninstall() {
return;
}
}
if value.is_empty() {
options.remove(&key);
} else {
options.insert(key.clone(), value.clone());
}
ipc::set_options(options.clone()).ok();
}
} }
pub fn install_path() -> String { pub fn install_path() -> String {