commit
da408ff822
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -4419,6 +4419,7 @@ dependencies = [
|
||||
"system_shutdown",
|
||||
"tray-item",
|
||||
"trayicon",
|
||||
"url",
|
||||
"uuid",
|
||||
"virtual_display",
|
||||
"whoami",
|
||||
@ -5492,6 +5493,7 @@ dependencies = [
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde 1.0.144",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -64,9 +64,9 @@ wol-rs = "0.9.1"
|
||||
flutter_rust_bridge = { git = "https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge", optional = true }
|
||||
errno = "0.2.8"
|
||||
rdev = { git = "https://github.com/asur4s/rdev" }
|
||||
url = { version = "2.1", features = ["serde"] }
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features=false }
|
||||
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features=false }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "linux")))'.dependencies]
|
||||
cpal = "0.13.5"
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/login.dart';
|
||||
import '../../consts.dart';
|
||||
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||
import 'package:get/get.dart';
|
||||
|
@ -470,141 +470,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
}
|
||||
}
|
||||
|
||||
/// common login dialog for desktop
|
||||
/// call this directly
|
||||
Future<bool> loginDialog() async {
|
||||
String userName = "";
|
||||
var userNameMsg = "";
|
||||
String pass = "";
|
||||
var passMsg = "";
|
||||
var userController = TextEditingController(text: userName);
|
||||
var pwdController = TextEditingController(text: pass);
|
||||
|
||||
var isInProgress = false;
|
||||
var completer = Completer<bool>();
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
submit() async {
|
||||
setState(() {
|
||||
userNameMsg = "";
|
||||
passMsg = "";
|
||||
isInProgress = true;
|
||||
});
|
||||
cancel() {
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
});
|
||||
}
|
||||
|
||||
userName = userController.text;
|
||||
pass = pwdController.text;
|
||||
if (userName.isEmpty) {
|
||||
userNameMsg = translate("Username missed");
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (pass.isEmpty) {
|
||||
passMsg = translate("Password missed");
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final resp = await gFFI.userModel.login(userName, pass);
|
||||
if (resp.containsKey('error')) {
|
||||
passMsg = resp['error'];
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
|
||||
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
|
||||
debugPrint("$resp");
|
||||
completer.complete(true);
|
||||
} catch (err) {
|
||||
debugPrint(err.toString());
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
cancel() {
|
||||
completer.complete(false);
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Login")),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 500),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text(
|
||||
"${translate('Username')}:",
|
||||
textAlign: TextAlign.start,
|
||||
).marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
|
||||
controller: userController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text("${translate('Password')}:")
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: passMsg.isNotEmpty ? passMsg : null),
|
||||
controller: pwdController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4.0,
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: cancel, child: Text(translate("Cancel"))),
|
||||
TextButton(onPressed: submit, child: Text(translate("OK"))),
|
||||
],
|
||||
onSubmit: submit,
|
||||
onCancel: cancel,
|
||||
);
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void setPasswordDialog() async {
|
||||
final pw = await bind.mainGetPermanentPassword();
|
||||
final p0 = TextEditingController(text: pw);
|
||||
|
@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/login.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
521
flutter/lib/desktop/widgets/login.dart
Normal file
521
flutter/lib/desktop/widgets/login.dart
Normal file
@ -0,0 +1,521 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
|
||||
final kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0);
|
||||
|
||||
class _IconOP extends StatelessWidget {
|
||||
final String icon;
|
||||
final double iconWidth;
|
||||
const _IconOP({Key? key, required this.icon, required this.iconWidth})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: SvgPicture.asset(
|
||||
'assets/$icon.svg',
|
||||
width: iconWidth,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonOP extends StatelessWidget {
|
||||
final String op;
|
||||
final RxString curOP;
|
||||
final double iconWidth;
|
||||
final Color primaryColor;
|
||||
final double height;
|
||||
final Function() onTap;
|
||||
|
||||
const ButtonOP({
|
||||
Key? key,
|
||||
required this.op,
|
||||
required this.curOP,
|
||||
required this.iconWidth,
|
||||
required this.primaryColor,
|
||||
required this.height,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: height,
|
||||
padding: kMidButtonPadding,
|
||||
child: Obx(() => ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: curOP.value.isEmpty || curOP.value == op
|
||||
? primaryColor
|
||||
: Colors.grey,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed:
|
||||
curOP.value.isEmpty || curOP.value == op ? onTap : null,
|
||||
child: Stack(children: [
|
||||
Center(child: Text('${translate("Continue with")} $op')),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
child: _IconOP(
|
||||
icon: op,
|
||||
iconWidth: iconWidth,
|
||||
)),
|
||||
),
|
||||
]),
|
||||
)),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigOP {
|
||||
final String op;
|
||||
final double iconWidth;
|
||||
ConfigOP({required this.op, required this.iconWidth});
|
||||
}
|
||||
|
||||
class WidgetOP extends StatefulWidget {
|
||||
final ConfigOP config;
|
||||
final RxString curOP;
|
||||
final Function(String) cbLogin;
|
||||
const WidgetOP({
|
||||
Key? key,
|
||||
required this.config,
|
||||
required this.curOP,
|
||||
required this.cbLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _WidgetOPState();
|
||||
}
|
||||
}
|
||||
|
||||
class _WidgetOPState extends State<WidgetOP> {
|
||||
Timer? _updateTimer;
|
||||
String _stateMsg = '';
|
||||
String _FailedMsg = '';
|
||||
String _url = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
|
||||
_beginQueryState() {
|
||||
_updateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
_updateState();
|
||||
});
|
||||
}
|
||||
|
||||
_updateState() {
|
||||
bind.mainAccountAuthResult().then((result) {
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final resultMap = jsonDecode(result);
|
||||
if (resultMap == null) {
|
||||
return;
|
||||
}
|
||||
final String stateMsg = resultMap['state_msg'];
|
||||
String failedMsg = resultMap['failed_msg'];
|
||||
final String? url = resultMap['url'];
|
||||
final authBody = resultMap['auth_body'];
|
||||
if (_stateMsg != stateMsg || _FailedMsg != failedMsg) {
|
||||
if (_url.isEmpty && url != null && url.isNotEmpty) {
|
||||
launchUrl(Uri.parse(url));
|
||||
_url = url;
|
||||
}
|
||||
if (authBody != null) {
|
||||
_updateTimer?.cancel();
|
||||
final String username = authBody['user']['name'];
|
||||
widget.curOP.value = '';
|
||||
widget.cbLogin(username);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_stateMsg = stateMsg;
|
||||
_FailedMsg = failedMsg;
|
||||
if (failedMsg.isNotEmpty) {
|
||||
widget.curOP.value = '';
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_resetState() {
|
||||
_stateMsg = '';
|
||||
_FailedMsg = '';
|
||||
_url = '';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
ButtonOP(
|
||||
op: widget.config.op,
|
||||
curOP: widget.curOP,
|
||||
iconWidth: widget.config.iconWidth,
|
||||
primaryColor: str2color(widget.config.op, 0x7f),
|
||||
height: 36,
|
||||
onTap: () async {
|
||||
_resetState();
|
||||
widget.curOP.value = widget.config.op;
|
||||
await bind.mainAccountAuth(op: widget.config.op);
|
||||
_beginQueryState();
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (widget.curOP.isNotEmpty &&
|
||||
widget.curOP.value != widget.config.op) {
|
||||
_FailedMsg = '';
|
||||
}
|
||||
return Offstage(
|
||||
offstage:
|
||||
_FailedMsg.isEmpty && widget.curOP.value != widget.config.op,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_stateMsg,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
_FailedMsg,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: widget.curOP.value != widget.config.op,
|
||||
child: const SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: widget.curOP.value != widget.config.op,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: 20),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
widget.curOP.value = '';
|
||||
_updateTimer?.cancel();
|
||||
_resetState();
|
||||
bind.mainAccountAuthCancel();
|
||||
},
|
||||
child: Text(
|
||||
translate('Cancel'),
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginWidgetOP extends StatelessWidget {
|
||||
final List<ConfigOP> ops;
|
||||
final RxString curOP;
|
||||
final Function(String) cbLogin;
|
||||
|
||||
LoginWidgetOP({
|
||||
Key? key,
|
||||
required this.ops,
|
||||
required this.curOP,
|
||||
required this.cbLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var children = ops
|
||||
.map((op) => [
|
||||
WidgetOP(
|
||||
config: op,
|
||||
curOP: curOP,
|
||||
cbLogin: cbLogin,
|
||||
),
|
||||
const Divider(
|
||||
indent: 5,
|
||||
endIndent: 5,
|
||||
)
|
||||
])
|
||||
.expand((i) => i)
|
||||
.toList();
|
||||
if (children.isNotEmpty) {
|
||||
children.removeLast();
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: children,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class LoginWidgetUserPass extends StatelessWidget {
|
||||
final String username;
|
||||
final String pass;
|
||||
final String usernameMsg;
|
||||
final String passMsg;
|
||||
final bool isInProgress;
|
||||
final RxString curOP;
|
||||
final Function(String, String) onLogin;
|
||||
const LoginWidgetUserPass({
|
||||
Key? key,
|
||||
required this.username,
|
||||
required this.pass,
|
||||
required this.usernameMsg,
|
||||
required this.passMsg,
|
||||
required this.isInProgress,
|
||||
required this.curOP,
|
||||
required this.onLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var userController = TextEditingController(text: username);
|
||||
var pwdController = TextEditingController(text: pass);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Container(
|
||||
padding: kMidButtonPadding,
|
||||
child: Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text(
|
||||
'${translate("Username")}:',
|
||||
textAlign: TextAlign.start,
|
||||
).marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: usernameMsg.isNotEmpty ? usernameMsg : null),
|
||||
controller: userController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Container(
|
||||
padding: kMidButtonPadding,
|
||||
child: Row(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 100),
|
||||
child: Text('${translate("Password")}:')
|
||||
.marginOnly(bottom: 16.0)),
|
||||
const SizedBox(
|
||||
width: 24.0,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: passMsg.isNotEmpty ? passMsg : null),
|
||||
controller: pwdController,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4.0,
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator()),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: kMidButtonPadding,
|
||||
child: Obx(() => ElevatedButton(
|
||||
style: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? null
|
||||
: ElevatedButton.styleFrom(
|
||||
primary: Colors.grey,
|
||||
),
|
||||
child: Text(
|
||||
translate('Login'),
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? () {
|
||||
onLogin(userController.text, pwdController.text);
|
||||
}
|
||||
: null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// common login dialog for desktop
|
||||
/// call this directly
|
||||
Future<bool> loginDialog() async {
|
||||
String username = '';
|
||||
var usernameMsg = '';
|
||||
String pass = '';
|
||||
var passMsg = '';
|
||||
var isInProgress = false;
|
||||
var completer = Completer<bool>();
|
||||
final RxString curOP = ''.obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
cancel() {
|
||||
isInProgress = false;
|
||||
completer.complete(false);
|
||||
close();
|
||||
}
|
||||
|
||||
onLogin(String username0, String pass0) async {
|
||||
setState(() {
|
||||
usernameMsg = '';
|
||||
passMsg = '';
|
||||
isInProgress = true;
|
||||
});
|
||||
cancel() {
|
||||
curOP.value = '';
|
||||
if (isInProgress) {
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
curOP.value = 'rustdesk';
|
||||
username = username0;
|
||||
pass = pass0;
|
||||
if (username.isEmpty) {
|
||||
usernameMsg = translate('Username missed');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (pass.isEmpty) {
|
||||
passMsg = translate('Password missed');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final resp = await gFFI.userModel.login(username, pass);
|
||||
if (resp.containsKey('error')) {
|
||||
passMsg = resp['error'];
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
|
||||
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
|
||||
debugPrint('$resp');
|
||||
completer.complete(true);
|
||||
} catch (err) {
|
||||
debugPrint(err.toString());
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Login')),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 500),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
LoginWidgetUserPass(
|
||||
username: username,
|
||||
pass: pass,
|
||||
usernameMsg: usernameMsg,
|
||||
passMsg: passMsg,
|
||||
isInProgress: isInProgress,
|
||||
curOP: curOP,
|
||||
onLogin: onLogin,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
translate('or'),
|
||||
style: TextStyle(fontSize: 16),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
LoginWidgetOP(
|
||||
ops: [
|
||||
ConfigOP(op: 'Github', iconWidth: 20),
|
||||
ConfigOP(op: 'Google', iconWidth: 20),
|
||||
ConfigOP(op: 'Okta', iconWidth: 38),
|
||||
],
|
||||
curOP: curOP,
|
||||
cbLogin: (String username) {
|
||||
gFFI.userModel.userName.value = username;
|
||||
completer.complete(true);
|
||||
close();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [msgBoxButton(translate('Close'), cancel)],
|
||||
onCancel: cancel,
|
||||
);
|
||||
});
|
||||
return completer.future;
|
||||
}
|
@ -291,12 +291,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
return SettingsList(
|
||||
sections: [
|
||||
SettingsSection(
|
||||
title: Text(translate("Account")),
|
||||
title: Text(translate('Account')),
|
||||
tiles: [
|
||||
SettingsTile.navigation(
|
||||
title: Obx(() => Text(gFFI.userModel.userName.value.isEmpty
|
||||
? translate("Login")
|
||||
: '${translate("Logout")} (${gFFI.userModel.userName.value})')),
|
||||
? translate('Login')
|
||||
: '${translate('Logout')} (${gFFI.userModel.userName.value})')),
|
||||
leading: Icon(Icons.person),
|
||||
onPressed: (context) {
|
||||
if (gFFI.userModel.userName.value.isEmpty) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
@ -9,7 +10,7 @@ import 'model.dart';
|
||||
import 'platform_model.dart';
|
||||
|
||||
class UserModel {
|
||||
var userName = "".obs;
|
||||
var userName = ''.obs;
|
||||
WeakReference<FFI> parent;
|
||||
|
||||
UserModel(this.parent) {
|
||||
@ -18,7 +19,7 @@ class UserModel {
|
||||
|
||||
void refreshCurrentUser() async {
|
||||
await getUserName();
|
||||
final token = await bind.mainGetLocalOption(key: "access_token");
|
||||
final token = await bind.mainGetLocalOption(key: 'access_token');
|
||||
if (token == '') return;
|
||||
final url = await bind.mainGetApiServer();
|
||||
final body = {
|
||||
@ -28,8 +29,8 @@ class UserModel {
|
||||
try {
|
||||
final response = await http.post(Uri.parse('$url/api/currentUser'),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer $token"
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $token'
|
||||
},
|
||||
body: json.encode(body));
|
||||
final status = response.statusCode;
|
||||
@ -44,9 +45,9 @@ class UserModel {
|
||||
}
|
||||
|
||||
void resetToken() async {
|
||||
await bind.mainSetLocalOption(key: "access_token", value: "");
|
||||
await bind.mainSetLocalOption(key: "user_info", value: "");
|
||||
userName.value = "";
|
||||
await bind.mainSetLocalOption(key: 'access_token', value: '');
|
||||
await bind.mainSetLocalOption(key: 'user_info', value: '');
|
||||
userName.value = '';
|
||||
}
|
||||
|
||||
Future<String> _parseResp(String body) async {
|
||||
@ -57,13 +58,13 @@ class UserModel {
|
||||
}
|
||||
final token = data['access_token'];
|
||||
if (token != null) {
|
||||
await bind.mainSetLocalOption(key: "access_token", value: token);
|
||||
await bind.mainSetLocalOption(key: 'access_token', value: token);
|
||||
}
|
||||
final info = data['user'];
|
||||
if (info != null) {
|
||||
final value = json.encode(info);
|
||||
await bind.mainSetOption(key: "user_info", value: value);
|
||||
userName.value = info["name"];
|
||||
await bind.mainSetOption(key: 'user_info', value: value);
|
||||
userName.value = info['name'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@ -74,10 +75,12 @@ class UserModel {
|
||||
}
|
||||
final userInfo = await bind.mainGetLocalOption(key: 'user_info');
|
||||
if (userInfo.trim().isEmpty) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
final m = jsonDecode(userInfo);
|
||||
if (m != null) {
|
||||
if (m == null) {
|
||||
userName.value = '';
|
||||
} else {
|
||||
userName.value = m['name'] ?? '';
|
||||
}
|
||||
return userName.value;
|
||||
@ -86,10 +89,10 @@ class UserModel {
|
||||
Future<void> logOut() async {
|
||||
final tag = gFFI.dialogManager.showLoading(translate('Waiting'));
|
||||
final url = await bind.mainGetApiServer();
|
||||
final _ = await http.post(Uri.parse("$url/api/logout"),
|
||||
final _ = await http.post(Uri.parse('$url/api/logout'),
|
||||
body: {
|
||||
"id": await bind.mainGetMyId(),
|
||||
"uuid": await bind.mainGetUuid(),
|
||||
'id': await bind.mainGetMyId(),
|
||||
'uuid': await bind.mainGetUuid(),
|
||||
},
|
||||
headers: await getHttpHeaders());
|
||||
await Future.wait([
|
||||
@ -98,30 +101,30 @@ class UserModel {
|
||||
bind.mainSetLocalOption(key: 'selected-tags', value: ''),
|
||||
]);
|
||||
parent.target?.abModel.clear();
|
||||
userName.value = "";
|
||||
userName.value = '';
|
||||
gFFI.dialogManager.dismissByTag(tag);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> login(String userName, String pass) async {
|
||||
final url = await bind.mainGetApiServer();
|
||||
try {
|
||||
final resp = await http.post(Uri.parse("$url/api/login"),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
final resp = await http.post(Uri.parse('$url/api/login'),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
"username": userName,
|
||||
"password": pass,
|
||||
"id": await bind.mainGetMyId(),
|
||||
"uuid": await bind.mainGetUuid()
|
||||
'username': userName,
|
||||
'password': pass,
|
||||
'id': await bind.mainGetMyId(),
|
||||
'uuid': await bind.mainGetUuid()
|
||||
}));
|
||||
final body = jsonDecode(resp.body);
|
||||
bind.mainSetLocalOption(
|
||||
key: "access_token", value: body['access_token'] ?? "");
|
||||
key: 'access_token', value: body['access_token'] ?? '');
|
||||
bind.mainSetLocalOption(
|
||||
key: "user_info", value: jsonEncode(body['user']));
|
||||
this.userName.value = body['user']?['name'] ?? "";
|
||||
key: 'user_info', value: jsonEncode(body['user']));
|
||||
this.userName.value = body['user']?['name'] ?? '';
|
||||
return body;
|
||||
} catch (err) {
|
||||
return {"error": "$err"};
|
||||
return {'error': '$err'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ use hbb_common::{
|
||||
fs, log,
|
||||
};
|
||||
|
||||
// use crate::hbbs_http::account::AuthResult;
|
||||
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
#[cfg(target_os = "android")]
|
||||
use crate::start_server;
|
||||
@ -1082,6 +1084,20 @@ pub fn install_install_path() -> SyncReturn<String> {
|
||||
SyncReturn(install_path())
|
||||
}
|
||||
|
||||
pub fn main_account_auth(op: String) {
|
||||
let id = get_id();
|
||||
let uuid = get_uuid();
|
||||
account_auth(op, id, uuid);
|
||||
}
|
||||
|
||||
pub fn main_account_auth_cancel() {
|
||||
account_auth_cancel()
|
||||
}
|
||||
|
||||
pub fn main_account_auth_result() -> String {
|
||||
account_auth_result()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use jni::{
|
||||
|
38
src/hbbs_http.rs
Normal file
38
src/hbbs_http.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use hbb_common::{
|
||||
anyhow::{self, bail},
|
||||
tokio, ResultType,
|
||||
};
|
||||
use reqwest::blocking::Response;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_derive::Deserialize;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub mod account;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HbbHttpResponse<T> {
|
||||
ErrorFormat,
|
||||
Error(String),
|
||||
DataTypeFormat,
|
||||
Data(T),
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> TryFrom<Response> for HbbHttpResponse<T> {
|
||||
type Error = reqwest::Error;
|
||||
|
||||
fn try_from(resp: Response) -> Result<Self, <Self as TryFrom<Response>>::Error> {
|
||||
let map = resp.json::<Map<String, Value>>()?;
|
||||
if let Some(error) = map.get("error") {
|
||||
if let Some(err) = error.as_str() {
|
||||
Ok(Self::Error(err.to_owned()))
|
||||
} else {
|
||||
Ok(Self::ErrorFormat)
|
||||
}
|
||||
} else {
|
||||
match serde_json::from_value(Value::Object(map)) {
|
||||
Ok(v) => Ok(Self::Data(v)),
|
||||
Err(_) => Ok(Self::DataTypeFormat),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
255
src/hbbs_http/account.rs
Normal file
255
src/hbbs_http/account.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use super::HbbHttpResponse;
|
||||
use hbb_common::{
|
||||
config::{Config, LocalConfig},
|
||||
log, sleep, tokio, ResultType,
|
||||
};
|
||||
use reqwest::blocking::Client;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, RwLock},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref API_SERVER: String = crate::get_api_server(
|
||||
Config::get_option("api-server"), Config::get_option("custom-rendezvous-server"));
|
||||
static ref OIDC_SESSION: Arc<RwLock<OidcSession>> = Arc::new(RwLock::new(OidcSession::new()));
|
||||
}
|
||||
|
||||
const QUERY_INTERVAL_SECS: f32 = 1.0;
|
||||
const QUERY_TIMEOUT_SECS: u64 = 60 * 3;
|
||||
const REQUESTING_ACCOUNT_AUTH: &str = "Requesting account auth";
|
||||
const WAITING_ACCOUNT_AUTH: &str = "Waiting account auth";
|
||||
const LOGIN_ACCOUNT_AUTH: &str = "Login account auth";
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct OidcAuthUrl {
|
||||
code: String,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserPayload {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub email: Option<String>,
|
||||
pub note: Option<String>,
|
||||
pub status: Option<i64>,
|
||||
pub grp: Option<String>,
|
||||
pub is_admin: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthBody {
|
||||
pub access_token: String,
|
||||
pub token_type: String,
|
||||
pub user: UserPayload,
|
||||
}
|
||||
|
||||
pub struct OidcSession {
|
||||
client: Client,
|
||||
state_msg: &'static str,
|
||||
failed_msg: String,
|
||||
code_url: Option<OidcAuthUrl>,
|
||||
auth_body: Option<AuthBody>,
|
||||
keep_querying: bool,
|
||||
running: bool,
|
||||
query_timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AuthResult {
|
||||
pub state_msg: String,
|
||||
pub failed_msg: String,
|
||||
pub url: Option<String>,
|
||||
pub auth_body: Option<AuthBody>,
|
||||
}
|
||||
|
||||
impl OidcSession {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
client: Client::new(),
|
||||
state_msg: REQUESTING_ACCOUNT_AUTH,
|
||||
failed_msg: "".to_owned(),
|
||||
code_url: None,
|
||||
auth_body: None,
|
||||
keep_querying: false,
|
||||
running: false,
|
||||
query_timeout: Duration::from_secs(QUERY_TIMEOUT_SECS),
|
||||
}
|
||||
}
|
||||
|
||||
fn auth(op: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
|
||||
Ok(OIDC_SESSION
|
||||
.read()
|
||||
.unwrap()
|
||||
.client
|
||||
.post(format!("{}/api/oidc/auth", *API_SERVER))
|
||||
.json(&HashMap::from([("op", op), ("id", id), ("uuid", uuid)]))
|
||||
.send()?
|
||||
.try_into()?)
|
||||
}
|
||||
|
||||
fn query(code: &str, id: &str, uuid: &str) -> ResultType<HbbHttpResponse<AuthBody>> {
|
||||
let url = reqwest::Url::parse_with_params(
|
||||
&format!("{}/api/oidc/auth-query", *API_SERVER),
|
||||
&[("code", code), ("id", id), ("uuid", uuid)],
|
||||
)?;
|
||||
Ok(OIDC_SESSION
|
||||
.read()
|
||||
.unwrap()
|
||||
.client
|
||||
.get(url)
|
||||
.send()?
|
||||
.try_into()?)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.state_msg = REQUESTING_ACCOUNT_AUTH;
|
||||
self.failed_msg = "".to_owned();
|
||||
self.keep_querying = true;
|
||||
self.running = false;
|
||||
self.code_url = None;
|
||||
self.auth_body = None;
|
||||
}
|
||||
|
||||
fn before_task(&mut self) {
|
||||
self.reset();
|
||||
self.running = true;
|
||||
}
|
||||
|
||||
fn after_task(&mut self) {
|
||||
self.running = false;
|
||||
}
|
||||
|
||||
fn sleep(secs: f32) {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(secs));
|
||||
}
|
||||
|
||||
fn auth_task(op: String, id: String, uuid: String) {
|
||||
let auth_request_res = Self::auth(&op, &id, &uuid);
|
||||
log::info!("Request oidc auth result: {:?}", &auth_request_res);
|
||||
let code_url = match auth_request_res {
|
||||
Ok(HbbHttpResponse::<_>::Data(code_url)) => code_url,
|
||||
Ok(HbbHttpResponse::<_>::Error(err)) => {
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(REQUESTING_ACCOUNT_AUTH, err);
|
||||
return;
|
||||
}
|
||||
Ok(_) => {
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(REQUESTING_ACCOUNT_AUTH, "Invalid auth response".to_owned());
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(REQUESTING_ACCOUNT_AUTH, err.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(WAITING_ACCOUNT_AUTH, "".to_owned());
|
||||
OIDC_SESSION.write().unwrap().code_url = Some(code_url.clone());
|
||||
|
||||
let begin = Instant::now();
|
||||
let query_timeout = OIDC_SESSION.read().unwrap().query_timeout;
|
||||
while OIDC_SESSION.read().unwrap().keep_querying && begin.elapsed() < query_timeout {
|
||||
match Self::query(&code_url.code, &id, &uuid) {
|
||||
Ok(HbbHttpResponse::<_>::Data(auth_body)) => {
|
||||
LocalConfig::set_option(
|
||||
"access_token".to_owned(),
|
||||
auth_body.access_token.clone(),
|
||||
);
|
||||
LocalConfig::set_option(
|
||||
"user_info".to_owned(),
|
||||
serde_json::to_string(&auth_body.user).unwrap_or_default(),
|
||||
);
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(LOGIN_ACCOUNT_AUTH, "".to_owned());
|
||||
OIDC_SESSION.write().unwrap().auth_body = Some(auth_body);
|
||||
return;
|
||||
}
|
||||
Ok(HbbHttpResponse::<_>::Error(err)) => {
|
||||
if err.contains("No authed oidc is found") {
|
||||
// ignore, keep querying
|
||||
} else {
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(WAITING_ACCOUNT_AUTH, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
// ignore
|
||||
}
|
||||
Err(err) => {
|
||||
log::trace!("Failed query oidc {}", err);
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
Self::sleep(QUERY_INTERVAL_SECS);
|
||||
}
|
||||
|
||||
if begin.elapsed() >= query_timeout {
|
||||
OIDC_SESSION
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_state(WAITING_ACCOUNT_AUTH, "timeout".to_owned());
|
||||
}
|
||||
|
||||
// no need to handle "keep_querying == false"
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state_msg: &'static str, failed_msg: String) {
|
||||
self.state_msg = state_msg;
|
||||
self.failed_msg = failed_msg;
|
||||
}
|
||||
|
||||
fn wait_stop_querying() {
|
||||
let wait_secs = 0.3;
|
||||
while OIDC_SESSION.read().unwrap().running {
|
||||
Self::sleep(wait_secs);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_auth(op: String, id: String, uuid: String) {
|
||||
Self::auth_cancel();
|
||||
Self::wait_stop_querying();
|
||||
OIDC_SESSION.write().unwrap().before_task();
|
||||
std::thread::spawn(|| {
|
||||
Self::auth_task(op, id, uuid);
|
||||
OIDC_SESSION.write().unwrap().after_task();
|
||||
});
|
||||
}
|
||||
|
||||
fn get_result_(&self) -> AuthResult {
|
||||
AuthResult {
|
||||
state_msg: self.state_msg.to_string(),
|
||||
failed_msg: self.failed_msg.clone(),
|
||||
url: self.code_url.as_ref().map(|x| x.url.to_string()),
|
||||
auth_body: self.auth_body.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auth_cancel() {
|
||||
OIDC_SESSION.write().unwrap().keep_querying = false;
|
||||
}
|
||||
|
||||
pub fn get_result() -> AuthResult {
|
||||
OIDC_SESSION.read().unwrap().get_result_()
|
||||
}
|
||||
}
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "请选择要分享的画面(对端操作)。"),
|
||||
("Show RustDesk", "显示rustdesk"),
|
||||
("This PC", "此电脑"),
|
||||
("or", "或"),
|
||||
("Continue with", "使用"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte prosím obrazovku, kterou chcete sdílet (Ovládejte na straně protějšku)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Vælg venligst den skærm, der skal deles (Betjen på peer-siden)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den Bildschirm aus, der freigegeben werden soll (auf der Peer-Seite arbeiten)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -35,5 +35,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("elevated_foreground_window_warning", "Temporarily unable to use the mouse and keyboard, because the current window of the remote desktop requires higher privilege to operate, you can request the remote user to minimize the current window. To avoid this problem, it is recommended to install the software on the remote device or run it with administrator privileges."),
|
||||
("JumpLink", "View"),
|
||||
("Stop service", "Stop Service"),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Bonvolu Elekti la ekranon por esti dividita (Funkciu ĉe la sama flanko)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Seleccione la pantalla que se compartirá (Operar en el lado del par)."),
|
||||
("Show RustDesk", "Mostrar RustDesk"),
|
||||
("This PC", "Este PC"),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Veuillez sélectionner l'écran à partager (opérer du côté pair)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Kérjük, válassza ki a megosztani kívánt képernyőt (a társoldalon működjön)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Silakan Pilih layar yang akan dibagikan (Operasi di sisi rekan)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato peer)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(ピア側で操作)。"),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하십시오(피어 측에서 작동)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Бөлісетін экранды таңдаңыз (бірдей жағынан жұмыс жасаңыз)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Wybierz ekran do udostępnienia (działaj po stronie równorzędnej)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Por favor, selecione a tela a ser compartilhada (operar no lado do peer)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", ""),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Пожалуйста, выберите экран для совместного использования (работайте на одноранговой стороне)."),
|
||||
("Show RustDesk", "Показать RustDesk"),
|
||||
("This PC", "Этот компьютер"),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Vyberte obrazovku, ktorú chcete zdieľať (Ovládajte na strane partnera)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", ""),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Lütfen paylaşılacak ekranı seçiniz (Ekran tarafında çalıştırın)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "請選擇要分享的畫面(在對端操作)。"),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Будь ласка, виберіть екран, до якого потрібно надати доступ (працюйте на стороні однорангового пристрою)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -387,5 +387,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "Vui lòng Chọn màn hình để chia sẻ (Hoạt động ở phía ngang hàng)."),
|
||||
("Show RustDesk", ""),
|
||||
("This PC", ""),
|
||||
("or", ""),
|
||||
("Continue with", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ mod ui_cm_interface;
|
||||
mod ui_interface;
|
||||
mod ui_session_interface;
|
||||
|
||||
mod hbbs_http;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod clipboard_file;
|
||||
|
||||
|
15
src/main.rs
15
src/main.rs
@ -32,8 +32,8 @@ fn main() {
|
||||
if !common::global_init() {
|
||||
return;
|
||||
}
|
||||
use hbb_common::log;
|
||||
use clap::App;
|
||||
use hbb_common::log;
|
||||
let args = format!(
|
||||
"-p, --port-forward=[PORT-FORWARD-OPTIONS] 'Format: remote-id:local-port:remote-port[:remote-host]'
|
||||
-k, --key=[KEY] ''
|
||||
@ -45,7 +45,7 @@ fn main() {
|
||||
.about("RustDesk command line tool")
|
||||
.args_from_usage(&args)
|
||||
.get_matches();
|
||||
use hbb_common::{env_logger::*, config::LocalConfig};
|
||||
use hbb_common::{config::LocalConfig, env_logger::*};
|
||||
init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info"));
|
||||
if let Some(p) = matches.value_of("port-forward") {
|
||||
let options: Vec<String> = p.split(":").map(|x| x.to_owned()).collect();
|
||||
@ -73,7 +73,14 @@ fn main() {
|
||||
}
|
||||
let key = matches.value_of("key").unwrap_or("").to_owned();
|
||||
let token = LocalConfig::get_option("access_token");
|
||||
cli::start_one_port_forward(options[0].clone(), port, remote_host, remote_port, key, token);
|
||||
cli::start_one_port_forward(
|
||||
options[0].clone(),
|
||||
port,
|
||||
remote_host,
|
||||
remote_port,
|
||||
key,
|
||||
token,
|
||||
);
|
||||
}
|
||||
common::global_clean();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::ipc::Data;
|
||||
use bytes::Bytes;
|
||||
pub use connection::*;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
@ -20,7 +21,6 @@ use std::{
|
||||
sync::{Arc, Mutex, RwLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
||||
pub mod audio_service;
|
||||
cfg_if::cfg_if! {
|
||||
@ -140,7 +140,8 @@ pub async fn create_tcp_connection(
|
||||
.write_to_bytes()
|
||||
.unwrap_or_default(),
|
||||
&sk,
|
||||
).into(),
|
||||
)
|
||||
.into(),
|
||||
..Default::default()
|
||||
});
|
||||
timeout(CONNECT_TIMEOUT, stream.send(&msg_out)).await??;
|
||||
@ -310,9 +311,9 @@ pub fn check_zombie() {
|
||||
}
|
||||
|
||||
/// Start the host server that allows the remote peer to control the current machine.
|
||||
///
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
///
|
||||
/// * `is_server` - Whether the current client is definitely the server.
|
||||
/// If true, the server will be started.
|
||||
/// Otherwise, client will check if there's already a server and start one if not.
|
||||
@ -323,9 +324,9 @@ pub async fn start_server(is_server: bool) {
|
||||
}
|
||||
|
||||
/// Start the host server that allows the remote peer to control the current machine.
|
||||
///
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
///
|
||||
/// * `is_server` - Whether the current client is definitely the server.
|
||||
/// If true, the server will be started.
|
||||
/// Otherwise, client will check if there's already a server and start one if not.
|
||||
|
@ -20,8 +20,7 @@ use hbb_common::{
|
||||
tokio::{self, sync::mpsc, time},
|
||||
};
|
||||
|
||||
use crate::ipc;
|
||||
use crate::{common::SOFTWARE_UPDATE_URL, platform};
|
||||
use crate::{common::SOFTWARE_UPDATE_URL, hbbs_http::account, ipc, platform};
|
||||
|
||||
type Message = RendezvousMessage;
|
||||
|
||||
@ -843,6 +842,18 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
|
||||
tx
|
||||
}
|
||||
|
||||
pub fn account_auth(op: String, id: String, uuid: String) {
|
||||
account::OidcSession::account_auth(op, id, uuid);
|
||||
}
|
||||
|
||||
pub fn account_auth_cancel() {
|
||||
account::OidcSession::auth_cancel();
|
||||
}
|
||||
|
||||
pub fn account_auth_result() -> String {
|
||||
serde_json::to_string(&account::OidcSession::get_result()).unwrap_or_default()
|
||||
}
|
||||
|
||||
// notice: avoiding create ipc connecton repeatly,
|
||||
// because windows named pipe has serious memory leak issue.
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user