Merge pull request #1404 from fufesou/flutter_desktop_new_remote_menu

Flutter desktop new remote menu
This commit is contained in:
RustDesk 2022-08-30 23:31:20 +08:00 committed by GitHub
commit 694896abda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 672 additions and 301 deletions

View File

@ -743,39 +743,3 @@ Future<List<Peer>>? matchPeers(String searchText, List<Peer> peers) async {
} }
return filteredList; return filteredList;
} }
class PrivacyModeState {
static String tag(String id) => 'privacy_mode_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class BlockInputState {
static String tag(String id) => 'block_input_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class CurrentDisplayState {
static String tag(String id) => 'current_display_' + id;
static void init(String id) {
final RxInt state = RxInt(0);
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
}

View File

@ -0,0 +1,87 @@
import 'package:get/get.dart';
import '../consts.dart';
class PrivacyModeState {
static String tag(String id) => 'privacy_mode_$id';
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class BlockInputState {
static String tag(String id) => 'block_input_$id';
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class CurrentDisplayState {
static String tag(String id) => 'current_display_$id';
static void init(String id) {
final RxInt state = RxInt(0);
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
}
class ConnectionType {
final Rx<String> _secure = kInvalidValueStr.obs;
final Rx<String> _direct = kInvalidValueStr.obs;
Rx<String> get secure => _secure;
Rx<String> get direct => _direct;
static String get strSecure => 'secure';
static String get strInsecure => 'insecure';
static String get strDirect => '';
static String get strIndirect => '_relay';
void setSecure(bool v) {
_secure.value = v ? strSecure : strInsecure;
}
void setDirect(bool v) {
_direct.value = v ? strDirect : strIndirect;
}
bool isValid() {
return _secure.value != kInvalidValueStr &&
_direct.value != kInvalidValueStr;
}
}
class ConnectionTypeState {
static String tag(String id) => 'connection_type_$id';
static void init(String id) {
final key = tag(id);
if (!Get.isRegistered(tag: key)) {
final ConnectionType collectionType = ConnectionType();
Get.put(collectionType, tag: key);
}
}
static void delete(String id) {
final key = tag(id);
if (Get.isRegistered(tag: key)) {
Get.delete(tag: key);
}
}
static ConnectionType find(String id) =>
Get.find<ConnectionType>(tag: tag(id));
}

View File

@ -10,3 +10,5 @@ const String kTabLabelSettingPage = "Settings";
const int kDefaultDisplayWidth = 1280; const int kDefaultDisplayWidth = 1280;
const int kDefaultDisplayHeight = 720; const int kDefaultDisplayHeight = 720;
const kInvalidValueStr = "InvalidValueStr";

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.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/common/shared_state.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/pages/remote_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
@ -20,22 +21,24 @@ class ConnectionTabPage extends StatefulWidget {
class _ConnectionTabPageState extends State<ConnectionTabPage> { class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabController = Get.put(DesktopTabController()); final tabController = Get.put(DesktopTabController());
static final IconData selectedIcon = Icons.desktop_windows_sharp; static const IconData selectedIcon = Icons.desktop_windows_sharp;
static final IconData unselectedIcon = Icons.desktop_windows_outlined; static const IconData unselectedIcon = Icons.desktop_windows_outlined;
var connectionMap = RxList<Widget>.empty(growable: true); var connectionMap = RxList<Widget>.empty(growable: true);
_ConnectionTabPageState(Map<String, dynamic> params) { _ConnectionTabPageState(Map<String, dynamic> params) {
final RxBool fullscreen = Get.find(tag: 'fullscreen'); final RxBool fullscreen = Get.find(tag: 'fullscreen');
if (params['id'] != null) { final peerId = params['id'];
if (peerId != null) {
ConnectionTypeState.init(peerId);
tabController.add(TabInfo( tabController.add(TabInfo(
key: params['id'], key: peerId,
label: params['id'], label: peerId,
selectedIcon: selectedIcon, selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon, unselectedIcon: unselectedIcon,
page: Obx(() => RemotePage( page: Obx(() => RemotePage(
key: ValueKey(params['id']), key: ValueKey(peerId),
id: params['id'], id: peerId,
tabBarHeight: tabBarHeight:
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight, fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
)))); ))));
@ -103,6 +106,44 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
.setFullscreen(fullscreen.isTrue); .setFullscreen(fullscreen.isTrue);
return pageView; return pageView;
}, },
tabBuilder: (key, icon, label, themeConf) => Obx(() {
final connectionType = ConnectionTypeState.find(key);
if (!ConnectionTypeState.find(key).isValid()) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
label,
],
);
} else {
final msgDirect = translate(
connectionType.direct.value ==
ConnectionType.strDirect
? 'Direct Connection'
: 'Relay Connection');
final msgSecure = translate(
connectionType.secure.value ==
ConnectionType.strSecure
? 'Secure Connection'
: 'Insecure Connection');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
Tooltip(
message: '$msgDirect\n$msgSecure',
child: Image.asset(
'assets/${connectionType.secure.value}${connectionType.direct.value}.png',
width: themeConf.iconSize,
height: themeConf.iconSize,
).paddingOnly(right: 5),
),
label,
],
);
}
}),
))), ))),
), ),
)); ));
@ -112,6 +153,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
if (tabController.state.value.tabs.isEmpty) { if (tabController.state.value.tabs.isEmpty) {
WindowController.fromWindowId(windowId()).hide(); WindowController.fromWindowId(windowId()).hide();
} }
ConnectionTypeState.delete(id);
} }
int windowId() { int windowId() {

View File

@ -5,11 +5,9 @@ import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import 'package:tuple/tuple.dart';
// import 'package:window_manager/window_manager.dart'; // import 'package:window_manager/window_manager.dart';
@ -19,6 +17,8 @@ import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../models/chat_model.dart';
import '../../common/shared_state.dart';
final initText = '\1' * 1024; final initText = '\1' * 1024;
@ -41,7 +41,7 @@ class _RemotePageState extends State<RemotePage>
Timer? _timer; Timer? _timer;
bool _showBar = !isWebDesktop; bool _showBar = !isWebDesktop;
String _value = ''; String _value = '';
var _cursorOverImage = false.obs; final _cursorOverImage = false.obs;
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
final FocusNode _physicalFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode();
@ -54,6 +54,18 @@ class _RemotePageState extends State<RemotePage>
_ffi.canvasModel.tabBarHeight = widget.tabBarHeight; _ffi.canvasModel.tabBarHeight = widget.tabBarHeight;
} }
void _initStates(String id) {
PrivacyModeState.init(id);
BlockInputState.init(id);
CurrentDisplayState.init(id);
}
void _removeStates(String id) {
PrivacyModeState.delete(id);
BlockInputState.delete(id);
CurrentDisplayState.delete(id);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -74,14 +86,12 @@ class _RemotePageState extends State<RemotePage>
_ffi.listenToMouse(true); _ffi.listenToMouse(true);
_ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); _ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
// WindowManager.instance.addListener(this); // WindowManager.instance.addListener(this);
PrivacyModeState.init(widget.id); _initStates(widget.id);
BlockInputState.init(widget.id);
CurrentDisplayState.init(widget.id);
} }
@override @override
void dispose() { void dispose() {
print("REMOTE PAGE dispose ${widget.id}"); debugPrint("REMOTE PAGE dispose ${widget.id}");
hideMobileActionsOverlay(); hideMobileActionsOverlay();
_ffi.listenToMouse(false); _ffi.listenToMouse(false);
_mobileFocusNode.dispose(); _mobileFocusNode.dispose();
@ -97,9 +107,7 @@ class _RemotePageState extends State<RemotePage>
// WindowManager.instance.removeListener(this); // WindowManager.instance.removeListener(this);
Get.delete<FFI>(tag: widget.id); Get.delete<FFI>(tag: widget.id);
super.dispose(); super.dispose();
PrivacyModeState.delete(widget.id); _removeStates(widget.id);
BlockInputState.delete(widget.id);
CurrentDisplayState.delete(widget.id);
} }
void resetTool() { void resetTool() {

View File

@ -4,12 +4,10 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import './material_mod_popup_menu.dart' as modMenu; import './material_mod_popup_menu.dart' as mod_menu;
const kInvalidValueStr = "InvalidValueStr";
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu // https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
class PopupMenuChildrenItem<T> extends modMenu.PopupMenuEntry<T> { class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
const PopupMenuChildrenItem({ const PopupMenuChildrenItem({
key, key,
this.height = kMinInteractiveDimension, this.height = kMinInteractiveDimension,
@ -17,19 +15,19 @@ class PopupMenuChildrenItem<T> extends modMenu.PopupMenuEntry<T> {
this.enable = true, this.enable = true,
this.textStyle, this.textStyle,
this.onTap, this.onTap,
this.position = modMenu.PopupMenuPosition.overSide, this.position = mod_menu.PopupMenuPosition.overSide,
this.offset = Offset.zero, this.offset = Offset.zero,
required this.itemBuilder, required this.itemBuilder,
required this.child, required this.child,
}) : super(key: key); }) : super(key: key);
final modMenu.PopupMenuPosition position; final mod_menu.PopupMenuPosition position;
final Offset offset; final Offset offset;
final TextStyle? textStyle; final TextStyle? textStyle;
final EdgeInsets? padding; final EdgeInsets? padding;
final bool enable; final bool enable;
final void Function()? onTap; final void Function()? onTap;
final List<modMenu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder; final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
final Widget child; final Widget child;
@override @override
@ -59,7 +57,7 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
popupMenuTheme.textStyle ?? popupMenuTheme.textStyle ??
theme.textTheme.subtitle1!; theme.textTheme.subtitle1!;
return modMenu.PopupMenuButton<T>( return mod_menu.PopupMenuButton<T>(
enabled: widget.enable, enabled: widget.enable,
position: widget.position, position: widget.position,
offset: widget.offset, offset: widget.offset,
@ -88,22 +86,29 @@ class MenuConfig {
static const iconWidth = 12.0; static const iconWidth = 12.0;
static const iconHeight = 12.0; static const iconHeight = 12.0;
final double secondMenuHeight; final double height;
final double dividerHeight;
final Color commonColor; final Color commonColor;
const MenuConfig( const MenuConfig(
{required this.commonColor, {required this.commonColor,
this.secondMenuHeight = kMinInteractiveDimension}); this.height = kMinInteractiveDimension,
this.dividerHeight = 16.0});
} }
abstract class MenuEntryBase<T> { abstract class MenuEntryBase<T> {
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf); List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
} }
class MenuEntryDivider<T> extends MenuEntryBase<T> { class MenuEntryDivider<T> extends MenuEntryBase<T> {
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { List<mod_menu.PopupMenuEntry<T>> build(
return const modMenu.PopupMenuDivider(); BuildContext context, MenuConfig conf) {
return [
mod_menu.PopupMenuDivider(
height: conf.dividerHeight,
)
];
} }
} }
@ -111,6 +116,85 @@ typedef RadioOptionsGetter = List<Tuple2<String, String>> Function();
typedef RadioCurOptionGetter = Future<String> Function(); typedef RadioCurOptionGetter = Future<String> Function();
typedef RadioOptionSetter = Future<void> Function(String); typedef RadioOptionSetter = Future<void> Function(String);
class MenuEntryRadioUtils<T> {}
class MenuEntryRadios<T> extends MenuEntryBase<T> {
final String text;
final RadioOptionsGetter optionsGetter;
final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
MenuEntryRadios(
{required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter}) {
() async {
_curOption.value = await curOptionGetter();
}();
}
List<Tuple2<String, String>> get options => optionsGetter();
RxString get curOption => _curOption;
setOption(String option) async {
await optionSetter(option);
final opt = await curOptionGetter();
if (_curOption.value != opt) {
_curOption.value = opt;
}
}
mod_menu.PopupMenuEntry<T> _buildMenuItem(
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero,
height: conf.height,
child: TextButton(
child: Container(
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
child: Row(
children: [
Text(
opt.item1,
style: const TextStyle(
color: Colors.black,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Obx(() => opt.item2 == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
: const SizedBox.shrink())),
)),
],
),
),
onPressed: () {
if (opt.item2 != curOption.value) {
setOption(opt.item2);
}
},
),
);
}
@override
List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) {
return options.map((opt) => _buildMenuItem(context, conf, opt)).toList();
}
}
class MenuEntrySubRadios<T> extends MenuEntryBase<T> { class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
final String text; final String text;
final RadioOptionsGetter optionsGetter; final RadioOptionsGetter optionsGetter;
@ -138,33 +222,37 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
} }
} }
modMenu.PopupMenuEntry<T> _buildSecondMenu( mod_menu.PopupMenuEntry<T> _buildSecondMenu(
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) { BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
return modMenu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), constraints: BoxConstraints(minHeight: conf.height),
child: Row( child: Row(
children: [ children: [
SizedBox(
width: 20.0,
height: 20.0,
child: Obx(() => opt.item2 == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
: SizedBox.shrink())),
const SizedBox(width: MenuConfig.midPadding),
Text( Text(
opt.item1, opt.item1,
style: const TextStyle( style: const TextStyle(
color: Colors.black, color: Colors.black,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
) ),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Obx(() => opt.item2 == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
: const SizedBox.shrink())),
)),
], ],
), ),
), ),
@ -178,31 +266,34 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
} }
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { List<mod_menu.PopupMenuEntry<T>> build(
return PopupMenuChildrenItem( BuildContext context, MenuConfig conf) {
height: conf.secondMenuHeight, return [
padding: EdgeInsets.zero, PopupMenuChildrenItem(
itemBuilder: (BuildContext context) => padding: EdgeInsets.zero,
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(), height: conf.height,
child: Row(children: [ itemBuilder: (BuildContext context) =>
const SizedBox(width: MenuConfig.midPadding), options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
Text( child: Row(children: [
text, const SizedBox(width: MenuConfig.midPadding),
style: const TextStyle( Text(
color: Colors.black, text,
fontSize: MenuConfig.fontSize, style: const TextStyle(
fontWeight: FontWeight.normal), color: Colors.black,
), fontSize: MenuConfig.fontSize,
Expanded( fontWeight: FontWeight.normal),
child: Align(
alignment: Alignment.centerRight,
child: Icon(
Icons.keyboard_arrow_right,
color: conf.commonColor,
), ),
)) Expanded(
]), child: Align(
); alignment: Alignment.centerRight,
child: Icon(
Icons.keyboard_arrow_right,
color: conf.commonColor,
),
))
]),
)
];
} }
} }
@ -218,34 +309,38 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
Future<void> setOption(bool option); Future<void> setOption(bool option);
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { List<mod_menu.PopupMenuEntry<T>> build(
return modMenu.PopupMenuItem( BuildContext context, MenuConfig conf) {
padding: EdgeInsets.zero, return [
child: Obx( mod_menu.PopupMenuItem(
() => SwitchListTile( padding: EdgeInsets.zero,
value: curOption.value, height: conf.height,
onChanged: (v) { child: Obx(
setOption(v); () => SwitchListTile(
}, value: curOption.value,
title: Container( onChanged: (v) {
alignment: AlignmentDirectional.centerStart, setOption(v);
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), },
child: Text( title: Container(
text, alignment: AlignmentDirectional.centerStart,
style: const TextStyle( constraints: BoxConstraints(minHeight: conf.height),
color: Colors.black, child: Text(
fontSize: MenuConfig.fontSize, text,
fontWeight: FontWeight.normal), style: const TextStyle(
)), color: Colors.black,
dense: true, fontSize: MenuConfig.fontSize,
visualDensity: const VisualDensity( fontWeight: FontWeight.normal),
horizontal: VisualDensity.minimumDensity, )),
vertical: VisualDensity.minimumDensity, dense: true,
visualDensity: const VisualDensity(
horizontal: VisualDensity.minimumDensity,
vertical: VisualDensity.minimumDensity,
),
contentPadding: const EdgeInsets.only(left: 8.0),
), ),
contentPadding: EdgeInsets.only(left: 8.0),
), ),
), )
); ];
} }
} }
@ -303,32 +398,37 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
}); });
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { List<mod_menu.PopupMenuEntry<T>> build(
return PopupMenuChildrenItem( BuildContext context, MenuConfig conf) {
height: conf.secondMenuHeight, return [
padding: EdgeInsets.zero, PopupMenuChildrenItem(
position: modMenu.PopupMenuPosition.overSide, height: conf.height,
itemBuilder: (BuildContext context) => padding: EdgeInsets.zero,
entries.map((entry) => entry.build(context, conf)).toList(), position: mod_menu.PopupMenuPosition.overSide,
child: Row(children: [ itemBuilder: (BuildContext context) => entries
const SizedBox(width: MenuConfig.midPadding), .map((entry) => entry.build(context, conf))
Text( .expand((i) => i)
text, .toList(),
style: const TextStyle( child: Row(children: [
color: Colors.black, const SizedBox(width: MenuConfig.midPadding),
fontSize: MenuConfig.fontSize, Text(
fontWeight: FontWeight.normal), text,
), style: const TextStyle(
Expanded( color: Colors.black,
child: Align( fontSize: MenuConfig.fontSize,
alignment: Alignment.centerRight, fontWeight: FontWeight.normal),
child: Icon(
Icons.keyboard_arrow_right,
color: conf.commonColor,
), ),
)) Expanded(
]), child: Align(
); alignment: Alignment.centerRight,
child: Icon(
Icons.keyboard_arrow_right,
color: conf.commonColor,
),
))
]),
)
];
} }
} }
@ -342,34 +442,27 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
}); });
@override @override
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) { List<mod_menu.PopupMenuEntry<T>> build(
return modMenu.PopupMenuItem( BuildContext context, MenuConfig conf) {
padding: EdgeInsets.zero, return [
child: TextButton( mod_menu.PopupMenuItem(
child: Container( padding: EdgeInsets.zero,
alignment: AlignmentDirectional.centerStart, height: conf.height,
constraints: BoxConstraints(minHeight: conf.secondMenuHeight), child: TextButton(
child: childBuilder( child: Container(
const TextStyle( alignment: AlignmentDirectional.centerStart,
color: Colors.black, constraints: BoxConstraints(minHeight: conf.height),
fontSize: MenuConfig.fontSize, child: childBuilder(
fontWeight: FontWeight.normal), const TextStyle(
)), color: Colors.black,
onPressed: () { fontSize: MenuConfig.fontSize,
proc(); fontWeight: FontWeight.normal),
}, )),
), onPressed: () {
); proc();
} },
} ),
)
class CustomMenu<T> { ];
final List<MenuEntryBase<T>> entries;
final MenuConfig conf;
const CustomMenu({required this.entries, required this.conf});
List<modMenu.PopupMenuEntry<T>> build(BuildContext context) {
return entries.map((entry) => entry.build(context, conf)).toList();
} }
} }

View File

@ -9,12 +9,15 @@ import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart'; import '../../mobile/widgets/overlay.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import './popup_menu.dart'; import './popup_menu.dart';
import './material_mod_popup_menu.dart' as mod_menu; import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme { class _MenubarTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
static const double height = kMinInteractiveDimension; // kMinInteractiveDimension
static const double height = 24.0;
static const double dividerHeight = 12.0;
} }
class RemoteMenubar extends StatefulWidget { class RemoteMenubar extends StatefulWidget {
@ -168,11 +171,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
), ),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
final List<Widget> rowChildren = []; final List<Widget> rowChildren = [];
const double selectorScale = 1.3;
for (int i = 0; i < pi.displays.length; i++) { for (int i = 0; i < pi.displays.length; i++) {
rowChildren.add(Transform.scale( rowChildren.add(
scale: selectorScale, Stack(
child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
const Icon( const Icon(
@ -203,7 +204,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
) )
], ],
), ),
)); );
} }
return <mod_menu.PopupMenuEntry<String>>[ return <mod_menu.PopupMenuEntry<String>>[
mod_menu.PopupMenuItem<String>( mod_menu.PopupMenuItem<String>(
@ -232,8 +233,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
context, context,
const MenuConfig( const MenuConfig(
commonColor: _MenubarTheme.commonColor, commonColor: _MenubarTheme.commonColor,
secondMenuHeight: _MenubarTheme.height, height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
))) )))
.expand((i) => i)
.toList(), .toList(),
); );
} }
@ -253,8 +256,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
context, context,
const MenuConfig( const MenuConfig(
commonColor: _MenubarTheme.commonColor, commonColor: _MenubarTheme.commonColor,
secondMenuHeight: _MenubarTheme.height, height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
))) )))
.expand((i) => i)
.toList(), .toList(),
); );
} }
@ -398,7 +403,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getDisplayMenu() { List<MenuEntryBase<String>> _getDisplayMenu() {
final displayMenu = [ final displayMenu = [
MenuEntrySubRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('Original'), 'original'), Tuple2<String, String>(translate('Original'), 'original'),
@ -415,7 +420,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
id: widget.id, name: "view-style", value: v); id: widget.id, name: "view-style", value: v);
widget.ffi.canvasModel.updateViewStyle(); widget.ffi.canvasModel.updateViewStyle();
}), }),
MenuEntrySubRadios<String>( MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Scroll Style'), text: translate('Scroll Style'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('ScrollAuto'), 'scrollauto'), Tuple2<String, String>(translate('ScrollAuto'), 'scrollauto'),
@ -431,7 +437,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
id: widget.id, name: "scroll-style", value: v); id: widget.id, name: "scroll-style", value: v);
widget.ffi.canvasModel.updateScrollStyle(); widget.ffi.canvasModel.updateScrollStyle();
}), }),
MenuEntrySubRadios<String>( MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Image Quality'), text: translate('Image Quality'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('Good image quality'), 'best'), Tuple2<String, String>(translate('Good image quality'), 'best'),
@ -448,6 +455,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
optionSetter: (String v) async { optionSetter: (String v) async {
await bind.sessionSetImageQuality(id: widget.id, value: v); await bind.sessionSetImageQuality(id: widget.id, value: v);
}), }),
MenuEntryDivider<String>(),
MenuEntrySwitch<String>( MenuEntrySwitch<String>(
text: translate('Show remote cursor'), text: translate('Show remote cursor'),
getter: () async { getter: () async {

View File

@ -121,6 +121,16 @@ class DesktopTabController {
} }
} }
class TabThemeConf {
double iconSize;
TarBarTheme theme;
TabThemeConf({required this.iconSize, required this.theme});
}
typedef TabBuilder = Widget Function(
String key, Widget icon, Widget label, TabThemeConf themeConf);
typedef LabelGetter = Rx<String> Function(String key);
class DesktopTab extends StatelessWidget { class DesktopTab extends StatelessWidget {
final Function(String)? onTabClose; final Function(String)? onTabClose;
final TarBarTheme theme; final TarBarTheme theme;
@ -134,24 +144,29 @@ class DesktopTab extends StatelessWidget {
final Widget Function(Widget pageView)? pageViewBuilder; final Widget Function(Widget pageView)? pageViewBuilder;
final Widget? tail; final Widget? tail;
final VoidCallback? onClose; final VoidCallback? onClose;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
final DesktopTabController controller; final DesktopTabController controller;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
const DesktopTab( const DesktopTab({
{required this.controller, required this.controller,
required this.isMainWindow, required this.isMainWindow,
this.theme = const TarBarTheme.light(), this.theme = const TarBarTheme.light(),
this.onTabClose, this.onTabClose,
this.showTabBar = true, this.showTabBar = true,
this.showLogo = true, this.showLogo = true,
this.showTitle = true, this.showTitle = true,
this.showMinimize = true, this.showMinimize = true,
this.showMaximize = true, this.showMaximize = true,
this.showClose = true, this.showClose = true,
this.pageViewBuilder, this.pageViewBuilder,
this.tail, this.tail,
this.onClose}); this.onClose,
this.tabBuilder,
this.labelGetter,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -194,8 +209,10 @@ class DesktopTab extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
Offstage( Offstage(
offstage: !Platform.isMacOS, offstage: !Platform.isMacOS,
child: const SizedBox(width: 78,)), child: const SizedBox(
width: 78,
)),
Row(children: [ Row(children: [
Offstage( Offstage(
offstage: !showLogo, offstage: !showLogo,
@ -228,6 +245,8 @@ class DesktopTab extends StatelessWidget {
controller: controller, controller: controller,
onTabClose: onTabClose, onTabClose: onTabClose,
theme: theme, theme: theme,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
)), )),
), ),
], ],
@ -356,10 +375,18 @@ class _ListView extends StatelessWidget {
final DesktopTabController controller; final DesktopTabController controller;
final Function(String key)? onTabClose; final Function(String key)? onTabClose;
final TarBarTheme theme; final TarBarTheme theme;
final TabBuilder? tabBuilder;
final LabelGetter? labelGetter;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
const _ListView( _ListView(
{required this.controller, required this.onTabClose, required this.theme}); {required this.controller,
required this.onTabClose,
required this.theme,
this.tabBuilder,
this.labelGetter});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -373,7 +400,9 @@ class _ListView extends StatelessWidget {
final tab = e.value; final tab = e.value;
return _Tab( return _Tab(
index: index, index: index,
label: tab.label, label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon, selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon, unselectedIcon: tab.unselectedIcon,
closable: tab.closable, closable: tab.closable,
@ -381,6 +410,16 @@ class _ListView extends StatelessWidget {
onClose: () => controller.remove(index), onClose: () => controller.remove(index),
onSelected: () => controller.jumpTo(index), onSelected: () => controller.jumpTo(index),
theme: theme, theme: theme,
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
},
); );
}).toList())); }).toList()));
} }
@ -388,7 +427,7 @@ class _ListView extends StatelessWidget {
class _Tab extends StatelessWidget { class _Tab extends StatelessWidget {
late final int index; late final int index;
late final String label; late final Rx<String> label;
late final IconData? selectedIcon; late final IconData? selectedIcon;
late final IconData? unselectedIcon; late final IconData? unselectedIcon;
late final bool closable; late final bool closable;
@ -397,6 +436,8 @@ class _Tab extends StatelessWidget {
late final Function() onSelected; late final Function() onSelected;
final RxBool _hover = false.obs; final RxBool _hover = false.obs;
late final TarBarTheme theme; late final TarBarTheme theme;
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
tabBuilder;
_Tab( _Tab(
{Key? key, {Key? key,
@ -404,6 +445,7 @@ class _Tab extends StatelessWidget {
required this.label, required this.label,
this.selectedIcon, this.selectedIcon,
this.unselectedIcon, this.unselectedIcon,
this.tabBuilder,
required this.closable, required this.closable,
required this.selected, required this.selected,
required this.onClose, required this.onClose,
@ -411,11 +453,49 @@ class _Tab extends StatelessWidget {
required this.theme}) required this.theme})
: super(key: key); : super(key: key);
Widget _buildTabContent() {
bool showIcon = selectedIcon != null && unselectedIcon != null;
bool isSelected = index == selected;
final icon = Offstage(
offstage: !showIcon,
child: Icon(
isSelected ? selectedIcon : unselectedIcon,
size: _kIconSize,
color: isSelected
? theme.selectedtabIconColor
: theme.unSelectedtabIconColor,
).paddingOnly(right: 5));
final labelWidget = Obx(() {
return Text(
translate(label.value),
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected
? theme.selectedTextColor
: theme.unSelectedTextColor),
);
});
if (tabBuilder == null) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
labelWidget,
],
);
} else {
return tabBuilder!(
icon, labelWidget, TabThemeConf(iconSize: _kIconSize, theme: theme));
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool show_icon = selectedIcon != null && unselectedIcon != null; bool showIcon = selectedIcon != null && unselectedIcon != null;
bool is_selected = index == selected; bool isSelected = index == selected;
bool show_divider = index != selected - 1 && index != selected; bool showDivider = index != selected - 1 && index != selected;
return Ink( return Ink(
child: InkWell( child: InkWell(
onHover: (hover) => _hover.value = hover, onHover: (hover) => _hover.value = hover,
@ -427,40 +507,19 @@ class _Tab extends StatelessWidget {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( _buildTabContent(),
mainAxisAlignment: MainAxisAlignment.center,
children: [
Offstage(
offstage: !show_icon,
child: Icon(
is_selected ? selectedIcon : unselectedIcon,
size: _kIconSize,
color: is_selected
? theme.selectedtabIconColor
: theme.unSelectedtabIconColor,
).paddingOnly(right: 5)),
Text(
translate(label),
textAlign: TextAlign.center,
style: TextStyle(
color: is_selected
? theme.selectedTextColor
: theme.unSelectedTextColor),
),
],
),
Offstage( Offstage(
offstage: !closable, offstage: !closable,
child: Obx((() => _CloseButton( child: Obx((() => _CloseButton(
visiable: _hover.value, visiable: _hover.value,
tabSelected: is_selected, tabSelected: isSelected,
onClose: () => onClose(), onClose: () => onClose(),
theme: theme, theme: theme,
))), ))),
) )
])).paddingSymmetric(horizontal: 10), ])).paddingSymmetric(horizontal: 10),
Offstage( Offstage(
offstage: !show_divider, offstage: !showDivider,
child: VerticalDivider( child: VerticalDivider(
width: 1, width: 1,
indent: _kDividerIndent, indent: _kDividerIndent,

View File

@ -202,7 +202,7 @@ class App extends StatelessWidget {
title: 'RustDesk', title: 'RustDesk',
theme: getCurrentTheme(), theme: getCurrentTheme(),
home: isDesktop home: isDesktop
? DesktopTabPage() ? const DesktopTabPage()
: !isAndroid : !isAndroid
? WebHomePage() ? WebHomePage()
: HomePage(), : HomePage(),

View File

@ -17,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../common.dart'; import '../common.dart';
import '../common/shared_state.dart';
import '../mobile/widgets/dialog.dart'; import '../mobile/widgets/dialog.dart';
import '../mobile/widgets/overlay.dart'; import '../mobile/widgets/overlay.dart';
import 'peer_model.dart'; import 'peer_model.dart';
@ -96,25 +97,26 @@ class FfiModel with ChangeNotifier {
clearPermissions(); clearPermissions();
} }
void setConnectionType(bool secure, bool direct) { void setConnectionType(String peerId, bool secure, bool direct) {
_secure = secure; _secure = secure;
_direct = direct; _direct = direct;
try {
var connectionType = ConnectionTypeState.find(peerId);
connectionType.setSecure(secure);
connectionType.setDirect(direct);
} catch (e) {
//
}
} }
Image? getConnectionImage() { Image? getConnectionImage() {
String? icon; if (secure == null || direct == null) {
if (secure == true && direct == true) { return null;
icon = 'secure'; } else {
} else if (secure == false && direct == true) { final icon =
icon = 'insecure'; '${secure == true ? "secure" : "insecure"}${direct == true ? "" : "_relay"}';
} else if (secure == false && direct == false) { return Image.asset('assets/$icon.png', width: 48, height: 48);
icon = 'insecure_relay';
} else if (secure == true && direct == false) {
icon = 'secure_relay';
} }
return icon == null
? null
: Image.asset('assets/$icon.png', width: 48, height: 48);
} }
void clearPermissions() { void clearPermissions() {
@ -130,7 +132,8 @@ class FfiModel with ChangeNotifier {
} else if (name == 'peer_info') { } else if (name == 'peer_info') {
handlePeerInfo(evt, peerId); handlePeerInfo(evt, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
setConnectionType(evt['secure'] == 'true', evt['direct'] == 'true'); setConnectionType(
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
} else if (name == 'switch_display') { } else if (name == 'switch_display') {
handleSwitchDisplay(evt); handleSwitchDisplay(evt);
} else if (name == 'cursor_data') { } else if (name == 'cursor_data') {
@ -189,7 +192,7 @@ class FfiModel with ChangeNotifier {
handlePeerInfo(evt, peerId); handlePeerInfo(evt, peerId);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
parent.target?.ffiModel.setConnectionType( parent.target?.ffiModel.setConnectionType(
evt['secure'] == 'true', evt['direct'] == 'true'); peerId, evt['secure'] == 'true', evt['direct'] == 'true');
} else if (name == 'switch_display') { } else if (name == 'switch_display') {
handleSwitchDisplay(evt); handleSwitchDisplay(evt);
} else if (name == 'cursor_data') { } else if (name == 'cursor_data') {
@ -1067,8 +1070,10 @@ class FFI {
imageModel._id = id; imageModel._id = id;
cursorModel.id = id; cursorModel.id = id;
} }
final stream = bind.sessionConnect( // ignore: unused_local_variable
final addRes = bind.sessionAddSync(
id: id, isFileTransfer: isFileTransfer, isPortForward: isPortForward); id: id, isFileTransfer: isFileTransfer, isPortForward: isPortForward);
final stream = bind.sessionStart(id: id);
final cb = ffiModel.startEventListener(id); final cb = ffiModel.startEventListener(id);
() async { () async {
await for (final message in stream) { await for (final message in stream) {

View File

@ -40,10 +40,7 @@ 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_2: dash_chat_2: ^0.0.14
git:
url: https://github.com/fufesou/Dash-Chat-2
ref: feat_maxWidth
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

View File

@ -847,7 +847,7 @@ impl VideoHandler {
pub struct LoginConfigHandler { pub struct LoginConfigHandler {
id: String, id: String,
pub is_file_transfer: bool, pub is_file_transfer: bool,
is_port_forward: bool, pub is_port_forward: bool,
hash: Hash, hash: Hash,
password: Vec<u8>, // remember password for reconnect password: Vec<u8>, // remember password for reconnect
pub remember: bool, pub remember: bool,

View File

@ -8,16 +8,13 @@ use std::{
use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer}; use flutter_rust_bridge::{StreamSink, ZeroCopyBuffer};
use hbb_common::config::{PeerConfig, TransferSerde};
use hbb_common::fs::get_job;
use hbb_common::{ use hbb_common::{
allow_err, allow_err, bail,
compress::decompress, compress::decompress,
config::{Config, LocalConfig}, config::{Config, LocalConfig, PeerConfig, TransferSerde},
fs,
fs::{ fs::{
can_enable_overwrite_detection, get_string, new_send_confirm, transform_windows_path, self, can_enable_overwrite_detection, get_job, get_string, new_send_confirm,
DigestCheckResult, transform_windows_path, DigestCheckResult,
}, },
log, log,
message_proto::*, message_proto::*,
@ -28,7 +25,7 @@ use hbb_common::{
sync::mpsc, sync::mpsc,
time::{self, Duration, Instant, Interval}, time::{self, Duration, Instant, Interval},
}, },
Stream, ResultType, Stream,
}; };
use crate::common::{self, make_fd_to_json, CLIPBOARD_INTERVAL}; use crate::common::{self, make_fd_to_json, CLIPBOARD_INTERVAL};
@ -60,7 +57,7 @@ pub struct Session {
id: String, id: String,
sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>, // UI to rust sender: Arc<RwLock<Option<mpsc::UnboundedSender<Data>>>>, // UI to rust
lc: Arc<RwLock<LoginConfigHandler>>, lc: Arc<RwLock<LoginConfigHandler>>,
events2ui: Arc<RwLock<StreamSink<EventToUI>>>, events2ui: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
} }
impl Session { impl Session {
@ -71,23 +68,17 @@ impl Session {
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+ /// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
/// * `is_file_transfer` - If the session is used for file transfer. /// * `is_file_transfer` - If the session is used for file transfer.
/// * `is_port_forward` - If the session is used for port forward. /// * `is_port_forward` - If the session is used for port forward.
pub fn start( pub fn add(id: &str, is_file_transfer: bool, is_port_forward: bool) -> ResultType<()> {
identifier: &str,
is_file_transfer: bool,
is_port_forward: bool,
events2ui: StreamSink<EventToUI>,
) {
// TODO check same id // TODO check same id
let session_id = get_session_id(identifier.to_owned()); let session_id = get_session_id(id.to_owned());
LocalConfig::set_remote_id(&session_id); LocalConfig::set_remote_id(&session_id);
// TODO close // TODO close
// Self::close(); // Self::close();
let events2ui = Arc::new(RwLock::new(events2ui));
let session = Session { let session = Session {
id: session_id.clone(), id: session_id.clone(),
sender: Default::default(), sender: Default::default(),
lc: Default::default(), lc: Default::default(),
events2ui, events2ui: Arc::new(RwLock::new(None)),
}; };
session.lc.write().unwrap().initialize( session.lc.write().unwrap().initialize(
session_id.clone(), session_id.clone(),
@ -97,10 +88,29 @@ impl Session {
SESSIONS SESSIONS
.write() .write()
.unwrap() .unwrap()
.insert(identifier.to_owned(), session.clone()); .insert(id.to_owned(), session.clone());
std::thread::spawn(move || { Ok(())
Connection::start(session, is_file_transfer, is_port_forward); }
});
/// Create a new remote session with the given id.
///
/// # Arguments
///
/// * `id` - The identifier of the remote session with prefix. Regex: [\w]*[\_]*[\d]+
/// * `events2ui` - The events channel to ui.
pub fn start(id: &str, events2ui: StreamSink<EventToUI>) -> ResultType<()> {
if let Some(session) = SESSIONS.write().unwrap().get_mut(id) {
*session.events2ui.write().unwrap() = Some(events2ui);
let session = session.clone();
std::thread::spawn(move || {
let is_file_transfer = session.lc.read().unwrap().is_file_transfer;
let is_port_forward = session.lc.read().unwrap().is_port_forward;
Connection::start(session, is_file_transfer, is_port_forward);
});
Ok(())
} else {
bail!("No session with peer id {}", id)
}
} }
/// Get the current session instance. /// Get the current session instance.
@ -305,7 +315,9 @@ impl Session {
assert!(h.get("name").is_none()); assert!(h.get("name").is_none());
h.insert("name", name); h.insert("name", name);
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned()); let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
self.events2ui.read().unwrap().add(EventToUI::Event(out)); if let Some(stream) = &*self.events2ui.read().unwrap() {
stream.add(EventToUI::Event(out));
}
} }
/// Get platform of peer. /// Get platform of peer.
@ -998,11 +1010,12 @@ impl Connection {
}) })
}; };
if let Ok(true) = self.video_handler.handle_frame(vf) { if let Ok(true) = self.video_handler.handle_frame(vf) {
let stream = self.session.events2ui.read().unwrap(); if let Some(stream) = &*self.session.events2ui.read().unwrap() {
self.frame_count.fetch_add(1, Ordering::Relaxed); self.frame_count.fetch_add(1, Ordering::Relaxed);
stream.add(EventToUI::Rgba(ZeroCopyBuffer( stream.add(EventToUI::Rgba(ZeroCopyBuffer(
self.video_handler.rgb.clone(), self.video_handler.rgb.clone(),
))); )));
}
} }
} }
Some(message::Union::Hash(hash)) => { Some(message::Union::Hash(hash)) => {

View File

@ -107,14 +107,18 @@ pub fn host_stop_system_key_propagate(stopped: bool) {
crate::platform::windows::stop_system_key_propagate(stopped); crate::platform::windows::stop_system_key_propagate(stopped);
} }
pub fn session_connect( // FIXME: -> ResultType<()> cannot be parsed by frb_codegen
events2ui: StreamSink<EventToUI>, // thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25
id: String, pub fn session_add_sync(id: String, is_file_transfer: bool, is_port_forward: bool) -> SyncReturn<String> {
is_file_transfer: bool, if let Err(e) = Session::add(&id, is_file_transfer, is_port_forward) {
is_port_forward: bool, SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
) -> ResultType<()> { } else {
Session::start(&id, is_file_transfer, is_port_forward, events2ui); SyncReturn("".to_owned())
Ok(()) }
}
pub fn session_start(events2ui: StreamSink<EventToUI>, id: String) -> ResultType<()> {
Session::start(&id, events2ui)
} }
pub fn session_get_remember(id: String) -> Option<bool> { pub fn session_get_remember(id: String) -> Option<bool> {
@ -602,7 +606,12 @@ pub fn main_load_lan_peers() {
}; };
} }
pub fn session_add_port_forward(id: String, local_port: i32, remote_host: String, remote_port: i32) { pub fn session_add_port_forward(
id: String,
local_port: i32,
remote_host: String,
remote_port: i32,
) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.add_port_forward(local_port, remote_host, remote_port); session.add_port_forward(local_port, remote_host, remote_port);
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "滚屏方式"), ("Scroll Style", "滚屏方式"),
("Show Menubar", "显示菜单栏"), ("Show Menubar", "显示菜单栏"),
("Hide Menubar", "隐藏菜单栏"), ("Hide Menubar", "隐藏菜单栏"),
("Direct Connection", "直接连接"),
("Relay Connection", "中继连接"),
("Secure Connection", "安全连接"),
("Insecure Connection", "非安全连接"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Štýl posúvania"), ("Scroll Style", "Štýl posúvania"),
("Show Menubar", "Zobrazit panel nabídek"), ("Show Menubar", "Zobrazit panel nabídek"),
("Hide Menubar", "skrýt panel nabídek"), ("Hide Menubar", "skrýt panel nabídek"),
("Direct Connection", "Přímé spojení"),
("Relay Connection", "Připojení relé"),
("Secure Connection", "Zabezpečené připojení"),
("Insecure Connection", "Nezabezpečené připojení"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Rulstil"), ("Scroll Style", "Rulstil"),
("Show Menubar", "Vis menulinje"), ("Show Menubar", "Vis menulinje"),
("Hide Menubar", "skjul menulinjen"), ("Hide Menubar", "skjul menulinjen"),
("Direct Connection", "Direkte forbindelse"),
("Relay Connection", "Relæforbindelse"),
("Secure Connection", "Sikker forbindelse"),
("Insecure Connection", "Usikker forbindelse"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Scroll-Stil"), ("Scroll Style", "Scroll-Stil"),
("Show Menubar", "Menüleiste anzeigen"), ("Show Menubar", "Menüleiste anzeigen"),
("Hide Menubar", "Menüleiste ausblenden"), ("Hide Menubar", "Menüleiste ausblenden"),
("Direct Connection", "Direkte Verbindung"),
("Relay Connection", "Relaisverbindung"),
("Secure Connection", "Sichere Verbindung"),
("Insecure Connection", "Unsichere Verbindung"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Ruluma Stilo"), ("Scroll Style", "Ruluma Stilo"),
("Show Menubar", "Montru menubreton"), ("Show Menubar", "Montru menubreton"),
("Hide Menubar", "kaŝi menubreton"), ("Hide Menubar", "kaŝi menubreton"),
("Direct Connection", "Rekta Konekto"),
("Relay Connection", "Relajsa Konekto"),
("Secure Connection", "Sekura Konekto"),
("Insecure Connection", "Nesekura Konekto"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Estilo de desplazamiento"), ("Scroll Style", "Estilo de desplazamiento"),
("Show Menubar", "ajustes de pantalla"), ("Show Menubar", "ajustes de pantalla"),
("Hide Menubar", "ocultar barra de menú"), ("Hide Menubar", "ocultar barra de menú"),
("Direct Connection", "Conexión directa"),
("Relay Connection", "Conexión de relé"),
("Secure Connection", "Conexión segura"),
("Insecure Connection", "Conexión insegura"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Style de défilement"), ("Scroll Style", "Style de défilement"),
("Show Menubar", "Afficher la barre de menus"), ("Show Menubar", "Afficher la barre de menus"),
("Hide Menubar", "masquer la barre de menus"), ("Hide Menubar", "masquer la barre de menus"),
("Direct Connection", "Connexion directe"),
("Relay Connection", "Connexion relais"),
("Secure Connection", "Connexion sécurisée"),
("Insecure Connection", "Connexion non sécurisée"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Görgetési stílus"), ("Scroll Style", "Görgetési stílus"),
("Show Menubar", "Menüsor megjelenítése"), ("Show Menubar", "Menüsor megjelenítése"),
("Hide Menubar", "menüsor elrejtése"), ("Hide Menubar", "menüsor elrejtése"),
("Direct Connection", "Közvetlen kapcsolat"),
("Relay Connection", "Relé csatlakozás"),
("Secure Connection", "Biztonságos kapcsolat"),
("Insecure Connection", "Nem biztonságos kapcsolat"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Gaya Gulir"), ("Scroll Style", "Gaya Gulir"),
("Show Menubar", "Tampilkan bilah menu"), ("Show Menubar", "Tampilkan bilah menu"),
("Hide Menubar", "sembunyikan bilah menu"), ("Hide Menubar", "sembunyikan bilah menu"),
("Direct Connection", "Koneksi langsung"),
("Relay Connection", "Koneksi Relay"),
("Secure Connection", "Koneksi aman"),
("Insecure Connection", "Koneksi Tidak Aman"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -312,5 +312,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Stile di scorrimento"), ("Scroll Style", "Stile di scorrimento"),
("Show Menubar", "Mostra la barra dei menu"), ("Show Menubar", "Mostra la barra dei menu"),
("Hide Menubar", "nascondi la barra dei menu"), ("Hide Menubar", "nascondi la barra dei menu"),
("Direct Connection", "Connessione diretta"),
("Relay Connection", "Collegamento a relè"),
("Secure Connection", "Connessione sicura"),
("Insecure Connection", "Connessione insicura"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "スクロール スタイル"), ("Scroll Style", "スクロール スタイル"),
("Show Menubar", "メニューバーを表示"), ("Show Menubar", "メニューバーを表示"),
("Hide Menubar", "メニューバーを隠す"), ("Hide Menubar", "メニューバーを隠す"),
("Direct Connection", "直接接続"),
("Relay Connection", "リレー接続"),
("Secure Connection", "安全な接続"),
("Insecure Connection", "安全でない接続"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "스크롤 스타일"), ("Scroll Style", "스크롤 스타일"),
("Show Menubar", "메뉴 표시줄 표시"), ("Show Menubar", "메뉴 표시줄 표시"),
("Hide Menubar", "메뉴 표시줄 숨기기"), ("Hide Menubar", "메뉴 표시줄 숨기기"),
("Direct Connection", "직접 연결"),
("Relay Connection", "릴레이 연결"),
("Secure Connection", "보안 연결"),
("Insecure Connection", "안전하지 않은 연결"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -314,5 +314,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Styl przewijania"), ("Scroll Style", "Styl przewijania"),
("Show Menubar", "Pokaż pasek menu"), ("Show Menubar", "Pokaż pasek menu"),
("Hide Menubar", "ukryj pasek menu"), ("Hide Menubar", "ukryj pasek menu"),
("Direct Connection", "Bezpośrednie połączenie"),
("Relay Connection", "Połączenie przekaźnika"),
("Secure Connection", "Bezpieczne połączenie"),
("Insecure Connection", "Niepewne połączenie"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -310,5 +310,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Estilo de rolagem"), ("Scroll Style", "Estilo de rolagem"),
("Show Menubar", "Mostrar barra de menus"), ("Show Menubar", "Mostrar barra de menus"),
("Hide Menubar", "ocultar barra de menu"), ("Hide Menubar", "ocultar barra de menu"),
("Direct Connection", "Conexão direta"),
("Relay Connection", "Conexão de relé"),
("Secure Connection", "Conexão segura"),
("Insecure Connection", "Conexão insegura"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", ""), ("Scroll Style", ""),
("Show Menubar", ""), ("Show Menubar", ""),
("Hide Menubar", ""), ("Hide Menubar", ""),
("Direct Connection", ""),
("Relay Connection", ""),
("Secure Connection", ""),
("Insecure Connection", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Стиль прокрутки"), ("Scroll Style", "Стиль прокрутки"),
("Show Menubar", "Показать строку меню"), ("Show Menubar", "Показать строку меню"),
("Hide Menubar", "скрыть строку меню"), ("Hide Menubar", "скрыть строку меню"),
("Direct Connection", "Прямая связь"),
("Relay Connection", "Релейное соединение"),
("Secure Connection", "Безопасное соединение"),
("Insecure Connection", "Небезопасное соединение"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Štýl posúvania"), ("Scroll Style", "Štýl posúvania"),
("Show Menubar", "Zobraziť panel s ponukami"), ("Show Menubar", "Zobraziť panel s ponukami"),
("Hide Menubar", "skryť panel s ponukami"), ("Hide Menubar", "skryť panel s ponukami"),
("Direct Connection", "Priame pripojenie"),
("Relay Connection", "Reléové pripojenie"),
("Secure Connection", "Zabezpečené pripojenie"),
("Insecure Connection", "Nezabezpečené pripojenie"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", ""), ("Scroll Style", ""),
("Show Menubar", ""), ("Show Menubar", ""),
("Hide Menubar", ""), ("Hide Menubar", ""),
("Direct Connection", ""),
("Relay Connection", ""),
("Secure Connection", ""),
("Insecure Connection", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Kaydırma Stili"), ("Scroll Style", "Kaydırma Stili"),
("Show Menubar", "Menü çubuğunu göster"), ("Show Menubar", "Menü çubuğunu göster"),
("Hide Menubar", "menü çubuğunu gizle"), ("Hide Menubar", "menü çubuğunu gizle"),
("Direct Connection", "Doğrudan Bağlantı"),
("Relay Connection", "Röle Bağlantısı"),
("Secure Connection", "Güvenli bağlantı"),
("Insecure Connection", "Güvenli Bağlantı"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "滾動樣式"), ("Scroll Style", "滾動樣式"),
("Show Menubar", "顯示菜單欄"), ("Show Menubar", "顯示菜單欄"),
("Hide Menubar", "隱藏菜單欄"), ("Hide Menubar", "隱藏菜單欄"),
("Direct Connection", "直接連接"),
("Relay Connection", "中繼連接"),
("Secure Connection", "安全連接"),
("Insecure Connection", "非安全連接"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -313,5 +313,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scroll Style", "Kiểu cuộn"), ("Scroll Style", "Kiểu cuộn"),
("Show Menubar", "Hiển thị thanh menu"), ("Show Menubar", "Hiển thị thanh menu"),
("Hide Menubar", "ẩn thanh menu"), ("Hide Menubar", "ẩn thanh menu"),
("Direct Connection", "Kết nối trực tiếp"),
("Relay Connection", "Kết nối chuyển tiếp"),
("Secure Connection", "Kết nối an toàn"),
("Insecure Connection", "Kết nối không an toàn"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }