Merge pull request #1404 from fufesou/flutter_desktop_new_remote_menu
Flutter desktop new remote menu
This commit is contained in:
commit
694896abda
@ -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));
|
|
||||||
}
|
|
||||||
|
87
flutter/lib/common/shared_state.dart
Normal file
87
flutter/lib/common/shared_state.dart
Normal 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));
|
||||||
|
}
|
@ -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";
|
||||||
|
@ -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() {
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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(),
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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)) => {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user