flutter_desktop: new remote menu, mid commit

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-08-26 23:28:08 +08:00
parent 7b4a655eaf
commit ea77d9284b
30 changed files with 2606 additions and 84 deletions

View File

@ -447,7 +447,10 @@ void msgBox(
0,
wrap(translate('OK'), () {
dialogManager.dismissAll();
closeConnection();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (type.indexOf("custom") < 0) {
closeConnection();
}
}));
}
if (hasCancel == null) {
@ -740,3 +743,39 @@ Future<List<Peer>>? matchPeers(String searchText, List<Peer> peers) async {
}
return filteredList;
}
class PrivacyModeState {
static String tag(String id) => 'privacy_mode_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class BlockInputState {
static String tag(String id) => 'block_input_' + id;
static void init(String id) {
final RxBool state = false.obs;
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
}
class CurrentDisplayState {
static String tag(String id) => 'current_display_' + id;
static void init(String id) {
final RxInt state = RxInt(0);
Get.put(state, tag: tag(id));
}
static void delete(String id) => Get.delete(tag: tag(id));
static RxInt find(String id) => Get.find<RxInt>(tag: tag(id));
}

View File

@ -22,26 +22,25 @@ class ConnectionTabPage extends StatefulWidget {
class _ConnectionTabPageState extends State<ConnectionTabPage> {
final tabController = Get.put(DesktopTabController());
static final Rx<String> _fullscreenID = "".obs;
static final IconData selectedIcon = Icons.desktop_windows_sharp;
static final IconData unselectedIcon = Icons.desktop_windows_outlined;
var connectionMap = RxList<Widget>.empty(growable: true);
_ConnectionTabPageState(Map<String, dynamic> params) {
final RxBool fullscreen = Get.find(tag: 'fullscreen');
if (params['id'] != null) {
tabController.add(TabInfo(
key: params['id'],
label: params['id'],
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
page: RemotePage(
key: ValueKey(params['id']),
id: params['id'],
tabBarHeight:
_fullscreenID.value.isNotEmpty ? 0 : kDesktopRemoteTabBarHeight,
fullscreenID: _fullscreenID,
)));
page: Obx(() => RemotePage(
key: ValueKey(params['id']),
id: params['id'],
tabBarHeight:
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
))));
}
}
@ -54,6 +53,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
print(
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
final RxBool fullscreen = Get.find(tag: 'fullscreen');
// for simplify, just replace connectionId
if (call.method == "new_remote_desktop") {
final args = jsonDecode(call.arguments);
@ -64,14 +65,13 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
label: id,
selectedIcon: selectedIcon,
unselectedIcon: unselectedIcon,
page: RemotePage(
key: ValueKey(id),
id: id,
tabBarHeight: _fullscreenID.value.isNotEmpty
? 0
: kDesktopRemoteTabBarHeight,
fullscreenID: _fullscreenID,
)));
closable: false,
page: Obx(() => RemotePage(
key: ValueKey(id),
id: id,
tabBarHeight:
fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight,
))));
} else if (call.method == "onDestroy") {
tabController.state.value.tabs.forEach((tab) {
print("executing onDestroy hook, closing ${tab.label}}");
@ -88,29 +88,31 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
@override
Widget build(BuildContext context) {
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
return SubWindowDragToResizeArea(
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: Obx(() => DesktopTab(
controller: tabController,
theme: theme,
isMainWindow: false,
showTabBar: _fullscreenID.value.isEmpty,
tail: AddButton(
theme: theme,
).paddingOnly(left: 10),
pageViewBuilder: (pageView) {
WindowController.fromWindowId(windowId())
.setFullscreen(_fullscreenID.value.isNotEmpty);
return pageView;
},
))),
),
);
final RxBool fullscreen = Get.find(tag: 'fullscreen');
return Obx(() => SubWindowDragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: Obx(() => DesktopTab(
controller: tabController,
theme: theme,
isMainWindow: false,
showTabBar: fullscreen.isFalse,
tail: AddButton(
theme: theme,
).paddingOnly(left: 10),
pageViewBuilder: (pageView) {
WindowController.fromWindowId(windowId())
.setFullscreen(fullscreen.isTrue);
return pageView;
},
))),
),
));
}
void onRemoveId(String id) {

View File

@ -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_setting_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:get/get.dart';
import 'package:window_manager/window_manager.dart';
class DesktopTabPage extends StatefulWidget {
@ -33,26 +34,29 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override
Widget build(BuildContext context) {
final dark = isDarkTheme();
return DragToResizeArea(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
isMainWindow: true,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
onTap: onAddSetting,
is_close: false,
),
)),
),
);
RxBool fullscreen = false.obs;
Get.put(fullscreen, tag: 'fullscreen');
return Obx(() => DragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
isMainWindow: true,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
onTap: onAddSetting,
is_close: false,
),
)),
),
));
}
void onAddSetting() {

View File

@ -9,9 +9,11 @@ import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:tuple/tuple.dart';
// import 'package:window_manager/window_manager.dart';
import '../widgets/remote_menubar.dart';
import '../../common.dart';
import '../../mobile/widgets/dialog.dart';
import '../../mobile/widgets/overlay.dart';
@ -21,16 +23,14 @@ import '../../models/platform_model.dart';
final initText = '\1' * 1024;
class RemotePage extends StatefulWidget {
RemotePage(
{Key? key,
required this.id,
required this.tabBarHeight,
required this.fullscreenID})
: super(key: key);
RemotePage({
Key? key,
required this.id,
required this.tabBarHeight,
}) : super(key: key);
final String id;
final double tabBarHeight;
final Rx<String> fullscreenID;
@override
_RemotePageState createState() => _RemotePageState();
@ -50,11 +50,15 @@ class _RemotePageState extends State<RemotePage>
late FFI _ffi;
void _updateTabBarHeight() {
_ffi.canvasModel.tabBarHeight = widget.tabBarHeight;
}
@override
void initState() {
super.initState();
_ffi = FFI();
_ffi.canvasModel.tabBarHeight = super.widget.tabBarHeight;
_updateTabBarHeight();
Get.put(_ffi, tag: widget.id);
_ffi.connect(widget.id, tabBarHeight: super.widget.tabBarHeight);
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -70,6 +74,9 @@ class _RemotePageState extends State<RemotePage>
_ffi.listenToMouse(true);
_ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
// WindowManager.instance.addListener(this);
PrivacyModeState.init(widget.id);
BlockInputState.init(widget.id);
CurrentDisplayState.init(widget.id);
}
@override
@ -90,6 +97,9 @@ class _RemotePageState extends State<RemotePage>
// WindowManager.instance.removeListener(this);
Get.delete<FFI>(tag: widget.id);
super.dispose();
PrivacyModeState.delete(widget.id);
BlockInputState.delete(widget.id);
CurrentDisplayState.delete(widget.id);
}
void resetTool() {
@ -217,6 +227,7 @@ class _RemotePageState extends State<RemotePage>
@override
Widget build(BuildContext context) {
super.build(context);
_updateTabBarHeight();
return WillPopScope(
onWillPop: () async {
clientClose(_ffi.dialogManager);
@ -289,6 +300,7 @@ class _RemotePageState extends State<RemotePage>
}
Widget? getBottomAppBar(FfiModel ffiModel) {
final RxBool fullscreen = Get.find(tag: 'fullscreen');
return MouseRegion(
cursor: SystemMouseCursors.basic,
child: BottomAppBar(
@ -323,15 +335,11 @@ class _RemotePageState extends State<RemotePage>
: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(widget.fullscreenID.value.isEmpty
icon: Icon(fullscreen.isTrue
? Icons.fullscreen
: Icons.close_fullscreen),
onPressed: () {
if (widget.fullscreenID.value.isEmpty) {
widget.fullscreenID.value = widget.id;
} else {
widget.fullscreenID.value = "";
}
fullscreen.value = !fullscreen.value;
},
)
]) +
@ -404,7 +412,7 @@ class _RemotePageState extends State<RemotePage>
}
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'),
tabBarHeight: super.widget.tabBarHeight);
tabBarHeight: widget.tabBarHeight);
}
}
@ -418,7 +426,7 @@ class _RemotePageState extends State<RemotePage>
}
if (_isPhysicalMouse) {
_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 (_isPhysicalMouse) {
_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 (_isPhysicalMouse) {
_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(RemoteMenubar(
id: widget.id,
ffi: _ffi,
));
return Stack(
children: paints,
);

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/connection_tab_page.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
/// multi-tab desktop remote screen
@ -11,6 +12,8 @@ class DesktopRemoteScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
RxBool fullscreen = false.obs;
Get.put(fullscreen, tag: 'fullscreen');
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: gFFI.ffiModel),

File diff suppressed because it is too large Load Diff

View 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();
}
}

View 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')),
),
]);
});
}

View File

@ -172,9 +172,9 @@ class FfiModel with ChangeNotifier {
} else if (name == 'update_quality_status') {
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
} else if (name == 'update_block_input_state') {
updateBlockInputState(evt);
updateBlockInputState(evt, peerId);
} 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') {
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
} else if (name == 'update_block_input_state') {
updateBlockInputState(evt);
updateBlockInputState(evt, peerId);
} else if (name == 'update_privacy_mode') {
updatePrivacyMode(evt);
updatePrivacyMode(evt, peerId);
}
};
platformFFI.setEventCallback(cb);
@ -305,6 +305,12 @@ class FfiModel with ChangeNotifier {
_pi.sasEnabled = evt['sas_enabled'] == "true";
_pi.currentDisplay = int.parse(evt['current_display']);
try {
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
} catch (e) {
//
}
if (isPeerAndroid) {
_touchMode = true;
if (parent.target?.ffiModel.permissions['keyboard'] != false) {
@ -343,13 +349,24 @@ class FfiModel with ChangeNotifier {
notifyListeners();
}
updateBlockInputState(Map<String, dynamic> evt) {
updateBlockInputState(Map<String, dynamic> evt, String peerId) {
_inputBlocked = evt['input_state'] == 'on';
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();
try {
PrivacyModeState.find(peerId).value =
bind.sessionGetToggleOptionSync(id: peerId, arg: 'privacy-mode');
} catch (e) {
//
}
}
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", "正在重启远程设备"),
("remote_restarting_tip", "远程设备正在重启, 请关闭当前提示框, 并在一段时间后使用永久密码重新连接"),
("Copied", "已复制"),
("Exit Fullscreen", "退出全屏"),
("Fullscreen", "全屏"),
("Mobile Actions", "移动端操作"),
("Select Monitor", "选择监视器"),
("Control Actions", "控制操作"),
("Display Settings", "显示设置"),
("Ratio", "比例"),
("Image Quality", "画质"),
("Scroll Style", "滚屏方式"),
].iter().cloned().collect();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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."),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("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."),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", "Memulai Ulang Perangkat Jarak Jauh"),
("remote_restarting_tip", ""),
("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();
}

View File

@ -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?"),
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
("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();
}

View File

@ -299,5 +299,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "本当に再起動しますか"),
("Restarting Remote Device", "リモート端末を再起動中"),
("remote_restarting_tip", "リモート端末は再起動中です。このメッセージボックスを閉じて、しばらくした後に固定のパスワードを使用して再接続してください。"),
("Exit Fullscreen", "全画面表示を終了"),
("Fullscreen", "全画面表示"),
("Mobile Actions", "モバイル アクション"),
("Select Monitor", "モニターを選択"),
("Control Actions", "コントロール アクション"),
("Display Settings", "ディスプレイの設定"),
("Ratio", "比率"),
("Image Quality", "画質"),
("Scroll Style", "スクロール スタイル"),
].iter().cloned().collect();
}

View File

@ -299,5 +299,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "정말로 재시작 하시겠습니까"),
("Restarting Remote Device", "원격 기기를 다시 시작하는중"),
("remote_restarting_tip", "원격 장치를 다시 시작하는 중입니다. 이 메시지 상자를 닫고 잠시 후 영구 비밀번호로 다시 연결하십시오."),
("Exit Fullscreen", "전체 화면 종료"),
("Fullscreen", "전체화면"),
("Mobile Actions", "모바일 액션"),
("Select Monitor", "모니터 선택"),
("Control Actions", "제어 작업"),
("Display Settings", "화면 설정"),
("Ratio", "비율"),
("Image Quality", "이미지 품질"),
("Scroll Style", "스크롤 스타일"),
].iter().cloned().collect();
}

View File

@ -303,5 +303,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Set security password", "Ustaw hasło zabezpieczające"),
("Connection not allowed", "Połączenie niedozwolone"),
("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();
}

View File

@ -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"),
("Restarting Remote Device", "A reiniciar sistema remoto"),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", ""),
("Fullscreen", ""),
("Mobile Actions", ""),
("Select Monitor", ""),
("Control Actions", ""),
("Display Settings", ""),
("Ratio", ""),
("Image Quality", ""),
("Scroll Style", ""),
].iter().cloned().collect();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", "Перезагрузка удаленного устройства"),
("remote_restarting_tip", "Удаленное устройство перезапускается. Пожалуйста, закройте это сообщение и через некоторое время переподключитесь, используя постоянный пароль."),
("Copied", ""),
("Exit Fullscreen", "Выйти из полноэкранного режима"),
("Fullscreen", "Полноэкранный"),
("Mobile Actions", "Мобильные действия"),
("Select Monitor", "Выберите монитор"),
("Control Actions", "Действия по управлению"),
("Display Settings", "Настройки отображения"),
("Ratio", "Соотношение"),
("Image Quality", "Качество изображения"),
("Scroll Style", "Стиль прокрутки"),
].iter().cloned().collect();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", ""),
("Fullscreen", ""),
("Mobile Actions", ""),
("Select Monitor", ""),
("Control Actions", ""),
("Display Settings", ""),
("Ratio", ""),
("Image Quality", ""),
("Scroll Style", ""),
].iter().cloned().collect();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", "Uzaktan yeniden başlatılıyor"),
("remote_restarting_tip", ""),
("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();
}

View File

@ -302,5 +302,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Restarting Remote Device", "正在重啓遠程設備"),
("remote_restarting_tip", "遠程設備正在重啓,請關閉當前提示框,並在一段時間後使用永久密碼重新連接"),
("Copied", "已複製"),
("Exit Fullscreen", "退出全屏"),
("Fullscreen", "全屏"),
("Mobile Actions", "移動端操作"),
("Select Monitor", "選擇監視器"),
("Control Actions", "控制操作"),
("Display Settings", "顯示設置"),
("Ratio", "比例"),
("Image Quality", "畫質"),
("Scroll Style", "滾動樣式"),
].iter().cloned().collect();
}

View File

@ -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"),
("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", ""),
("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();
}