flutter_desktop: new remote menu, mid commit
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
7b4a655eaf
commit
ea77d9284b
@ -447,7 +447,10 @@ void msgBox(
|
|||||||
0,
|
0,
|
||||||
wrap(translate('OK'), () {
|
wrap(translate('OK'), () {
|
||||||
dialogManager.dismissAll();
|
dialogManager.dismissAll();
|
||||||
|
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
|
||||||
|
if (type.indexOf("custom") < 0) {
|
||||||
closeConnection();
|
closeConnection();
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (hasCancel == null) {
|
if (hasCancel == null) {
|
||||||
@ -740,3 +743,39 @@ 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));
|
||||||
|
}
|
||||||
|
@ -22,26 +22,25 @@ 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 Rx<String> _fullscreenID = "".obs;
|
|
||||||
static final IconData selectedIcon = Icons.desktop_windows_sharp;
|
static final IconData selectedIcon = Icons.desktop_windows_sharp;
|
||||||
static final IconData unselectedIcon = Icons.desktop_windows_outlined;
|
static final 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');
|
||||||
if (params['id'] != null) {
|
if (params['id'] != null) {
|
||||||
tabController.add(TabInfo(
|
tabController.add(TabInfo(
|
||||||
key: params['id'],
|
key: params['id'],
|
||||||
label: params['id'],
|
label: params['id'],
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
page: RemotePage(
|
page: Obx(() => RemotePage(
|
||||||
key: ValueKey(params['id']),
|
key: ValueKey(params['id']),
|
||||||
id: params['id'],
|
id: params['id'],
|
||||||
tabBarHeight:
|
tabBarHeight:
|
||||||
_fullscreenID.value.isNotEmpty ? 0 : kDesktopRemoteTabBarHeight,
|
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
|
||||||
fullscreenID: _fullscreenID,
|
))));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +53,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
||||||
print(
|
print(
|
||||||
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
|
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
|
||||||
|
|
||||||
|
final RxBool fullscreen = Get.find(tag: 'fullscreen');
|
||||||
// for simplify, just replace connectionId
|
// for simplify, just replace connectionId
|
||||||
if (call.method == "new_remote_desktop") {
|
if (call.method == "new_remote_desktop") {
|
||||||
final args = jsonDecode(call.arguments);
|
final args = jsonDecode(call.arguments);
|
||||||
@ -64,14 +65,13 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
label: id,
|
label: id,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
page: RemotePage(
|
closable: false,
|
||||||
|
page: Obx(() => RemotePage(
|
||||||
key: ValueKey(id),
|
key: ValueKey(id),
|
||||||
id: id,
|
id: id,
|
||||||
tabBarHeight: _fullscreenID.value.isNotEmpty
|
tabBarHeight:
|
||||||
? 0
|
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
|
||||||
: kDesktopRemoteTabBarHeight,
|
))));
|
||||||
fullscreenID: _fullscreenID,
|
|
||||||
)));
|
|
||||||
} else if (call.method == "onDestroy") {
|
} else if (call.method == "onDestroy") {
|
||||||
tabController.state.value.tabs.forEach((tab) {
|
tabController.state.value.tabs.forEach((tab) {
|
||||||
print("executing onDestroy hook, closing ${tab.label}}");
|
print("executing onDestroy hook, closing ${tab.label}}");
|
||||||
@ -88,7 +88,9 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
|
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
|
||||||
return SubWindowDragToResizeArea(
|
final RxBool fullscreen = Get.find(tag: 'fullscreen');
|
||||||
|
return Obx(() => SubWindowDragToResizeArea(
|
||||||
|
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
|
||||||
windowId: windowId(),
|
windowId: windowId(),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -99,18 +101,18 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
controller: tabController,
|
controller: tabController,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
isMainWindow: false,
|
isMainWindow: false,
|
||||||
showTabBar: _fullscreenID.value.isEmpty,
|
showTabBar: fullscreen.isFalse,
|
||||||
tail: AddButton(
|
tail: AddButton(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
).paddingOnly(left: 10),
|
).paddingOnly(left: 10),
|
||||||
pageViewBuilder: (pageView) {
|
pageViewBuilder: (pageView) {
|
||||||
WindowController.fromWindowId(windowId())
|
WindowController.fromWindowId(windowId())
|
||||||
.setFullscreen(_fullscreenID.value.isNotEmpty);
|
.setFullscreen(fullscreen.isTrue);
|
||||||
return pageView;
|
return pageView;
|
||||||
},
|
},
|
||||||
))),
|
))),
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRemoveId(String id) {
|
void onRemoveId(String id) {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter_hbb/consts.dart';
|
|||||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class DesktopTabPage extends StatefulWidget {
|
class DesktopTabPage extends StatefulWidget {
|
||||||
@ -33,7 +34,10 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dark = isDarkTheme();
|
final dark = isDarkTheme();
|
||||||
return DragToResizeArea(
|
RxBool fullscreen = false.obs;
|
||||||
|
Get.put(fullscreen, tag: 'fullscreen');
|
||||||
|
return Obx(() => DragToResizeArea(
|
||||||
|
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: MyTheme.color(context).border!)),
|
border: Border.all(color: MyTheme.color(context).border!)),
|
||||||
@ -52,7 +56,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAddSetting() {
|
void onAddSetting() {
|
||||||
|
@ -9,9 +9,11 @@ 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';
|
||||||
|
|
||||||
|
import '../widgets/remote_menubar.dart';
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../mobile/widgets/dialog.dart';
|
import '../../mobile/widgets/dialog.dart';
|
||||||
import '../../mobile/widgets/overlay.dart';
|
import '../../mobile/widgets/overlay.dart';
|
||||||
@ -21,16 +23,14 @@ import '../../models/platform_model.dart';
|
|||||||
final initText = '\1' * 1024;
|
final initText = '\1' * 1024;
|
||||||
|
|
||||||
class RemotePage extends StatefulWidget {
|
class RemotePage extends StatefulWidget {
|
||||||
RemotePage(
|
RemotePage({
|
||||||
{Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.tabBarHeight,
|
required this.tabBarHeight,
|
||||||
required this.fullscreenID})
|
}) : super(key: key);
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final double tabBarHeight;
|
final double tabBarHeight;
|
||||||
final Rx<String> fullscreenID;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RemotePageState createState() => _RemotePageState();
|
_RemotePageState createState() => _RemotePageState();
|
||||||
@ -50,11 +50,15 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
|
|
||||||
late FFI _ffi;
|
late FFI _ffi;
|
||||||
|
|
||||||
|
void _updateTabBarHeight() {
|
||||||
|
_ffi.canvasModel.tabBarHeight = widget.tabBarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_ffi = FFI();
|
_ffi = FFI();
|
||||||
_ffi.canvasModel.tabBarHeight = super.widget.tabBarHeight;
|
_updateTabBarHeight();
|
||||||
Get.put(_ffi, tag: widget.id);
|
Get.put(_ffi, tag: widget.id);
|
||||||
_ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight);
|
_ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@ -70,6 +74,9 @@ 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);
|
||||||
|
BlockInputState.init(widget.id);
|
||||||
|
CurrentDisplayState.init(widget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -90,6 +97,9 @@ 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);
|
||||||
|
BlockInputState.delete(widget.id);
|
||||||
|
CurrentDisplayState.delete(widget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetTool() {
|
void resetTool() {
|
||||||
@ -217,6 +227,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
|
_updateTabBarHeight();
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
clientClose(_ffi.dialogManager);
|
clientClose(_ffi.dialogManager);
|
||||||
@ -289,6 +300,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget? getBottomAppBar(FfiModel ffiModel) {
|
Widget? getBottomAppBar(FfiModel ffiModel) {
|
||||||
|
final RxBool fullscreen = Get.find(tag: 'fullscreen');
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: SystemMouseCursors.basic,
|
cursor: SystemMouseCursors.basic,
|
||||||
child: BottomAppBar(
|
child: BottomAppBar(
|
||||||
@ -323,15 +335,11 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
: <Widget>[
|
: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
icon: Icon(widget.fullscreenID.value.isEmpty
|
icon: Icon(fullscreen.isTrue
|
||||||
? Icons.fullscreen
|
? Icons.fullscreen
|
||||||
: Icons.close_fullscreen),
|
: Icons.close_fullscreen),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (widget.fullscreenID.value.isEmpty) {
|
fullscreen.value = !fullscreen.value;
|
||||||
widget.fullscreenID.value = widget.id;
|
|
||||||
} else {
|
|
||||||
widget.fullscreenID.value = "";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]) +
|
]) +
|
||||||
@ -404,7 +412,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
if (_isPhysicalMouse) {
|
if (_isPhysicalMouse) {
|
||||||
_ffi.handleMouse(getEvent(e, 'mousemove'),
|
_ffi.handleMouse(getEvent(e, 'mousemove'),
|
||||||
tabBarHeight: super.widget.tabBarHeight);
|
tabBarHeight: widget.tabBarHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +426,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
if (_isPhysicalMouse) {
|
if (_isPhysicalMouse) {
|
||||||
_ffi.handleMouse(getEvent(e, 'mousedown'),
|
_ffi.handleMouse(getEvent(e, 'mousedown'),
|
||||||
tabBarHeight: super.widget.tabBarHeight);
|
tabBarHeight: widget.tabBarHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,7 +434,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (_isPhysicalMouse) {
|
if (_isPhysicalMouse) {
|
||||||
_ffi.handleMouse(getEvent(e, 'mouseup'),
|
_ffi.handleMouse(getEvent(e, 'mouseup'),
|
||||||
tabBarHeight: super.widget.tabBarHeight);
|
tabBarHeight: widget.tabBarHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,7 +442,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (_isPhysicalMouse) {
|
if (_isPhysicalMouse) {
|
||||||
_ffi.handleMouse(getEvent(e, 'mousemove'),
|
_ffi.handleMouse(getEvent(e, 'mousemove'),
|
||||||
tabBarHeight: super.widget.tabBarHeight);
|
tabBarHeight: widget.tabBarHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,6 +508,10 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
paints.add(QualityMonitor(_ffi.qualityMonitorModel));
|
paints.add(QualityMonitor(_ffi.qualityMonitorModel));
|
||||||
|
paints.add(RemoteMenubar(
|
||||||
|
id: widget.id,
|
||||||
|
ffi: _ffi,
|
||||||
|
));
|
||||||
return Stack(
|
return Stack(
|
||||||
children: paints,
|
children: paints,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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/desktop/pages/connection_tab_page.dart';
|
import 'package:flutter_hbb/desktop/pages/connection_tab_page.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
/// multi-tab desktop remote screen
|
/// multi-tab desktop remote screen
|
||||||
@ -11,6 +12,8 @@ class DesktopRemoteScreen extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
RxBool fullscreen = false.obs;
|
||||||
|
Get.put(fullscreen, tag: 'fullscreen');
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider.value(value: gFFI.ffiModel),
|
ChangeNotifierProvider.value(value: gFFI.ffiModel),
|
||||||
|
1321
flutter/lib/desktop/widgets/material_mod_popup_menu.dart
Normal file
1321
flutter/lib/desktop/widgets/material_mod_popup_menu.dart
Normal file
File diff suppressed because it is too large
Load Diff
375
flutter/lib/desktop/widgets/popup_menu.dart
Normal file
375
flutter/lib/desktop/widgets/popup_menu.dart
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import './material_mod_popup_menu.dart' as modMenu;
|
||||||
|
|
||||||
|
const kInvalidValueStr = "InvalidValueStr";
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
|
||||||
|
class PopupMenuChildrenItem<T> extends modMenu.PopupMenuEntry<T> {
|
||||||
|
const PopupMenuChildrenItem({
|
||||||
|
key,
|
||||||
|
this.height = kMinInteractiveDimension,
|
||||||
|
this.padding,
|
||||||
|
this.enable = true,
|
||||||
|
this.textStyle,
|
||||||
|
this.onTap,
|
||||||
|
this.position = modMenu.PopupMenuPosition.overSide,
|
||||||
|
this.offset = Offset.zero,
|
||||||
|
required this.itemBuilder,
|
||||||
|
required this.child,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final modMenu.PopupMenuPosition position;
|
||||||
|
final Offset offset;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final bool enable;
|
||||||
|
final void Function()? onTap;
|
||||||
|
final List<modMenu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool represents(T? value) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MyPopupMenuItemState<T, PopupMenuChildrenItem<T>> createState() =>
|
||||||
|
MyPopupMenuItemState<T, PopupMenuChildrenItem<T>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
|
||||||
|
extends State<W> {
|
||||||
|
@protected
|
||||||
|
void handleTap(T value) {
|
||||||
|
widget.onTap?.call();
|
||||||
|
Navigator.pop<T>(context, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||||
|
TextStyle style = widget.textStyle ??
|
||||||
|
popupMenuTheme.textStyle ??
|
||||||
|
theme.textTheme.subtitle1!;
|
||||||
|
|
||||||
|
return modMenu.PopupMenuButton<T>(
|
||||||
|
enabled: widget.enable,
|
||||||
|
position: widget.position,
|
||||||
|
offset: widget.offset,
|
||||||
|
onSelected: handleTap,
|
||||||
|
itemBuilder: widget.itemBuilder,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: AnimatedDefaultTextStyle(
|
||||||
|
style: style,
|
||||||
|
duration: kThemeChangeDuration,
|
||||||
|
child: Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: BoxConstraints(minHeight: widget.height),
|
||||||
|
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuConfig {
|
||||||
|
// adapt to the screen height
|
||||||
|
static const fontSize = 14.0;
|
||||||
|
static const midPadding = 10.0;
|
||||||
|
static const iconScale = 0.8;
|
||||||
|
static const iconWidth = 12.0;
|
||||||
|
static const iconHeight = 12.0;
|
||||||
|
|
||||||
|
final double secondMenuHeight;
|
||||||
|
final Color commonColor;
|
||||||
|
|
||||||
|
const MenuConfig(
|
||||||
|
{required this.commonColor,
|
||||||
|
this.secondMenuHeight = kMinInteractiveDimension});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class MenuEntryBase<T> {
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuEntryDivider<T> extends MenuEntryBase<T> {
|
||||||
|
@override
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
|
||||||
|
return const modMenu.PopupMenuDivider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef RadioOptionsGetter = List<Tuple2<String, String>> Function();
|
||||||
|
typedef RadioCurOptionGetter = Future<String> Function();
|
||||||
|
typedef RadioOptionSetter = Future<void> Function(String);
|
||||||
|
|
||||||
|
class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||||
|
final String text;
|
||||||
|
final RadioOptionsGetter optionsGetter;
|
||||||
|
final RadioCurOptionGetter curOptionGetter;
|
||||||
|
final RadioOptionSetter optionSetter;
|
||||||
|
final RxString _curOption = "".obs;
|
||||||
|
|
||||||
|
MenuEntrySubRadios(
|
||||||
|
{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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modMenu.PopupMenuEntry<T> _buildSecondMenu(
|
||||||
|
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
|
||||||
|
return modMenu.PopupMenuItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: TextButton(
|
||||||
|
child: Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: BoxConstraints(minHeight: conf.secondMenuHeight),
|
||||||
|
child: Row(
|
||||||
|
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(
|
||||||
|
opt.item1,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (opt.item2 != curOption.value) {
|
||||||
|
setOption(opt.item2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
|
||||||
|
return PopupMenuChildrenItem(
|
||||||
|
height: conf.secondMenuHeight,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
|
||||||
|
child: Row(children: [
|
||||||
|
const SizedBox(width: MenuConfig.midPadding),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Icon(
|
||||||
|
Icons.keyboard_arrow_right,
|
||||||
|
color: conf.commonColor,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef SwitchGetter = Future<bool> Function();
|
||||||
|
typedef SwitchSetter = Future<void> Function(bool);
|
||||||
|
|
||||||
|
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
MenuEntrySwitchBase({required this.text});
|
||||||
|
|
||||||
|
RxBool get curOption;
|
||||||
|
Future<void> setOption(bool option);
|
||||||
|
|
||||||
|
@override
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
|
||||||
|
return modMenu.PopupMenuItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Obx(
|
||||||
|
() => SwitchListTile(
|
||||||
|
value: curOption.value,
|
||||||
|
onChanged: (v) {
|
||||||
|
setOption(v);
|
||||||
|
},
|
||||||
|
title: Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: BoxConstraints(minHeight: conf.secondMenuHeight),
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal),
|
||||||
|
)),
|
||||||
|
dense: true,
|
||||||
|
visualDensity: const VisualDensity(
|
||||||
|
horizontal: VisualDensity.minimumDensity,
|
||||||
|
vertical: VisualDensity.minimumDensity,
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.only(left: 8.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||||
|
final SwitchGetter getter;
|
||||||
|
final SwitchSetter setter;
|
||||||
|
final RxBool _curOption = false.obs;
|
||||||
|
|
||||||
|
MenuEntrySwitch(
|
||||||
|
{required String text, required this.getter, required this.setter})
|
||||||
|
: super(text: text) {
|
||||||
|
() async {
|
||||||
|
_curOption.value = await getter();
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
RxBool get curOption => _curOption;
|
||||||
|
@override
|
||||||
|
setOption(bool option) async {
|
||||||
|
await setter(option);
|
||||||
|
final opt = await getter();
|
||||||
|
if (_curOption.value != opt) {
|
||||||
|
_curOption.value = opt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Switch2Getter = RxBool Function();
|
||||||
|
typedef Switch2Setter = Future<void> Function(bool);
|
||||||
|
|
||||||
|
class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
||||||
|
final Switch2Getter getter;
|
||||||
|
final SwitchSetter setter;
|
||||||
|
|
||||||
|
MenuEntrySwitch2(
|
||||||
|
{required String text, required this.getter, required this.setter})
|
||||||
|
: super(text: text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
RxBool get curOption => getter();
|
||||||
|
@override
|
||||||
|
setOption(bool option) async {
|
||||||
|
await setter(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||||
|
final String text;
|
||||||
|
final List<MenuEntryBase<T>> entries;
|
||||||
|
|
||||||
|
MenuEntrySubMenu({
|
||||||
|
required this.text,
|
||||||
|
required this.entries,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
|
||||||
|
return PopupMenuChildrenItem(
|
||||||
|
height: conf.secondMenuHeight,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
position: modMenu.PopupMenuPosition.overSide,
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
entries.map((entry) => entry.build(context, conf)).toList(),
|
||||||
|
child: Row(children: [
|
||||||
|
const SizedBox(width: MenuConfig.midPadding),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Icon(
|
||||||
|
Icons.keyboard_arrow_right,
|
||||||
|
color: conf.commonColor,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||||
|
final Widget Function(TextStyle? style) childBuilder;
|
||||||
|
Function() proc;
|
||||||
|
|
||||||
|
MenuEntryButton({
|
||||||
|
required this.childBuilder,
|
||||||
|
required this.proc,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
modMenu.PopupMenuEntry<T> build(BuildContext context, MenuConfig conf) {
|
||||||
|
return modMenu.PopupMenuItem(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: TextButton(
|
||||||
|
child: Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: BoxConstraints(minHeight: conf.secondMenuHeight),
|
||||||
|
child: childBuilder(
|
||||||
|
const TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
560
flutter/lib/desktop/widgets/remote_menubar.dart
Normal file
560
flutter/lib/desktop/widgets/remote_menubar.dart
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../../common.dart';
|
||||||
|
import '../../mobile/widgets/dialog.dart';
|
||||||
|
import '../../mobile/widgets/overlay.dart';
|
||||||
|
import '../../models/model.dart';
|
||||||
|
import '../../models/platform_model.dart';
|
||||||
|
import './popup_menu.dart';
|
||||||
|
import './material_mod_popup_menu.dart' as modMenu;
|
||||||
|
|
||||||
|
class _MenubarTheme {
|
||||||
|
static const Color commonColor = MyTheme.accent;
|
||||||
|
static const double height = kMinInteractiveDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteMenubar extends StatefulWidget {
|
||||||
|
final String id;
|
||||||
|
final FFI ffi;
|
||||||
|
|
||||||
|
const RemoteMenubar({
|
||||||
|
Key? key,
|
||||||
|
required this.id,
|
||||||
|
required this.ffi,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RemoteMenubar> createState() => _RemoteMenubarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||||
|
final RxBool _show = false.obs;
|
||||||
|
final Rx<Color> _hideColor = Colors.white12.obs;
|
||||||
|
|
||||||
|
bool get isFullscreen => Get.find<RxBool>(tag: 'fullscreen').isTrue;
|
||||||
|
void setFullscreen(bool v) {
|
||||||
|
Get.find<RxBool>(tag: 'fullscreen').value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: Obx(
|
||||||
|
() => _show.value ? _buildMenubar(context) : _buildShowHide(context)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildShowHide(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 100,
|
||||||
|
height: 5,
|
||||||
|
child: TextButton(
|
||||||
|
onHover: (bool v) {
|
||||||
|
_hideColor.value = v ? Colors.white60 : Colors.white24;
|
||||||
|
},
|
||||||
|
onPressed: () {
|
||||||
|
_show.value = !_show.value;
|
||||||
|
},
|
||||||
|
child: Obx(() => Container(
|
||||||
|
color: _hideColor.value,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenubar(BuildContext context) {
|
||||||
|
final List<Widget> menubarItems = [];
|
||||||
|
if (!isWebDesktop) {
|
||||||
|
menubarItems.add(_buildFullscreen(context));
|
||||||
|
if (widget.ffi.ffiModel.isPeerAndroid) {
|
||||||
|
menubarItems.add(IconButton(
|
||||||
|
tooltip: translate('Mobile Actions'),
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
icon: Icon(Icons.build),
|
||||||
|
onPressed: () {
|
||||||
|
if (mobileActionsOverlayEntry == null) {
|
||||||
|
showMobileActionsOverlay();
|
||||||
|
} else {
|
||||||
|
hideMobileActionsOverlay();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menubarItems.add(_buildMonitor(context));
|
||||||
|
menubarItems.add(_buildControl(context));
|
||||||
|
menubarItems.add(_buildDisplay(context));
|
||||||
|
if (!isWeb) {
|
||||||
|
menubarItems.add(_buildChat(context));
|
||||||
|
}
|
||||||
|
menubarItems.add(_buildClose(context));
|
||||||
|
return PopupMenuTheme(
|
||||||
|
data: PopupMenuThemeData(
|
||||||
|
textStyle: TextStyle(color: _MenubarTheme.commonColor)),
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: menubarItems,
|
||||||
|
)),
|
||||||
|
_buildShowHide(context),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFullscreen(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
tooltip: translate(isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'),
|
||||||
|
onPressed: () {
|
||||||
|
setFullscreen(!isFullscreen);
|
||||||
|
},
|
||||||
|
icon: Obx(() => isFullscreen
|
||||||
|
? Icon(
|
||||||
|
Icons.fullscreen_exit,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.fullscreen,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChat(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
tooltip: translate('Chat'),
|
||||||
|
onPressed: () {
|
||||||
|
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||||
|
widget.ffi.chatModel.toggleChatOverlay();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.message,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMonitor(BuildContext context) {
|
||||||
|
final pi = widget.ffi.ffiModel.pi;
|
||||||
|
return modMenu.PopupMenuButton(
|
||||||
|
tooltip: translate('Select Monitor'),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
position: modMenu.PopupMenuPosition.under,
|
||||||
|
icon: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.personal_video,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 3.9),
|
||||||
|
child: Obx(() {
|
||||||
|
RxInt display = CurrentDisplayState.find(widget.id);
|
||||||
|
return Text(
|
||||||
|
"${display.value + 1}/${pi.displays.length}",
|
||||||
|
style: TextStyle(color: _MenubarTheme.commonColor, fontSize: 8),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
itemBuilder: (BuildContext context) {
|
||||||
|
final List<Widget> rowChildren = [];
|
||||||
|
final double selectorScale = 1.3;
|
||||||
|
for (int i = 0; i < pi.displays.length; i++) {
|
||||||
|
rowChildren.add(Transform.scale(
|
||||||
|
scale: selectorScale,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.personal_video,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Container(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
constraints:
|
||||||
|
BoxConstraints(minHeight: _MenubarTheme.height),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 2.5),
|
||||||
|
child: Text(
|
||||||
|
(i + 1).toString(),
|
||||||
|
style: TextStyle(color: _MenubarTheme.commonColor),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
onPressed: () {
|
||||||
|
RxInt display = CurrentDisplayState.find(widget.id);
|
||||||
|
if (display.value != i) {
|
||||||
|
bind.sessionSwitchDisplay(id: widget.id, value: i);
|
||||||
|
pi.currentDisplay = i;
|
||||||
|
display.value = i;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return <modMenu.PopupMenuEntry<String>>[
|
||||||
|
modMenu.PopupMenuItem<String>(
|
||||||
|
height: _MenubarTheme.height,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: rowChildren),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildControl(BuildContext context) {
|
||||||
|
return modMenu.PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.bolt,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
tooltip: translate('Control Actions'),
|
||||||
|
position: modMenu.PopupMenuPosition.under,
|
||||||
|
itemBuilder: (BuildContext context) => _getControlMenu()
|
||||||
|
.map((entry) => entry.build(
|
||||||
|
context,
|
||||||
|
MenuConfig(
|
||||||
|
commonColor: _MenubarTheme.commonColor,
|
||||||
|
secondMenuHeight: _MenubarTheme.height,
|
||||||
|
)))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDisplay(BuildContext context) {
|
||||||
|
return modMenu.PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.tv,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
tooltip: translate('Display Settings'),
|
||||||
|
position: modMenu.PopupMenuPosition.under,
|
||||||
|
onSelected: (String item) {},
|
||||||
|
itemBuilder: (BuildContext context) => _getDisplayMenu()
|
||||||
|
.map((entry) => entry.build(
|
||||||
|
context,
|
||||||
|
MenuConfig(
|
||||||
|
commonColor: _MenubarTheme.commonColor,
|
||||||
|
secondMenuHeight: _MenubarTheme.height,
|
||||||
|
)))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildClose(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
tooltip: translate('Close'),
|
||||||
|
onPressed: () {
|
||||||
|
clientClose(widget.ffi.dialogManager);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: _MenubarTheme.commonColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MenuEntryBase<String>> _getControlMenu() {
|
||||||
|
final pi = widget.ffi.ffiModel.pi;
|
||||||
|
final perms = widget.ffi.ffiModel.permissions;
|
||||||
|
|
||||||
|
final List<MenuEntryBase<String>> displayMenu = [];
|
||||||
|
|
||||||
|
if (pi.version.isNotEmpty) {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Refresh'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
bind.sessionRefresh(id: widget.id);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('OS Password'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!isWebDesktop) {
|
||||||
|
if (perms['keyboard'] != false && perms['clipboard'] != false) {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Paste'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
() async {
|
||||||
|
ClipboardData? data =
|
||||||
|
await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
if (data != null && data.text != null) {
|
||||||
|
bind.sessionInputString(id: widget.id, value: data.text ?? "");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Reset canvas'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
widget.ffi.cursorModel.reset();
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (perms['keyboard'] != false) {
|
||||||
|
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Insert') + ' Ctrl + Alt + Del',
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
bind.sessionCtrlAltDel(id: widget.id);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Insert Lock'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
bind.sessionLockScreen(id: widget.id);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
if (pi.platform == 'Windows') {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Obx(() => Text(
|
||||||
|
translate(
|
||||||
|
(BlockInputState.find(widget.id).value ? 'Unb' : 'B') +
|
||||||
|
'lock user input'),
|
||||||
|
style: style,
|
||||||
|
)),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
RxBool blockInput = BlockInputState.find(widget.id);
|
||||||
|
bind.sessionToggleOption(
|
||||||
|
id: widget.id,
|
||||||
|
value: (blockInput.value ? 'un' : '') + 'block-input');
|
||||||
|
blockInput.value = !blockInput.value;
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gFFI.ffiModel.permissions["restart"] != false &&
|
||||||
|
(pi.platform == "Linux" ||
|
||||||
|
pi.platform == "Windows" ||
|
||||||
|
pi.platform == "Mac OS")) {
|
||||||
|
displayMenu.add(MenuEntryButton<String>(
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Restart Remote Device'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MenuEntryBase<String>> _getDisplayMenu() {
|
||||||
|
final displayMenu = [
|
||||||
|
MenuEntrySubRadios<String>(
|
||||||
|
text: translate('Ratio'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
Tuple2<String, String>(translate('Original'), 'original'),
|
||||||
|
Tuple2<String, String>(translate('Shrink'), 'shrink'),
|
||||||
|
Tuple2<String, String>(translate('Stretch'), 'stretch'),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return await bind.sessionGetOption(
|
||||||
|
id: widget.id, arg: 'view-style') ??
|
||||||
|
'';
|
||||||
|
},
|
||||||
|
optionSetter: (String v) async {
|
||||||
|
await bind.sessionPeerOption(
|
||||||
|
id: widget.id, name: "view-style", value: v);
|
||||||
|
widget.ffi.canvasModel.updateViewStyle();
|
||||||
|
}),
|
||||||
|
MenuEntrySubRadios<String>(
|
||||||
|
text: translate('Scroll Style'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
Tuple2<String, String>(translate('ScrollAuto'), 'scrollauto'),
|
||||||
|
Tuple2<String, String>(translate('Scrollbar'), 'scrollbar'),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return await bind.sessionGetOption(
|
||||||
|
id: widget.id, arg: 'scroll-style') ??
|
||||||
|
'';
|
||||||
|
},
|
||||||
|
optionSetter: (String v) async {
|
||||||
|
await bind.sessionPeerOption(
|
||||||
|
id: widget.id, name: "scroll-style", value: v);
|
||||||
|
widget.ffi.canvasModel.updateScrollStyle();
|
||||||
|
}),
|
||||||
|
MenuEntrySubRadios<String>(
|
||||||
|
text: translate('Image Quality'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
Tuple2<String, String>(translate('Good image quality'), 'best'),
|
||||||
|
Tuple2<String, String>(translate('Balanced'), 'balanced'),
|
||||||
|
Tuple2<String, String>(
|
||||||
|
translate('Optimize reaction time'), 'low'),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
String quality =
|
||||||
|
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
|
||||||
|
if (quality == '') quality = 'balanced';
|
||||||
|
return quality;
|
||||||
|
},
|
||||||
|
optionSetter: (String v) async {
|
||||||
|
await bind.sessionSetImageQuality(id: widget.id, value: v);
|
||||||
|
}),
|
||||||
|
MenuEntrySwitch<String>(
|
||||||
|
text: translate('Show remote cursor'),
|
||||||
|
getter: () async {
|
||||||
|
return await bind.sessionGetToggleOptionSync(
|
||||||
|
id: widget.id, arg: 'show-remote-cursor');
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
id: widget.id, value: 'show-remote-cursor');
|
||||||
|
}),
|
||||||
|
MenuEntrySwitch<String>(
|
||||||
|
text: translate('Show quality monitor'),
|
||||||
|
getter: () async {
|
||||||
|
return await bind.sessionGetToggleOptionSync(
|
||||||
|
id: widget.id, arg: 'show-quality-monitor');
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
id: widget.id, value: 'show-quality-monitor');
|
||||||
|
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
final perms = widget.ffi.ffiModel.permissions;
|
||||||
|
final pi = widget.ffi.ffiModel.pi;
|
||||||
|
|
||||||
|
if (perms['audio'] != false) {
|
||||||
|
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio'));
|
||||||
|
}
|
||||||
|
if (perms['keyboard'] != false) {
|
||||||
|
if (perms['clipboard'] != false) {
|
||||||
|
displayMenu.add(
|
||||||
|
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard'));
|
||||||
|
}
|
||||||
|
displayMenu.add(_createSwitchMenuEntry(
|
||||||
|
'Lock after session end', 'lock-after-session-end'));
|
||||||
|
if (pi.platform == 'Windows') {
|
||||||
|
displayMenu.add(MenuEntrySwitch2<String>(
|
||||||
|
text: translate('Privacy mode'),
|
||||||
|
getter: () {
|
||||||
|
return PrivacyModeState.find(widget.id);
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
id: widget.id, value: 'privacy-mode');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return displayMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) {
|
||||||
|
return MenuEntrySwitch<String>(
|
||||||
|
text: translate(text),
|
||||||
|
getter: () async {
|
||||||
|
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
await bind.sessionToggleOption(id: widget.id, value: option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSetOSPassword(
|
||||||
|
String id, bool login, OverlayDialogManager dialogManager) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
|
||||||
|
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
|
||||||
|
controller.text = password;
|
||||||
|
dialogManager.show((setState, close) {
|
||||||
|
return CustomAlertDialog(
|
||||||
|
title: Text(translate('OS Password')),
|
||||||
|
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
PasswordWidget(controller: controller),
|
||||||
|
CheckboxListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
title: Text(
|
||||||
|
translate('Auto Login'),
|
||||||
|
),
|
||||||
|
value: autoLogin,
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => autoLogin = v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: () {
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
child: Text(translate('Cancel')),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: flatButtonStyle,
|
||||||
|
onPressed: () {
|
||||||
|
var text = controller.text.trim();
|
||||||
|
bind.sessionPeerOption(id: id, name: "os-password", value: text);
|
||||||
|
bind.sessionPeerOption(
|
||||||
|
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
|
||||||
|
if (text != "" && login) {
|
||||||
|
bind.sessionInputOsPassword(id: id, value: text);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
child: Text(translate('OK')),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
@ -172,9 +172,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
} else if (name == 'update_quality_status') {
|
} else if (name == 'update_quality_status') {
|
||||||
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
|
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
|
||||||
} else if (name == 'update_block_input_state') {
|
} else if (name == 'update_block_input_state') {
|
||||||
updateBlockInputState(evt);
|
updateBlockInputState(evt, peerId);
|
||||||
} else if (name == 'update_privacy_mode') {
|
} else if (name == 'update_privacy_mode') {
|
||||||
updatePrivacyMode(evt);
|
updatePrivacyMode(evt, peerId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -231,9 +231,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
} else if (name == 'update_quality_status') {
|
} else if (name == 'update_quality_status') {
|
||||||
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
|
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
|
||||||
} else if (name == 'update_block_input_state') {
|
} else if (name == 'update_block_input_state') {
|
||||||
updateBlockInputState(evt);
|
updateBlockInputState(evt, peerId);
|
||||||
} else if (name == 'update_privacy_mode') {
|
} else if (name == 'update_privacy_mode') {
|
||||||
updatePrivacyMode(evt);
|
updatePrivacyMode(evt, peerId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
platformFFI.setEventCallback(cb);
|
platformFFI.setEventCallback(cb);
|
||||||
@ -305,6 +305,12 @@ class FfiModel with ChangeNotifier {
|
|||||||
_pi.sasEnabled = evt['sas_enabled'] == "true";
|
_pi.sasEnabled = evt['sas_enabled'] == "true";
|
||||||
_pi.currentDisplay = int.parse(evt['current_display']);
|
_pi.currentDisplay = int.parse(evt['current_display']);
|
||||||
|
|
||||||
|
try {
|
||||||
|
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
if (isPeerAndroid) {
|
if (isPeerAndroid) {
|
||||||
_touchMode = true;
|
_touchMode = true;
|
||||||
if (parent.target?.ffiModel.permissions['keyboard'] != false) {
|
if (parent.target?.ffiModel.permissions['keyboard'] != false) {
|
||||||
@ -343,13 +349,24 @@ class FfiModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBlockInputState(Map<String, dynamic> evt) {
|
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
|
||||||
_inputBlocked = evt['input_state'] == 'on';
|
_inputBlocked = evt['input_state'] == 'on';
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
BlockInputState.find(peerId).value = evt['input_state'] == 'on';
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePrivacyMode(Map<String, dynamic> evt) {
|
updatePrivacyMode(Map<String, dynamic> evt, String peerId) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
PrivacyModeState.find(peerId).value =
|
||||||
|
bind.sessionGetToggleOptionSync(id: peerId, arg: 'privacy-mode');
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "正在重启远程设备"),
|
("Restarting Remote Device", "正在重启远程设备"),
|
||||||
("remote_restarting_tip", "远程设备正在重启, 请关闭当前提示框, 并在一段时间后使用永久密码重新连接"),
|
("remote_restarting_tip", "远程设备正在重启, 请关闭当前提示框, 并在一段时间后使用永久密码重新连接"),
|
||||||
("Copied", "已复制"),
|
("Copied", "已复制"),
|
||||||
|
("Exit Fullscreen", "退出全屏"),
|
||||||
|
("Fullscreen", "全屏"),
|
||||||
|
("Mobile Actions", "移动端操作"),
|
||||||
|
("Select Monitor", "选择监视器"),
|
||||||
|
("Control Actions", "控制操作"),
|
||||||
|
("Display Settings", "显示设置"),
|
||||||
|
("Ratio", "比例"),
|
||||||
|
("Image Quality", "画质"),
|
||||||
|
("Scroll Style", "滚屏方式"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Ukončete celou obrazovku"),
|
||||||
|
("Fullscreen", "Celá obrazovka"),
|
||||||
|
("Mobile Actions", "Mobilní akce"),
|
||||||
|
("Select Monitor", "Vyberte možnost Monitor"),
|
||||||
|
("Control Actions", "Ovládací akce"),
|
||||||
|
("Display Settings", "Nastavení obrazovky"),
|
||||||
|
("Ratio", "Poměr"),
|
||||||
|
("Image Quality", "Kvalita obrazu"),
|
||||||
|
("Scroll Style", "Štýl posúvania"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Afslut fuldskærm"),
|
||||||
|
("Fullscreen", "Fuld skærm"),
|
||||||
|
("Mobile Actions", "Mobile handlinger"),
|
||||||
|
("Select Monitor", "Vælg Monitor"),
|
||||||
|
("Control Actions", "Kontrolhandlinger"),
|
||||||
|
("Display Settings", "Skærmindstillinger"),
|
||||||
|
("Ratio", "Forhold"),
|
||||||
|
("Image Quality", "Billede kvalitet"),
|
||||||
|
("Scroll Style", "Rulstil"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Entferntes Gerät wird neu gestartet"),
|
("Restarting Remote Device", "Entferntes Gerät wird neu gestartet"),
|
||||||
("remote_restarting_tip", "Entferntes Gerät startet neu, bitte schließen Sie diese Meldung und verbinden Sie sich mit dem dauerhaften Passwort erneut."),
|
("remote_restarting_tip", "Entferntes Gerät startet neu, bitte schließen Sie diese Meldung und verbinden Sie sich mit dem dauerhaften Passwort erneut."),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Vollbild beenden"),
|
||||||
|
("Fullscreen", "Ganzer Bildschirm"),
|
||||||
|
("Mobile Actions", "Mobile Aktionen"),
|
||||||
|
("Select Monitor", "Wählen Sie Überwachen aus"),
|
||||||
|
("Control Actions", "Kontrollaktionen"),
|
||||||
|
("Display Settings", "Bildschirmeinstellungen"),
|
||||||
|
("Ratio", "Verhältnis"),
|
||||||
|
("Image Quality", "Bildqualität"),
|
||||||
|
("Scroll Style", "Scroll-Stil"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Eliru Plenekranon"),
|
||||||
|
("Fullscreen", "Plenekrane"),
|
||||||
|
("Mobile Actions", "Poŝtelefonaj Agoj"),
|
||||||
|
("Select Monitor", "Elektu Monitoron"),
|
||||||
|
("Control Actions", "Kontrolaj Agoj"),
|
||||||
|
("Display Settings", "Montraj Agordoj"),
|
||||||
|
("Ratio", "Proporcio"),
|
||||||
|
("Image Quality", "Bilda Kvalito"),
|
||||||
|
("Scroll Style", "Ruluma Stilo"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Reiniciando dispositivo remoto"),
|
("Restarting Remote Device", "Reiniciando dispositivo remoto"),
|
||||||
("remote_restarting_tip", "Dispositivo remoto reiniciando, favor de cerrar este mensaje y reconectarse con la contraseña permamente despues de un momento."),
|
("remote_restarting_tip", "Dispositivo remoto reiniciando, favor de cerrar este mensaje y reconectarse con la contraseña permamente despues de un momento."),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Salir de pantalla completa"),
|
||||||
|
("Fullscreen", "Pantalla completa"),
|
||||||
|
("Mobile Actions", "Acciones móviles"),
|
||||||
|
("Select Monitor", "Seleccionar monitor"),
|
||||||
|
("Control Actions", "Acciones de control"),
|
||||||
|
("Display Settings", "Configuración de pantalla"),
|
||||||
|
("Ratio", "Relación"),
|
||||||
|
("Image Quality", "La calidad de imagen"),
|
||||||
|
("Scroll Style", "Estilo de desplazamiento"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Quitter le mode plein écran"),
|
||||||
|
("Fullscreen", "Plein écran"),
|
||||||
|
("Mobile Actions", "Actions mobiles"),
|
||||||
|
("Select Monitor", "Sélectionnez Moniteur"),
|
||||||
|
("Control Actions", "Actions de contrôle"),
|
||||||
|
("Display Settings", "Paramètres d'affichage"),
|
||||||
|
("Ratio", "Rapport"),
|
||||||
|
("Image Quality", "Qualité d'image"),
|
||||||
|
("Scroll Style", "Style de défilement"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Lépjen ki a teljes képernyőről"),
|
||||||
|
("Fullscreen", "Teljes képernyő"),
|
||||||
|
("Mobile Actions", "mobil műveletek"),
|
||||||
|
("Select Monitor", "Válassza a Monitor lehetőséget"),
|
||||||
|
("Control Actions", "Irányítási műveletek"),
|
||||||
|
("Display Settings", "Megjelenítési beállítások"),
|
||||||
|
("Ratio", "Hányados"),
|
||||||
|
("Image Quality", "Képminőség"),
|
||||||
|
("Scroll Style", "Görgetési stílus"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Memulai Ulang Perangkat Jarak Jauh"),
|
("Restarting Remote Device", "Memulai Ulang Perangkat Jarak Jauh"),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Keluar dari Layar Penuh"),
|
||||||
|
("Fullscreen", "Layar penuh"),
|
||||||
|
("Mobile Actions", "Tindakan Seluler"),
|
||||||
|
("Select Monitor", "Pilih Monitor"),
|
||||||
|
("Control Actions", "Tindakan Kontrol"),
|
||||||
|
("Display Settings", "Pengaturan tampilan"),
|
||||||
|
("Ratio", "Perbandingan"),
|
||||||
|
("Image Quality", "Kualitas gambar"),
|
||||||
|
("Scroll Style", "Gaya Gulir"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -301,5 +301,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
|
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
|
||||||
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
|
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
|
||||||
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
|
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
|
||||||
|
("Exit Fullscreen", "Esci dalla modalità schermo intero"),
|
||||||
|
("Fullscreen", "A schermo intero"),
|
||||||
|
("Mobile Actions", "Azioni mobili"),
|
||||||
|
("Select Monitor", "Seleziona Monitora"),
|
||||||
|
("Control Actions", "Azioni di controllo"),
|
||||||
|
("Display Settings", "Impostazioni di visualizzazione"),
|
||||||
|
("Ratio", "Rapporto"),
|
||||||
|
("Image Quality", "Qualità dell'immagine"),
|
||||||
|
("Scroll Style", "Stile di scorrimento"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -299,5 +299,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Are you sure you want to restart", "本当に再起動しますか"),
|
("Are you sure you want to restart", "本当に再起動しますか"),
|
||||||
("Restarting Remote Device", "リモート端末を再起動中"),
|
("Restarting Remote Device", "リモート端末を再起動中"),
|
||||||
("remote_restarting_tip", "リモート端末は再起動中です。このメッセージボックスを閉じて、しばらくした後に固定のパスワードを使用して再接続してください。"),
|
("remote_restarting_tip", "リモート端末は再起動中です。このメッセージボックスを閉じて、しばらくした後に固定のパスワードを使用して再接続してください。"),
|
||||||
|
("Exit Fullscreen", "全画面表示を終了"),
|
||||||
|
("Fullscreen", "全画面表示"),
|
||||||
|
("Mobile Actions", "モバイル アクション"),
|
||||||
|
("Select Monitor", "モニターを選択"),
|
||||||
|
("Control Actions", "コントロール アクション"),
|
||||||
|
("Display Settings", "ディスプレイの設定"),
|
||||||
|
("Ratio", "比率"),
|
||||||
|
("Image Quality", "画質"),
|
||||||
|
("Scroll Style", "スクロール スタイル"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -299,5 +299,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Are you sure you want to restart", "정말로 재시작 하시겠습니까"),
|
("Are you sure you want to restart", "정말로 재시작 하시겠습니까"),
|
||||||
("Restarting Remote Device", "원격 기기를 다시 시작하는중"),
|
("Restarting Remote Device", "원격 기기를 다시 시작하는중"),
|
||||||
("remote_restarting_tip", "원격 장치를 다시 시작하는 중입니다. 이 메시지 상자를 닫고 잠시 후 영구 비밀번호로 다시 연결하십시오."),
|
("remote_restarting_tip", "원격 장치를 다시 시작하는 중입니다. 이 메시지 상자를 닫고 잠시 후 영구 비밀번호로 다시 연결하십시오."),
|
||||||
|
("Exit Fullscreen", "전체 화면 종료"),
|
||||||
|
("Fullscreen", "전체화면"),
|
||||||
|
("Mobile Actions", "모바일 액션"),
|
||||||
|
("Select Monitor", "모니터 선택"),
|
||||||
|
("Control Actions", "제어 작업"),
|
||||||
|
("Display Settings", "화면 설정"),
|
||||||
|
("Ratio", "비율"),
|
||||||
|
("Image Quality", "이미지 품질"),
|
||||||
|
("Scroll Style", "스크롤 스타일"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
@ -303,5 +303,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Set security password", "Ustaw hasło zabezpieczające"),
|
("Set security password", "Ustaw hasło zabezpieczające"),
|
||||||
("Connection not allowed", "Połączenie niedozwolone"),
|
("Connection not allowed", "Połączenie niedozwolone"),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"),
|
||||||
|
("Fullscreen", "Pełny ekran"),
|
||||||
|
("Mobile Actions", "Działania mobilne"),
|
||||||
|
("Select Monitor", "Wybierz Monitor"),
|
||||||
|
("Control Actions", "Działania kontrolne"),
|
||||||
|
("Display Settings", "Ustawienia wyświetlania"),
|
||||||
|
("Ratio", "Stosunek"),
|
||||||
|
("Image Quality", "Jakość obrazu"),
|
||||||
|
("Scroll Style", "Styl przewijania"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -299,5 +299,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Are you sure you want to restart", "Tem a certeza que pretende reiniciar"),
|
("Are you sure you want to restart", "Tem a certeza que pretende reiniciar"),
|
||||||
("Restarting Remote Device", "A reiniciar sistema remoto"),
|
("Restarting Remote Device", "A reiniciar sistema remoto"),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
|
("Exit Fullscreen", "Sair da tela cheia"),
|
||||||
|
("Fullscreen", "Tela cheia"),
|
||||||
|
("Mobile Actions", "Ações para celular"),
|
||||||
|
("Select Monitor", "Selecionar monitor"),
|
||||||
|
("Control Actions", "Ações de controle"),
|
||||||
|
("Display Settings", "Configurações do visor"),
|
||||||
|
("Ratio", "Razão"),
|
||||||
|
("Image Quality", "Qualidade da imagem"),
|
||||||
|
("Scroll Style", "Estilo de rolagem"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", ""),
|
||||||
|
("Fullscreen", ""),
|
||||||
|
("Mobile Actions", ""),
|
||||||
|
("Select Monitor", ""),
|
||||||
|
("Control Actions", ""),
|
||||||
|
("Display Settings", ""),
|
||||||
|
("Ratio", ""),
|
||||||
|
("Image Quality", ""),
|
||||||
|
("Scroll Style", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Перезагрузка удаленного устройства"),
|
("Restarting Remote Device", "Перезагрузка удаленного устройства"),
|
||||||
("remote_restarting_tip", "Удаленное устройство перезапускается. Пожалуйста, закройте это сообщение и через некоторое время переподключитесь, используя постоянный пароль."),
|
("remote_restarting_tip", "Удаленное устройство перезапускается. Пожалуйста, закройте это сообщение и через некоторое время переподключитесь, используя постоянный пароль."),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Выйти из полноэкранного режима"),
|
||||||
|
("Fullscreen", "Полноэкранный"),
|
||||||
|
("Mobile Actions", "Мобильные действия"),
|
||||||
|
("Select Monitor", "Выберите монитор"),
|
||||||
|
("Control Actions", "Действия по управлению"),
|
||||||
|
("Display Settings", "Настройки отображения"),
|
||||||
|
("Ratio", "Соотношение"),
|
||||||
|
("Image Quality", "Качество изображения"),
|
||||||
|
("Scroll Style", "Стиль прокрутки"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Ukončiť celú obrazovku"),
|
||||||
|
("Fullscreen", "Celá obrazovka"),
|
||||||
|
("Mobile Actions", "Mobilné akcie"),
|
||||||
|
("Select Monitor", "Vyberte možnosť Monitor"),
|
||||||
|
("Control Actions", "Kontrolné akcie"),
|
||||||
|
("Display Settings", "Nastavenia displeja"),
|
||||||
|
("Ratio", "Pomer"),
|
||||||
|
("Image Quality", "Kvalita obrazu"),
|
||||||
|
("Scroll Style", "Štýl posúvania"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", ""),
|
("Restarting Remote Device", ""),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", ""),
|
||||||
|
("Fullscreen", ""),
|
||||||
|
("Mobile Actions", ""),
|
||||||
|
("Select Monitor", ""),
|
||||||
|
("Control Actions", ""),
|
||||||
|
("Display Settings", ""),
|
||||||
|
("Ratio", ""),
|
||||||
|
("Image Quality", ""),
|
||||||
|
("Scroll Style", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Uzaktan yeniden başlatılıyor"),
|
("Restarting Remote Device", "Uzaktan yeniden başlatılıyor"),
|
||||||
("remote_restarting_tip", ""),
|
("remote_restarting_tip", ""),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Tam ekrandan çık"),
|
||||||
|
("Fullscreen", "Tam ekran"),
|
||||||
|
("Mobile Actions", "Mobil İşlemler"),
|
||||||
|
("Select Monitor", "Monitörü Seç"),
|
||||||
|
("Control Actions", "Kontrol Eylemleri"),
|
||||||
|
("Display Settings", "Görüntü ayarları"),
|
||||||
|
("Ratio", "Oran"),
|
||||||
|
("Image Quality", "Görüntü kalitesi"),
|
||||||
|
("Scroll Style", "Kaydırma Stili"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "正在重啓遠程設備"),
|
("Restarting Remote Device", "正在重啓遠程設備"),
|
||||||
("remote_restarting_tip", "遠程設備正在重啓,請關閉當前提示框,並在一段時間後使用永久密碼重新連接"),
|
("remote_restarting_tip", "遠程設備正在重啓,請關閉當前提示框,並在一段時間後使用永久密碼重新連接"),
|
||||||
("Copied", "已複製"),
|
("Copied", "已複製"),
|
||||||
|
("Exit Fullscreen", "退出全屏"),
|
||||||
|
("Fullscreen", "全屏"),
|
||||||
|
("Mobile Actions", "移動端操作"),
|
||||||
|
("Select Monitor", "選擇監視器"),
|
||||||
|
("Control Actions", "控制操作"),
|
||||||
|
("Display Settings", "顯示設置"),
|
||||||
|
("Ratio", "比例"),
|
||||||
|
("Image Quality", "畫質"),
|
||||||
|
("Scroll Style", "滾動樣式"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Restarting Remote Device", "Đang khởi động lại thiết bị từ xa"),
|
("Restarting Remote Device", "Đang khởi động lại thiết bị từ xa"),
|
||||||
("remote_restarting_tip", "Thiết bị từ xa đang khởi động lại, hãy đóng cửa sổ tin nhắn này và kết nối lại với mật khẩu vĩnh viễn sau một khoảng thời gian"),
|
("remote_restarting_tip", "Thiết bị từ xa đang khởi động lại, hãy đóng cửa sổ tin nhắn này và kết nối lại với mật khẩu vĩnh viễn sau một khoảng thời gian"),
|
||||||
("Copied", ""),
|
("Copied", ""),
|
||||||
|
("Exit Fullscreen", "Thoát toàn màn hình"),
|
||||||
|
("Fullscreen", "Toàn màn hình"),
|
||||||
|
("Mobile Actions", "Hành động trên thiết bị di động"),
|
||||||
|
("Select Monitor", "Chọn màn hình"),
|
||||||
|
("Control Actions", "Kiểm soát hành động"),
|
||||||
|
("Display Settings", "Thiết lập hiển thị"),
|
||||||
|
("Ratio", "Tỉ lệ"),
|
||||||
|
("Image Quality", "Chất lượng hình ảnh"),
|
||||||
|
("Scroll Style", "Kiểu cuộn"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user