Merge pull request #1116 from Heap-Hop/opt_mobile_ui

Opt mobile UI
This commit is contained in:
RustDesk 2022-08-01 22:39:31 +08:00 committed by GitHub
commit 5baed21fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 614 additions and 227 deletions

View File

@ -32,7 +32,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 31 compileSdkVersion 32
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }

View File

@ -199,7 +199,7 @@ const G = M * K;
String readableFileSize(double size) { String readableFileSize(double size) {
if (size < K) { if (size < K) {
return size.toString() + " B"; return size.toStringAsFixed(2) + " B";
} else if (size < M) { } else if (size < M) {
return (size / K).toStringAsFixed(2) + " KB"; return (size / K).toStringAsFixed(2) + " KB";
} else if (size < G) { } else if (size < G) {

View File

@ -39,7 +39,7 @@ class App extends StatelessWidget {
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
), ),
home: !isAndroid ? WebHomePage() : HomePage(), home: !isAndroid ? WebHomePage() : HomePage(key: homeKey),
navigatorObservers: [ navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics), FirebaseAnalyticsObserver(analytics: analytics),
FlutterSmartDialog.observer FlutterSmartDialog.observer

View File

@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dash_chat/dash_chat.dart'; import 'package:dash_chat_2/dash_chat_2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../widgets/overlay.dart'; import '../widgets/overlay.dart';
@ -11,8 +11,8 @@ class MessageBody {
List<ChatMessage> chatMessages; List<ChatMessage> chatMessages;
MessageBody(this.chatUser, this.chatMessages); MessageBody(this.chatUser, this.chatMessages);
void add(ChatMessage cm) { void insert(ChatMessage cm) {
this.chatMessages.add(cm); this.chatMessages.insert(0, cm);
} }
void clear() { void clear() {
@ -24,19 +24,15 @@ class ChatModel with ChangeNotifier {
static final clientModeID = -1; static final clientModeID = -1;
final ChatUser me = ChatUser( final ChatUser me = ChatUser(
uid: "", id: "",
name: "Me", firstName: "Me",
); );
late final Map<int, MessageBody> _messages = Map() late final Map<int, MessageBody> _messages = Map()
..[clientModeID] = MessageBody(me, []); ..[clientModeID] = MessageBody(me, []);
final _scroller = ScrollController();
var _currentID = clientModeID; var _currentID = clientModeID;
ScrollController get scroller => _scroller;
Map<int, MessageBody> get messages => _messages; Map<int, MessageBody> get messages => _messages;
int get currentID => _currentID; int get currentID => _currentID;
@ -62,8 +58,8 @@ class ChatModel with ChangeNotifier {
"Failed to changeCurrentID,remote user doesn't exist"); "Failed to changeCurrentID,remote user doesn't exist");
} }
final chatUser = ChatUser( final chatUser = ChatUser(
uid: client.peerId, id: client.peerId,
name: client.name, firstName: client.name,
); );
_messages[id] = MessageBody(chatUser, []); _messages[id] = MessageBody(chatUser, []);
_currentID = id; _currentID = id;
@ -80,48 +76,39 @@ class ChatModel with ChangeNotifier {
late final chatUser; late final chatUser;
if (id == clientModeID) { if (id == clientModeID) {
chatUser = ChatUser( chatUser = ChatUser(
name: FFI.ffiModel.pi.username, firstName: FFI.ffiModel.pi.username,
uid: FFI.getId(), id: FFI.getId(),
); );
} else { } else {
final client = FFI.serverModel.clients[id]; final client = FFI.serverModel.clients[id];
if (client == null) { if (client == null) {
return debugPrint("Failed to receive msg,user doesn't exist"); return debugPrint("Failed to receive msg,user doesn't exist");
} }
chatUser = ChatUser(uid: client.peerId, name: client.name); chatUser = ChatUser(id: client.peerId, firstName: client.name);
} }
if (!_messages.containsKey(id)) { if (!_messages.containsKey(id)) {
_messages[id] = MessageBody(chatUser, []); _messages[id] = MessageBody(chatUser, []);
} }
_messages[id]!.add(ChatMessage(text: text, user: chatUser)); _messages[id]!.insert(
ChatMessage(text: text, user: chatUser, createdAt: DateTime.now()));
_currentID = id; _currentID = id;
notifyListeners(); notifyListeners();
scrollToBottom();
}
scrollToBottom() {
Future.delayed(Duration(milliseconds: 500), () {
_scroller.animateTo(_scroller.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.fastLinearToSlowEaseIn);
});
} }
send(ChatMessage message) { send(ChatMessage message) {
if (message.text != null && message.text!.isNotEmpty) { if (message.text.isNotEmpty) {
_messages[_currentID]?.add(message); _messages[_currentID]?.insert(message);
if (_currentID == clientModeID) { if (_currentID == clientModeID) {
FFI.setByName("chat_client_mode", message.text!); FFI.setByName("chat_client_mode", message.text);
} else { } else {
final msg = Map() final msg = Map()
..["id"] = _currentID ..["id"] = _currentID
..["text"] = message.text!; ..["text"] = message.text;
FFI.setByName("chat_server_mode", jsonEncode(msg)); FFI.setByName("chat_server_mode", jsonEncode(msg));
} }
} }
notifyListeners(); notifyListeners();
scrollToBottom();
} }
close() { close() {

View File

@ -199,6 +199,7 @@ class FileModel extends ChangeNotifier {
onClose() { onClose() {
SmartDialog.dismiss(); SmartDialog.dismiss();
jobReset();
// save config // save config
Map<String, String> msg = Map(); Map<String, String> msg = Map();

View File

@ -68,7 +68,7 @@ class FfiModel with ChangeNotifier {
void updatePermission(Map<String, dynamic> evt) { void updatePermission(Map<String, dynamic> evt) {
evt.forEach((k, v) { evt.forEach((k, v) {
if (k == 'name') return; if (k == 'name' || k.isEmpty) return;
_permissions[k] = v == 'true'; _permissions[k] = v == 'true';
}); });
print('$_permissions'); print('$_permissions');
@ -162,6 +162,8 @@ class FfiModel with ChangeNotifier {
FFI.serverModel.onClientAuthorized(evt); FFI.serverModel.onClientAuthorized(evt);
} else if (name == 'on_client_remove') { } else if (name == 'on_client_remove') {
FFI.serverModel.onClientRemove(evt); FFI.serverModel.onClientRemove(evt);
} else if (name == 'update_quality_status') {
FFI.qualityMonitorModel.updateQualityStatus(evt);
} }
}; };
PlatformFFI.setEventCallback(cb); PlatformFFI.setEventCallback(cb);
@ -193,14 +195,17 @@ class FfiModel with ChangeNotifier {
wrongPasswordDialog(id); wrongPasswordDialog(id);
} else if (type == 'input-password') { } else if (type == 'input-password') {
enterPasswordDialog(id); enterPasswordDialog(id);
} else if (type == 'restarting') {
showMsgBox(type, title, text, false, hasCancel: false);
} else { } else {
var hasRetry = evt['hasRetry'] == 'true'; var hasRetry = evt['hasRetry'] == 'true';
showMsgBox(type, title, text, hasRetry); showMsgBox(type, title, text, hasRetry);
} }
} }
void showMsgBox(String type, String title, String text, bool hasRetry) { void showMsgBox(String type, String title, String text, bool hasRetry,
msgBox(type, title, text); {bool? hasCancel}) {
msgBox(type, title, text, hasCancel: hasCancel);
_timer?.cancel(); _timer?.cancel();
if (hasRetry) { if (hasRetry) {
_timer = Timer(Duration(seconds: _reconnects), () { _timer = Timer(Duration(seconds: _reconnects), () {
@ -655,6 +660,44 @@ class CursorModel with ChangeNotifier {
} }
} }
class QualityMonitorData {
String? speed;
String? fps;
String? delay;
String? targetBitrate;
String? codecFormat;
}
class QualityMonitorModel with ChangeNotifier {
var _show = FFI.getByName('toggle_option', 'show-quality-monitor') == 'true';
final _data = QualityMonitorData();
bool get show => _show;
QualityMonitorData get data => _data;
checkShowQualityMonitor() {
final show =
FFI.getByName('toggle_option', 'show-quality-monitor') == 'true';
if (_show != show) {
_show = show;
notifyListeners();
}
}
updateQualityStatus(Map<String, dynamic> evt) {
try {
if ((evt["speed"] as String).isNotEmpty) _data.speed = evt["speed"];
if ((evt["fps"] as String).isNotEmpty) _data.fps = evt["fps"];
if ((evt["delay"] as String).isNotEmpty) _data.delay = evt["delay"];
if ((evt["target_bitrate"] as String).isNotEmpty)
_data.targetBitrate = evt["target_bitrate"];
if ((evt["codec_format"] as String).isNotEmpty)
_data.codecFormat = evt["codec_format"];
notifyListeners();
} catch (e) {}
}
}
enum MouseButtons { left, right, wheel } enum MouseButtons { left, right, wheel }
extension ToString on MouseButtons { extension ToString on MouseButtons {
@ -684,6 +727,7 @@ class FFI {
static final serverModel = ServerModel(); static final serverModel = ServerModel();
static final chatModel = ChatModel(); static final chatModel = ChatModel();
static final fileModel = FileModel(); static final fileModel = FileModel();
static final qualityMonitorModel = QualityMonitorModel();
static String getId() { static String getId() {
return getByName('remote_id'); return getByName('remote_id');

View File

@ -1,4 +1,4 @@
import 'package:dash_chat/dash_chat.dart'; import 'package:dash_chat_2/dash_chat_2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
@ -6,8 +6,6 @@ import 'package:provider/provider.dart';
import '../models/model.dart'; import '../models/model.dart';
import 'home_page.dart'; import 'home_page.dart';
ChatPage chatPage = ChatPage();
class ChatPage extends StatelessWidget implements PageShape { class ChatPage extends StatelessWidget implements PageShape {
@override @override
final title = translate("Chat"); final title = translate("Chat");
@ -25,7 +23,7 @@ class ChatPage extends StatelessWidget implements PageShape {
final id = entry.key; final id = entry.key;
final user = entry.value.chatUser; final user = entry.value.chatUser;
return PopupMenuItem<int>( return PopupMenuItem<int>(
child: Text("${user.name} ${user.uid}"), child: Text("${user.firstName} ${user.id}"),
value: id, value: id,
); );
}).toList(); }).toList();
@ -46,19 +44,24 @@ class ChatPage extends StatelessWidget implements PageShape {
return Stack( return Stack(
children: [ children: [
DashChat( DashChat(
inputContainerStyle: BoxDecoration(color: Colors.white70),
sendOnEnter: false,
// if true,reload keyboard everytime,need fix
onSend: (chatMsg) { onSend: (chatMsg) {
chatModel.send(chatMsg); chatModel.send(chatMsg);
}, },
user: chatModel.me, currentUser: chatModel.me,
messages: messages:
chatModel.messages[chatModel.currentID]?.chatMessages ?? chatModel.messages[chatModel.currentID]?.chatMessages ??
[], [],
// default scrollToBottom has bug https://github.com/fayeed/dash_chat/issues/53 messageOptions: MessageOptions(
scrollToBottom: false, showOtherUsersAvatar: false,
scrollController: chatModel.scroller, showTime: true,
messageDecorationBuilder: (_, __, ___) =>
defaultMessageDecoration(
color: MyTheme.accent80,
borderTopLeft: 8,
borderTopRight: 8,
borderBottomRight: 8,
borderBottomLeft: 8,
)),
), ),
chatModel.currentID == ChatModel.clientModeID chatModel.currentID == ChatModel.clientModeID
? SizedBox.shrink() ? SizedBox.shrink()
@ -70,7 +73,7 @@ class ChatPage extends StatelessWidget implements PageShape {
color: MyTheme.accent80), color: MyTheme.accent80),
SizedBox(width: 5), SizedBox(width: 5),
Text( Text(
"${currentUser.name ?? ""} ${currentUser.uid ?? ""}", "${currentUser.firstName} ${currentUser.id}",
style: TextStyle(color: MyTheme.accent50), style: TextStyle(color: MyTheme.accent50),
), ),
], ],

View File

@ -28,6 +28,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
void initState() { void initState() {
super.initState(); super.initState();
FFI.connect(widget.id, isFileTransfer: true); FFI.connect(widget.id, isFileTransfer: true);
showLoading(translate('Connecting...'));
FFI.ffiModel.updateEventListener(widget.id); FFI.ffiModel.updateEventListener(widget.id);
Wakelock.enable(); Wakelock.enable();
} }

View File

@ -12,6 +12,8 @@ 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); HomePage({Key? key}) : super(key: key);
@ -23,12 +25,23 @@ class _HomePageState extends State<HomePage> {
var _selectedIndex = 0; var _selectedIndex = 0;
final List<PageShape> _pages = []; final List<PageShape> _pages = [];
void refreshPages() {
setState(() {
initPages();
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initPages();
}
void initPages() {
_pages.clear();
_pages.add(ConnectionPage()); _pages.add(ConnectionPage());
if (isAndroid) { if (isAndroid) {
_pages.addAll([chatPage, ServerPage()]); _pages.addAll([ChatPage(), ServerPage()]);
} }
_pages.add(SettingsPage()); _pages.add(SettingsPage());
} }

View File

@ -592,6 +592,7 @@ class _RemotePageState extends State<RemotePage> {
child: Stack(children: [ child: Stack(children: [
ImagePaint(), ImagePaint(),
CursorPaint(), CursorPaint(),
QualityMonitor(),
getHelpTools(), getHelpTools(),
SizedBox( SizedBox(
width: 0, width: 0,
@ -658,7 +659,7 @@ class _RemotePageState extends State<RemotePage> {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Row( child: Row(
children: ([ children: ([
Container(width: 100.0, child: Text(translate('OS Password'))), Text(translate('OS Password')),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: () {
@ -693,6 +694,13 @@ class _RemotePageState extends State<RemotePage> {
value: 'block-input')); value: 'block-input'));
} }
} }
if (FFI.ffiModel.permissions["restart"] != false &&
(pi.platform == "Linux" ||
pi.platform == "Windows" ||
pi.platform == "Mac OS")) {
more.add(PopupMenuItem<String>(
child: Text(translate('Restart Remote Device')), value: 'restart'));
}
() async { () async {
var value = await showMenu( var value = await showMenu(
context: context, context: context,
@ -726,6 +734,8 @@ class _RemotePageState extends State<RemotePage> {
} }
} else if (value == 'reset_canvas') { } else if (value == 'reset_canvas') {
FFI.cursorModel.reset(); FFI.cursorModel.reset();
} else if (value == 'restart') {
showRestartRemoteDevice(pi, widget.id);
} }
}(); }();
} }
@ -948,6 +958,47 @@ class ImagePainter extends CustomPainter {
} }
} }
class QualityMonitor extends StatelessWidget {
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: FFI.qualityMonitorModel,
child: Consumer<QualityMonitorModel>(
builder: (context, qualityMonitorModel, child) => Positioned(
top: 10,
right: 10,
child: qualityMonitorModel.show
? Container(
padding: EdgeInsets.all(8),
color: MyTheme.canvasColor.withAlpha(120),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed}",
style: TextStyle(color: MyTheme.grayBg),
),
Text(
"FPS: ${qualityMonitorModel.data.fps}",
style: TextStyle(color: MyTheme.grayBg),
),
Text(
"Delay: ${qualityMonitorModel.data.delay} ms",
style: TextStyle(color: MyTheme.grayBg),
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate}kb",
style: TextStyle(color: MyTheme.grayBg),
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat}",
style: TextStyle(color: MyTheme.grayBg),
),
],
),
)
: SizedBox.shrink())));
}
CheckboxListTile getToggle( CheckboxListTile getToggle(
void Function(void Function()) setState, option, name) { void Function(void Function()) setState, option, name) {
return CheckboxListTile( return CheckboxListTile(
@ -956,6 +1007,9 @@ CheckboxListTile getToggle(
setState(() { setState(() {
FFI.setByName('toggle_option', option); FFI.setByName('toggle_option', option);
}); });
if (option == "show-quality-monitor") {
FFI.qualityMonitorModel.checkShowQualityMonitor();
}
}, },
dense: true, dense: true,
title: Text(translate(name))); title: Text(translate(name)));
@ -1058,6 +1112,27 @@ void showOptions() {
}, clickMaskDismiss: true, backDismiss: true); }, clickMaskDismiss: true, backDismiss: true);
} }
void showRestartRemoteDevice(PeerInfo pi, String id) async {
final res =
await DialogManager.show<bool>((setState, close) => CustomAlertDialog(
title: Row(children: [
Icon(Icons.warning_amber_sharp,
color: Colors.redAccent, size: 28),
SizedBox(width: 10),
Text(translate("Restart Remote Device")),
]),
content: Text(
"${translate('Are you sure you want to restart')} \n${pi.username}@${pi.hostname}($id) ?"),
actions: [
TextButton(
onPressed: () => close(), child: Text(translate("Cancel"))),
ElevatedButton(
onPressed: () => close(true), child: Text(translate("OK"))),
],
));
if (res == true) FFI.setByName('restart_remote_device');
}
void showSetOSPassword(bool login) { void showSetOSPassword(bool login) {
final controller = TextEditingController(); final controller = TextEditingController();
var password = FFI.getByName('peer_option', "os-password"); var password = FFI.getByName('peer_option', "os-password");

View File

@ -200,7 +200,8 @@ class ServerInfo extends StatelessWidget {
Icon(Icons.warning_amber_sharp, Icon(Icons.warning_amber_sharp,
color: Colors.redAccent, size: 24), color: Colors.redAccent, size: 24),
SizedBox(width: 10), SizedBox(width: 10),
Text( Expanded(
child: Text(
translate("Service is not running"), translate("Service is not running"),
style: TextStyle( style: TextStyle(
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
@ -208,7 +209,7 @@ class ServerInfo extends StatelessWidget {
fontSize: 18, fontSize: 18,
color: MyTheme.accent80, color: MyTheme.accent80,
), ),
) ))
], ],
)), )),
SizedBox(height: 5), SizedBox(height: 5),
@ -316,30 +317,35 @@ class PermissionRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Expanded(
children: [ flex: 5,
SizedBox( child: FittedBox(
width: 140, fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(name, child: Text(name,
style: TextStyle(fontSize: 16.0, color: MyTheme.accent50))), style:
SizedBox( TextStyle(fontSize: 16.0, color: MyTheme.accent50)))),
width: 50, Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(isOk ? translate("ON") : translate("OFF"), child: Text(isOk ? translate("ON") : translate("OFF"),
style: TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 16.0,
color: isOk ? Colors.green : Colors.grey)), color: isOk ? Colors.green : Colors.grey))),
)
],
), ),
TextButton( Expanded(
onPressed: onPressed, flex: 3,
child: Text( child: FittedBox(
translate(isOk ? "CLOSE" : "OPEN"), fit: BoxFit.scaleDown,
style: TextStyle(fontWeight: FontWeight.bold), alignment: Alignment.centerRight,
)), child: TextButton(
const Divider(height: 0) onPressed: onPressed,
child: Text(
translate(isOk ? "CLOSE" : "OPEN"),
style: TextStyle(fontWeight: FontWeight.bold),
)))),
], ],
); );
} }

View File

@ -26,11 +26,11 @@ class SettingsPage extends StatefulWidget implements PageShape {
_SettingsState createState() => _SettingsState(); _SettingsState createState() => _SettingsState();
} }
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver { const url = 'https://rustdesk.com/';
static const url = 'https://rustdesk.com/'; final _hasIgnoreBattery = androidVersion >= 26;
final _hasIgnoreBattery = androidVersion >= 26; var _ignoreBatteryOpt = false;
var _ignoreBatteryOpt = false;
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -146,6 +146,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
leading: Icon(Icons.cloud), leading: Icon(Icons.cloud),
onPressed: (context) { onPressed: (context) {
showServerSettings(); showServerSettings();
}),
SettingsTile.navigation(
title: Text(translate('Language')),
leading: Icon(Icons.translate),
onPressed: (context) {
showLanguageSettings();
}) })
]), ]),
SettingsSection( SettingsSection(
@ -185,6 +191,42 @@ void showServerSettings() {
showServerSettingsWithValue(id, relay, key, api); showServerSettingsWithValue(id, relay, key, api);
} }
void showLanguageSettings() {
try {
final langs = json.decode(FFI.getByName('langs')) as List<dynamic>;
var lang = FFI.getByName('local_option', 'lang');
DialogManager.show((setState, close) {
final setLang = (v) {
if (lang != v) {
setState(() {
lang = v;
});
final msg = Map()
..['name'] = 'lang'
..['value'] = v;
FFI.setByName('local_option', json.encode(msg));
homeKey.currentState?.refreshPages();
Future.delayed(Duration(milliseconds: 200), close);
}
};
return CustomAlertDialog(
title: SizedBox.shrink(),
content: Column(
children: [
getRadio('Default', '', lang, setLang),
Divider(color: MyTheme.border),
] +
langs.map((e) {
final key = e[0] as String;
final name = e[1] as String;
return getRadio(name, key, lang, setLang);
}).toList(),
),
actions: []);
}, backDismiss: true, clickMaskDismiss: true);
} catch (_e) {}
}
void showAbout() { void showAbout() {
DialogManager.show((setState, close) { DialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(

View File

@ -27,7 +27,7 @@ class DraggableChatWindow extends StatelessWidget {
height: height, height: height,
builder: (_, onPanUpdate) { builder: (_, onPanUpdate) {
return isIOS return isIOS
? chatPage ? ChatPage()
: Scaffold( : Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: CustomAppBar( appBar: CustomAppBar(
@ -68,7 +68,7 @@ class DraggableChatWindow extends StatelessWidget {
), ),
), ),
), ),
body: chatPage, body: ChatPage(),
); );
}); });
} }

View File

@ -29,6 +29,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
cached_network_image:
dependency: transitive
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "3.2.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -71,6 +92,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.2" version: "3.0.2"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.2"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -78,13 +106,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
dash_chat: dash_chat_2:
dependency: "direct main" dependency: "direct main"
description: description:
name: dash_chat name: dash_chat_2
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.16" version: "0.0.12"
device_info: device_info:
dependency: "direct main" dependency: "direct main"
description: description:
@ -195,6 +223,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_blurhash:
dependency: transitive
description:
name: flutter_blurhash
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.0"
flutter_breadcrumb: flutter_breadcrumb:
dependency: "direct main" dependency: "direct main"
description: description:
@ -202,6 +237,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -247,6 +289,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
html:
dependency: transitive
description:
name: html
url: "https://pub.dartlang.org"
source: hosted
version: "0.15.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -345,6 +394,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
octo_image:
dependency: transitive
description:
name: octo_image
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
package_info: package_info:
dependency: "direct main" dependency: "direct main"
description: description:
@ -449,16 +505,14 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0" version: "6.0.3"
qr_code_scanner: qr_code_scanner:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." name: qr_code_scanner
ref: fix_break_changes_platform url: "https://pub.dartlang.org"
resolved-ref: "0feca6f15042c279ff575c559a3430df917b623d" source: hosted
url: "https://github.com/Heap-Hop/qr_code_scanner.git" version: "1.0.0"
source: git
version: "0.7.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
@ -466,6 +520,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.27.5"
settings_ui: settings_ui:
dependency: "direct main" dependency: "direct main"
description: description:
@ -541,6 +602,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.8.2"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -562,6 +637,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0+2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -583,13 +665,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
transparent_image:
dependency: transitive
description:
name: transparent_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
tuple: tuple:
dependency: "direct main" dependency: "direct main"
description: description:
@ -674,6 +749,41 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
video_player:
dependency: transitive
description:
name: video_player
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.5"
video_player_android:
dependency: transitive
description:
name: video_player_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.8"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.5"
video_player_platform_interface:
dependency: transitive
description:
name: video_player_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.3"
video_player_web:
dependency: transitive
description:
name: video_player_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.12"
wakelock: wakelock:
dependency: "direct main" dependency: "direct main"
description: description:
@ -745,5 +855,5 @@ packages:
source: hosted source: hosted
version: "0.1.0" version: "0.1.0"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"

View File

@ -32,7 +32,7 @@ dependencies:
ffi: ^1.1.2 ffi: ^1.1.2
path_provider: ^2.0.2 path_provider: ^2.0.2
external_path: ^1.0.1 external_path: ^1.0.1
provider: ^5.0.0 provider: ^6.0.3
tuple: ^2.0.0 tuple: ^2.0.0
wakelock: ^0.5.2 wakelock: ^0.5.2
device_info: ^2.0.2 device_info: ^2.0.2
@ -41,15 +41,12 @@ dependencies:
url_launcher: ^6.0.9 url_launcher: ^6.0.9
shared_preferences: ^2.0.6 shared_preferences: ^2.0.6
toggle_switch: ^1.4.0 toggle_switch: ^1.4.0
dash_chat: ^1.1.16 dash_chat_2: ^0.0.12
draggable_float_widget: ^0.0.2 draggable_float_widget: ^0.0.2
settings_ui: ^2.0.2 settings_ui: ^2.0.2
flutter_breadcrumb: ^1.0.1 flutter_breadcrumb: ^1.0.1
http: ^0.13.4 http: ^0.13.4
qr_code_scanner: qr_code_scanner: ^1.0.0
git:
url: https://github.com/Heap-Hop/qr_code_scanner.git
ref: fix_break_changes_platform
zxing2: ^0.1.0 zxing2: ^0.1.0
image_picker: ^0.8.5 image_picker: ^0.8.5
image: ^3.1.3 image: ^3.1.3

View File

@ -3,7 +3,10 @@ use std::{
time::Instant, time::Instant,
}; };
use hbb_common::{log, message_proto::{VideoFrame, video_frame}}; use hbb_common::{
log,
message_proto::{video_frame, VideoFrame},
};
const MAX_LATENCY: i64 = 500; const MAX_LATENCY: i64 = 500;
const MIN_LATENCY: i64 = 100; const MIN_LATENCY: i64 = 100;
@ -87,3 +90,12 @@ impl ToString for CodecFormat {
} }
} }
} }
#[derive(Debug, Default)]
pub struct QualityStatus {
pub speed: Option<String>,
pub fps: Option<i32>,
pub delay: Option<i32>,
pub target_bitrate: Option<i32>,
pub codec_format: Option<CodecFormat>,
}

View File

@ -8,17 +8,17 @@ mod de;
mod en; mod en;
mod eo; mod eo;
mod es; mod es;
mod hu;
mod fr; mod fr;
mod hu;
mod id; mod id;
mod it; mod it;
mod pl;
mod ptbr; mod ptbr;
mod ru; mod ru;
mod sk; mod sk;
mod tr; mod tr;
mod tw; mod tw;
mod vn; mod vn;
mod pl;
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref LANGS: Value = pub static ref LANGS: Value =
@ -90,13 +90,15 @@ pub fn translate_locale(name: String, locale: &str) -> String {
_ => en::T.deref(), _ => en::T.deref(),
}; };
if let Some(v) = m.get(&name as &str) { if let Some(v) = m.get(&name as &str) {
v.to_string() if v.is_empty() {
} else { if lang != "en" {
if lang != "en" { if let Some(v) = en::T.get(&name as &str) {
if let Some(v) = en::T.get(&name as &str) { return v.to_string();
return v.to_string(); }
} }
} else {
return v.to_string();
} }
name
} }
name
} }

View File

@ -1,12 +1,16 @@
use crate::client::*; use crate::client::*;
use crate::common::{make_fd_to_json}; use crate::common::make_fd_to_json;
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
compress::decompress, compress::decompress,
config::{Config, LocalConfig}, config::{Config, LocalConfig},
fs, log, fs,
fs::{can_enable_overwrite_detection, new_send_confirm, DigestCheckResult, get_string, transform_windows_path}, fs::{
can_enable_overwrite_detection, get_string, new_send_confirm, transform_windows_path,
DigestCheckResult,
},
log,
message_proto::*, message_proto::*,
protobuf::Message as _, protobuf::Message as _,
rendezvous_proto::ConnType, rendezvous_proto::ConnType,
@ -17,6 +21,7 @@ use hbb_common::{
}, },
Stream, Stream,
}; };
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex, RwLock},
@ -83,6 +88,15 @@ impl Session {
} }
} }
pub fn restart_remote_device() {
if let Some(session) = SESSION.write().unwrap().as_ref() {
let mut lc = session.lc.write().unwrap();
lc.restarting_remote_device = true;
let msg = lc.restart_remote_device();
session.send(Data::Message(msg));
}
}
fn send(data: Data) { fn send(data: Data) {
if let Some(session) = SESSION.read().unwrap().as_ref() { if let Some(session) = SESSION.read().unwrap().as_ref() {
session.send(data); session.send(data);
@ -397,6 +411,26 @@ impl Session {
log::debug!("{:?}", msg_out); log::debug!("{:?}", msg_out);
self.send_msg(msg_out); self.send_msg(msg_out);
} }
fn update_quality_status(&self, status: QualityStatus) {
const NULL: String = String::new();
self.push_event(
"update_quality_status",
vec![
("speed", &status.speed.map_or(NULL, |it| it)),
("fps", &status.fps.map_or(NULL, |it| it.to_string())),
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
(
"target_bitrate",
&status.target_bitrate.map_or(NULL, |it| it.to_string()),
),
(
"codec_format",
&status.codec_format.map_or(NULL, |it| it.to_string()),
),
],
);
}
} }
impl FileManager for Session {} impl FileManager for Session {}
@ -438,7 +472,11 @@ impl Interface for Session {
if lc.is_file_transfer { if lc.is_file_transfer {
if pi.username.is_empty() { if pi.username.is_empty() {
self.msgbox("error", "Error", "No active console user logged on, please connect and logon first."); self.msgbox(
"error",
"Error",
"No active console user logged on, please connect and logon first.",
);
return; return;
} }
} else { } else {
@ -487,7 +525,14 @@ impl Interface for Session {
} }
async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) { async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {
handle_test_delay(t, peer).await; if !t.from_client {
self.update_quality_status(QualityStatus {
delay: Some(t.last_delay as _),
target_bitrate: Some(t.target_bitrate as _),
..Default::default()
});
handle_test_delay(t, peer).await;
}
} }
} }
@ -502,6 +547,9 @@ struct Connection {
write_jobs: Vec<fs::TransferJob>, write_jobs: Vec<fs::TransferJob>,
timer: Interval, timer: Interval,
last_update_jobs_status: (Instant, HashMap<i32, u64>), last_update_jobs_status: (Instant, HashMap<i32, u64>),
data_count: Arc<AtomicUsize>,
frame_count: Arc<AtomicUsize>,
video_format: CodecFormat,
} }
impl Connection { impl Connection {
@ -528,6 +576,9 @@ impl Connection {
write_jobs: Vec::new(), write_jobs: Vec::new(),
timer: time::interval(SEC30), timer: time::interval(SEC30),
last_update_jobs_status: (Instant::now(), Default::default()), last_update_jobs_status: (Instant::now(), Default::default()),
data_count: Arc::new(AtomicUsize::new(0)),
frame_count: Arc::new(AtomicUsize::new(0)),
video_format: CodecFormat::Unknown,
}; };
let key = Config::get_option("key"); let key = Config::get_option("key");
let token = Config::get_option("access_token"); let token = Config::get_option("access_token");
@ -541,6 +592,9 @@ impl Connection {
("direct", &direct.to_string()), ("direct", &direct.to_string()),
], ],
); );
let mut status_timer = time::interval(Duration::new(1, 0));
loop { loop {
tokio::select! { tokio::select! {
res = peer.next() => { res = peer.next() => {
@ -553,14 +607,20 @@ impl Connection {
} }
Ok(ref bytes) => { Ok(ref bytes) => {
last_recv_time = Instant::now(); last_recv_time = Instant::now();
conn.data_count.fetch_add(bytes.len(), Ordering::Relaxed);
if !conn.handle_msg_from_peer(bytes, &mut peer).await { if !conn.handle_msg_from_peer(bytes, &mut peer).await {
break break
} }
} }
} }
} else { } else {
log::info!("Reset by the peer"); if session.lc.read().unwrap().restarting_remote_device {
session.msgbox("error", "Connection Error", "Reset by the peer"); log::info!("Restart remote device");
session.msgbox("restarting", "Restarting Remote Device", "remote_restarting_tip");
} else {
log::info!("Reset by the peer");
session.msgbox("error", "Connection Error", "Reset by the peer");
}
break; break;
} }
} }
@ -586,6 +646,16 @@ impl Connection {
conn.timer = time::interval_at(Instant::now() + SEC30, SEC30); conn.timer = time::interval_at(Instant::now() + SEC30, SEC30);
} }
} }
_ = status_timer.tick() => {
let speed = conn.data_count.swap(0, Ordering::Relaxed);
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
let fps = conn.frame_count.swap(0, Ordering::Relaxed) as _;
conn.session.update_quality_status(QualityStatus {
speed:Some(speed),
fps:Some(fps),
..Default::default()
});
}
} }
} }
log::debug!("Exit io_loop of id={}", session.id); log::debug!("Exit io_loop of id={}", session.id);
@ -603,10 +673,19 @@ impl Connection {
if !self.first_frame { if !self.first_frame {
self.first_frame = true; self.first_frame = true;
} }
let incomming_format = CodecFormat::from(&vf);
if self.video_format != incomming_format {
self.video_format = incomming_format.clone();
self.session.update_quality_status(QualityStatus {
codec_format: Some(incomming_format),
..Default::default()
})
};
if let (Ok(true), Some(s)) = ( if let (Ok(true), Some(s)) = (
self.video_handler.handle_frame(vf), self.video_handler.handle_frame(vf),
RGBA_STREAM.read().unwrap().as_ref(), RGBA_STREAM.read().unwrap().as_ref(),
) { ) {
self.frame_count.fetch_add(1, Ordering::Relaxed);
s.add(ZeroCopyBuffer(self.video_handler.rgb.clone())); s.add(ZeroCopyBuffer(self.video_handler.rgb.clone()));
} }
} }
@ -664,113 +743,114 @@ impl Connection {
vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())], vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
); );
} }
Some(message::Union::FileResponse(fr)) => match fr.union { Some(message::Union::FileResponse(fr)) => {
Some(file_response::Union::Dir(fd)) => { match fr.union {
let mut entries = fd.entries.to_vec(); Some(file_response::Union::Dir(fd)) => {
if self.session.peer_platform() == "Windows" { let mut entries = fd.entries.to_vec();
fs::transform_windows_path(&mut entries); if self.session.peer_platform() == "Windows" {
} fs::transform_windows_path(&mut entries);
let id = fd.id; }
self.session.push_event( let id = fd.id;
"file_dir", self.session.push_event(
vec![("value", &make_fd_to_json(fd)), ("is_local", "false")], "file_dir",
); vec![("value", &make_fd_to_json(fd)), ("is_local", "false")],
if let Some(job) = fs::get_job(id, &mut self.write_jobs) { );
job.set_files(entries); if let Some(job) = fs::get_job(id, &mut self.write_jobs) {
} job.set_files(entries);
}
Some(file_response::Union::Block(block)) => {
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Err(_err) = job.write(block, None).await {
// to-do: add "skip" for writing job
} }
self.update_jobs_status();
} }
} Some(file_response::Union::Block(block)) => {
Some(file_response::Union::Done(d)) => { if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { if let Err(_err) = job.write(block, None).await {
job.modify_time(); // to-do: add "skip" for writing job
fs::remove_job(d.id, &mut self.write_jobs); }
self.update_jobs_status();
}
} }
self.handle_job_status(d.id, d.file_num, None); Some(file_response::Union::Done(d)) => {
} if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
Some(file_response::Union::Error(e)) => { job.modify_time();
self.handle_job_status(e.id, e.file_num, Some(e.error)); fs::remove_job(d.id, &mut self.write_jobs);
} }
Some(file_response::Union::Digest(digest)) => { self.handle_job_status(d.id, d.file_num, None);
if digest.is_upload { }
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { Some(file_response::Union::Error(e)) => {
if let Some(file) = job.files().get(digest.file_num as usize) { self.handle_job_status(e.id, e.file_num, Some(e.error));
let read_path = get_string(&job.join(&file.name)); }
let overwrite_strategy = job.default_overwrite_strategy(); Some(file_response::Union::Digest(digest)) => {
if let Some(overwrite) = overwrite_strategy { if digest.is_upload {
let req = FileTransferSendConfirmRequest { if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
id: digest.id, if let Some(file) = job.files().get(digest.file_num as usize) {
file_num: digest.file_num, let read_path = get_string(&job.join(&file.name));
union: Some(if overwrite { let overwrite_strategy = job.default_overwrite_strategy();
file_transfer_send_confirm_request::Union::OffsetBlk(0) if let Some(overwrite) = overwrite_strategy {
} else { let req = FileTransferSendConfirmRequest {
file_transfer_send_confirm_request::Union::Skip( id: digest.id,
true, file_num: digest.file_num,
) union: Some(if overwrite {
}), file_transfer_send_confirm_request::Union::OffsetBlk(0)
..Default::default() } else {
}; file_transfer_send_confirm_request::Union::Skip(
job.confirm(&req); true,
let msg = new_send_confirm(req); )
allow_err!(peer.send(&msg).await); }),
} else { ..Default::default()
self.handle_override_file_confirm( };
digest.id, job.confirm(&req);
digest.file_num, let msg = new_send_confirm(req);
read_path, allow_err!(peer.send(&msg).await);
true, } else {
); self.handle_override_file_confirm(
digest.id,
digest.file_num,
read_path,
true,
);
}
} }
} }
} } else {
} else { if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) {
if let Some(job) = fs::get_job(digest.id, &mut self.write_jobs) { if let Some(file) = job.files().get(digest.file_num as usize) {
if let Some(file) = job.files().get(digest.file_num as usize) { let write_path = get_string(&job.join(&file.name));
let write_path = get_string(&job.join(&file.name)); let overwrite_strategy = job.default_overwrite_strategy();
let overwrite_strategy = job.default_overwrite_strategy(); match fs::is_write_need_confirmation(&write_path, &digest) {
match fs::is_write_need_confirmation(&write_path, &digest) { Ok(res) => match res {
Ok(res) => match res { DigestCheckResult::IsSame => {
DigestCheckResult::IsSame => { let msg= new_send_confirm(FileTransferSendConfirmRequest {
let msg= new_send_confirm(FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::Skip(true)), union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default() ..Default::default()
}); });
self.session.send_msg(msg);
}
DigestCheckResult::NeedConfirm(digest) => {
if let Some(overwrite) = overwrite_strategy {
let msg = new_send_confirm(
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
},
);
self.session.send_msg(msg); self.session.send_msg(msg);
} else {
self.handle_override_file_confirm(
digest.id,
digest.file_num,
write_path.to_string(),
false,
);
} }
} DigestCheckResult::NeedConfirm(digest) => {
DigestCheckResult::NoSuchFile => { if let Some(overwrite) = overwrite_strategy {
let msg = new_send_confirm( let msg = new_send_confirm(
FileTransferSendConfirmRequest {
id: digest.id,
file_num: digest.file_num,
union: Some(if overwrite {
file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else {
file_transfer_send_confirm_request::Union::Skip(true)
}),
..Default::default()
},
);
self.session.send_msg(msg);
} else {
self.handle_override_file_confirm(
digest.id,
digest.file_num,
write_path.to_string(),
false,
);
}
}
DigestCheckResult::NoSuchFile => {
let msg = new_send_confirm(
FileTransferSendConfirmRequest { FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
@ -778,19 +858,20 @@ impl Connection {
..Default::default() ..Default::default()
}, },
); );
self.session.send_msg(msg); self.session.send_msg(msg);
}
},
Err(err) => {
println!("error recving digest: {}", err);
} }
},
Err(err) => {
println!("error recving digest: {}", err);
} }
} }
} }
} }
} }
_ => {}
} }
_ => {} }
},
Some(message::Union::Misc(misc)) => match misc.union { Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::AudioFormat(f)) => { Some(misc::Union::AudioFormat(f)) => {
self.audio_handler.handle_format(f); // self.audio_handler.handle_format(f); //
@ -809,6 +890,7 @@ impl Connection {
Permission::Keyboard => "keyboard", Permission::Keyboard => "keyboard",
Permission::Clipboard => "clipboard", Permission::Clipboard => "clipboard",
Permission::Audio => "audio", Permission::Audio => "audio",
Permission::Restart => "restart",
_ => "", _ => "",
}, },
&p.enabled.to_string(), &p.enabled.to_string(),

View File

@ -113,6 +113,14 @@ unsafe extern "C" fn get_by_name(name: *const c_char, arg: *const c_char) -> *co
res = Session::get_option(arg); res = Session::get_option(arg);
} }
} }
"local_option" => {
if let Ok(arg) = arg.to_str() {
res = LocalConfig::get_option(arg);
}
}
"langs" => {
res = crate::lang::LANGS.to_string();
}
// File Action // File Action
"get_home_dir" => { "get_home_dir" => {
res = fs::get_home_as_string(); res = fs::get_home_as_string();
@ -311,9 +319,21 @@ unsafe extern "C" fn set_by_name(name: *const c_char, value: *const c_char) {
} }
} }
} }
"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" => { "input_os_password" => {
Session::input_os_password(value.to_owned(), true); Session::input_os_password(value.to_owned(), true);
} }
"restart_remote_device" => {
Session::restart_remote_device();
}
// File Action // File Action
"read_remote_dir" => { "read_remote_dir" => {
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) { if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(value) {

View File

@ -239,15 +239,6 @@ impl sciter::EventHandler for Handler {
} }
} }
#[derive(Debug, Default)]
struct QualityStatus {
speed: Option<String>,
fps: Option<i32>,
delay: Option<i32>,
target_bitrate: Option<i32>,
codec_format: Option<CodecFormat>,
}
impl Handler { impl Handler {
pub fn new(cmd: String, id: String, password: String, args: Vec<String>) -> Self { pub fn new(cmd: String, id: String, password: String, args: Vec<String>) -> Self {
let me = Self { let me = Self {
@ -638,8 +629,9 @@ impl Handler {
} }
fn restart_remote_device(&mut self) { fn restart_remote_device(&mut self) {
self.lc.write().unwrap().restarting_remote_device = true; let mut lc = self.lc.write().unwrap();
let msg = self.lc.write().unwrap().restart_remote_device(); lc.restarting_remote_device = true;
let msg = lc.restart_remote_device();
self.send(Data::Message(msg)); self.send(Data::Message(msg));
} }