Merge remote-tracking branch 'rustdesk/master' into flutter_desktop

# Conflicts:
#	.github/workflows/ci.yml
#	Cargo.lock
#	Cargo.toml
#	flutter/lib/common.dart
#	flutter/lib/mobile/pages/remote_page.dart
#	flutter/lib/mobile/pages/server_page.dart
#	flutter/lib/mobile/pages/settings_page.dart
#	flutter/lib/mobile/widgets/dialog.dart
#	flutter/lib/models/model.dart
#	flutter/lib/models/server_model.dart
#	src/client.rs
#	src/common.rs
#	src/ipc.rs
#	src/mobile_ffi.rs
#	src/rendezvous_mediator.rs
#	src/ui.rs
This commit is contained in:
Kingtous
2022-08-01 10:44:05 +08:00
142 changed files with 8721 additions and 2231 deletions

View File

@@ -264,7 +264,6 @@ class _RemotePageState extends State<RemotePage> {
: SafeArea(child:
OrientationBuilder(builder: (ctx, orientation) {
if (_currentOrientation != orientation) {
debugPrint("on orientation changed");
Timer(Duration(milliseconds: 200), () {
resetMobileActionsOverlay();
_currentOrientation = orientation;
@@ -345,9 +344,14 @@ class _RemotePageState extends State<RemotePage> {
onKey: (data, e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat) {
if (e.repeat &&
!e.isAltPressed &&
!e.isControlPressed &&
!e.isShiftPressed &&
!e.isMetaPressed) {
sendRawKey(e, press: true);
} else {
sendRawKey(e, down: true);
if (e.isAltPressed && !gFFI.alt) {
gFFI.alt = true;
} else if (e.isControlPressed && !gFFI.ctrl) {
@@ -357,7 +361,6 @@ class _RemotePageState extends State<RemotePage> {
} else if (e.isMetaPressed && !gFFI.command) {
gFFI.command = true;
}
sendRawKey(e, down: true);
}
}
// [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter
@@ -483,6 +486,7 @@ class _RemotePageState extends State<RemotePage> {
/// DoubleFiner -> right click
/// HoldDrag -> left drag
Offset _cacheLongPressPosition = Offset(0, 0);
Widget getBodyForMobileWithGesture() {
final touchMode = gFFI.ffiModel.touchMode;
return getMixinGestureDetector(
@@ -507,10 +511,15 @@ class _RemotePageState extends State<RemotePage> {
onLongPressDown: (d) {
if (touchMode) {
gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
_cacheLongPressPosition = d.localPosition;
}
},
onLongPress: () {
gFFI.tap(MouseButtons.right);
if (touchMode) {
FFI.cursorModel
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
}
FFI.tap(MouseButtons.right);
},
onDoubleFinerTap: (d) {
if (!touchMode) {
@@ -536,6 +545,15 @@ class _RemotePageState extends State<RemotePage> {
if (touchMode) {
gFFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
gFFI.sendMouse('down', MouseButtons.left);
} else {
final cursorX = FFI.cursorModel.x;
final cursorY = FFI.cursorModel.y;
final visible =
FFI.cursorModel.getVisibleRect().inflate(1); // extend edges
final size = MediaQueryData.fromWindow(ui.window).size;
if (!visible.contains(Offset(cursorX, cursorY))) {
FFI.cursorModel.move(size.width / 2, size.height / 2);
}
}
},
onOneFingerPanUpdate: (d) {
@@ -946,18 +964,6 @@ CheckboxListTile getToggle(void Function(void Function()) setState, option, name
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 quality = gFFI.getByName('image_quality');
if (quality == '') quality = 'balanced';
@@ -1045,6 +1051,8 @@ void showOptions() {
getRadio('Optimize reaction time', 'low', quality, setQuality),
Divider(color: MyTheme.border),
getToggle(setState, 'show-remote-cursor', 'Show remote cursor'),
getToggle(
setState, 'show-quality-monitor', 'Show quality monitor'),
] +
more),
actions: [],

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
import 'package:flutter_hbb/models/model.dart';
@@ -24,36 +26,84 @@ class ServerPage extends StatelessWidget implements PageShape {
return [
PopupMenuItem(
child: Text(translate("Change ID")),
padding: EdgeInsets.symmetric(horizontal: 16.0),
value: "changeID",
enabled: false,
),
PopupMenuItem(
child: Text(translate("Set your own password")),
value: "changePW",
enabled: gFFI.serverModel.isStart,
child: Text(translate("Set permanent password")),
padding: EdgeInsets.symmetric(horizontal: 16.0),
value: "setPermanentPassword",
enabled:
gFFI.serverModel.verificationMethod != kUseTemporaryPassword,
),
PopupMenuItem(
child: Text(translate("Refresh random password")),
value: "refreshPW",
enabled: gFFI.serverModel.isStart,
)
child: Text(translate("Set temporary password length")),
padding: EdgeInsets.symmetric(horizontal: 16.0),
value: "setTemporaryPasswordLength",
enabled:
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
),
const PopupMenuDivider(),
PopupMenuItem(
padding: EdgeInsets.symmetric(horizontal: 0.0),
value: kUseTemporaryPassword,
child: Container(
child: ListTile(
title: Text(translate("Use temporary password")),
trailing: Icon(
Icons.check,
color: gFFI.serverModel.verificationMethod ==
kUseTemporaryPassword
? null
: Color(0xFFFFFFFF),
))),
),
PopupMenuItem(
padding: EdgeInsets.symmetric(horizontal: 0.0),
value: kUsePermanentPassword,
child: ListTile(
title: Text(translate("Use permanent password")),
trailing: Icon(
Icons.check,
color: gFFI.serverModel.verificationMethod ==
kUsePermanentPassword
? null
: Color(0xFFFFFFFF),
)),
),
PopupMenuItem(
padding: EdgeInsets.symmetric(horizontal: 0.0),
value: kUseBothPasswords,
child: ListTile(
title: Text(translate("Use both passwords")),
trailing: Icon(
Icons.check,
color: gFFI.serverModel.verificationMethod !=
kUseTemporaryPassword &&
gFFI.serverModel.verificationMethod !=
kUsePermanentPassword
? null
: Color(0xFFFFFFFF),
)),
),
];
},
onSelected: (value) {
if (value == "changeID") {
// TODO
} else if (value == "changePW") {
updatePasswordDialog();
} else if (value == "refreshPW") {
() async {
showLoading(translate("Waiting"));
if (await gFFI.serverModel.updatePassword("")) {
showSuccess();
} else {
showError();
}
debugPrint("end updatePassword");
}();
} else if (value == "setPermanentPassword") {
setPermanentPasswordDialog();
} else if (value == "setTemporaryPasswordLength") {
setTemporaryPasswordLengthDialog();
} else if (value == kUsePermanentPassword ||
value == kUseTemporaryPassword ||
value == kUseBothPasswords) {
Map<String, String> msg = Map()
..["name"] = "verification-method"
..["value"] = value;
gFFI.setByName('option', jsonEncode(msg));
gFFI.serverModel.updatePasswordModel();
}
})
];
@@ -90,17 +140,13 @@ void checkService() async {
}
}
class ServerInfo extends StatefulWidget {
@override
_ServerInfoState createState() => _ServerInfoState();
}
class _ServerInfoState extends State<ServerInfo> {
class ServerInfo extends StatelessWidget {
final model = gFFI.serverModel;
var _passwdShow = false;
final emptyController = TextEditingController(text: "-");
@override
Widget build(BuildContext context) {
final isPermanent = model.verificationMethod == kUsePermanentPassword;
return model.isStart
? PaddingCard(
child: Column(
@@ -123,24 +169,23 @@ class _ServerInfoState extends State<ServerInfo> {
),
TextFormField(
readOnly: true,
obscureText: !_passwdShow,
style: TextStyle(
fontSize: 25.0,
fontWeight: FontWeight.bold,
color: MyTheme.accent),
controller: model.serverPasswd,
controller: isPermanent ? emptyController : model.serverPasswd,
decoration: InputDecoration(
icon: const Icon(Icons.lock),
labelText: translate("Password"),
labelStyle: TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50),
suffix: IconButton(
icon: Icon(Icons.visibility),
onPressed: () {
setState(() {
_passwdShow = !_passwdShow;
});
})),
suffix: isPermanent
? null
: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
gFFI.setByName("temporary_password");
})),
onSaved: (String? value) {},
),
],

View File

@@ -1,3 +1,9 @@
import 'dart:async';
import 'package:settings_ui/settings_ui.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:provider/provider.dart';
import 'dart:convert';
import 'package:flutter/material.dart';
@@ -26,13 +32,100 @@ class SettingsPage extends StatefulWidget implements PageShape {
_SettingsState createState() => _SettingsState();
}
class _SettingsState extends State<SettingsPage> {
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
static const url = 'https://rustdesk.com/';
final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
if (_hasIgnoreBattery) {
updateIgnoreBatteryStatus();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
updateIgnoreBatteryStatus();
}
}
Future<bool> updateIgnoreBatteryStatus() async {
final res = await PermissionManager.check("ignore_battery_optimizations");
if (_ignoreBatteryOpt != res) {
setState(() {
_ignoreBatteryOpt = res;
});
return true;
} else {
return false;
}
}
@override
Widget build(BuildContext context) {
Provider.of<FfiModel>(context);
final username = getUsername();
final enableAbr = FFI.getByName("option", "enable-abr") != 'N';
final enhancementsTiles = [
SettingsTile.switchTile(
title: Text(translate('Adaptive Bitrate') + '(beta)'),
initialValue: enableAbr,
onToggle: (v) {
final msg = Map()
..["name"] = "enable-abr"
..["value"] = "";
if (!v) {
msg["value"] = "N";
}
FFI.setByName("option", json.encode(msg));
setState(() {});
},
)
];
if (_hasIgnoreBattery) {
enhancementsTiles.insert(
0,
SettingsTile.switchTile(
initialValue: _ignoreBatteryOpt,
title: Text(translate('Keep RustDesk background service')),
description:
Text('* ${translate('Ignore Battery Optimizations')}'),
onToggle: (v) async {
if (v) {
PermissionManager.request("ignore_battery_optimizations");
} else {
final res = await DialogManager.show<bool>(
(setState, close) => CustomAlertDialog(
title: Text(translate("Open System Setting")),
content: Text(translate(
"android_open_battery_optimizations_tip")),
actions: [
TextButton(
onPressed: () => close(),
child: Text(translate("Cancel"))),
ElevatedButton(
onPressed: () => close(true),
child:
Text(translate("Open System Setting"))),
],
));
if (res == true) {
PermissionManager.request("application_details_settings");
}
}
}));
}
return SettingsList(
sections: [
SettingsSection(
@@ -53,17 +146,17 @@ class _SettingsState extends State<SettingsPage> {
),
],
),
SettingsSection(
title: Text(translate("Settings")),
tiles: [
SettingsTile.navigation(
SettingsSection(title: Text(translate("Settings")), tiles: [
SettingsTile.navigation(
title: Text(translate('ID/Relay Server')),
leading: Icon(Icons.cloud),
onPressed: (context) {
showServerSettings();
},
),
],
})
]),
SettingsSection(
title: Text(translate("Enhancements")),
tiles: enhancementsTiles,
),
SettingsSection(
title: Text(translate("About")),