mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-02 13:01:30 +03:00
Merge branch 'master' of https://github.com/rustdesk/rustdesk into opt_chat_overlay_and_fix_pageview_2
This commit is contained in:
@@ -44,6 +44,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
var watchIsCanScreenRecording = false;
|
||||
var watchIsProcessTrust = false;
|
||||
var watchIsInputMonitoring = false;
|
||||
var watchIsCanRecordAudio = false;
|
||||
Timer? _updateTimer;
|
||||
|
||||
@override
|
||||
@@ -79,7 +80,16 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
buildTip(context),
|
||||
buildIDBoard(context),
|
||||
buildPasswordBoard(context),
|
||||
buildHelpCards(),
|
||||
FutureBuilder<Widget>(
|
||||
future: buildHelpCards(),
|
||||
builder: (_, data) {
|
||||
if (data.hasData) {
|
||||
return data.data!;
|
||||
} else {
|
||||
return const Offstage();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -302,7 +312,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHelpCards() {
|
||||
Future<Widget> buildHelpCards() async {
|
||||
if (updateUrl.isNotEmpty) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
@@ -349,6 +359,15 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
bind.mainIsInstalledDaemon(prompt: true);
|
||||
});
|
||||
}
|
||||
//// Disable microphone configuration for macOS. We will request the permission when needed.
|
||||
// else if ((await osxCanRecordAudio() !=
|
||||
// PermissionAuthorizeType.authorized)) {
|
||||
// return buildInstallCard("Permissions", "config_microphone", "Configure",
|
||||
// () async {
|
||||
// osxRequestAudio();
|
||||
// watchIsCanRecordAudio = true;
|
||||
// });
|
||||
// }
|
||||
} else if (Platform.isLinux) {
|
||||
if (bind.mainCurrentIsWayland()) {
|
||||
return buildInstallCard(
|
||||
@@ -481,6 +500,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
if (watchIsCanRecordAudio) {
|
||||
if (Platform.isMacOS) {
|
||||
Future.microtask(() async {
|
||||
if ((await osxCanRecordAudio() ==
|
||||
PermissionAuthorizeType.authorized)) {
|
||||
watchIsCanRecordAudio = false;
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
watchIsCanRecordAudio = false;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
});
|
||||
Get.put<RxBool>(svcStopped, tag: 'stop-service');
|
||||
rustDeskWinManager.registerActiveWindowListener(onActiveWindowChanged);
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_custom_cursor/cursor_manager.dart'
|
||||
@@ -376,10 +375,10 @@ class _RemotePageState extends State<RemotePage>
|
||||
|
||||
class ImagePaint extends StatefulWidget {
|
||||
final String id;
|
||||
final Rx<bool> zoomCursor;
|
||||
final Rx<bool> cursorOverImage;
|
||||
final Rx<bool> keyboardEnabled;
|
||||
final Rx<bool> remoteCursorMoved;
|
||||
final RxBool zoomCursor;
|
||||
final RxBool cursorOverImage;
|
||||
final RxBool keyboardEnabled;
|
||||
final RxBool remoteCursorMoved;
|
||||
final Widget Function(Widget)? listenerBuilder;
|
||||
|
||||
ImagePaint(
|
||||
@@ -402,10 +401,10 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
final ScrollController _vertical = ScrollController();
|
||||
|
||||
String get id => widget.id;
|
||||
Rx<bool> get zoomCursor => widget.zoomCursor;
|
||||
Rx<bool> get cursorOverImage => widget.cursorOverImage;
|
||||
Rx<bool> get keyboardEnabled => widget.keyboardEnabled;
|
||||
Rx<bool> get remoteCursorMoved => widget.remoteCursorMoved;
|
||||
RxBool get zoomCursor => widget.zoomCursor;
|
||||
RxBool get cursorOverImage => widget.cursorOverImage;
|
||||
RxBool get keyboardEnabled => widget.keyboardEnabled;
|
||||
RxBool get remoteCursorMoved => widget.remoteCursorMoved;
|
||||
Widget Function(Widget)? get listenerBuilder => widget.listenerBuilder;
|
||||
|
||||
@override
|
||||
@@ -414,27 +413,50 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
final s = c.scale;
|
||||
|
||||
mouseRegion({child}) => Obx(() => MouseRegion(
|
||||
cursor: cursorOverImage.isTrue
|
||||
? c.cursorEmbedded
|
||||
? SystemMouseCursors.none
|
||||
: keyboardEnabled.isTrue
|
||||
? (() {
|
||||
if (remoteCursorMoved.isTrue) {
|
||||
_lastRemoteCursorMoved = true;
|
||||
return SystemMouseCursors.none;
|
||||
} else {
|
||||
if (_lastRemoteCursorMoved) {
|
||||
_lastRemoteCursorMoved = false;
|
||||
_firstEnterImage.value = true;
|
||||
}
|
||||
return _buildCustomCursor(context, s);
|
||||
}
|
||||
}())
|
||||
: _buildDisabledCursor(context, s)
|
||||
: MouseCursor.defer,
|
||||
onHover: (evt) {},
|
||||
child: child));
|
||||
mouseRegion({child}) => Obx(() {
|
||||
double getCursorScale() {
|
||||
var c = Provider.of<CanvasModel>(context);
|
||||
var cursorScale = 1.0;
|
||||
if (Platform.isWindows) {
|
||||
// debug win10
|
||||
final isViewAdaptive =
|
||||
c.viewStyle.style == kRemoteViewStyleAdaptive;
|
||||
if (zoomCursor.value && isViewAdaptive) {
|
||||
cursorScale = s * c.devicePixelRatio;
|
||||
}
|
||||
} else {
|
||||
final isViewOriginal =
|
||||
c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
if (zoomCursor.value || isViewOriginal) {
|
||||
cursorScale = s;
|
||||
}
|
||||
}
|
||||
return cursorScale;
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: cursorOverImage.isTrue
|
||||
? c.cursorEmbedded
|
||||
? SystemMouseCursors.none
|
||||
: keyboardEnabled.isTrue
|
||||
? (() {
|
||||
if (remoteCursorMoved.isTrue) {
|
||||
_lastRemoteCursorMoved = true;
|
||||
return SystemMouseCursors.none;
|
||||
} else {
|
||||
if (_lastRemoteCursorMoved) {
|
||||
_lastRemoteCursorMoved = false;
|
||||
_firstEnterImage.value = true;
|
||||
}
|
||||
return _buildCustomCursor(
|
||||
context, getCursorScale());
|
||||
}
|
||||
}())
|
||||
: _buildDisabledCursor(context, getCursorScale())
|
||||
: MouseCursor.defer,
|
||||
onHover: (evt) {},
|
||||
child: child);
|
||||
});
|
||||
|
||||
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
|
||||
final imageWidth = c.getDisplayWidth() * s;
|
||||
@@ -480,7 +502,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
if (cache == null) {
|
||||
return MouseCursor.defer;
|
||||
} else {
|
||||
final key = cache.updateGetKey(scale, zoomCursor.value);
|
||||
final key = cache.updateGetKey(scale);
|
||||
if (!cursor.cachedKeys.contains(key)) {
|
||||
debugPrint("Register custom cursor with key $key");
|
||||
// [Safety]
|
||||
@@ -646,7 +668,8 @@ class CursorPaint extends StatelessWidget {
|
||||
double x = (m.x - hotx) * c.scale + cx;
|
||||
double y = (m.y - hoty) * c.scale + cy;
|
||||
double scale = 1.0;
|
||||
if (zoomCursor.isTrue) {
|
||||
final isViewOriginal = c.viewStyle.style == kRemoteViewStyleOriginal;
|
||||
if (zoomCursor.value || isViewOriginal) {
|
||||
x = m.x - hotx + cx / c.scale;
|
||||
y = m.y - hoty + cy / c.scale;
|
||||
scale = c.scale;
|
||||
|
||||
@@ -243,96 +243,35 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
padding: padding,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'),
|
||||
value: kRemoteViewStyleOriginal,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async =>
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
await bind.sessionGetViewStyle(id: key) ?? '',
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetViewStyle(id: key, value: newValue);
|
||||
ffi.canvasModel.updateViewStyle();
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
RemoteMenuEntry.viewStyle(
|
||||
key,
|
||||
ffi,
|
||||
padding,
|
||||
dismissFunc: cancelFunc,
|
||||
),
|
||||
]);
|
||||
|
||||
if (!ffi.canvasModel.cursorEmbedded) {
|
||||
menu.add(MenuEntryDivider<String>());
|
||||
menu.add(() {
|
||||
final state = ShowRemoteCursorState.find(key);
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: key, value: 'show-remote-cursor');
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
);
|
||||
}());
|
||||
menu.add(RemoteMenuEntry.showRemoteCursor(
|
||||
key,
|
||||
padding,
|
||||
dismissFunc: cancelFunc,
|
||||
));
|
||||
}
|
||||
|
||||
if (perms['keyboard'] != false) {
|
||||
if (perms['clipboard'] != false) {
|
||||
menu.add(MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Disable clipboard'),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(
|
||||
id: key, arg: 'disable-clipboard');
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(id: key, value: 'disable-clipboard');
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
));
|
||||
menu.add(RemoteMenuEntry.disableClipboard(key, padding,
|
||||
dismissFunc: cancelFunc));
|
||||
}
|
||||
|
||||
menu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Insert Lock'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionLockScreen(id: key);
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
menu.add(
|
||||
RemoteMenuEntry.insertLock(key, padding, dismissFunc: cancelFunc));
|
||||
|
||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
||||
menu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
'${translate("Insert")} Ctrl + Alt + Del',
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionCtrlAltDel(id: key);
|
||||
cancelFunc();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
menu.add(RemoteMenuEntry.insertCtrlAltDel(key, padding,
|
||||
dismissFunc: cancelFunc));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -514,6 +514,39 @@ class _CmControlPanel extends StatelessWidget {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: !client.inVoiceCall,
|
||||
child: buildButton(context,
|
||||
color: Colors.red,
|
||||
onClick: () => closeVoiceCall(),
|
||||
icon: Icon(Icons.phone_disabled_rounded, color: Colors.white),
|
||||
text: "Stop voice call",
|
||||
textColor: Colors.white),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !client.incomingVoiceCall,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: buildButton(context,
|
||||
color: MyTheme.accent,
|
||||
onClick: () => handleVoiceCall(true),
|
||||
icon: Icon(Icons.phone_enabled, color: Colors.white),
|
||||
text: "Accept",
|
||||
textColor: Colors.white),
|
||||
),
|
||||
Expanded(
|
||||
child: buildButton(context,
|
||||
color: Colors.red,
|
||||
onClick: () => handleVoiceCall(false),
|
||||
icon:
|
||||
Icon(Icons.phone_disabled_rounded, color: Colors.white),
|
||||
text: "Dismiss",
|
||||
textColor: Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: !client.fromSwitch,
|
||||
child: buildButton(context,
|
||||
@@ -619,7 +652,7 @@ class _CmControlPanel extends StatelessWidget {
|
||||
.marginSymmetric(horizontal: showElevation ? 0 : bigMargin);
|
||||
}
|
||||
|
||||
buildButton(
|
||||
Widget buildButton(
|
||||
BuildContext context, {
|
||||
required Color? color,
|
||||
required Function() onClick,
|
||||
@@ -685,6 +718,14 @@ class _CmControlPanel extends StatelessWidget {
|
||||
void handleSwitchBack(BuildContext context) {
|
||||
bind.cmSwitchBack(connId: client.id);
|
||||
}
|
||||
|
||||
void handleVoiceCall(bool accept) {
|
||||
bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept);
|
||||
}
|
||||
|
||||
void closeVoiceCall() {
|
||||
bind.cmCloseVoiceCall(id: client.id);
|
||||
}
|
||||
}
|
||||
|
||||
void checkClickTime(int id, Function() callback) async {
|
||||
|
||||
@@ -790,6 +790,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
_PopupMenuRoute({
|
||||
required this.position,
|
||||
required this.items,
|
||||
this.menuWrapper,
|
||||
this.initialValue,
|
||||
this.elevation,
|
||||
required this.barrierLabel,
|
||||
@@ -802,6 +803,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
|
||||
final RelativeRect position;
|
||||
final List<PopupMenuEntry<T>> items;
|
||||
final MenuWrapper? menuWrapper;
|
||||
final List<Size?> itemSizes;
|
||||
final T? initialValue;
|
||||
final double? elevation;
|
||||
@@ -844,11 +846,14 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
}
|
||||
}
|
||||
|
||||
final Widget menu = _PopupMenu<T>(
|
||||
Widget menu = _PopupMenu<T>(
|
||||
route: this,
|
||||
semanticLabel: semanticLabel,
|
||||
constraints: constraints,
|
||||
);
|
||||
if (this.menuWrapper != null) {
|
||||
menu = this.menuWrapper!(menu);
|
||||
}
|
||||
final MediaQueryData mediaQuery = MediaQuery.of(context);
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
@@ -1035,6 +1040,7 @@ Future<T?> showMenu<T>({
|
||||
required BuildContext context,
|
||||
required RelativeRect position,
|
||||
required List<PopupMenuEntry<T>> items,
|
||||
MenuWrapper? menuWrapper,
|
||||
T? initialValue,
|
||||
double? elevation,
|
||||
String? semanticLabel,
|
||||
@@ -1062,6 +1068,7 @@ Future<T?> showMenu<T>({
|
||||
return navigator.push(_PopupMenuRoute<T>(
|
||||
position: position,
|
||||
items: items,
|
||||
menuWrapper: menuWrapper,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation,
|
||||
semanticLabel: semanticLabel,
|
||||
@@ -1094,6 +1101,8 @@ typedef PopupMenuCanceled = void Function();
|
||||
typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(
|
||||
BuildContext context);
|
||||
|
||||
typedef MenuWrapper = Widget Function(Widget child);
|
||||
|
||||
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
|
||||
/// because an item was selected. The value passed to [onSelected] is the value of
|
||||
/// the selected menu item.
|
||||
@@ -1124,6 +1133,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
const PopupMenuButton({
|
||||
Key? key,
|
||||
required this.itemBuilder,
|
||||
this.menuWrapper,
|
||||
this.initialValue,
|
||||
this.onHover,
|
||||
this.onSelected,
|
||||
@@ -1151,6 +1161,9 @@ class PopupMenuButton<T> extends StatefulWidget {
|
||||
/// Called when the button is pressed to create the items to show in the menu.
|
||||
final PopupMenuItemBuilder<T> itemBuilder;
|
||||
|
||||
/// Menu wrapper.
|
||||
final MenuWrapper? menuWrapper;
|
||||
|
||||
/// The value of the menu item, if any, that should be highlighted when the menu opens.
|
||||
final T? initialValue;
|
||||
|
||||
@@ -1333,6 +1346,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
context: context,
|
||||
elevation: widget.elevation ?? popupMenuTheme.elevation,
|
||||
items: items,
|
||||
menuWrapper: widget.menuWrapper,
|
||||
initialValue: widget.initialValue,
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
|
||||
@@ -109,13 +109,17 @@ class MenuConfig {
|
||||
this.boxWidth});
|
||||
}
|
||||
|
||||
typedef DismissCallback = Function();
|
||||
|
||||
abstract class MenuEntryBase<T> {
|
||||
bool dismissOnClicked;
|
||||
DismissCallback? dismissCallback;
|
||||
RxBool? enabled;
|
||||
|
||||
MenuEntryBase({
|
||||
this.dismissOnClicked = false,
|
||||
this.enabled,
|
||||
this.dismissCallback,
|
||||
});
|
||||
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
||||
|
||||
@@ -146,12 +150,14 @@ class MenuEntryRadioOption {
|
||||
String value;
|
||||
bool dismissOnClicked;
|
||||
RxBool? enabled;
|
||||
DismissCallback? dismissCallback;
|
||||
|
||||
MenuEntryRadioOption({
|
||||
required this.text,
|
||||
required this.value,
|
||||
this.dismissOnClicked = false,
|
||||
this.enabled,
|
||||
this.dismissCallback,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,8 +183,13 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
required this.optionSetter,
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
dismissCallback,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
() async {
|
||||
_curOption.value = await curOptionGetter();
|
||||
}();
|
||||
@@ -249,6 +260,9 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
onPressed() {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (opt.dismissCallback != null) {
|
||||
opt.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(opt.value);
|
||||
}
|
||||
@@ -360,6 +374,9 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
onPressed: () {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (opt.dismissCallback != null) {
|
||||
opt.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(opt.value);
|
||||
},
|
||||
@@ -421,7 +438,12 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
this.textStyle,
|
||||
this.padding,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
RxBool get curOption;
|
||||
Future<void> setOption(bool? option);
|
||||
@@ -463,6 +485,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
@@ -474,6 +499,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
@@ -485,6 +513,9 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
onPressed: () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
setOption(!curOption.value);
|
||||
},
|
||||
@@ -508,6 +539,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
@@ -515,6 +547,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
) {
|
||||
() async {
|
||||
_curOption.value = await getter();
|
||||
@@ -551,12 +584,15 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked);
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
@override
|
||||
RxBool get curOption => getter();
|
||||
@@ -627,9 +663,11 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
dismissCallback,
|
||||
}) : super(
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
|
||||
Widget _buildChild(BuildContext context, MenuConfig conf) {
|
||||
@@ -641,6 +679,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
? () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
if (super.dismissCallback != null) {
|
||||
super.dismissCallback!();
|
||||
}
|
||||
}
|
||||
proc();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:debounce_throttle/debounce_throttle.dart';
|
||||
@@ -99,6 +100,175 @@ class _MenubarTheme {
|
||||
static const double dividerHeight = 12.0;
|
||||
}
|
||||
|
||||
typedef DismissFunc = void Function();
|
||||
|
||||
class RemoteMenuEntry {
|
||||
static MenuEntryRadios<String> viewStyle(
|
||||
String remoteId,
|
||||
FFI ffi,
|
||||
EdgeInsets padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
RxString? rxViewStyle,
|
||||
}) {
|
||||
return MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'),
|
||||
value: kRemoteViewStyleOriginal,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
final viewStyle = await bind.sessionGetViewStyle(id: remoteId) ?? '';
|
||||
if (rxViewStyle != null) {
|
||||
rxViewStyle.value = viewStyle;
|
||||
}
|
||||
return viewStyle;
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetViewStyle(id: remoteId, value: newValue);
|
||||
if (rxViewStyle != null) {
|
||||
rxViewStyle.value = newValue;
|
||||
}
|
||||
ffi.canvasModel.updateViewStyle();
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch2<String> showRemoteCursor(
|
||||
String remoteId,
|
||||
EdgeInsets padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
final state = ShowRemoteCursorState.find(remoteId);
|
||||
final optKey = 'show-remote-cursor';
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(id: remoteId, value: optKey);
|
||||
state.value =
|
||||
bind.sessionGetToggleOptionSync(id: remoteId, arg: optKey);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch<String> disableClipboard(
|
||||
String remoteId,
|
||||
EdgeInsets? padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return createSwitchMenuEntry(
|
||||
remoteId,
|
||||
'Disable clipboard',
|
||||
'disable-clipboard',
|
||||
padding,
|
||||
true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntrySwitch<String> createSwitchMenuEntry(
|
||||
String remoteId,
|
||||
String text,
|
||||
String option,
|
||||
EdgeInsets? padding,
|
||||
bool dismissOnClicked, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate(text),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(id: remoteId, arg: option);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(id: remoteId, value: option);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static MenuEntryButton<String> insertLock(
|
||||
String remoteId,
|
||||
EdgeInsets? padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Insert Lock'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionLockScreen(id: remoteId);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
|
||||
static insertCtrlAltDel(
|
||||
String remoteId,
|
||||
EdgeInsets? padding, {
|
||||
DismissFunc? dismissFunc,
|
||||
DismissCallback? dismissCallback,
|
||||
}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
'${translate("Insert")} Ctrl + Alt + Del',
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionCtrlAltDel(id: remoteId);
|
||||
if (dismissFunc != null) {
|
||||
dismissFunc();
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: dismissCallback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteMenubar extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -221,6 +391,18 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPointerTrackWidget(Widget child) {
|
||||
return Listener(
|
||||
onPointerHover: (PointerHoverEvent e) =>
|
||||
widget.ffi.inputModel.lastMousePos = e.position,
|
||||
child: MouseRegion(
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_menuDismissCallback() => widget.ffi.inputModel.refreshMousePos();
|
||||
|
||||
Widget _buildMenubar(BuildContext context) {
|
||||
final List<Widget> menubarItems = [];
|
||||
if (!isWebDesktop) {
|
||||
@@ -244,6 +426,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
menubarItems.add(_buildKeyboard(context));
|
||||
if (!isWeb) {
|
||||
menubarItems.add(_buildChat(context));
|
||||
menubarItems.add(_buildVoiceCall(context));
|
||||
}
|
||||
menubarItems.add(_buildRecording(context));
|
||||
menubarItems.add(_buildClose(context));
|
||||
@@ -297,31 +480,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
);
|
||||
}
|
||||
|
||||
final _chatButtonKey = GlobalKey();
|
||||
Widget _buildChat(BuildContext context) {
|
||||
return IconButton(
|
||||
key: _chatButtonKey,
|
||||
tooltip: translate('Chat'),
|
||||
onPressed: () {
|
||||
RenderBox? renderBox =
|
||||
_chatButtonKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
Offset? initPos;
|
||||
if (renderBox != null) {
|
||||
final pos = renderBox.localToGlobal(Offset.zero);
|
||||
initPos = Offset(pos.dx, pos.dy + _MenubarTheme.dividerHeight);
|
||||
}
|
||||
|
||||
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.message,
|
||||
color: _MenubarTheme.commonColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMonitor(BuildContext context) {
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
return mod_menu.PopupMenuButton(
|
||||
@@ -375,6 +533,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
if (display.value != i) {
|
||||
@@ -390,13 +549,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
mod_menu.PopupMenuItem<String>(
|
||||
height: _MenubarTheme.height,
|
||||
padding: EdgeInsets.zero,
|
||||
child: Listener(
|
||||
onPointerHover: (PointerHoverEvent e) =>
|
||||
widget.ffi.inputModel.lastMousePos = e.position,
|
||||
child: MouseRegion(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: rowChildren),
|
||||
child: _buildPointerTrackWidget(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: rowChildren,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -446,6 +602,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
),
|
||||
tooltip: translate('Display Settings'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
menuWrapper: _buildPointerTrackWidget,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
_getDisplayMenu(snapshot.data!, remoteCount)
|
||||
.map((entry) => entry.build(
|
||||
@@ -500,12 +657,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
? translate('Stop session recording')
|
||||
: translate('Start session recording'),
|
||||
onPressed: () => value.toggle(),
|
||||
icon: Icon(
|
||||
value.start
|
||||
? Icons.pause_circle_filled
|
||||
: Icons.videocam_outlined,
|
||||
color: _MenubarTheme.commonColor,
|
||||
),
|
||||
icon: value.start
|
||||
? Icon(
|
||||
Icons.pause_circle_filled,
|
||||
color: _MenubarTheme.commonColor,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
"assets/record_screen.svg",
|
||||
color: _MenubarTheme.commonColor,
|
||||
width: Theme.of(context).iconTheme.size ?? 22.0,
|
||||
height: Theme.of(context).iconTheme.size ?? 22.0,
|
||||
),
|
||||
));
|
||||
} else {
|
||||
return Offstage();
|
||||
@@ -526,6 +688,130 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
);
|
||||
}
|
||||
|
||||
final _chatButtonKey = GlobalKey();
|
||||
Widget _buildChat(BuildContext context) {
|
||||
FfiModel ffiModel = Provider.of<FfiModel>(context);
|
||||
return mod_menu.PopupMenuButton(
|
||||
key: _chatButtonKey,
|
||||
padding: EdgeInsets.zero,
|
||||
icon: SvgPicture.asset(
|
||||
"assets/chat.svg",
|
||||
color: _MenubarTheme.commonColor,
|
||||
width: Theme.of(context).iconTheme.size ?? 24.0,
|
||||
height: Theme.of(context).iconTheme.size ?? 24.0,
|
||||
),
|
||||
tooltip: translate('Chat'),
|
||||
position: mod_menu.PopupMenuPosition.under,
|
||||
itemBuilder: (BuildContext context) => _getChatMenu(context)
|
||||
.map((entry) => entry.build(
|
||||
context,
|
||||
const MenuConfig(
|
||||
commonColor: _MenubarTheme.commonColor,
|
||||
height: _MenubarTheme.height,
|
||||
dividerHeight: _MenubarTheme.dividerHeight,
|
||||
)))
|
||||
.expand((i) => i)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getVoiceCallIcon() {
|
||||
switch (widget.ffi.chatModel.voiceCallStatus.value) {
|
||||
case VoiceCallStatus.waitingForResponse:
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
widget.ffi.chatModel.closeVoiceCall(widget.id);
|
||||
},
|
||||
icon: SvgPicture.asset(
|
||||
"assets/voice_call_waiting.svg",
|
||||
color: Colors.red,
|
||||
width: Theme.of(context).iconTheme.size ?? 20.0,
|
||||
height: Theme.of(context).iconTheme.size ?? 20.0,
|
||||
));
|
||||
case VoiceCallStatus.connected:
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
widget.ffi.chatModel.closeVoiceCall(widget.id);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.phone_disabled_rounded,
|
||||
color: Colors.red,
|
||||
size: Theme.of(context).iconTheme.size ?? 22.0,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return const Offstage();
|
||||
}
|
||||
}
|
||||
|
||||
String? _getVoiceCallTooltip() {
|
||||
switch (widget.ffi.chatModel.voiceCallStatus.value) {
|
||||
case VoiceCallStatus.waitingForResponse:
|
||||
return "Waiting";
|
||||
case VoiceCallStatus.connected:
|
||||
return "Disconnect";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildVoiceCall(BuildContext context) {
|
||||
return Obx(
|
||||
() {
|
||||
final tooltipText = _getVoiceCallTooltip();
|
||||
return tooltipText == null
|
||||
? const Offstage()
|
||||
: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: _getVoiceCallIcon(),
|
||||
tooltip: translate(tooltipText),
|
||||
onPressed: () => bind.sessionRequestVoiceCall(id: widget.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getChatMenu(BuildContext context) {
|
||||
final List<MenuEntryBase<String>> chatMenu = [];
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
|
||||
chatMenu.addAll([
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Text chat'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
RenderBox? renderBox =
|
||||
_chatButtonKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
|
||||
Offset? initPos;
|
||||
if (renderBox != null) {
|
||||
final pos = renderBox.localToGlobal(Offset.zero);
|
||||
initPos = Offset(pos.dx, pos.dy + _MenubarTheme.dividerHeight);
|
||||
}
|
||||
|
||||
widget.ffi.chatModel.changeCurrentID(ChatModel.clientModeID);
|
||||
widget.ffi.chatModel.toggleChatOverlay(chatInitPos: initPos);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Voice call'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
// Request a voice call.
|
||||
bind.sessionRequestVoiceCall(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
]);
|
||||
return chatMenu;
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
@@ -554,6 +840,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
showSetOSPassword(
|
||||
widget.id, false, widget.ffi.dialogManager);
|
||||
@@ -566,6 +853,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@@ -577,6 +865,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@@ -588,6 +877,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
connect(context, widget.id, isTcpTunneling: true);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
]);
|
||||
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
|
||||
@@ -605,23 +895,15 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
displayMenu.add(MenuEntryDivider());
|
||||
if (perms['keyboard'] != false) {
|
||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
'${translate("Insert")} Ctrl + Alt + Del',
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionCtrlAltDel(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
displayMenu.add(RemoteMenuEntry.insertCtrlAltDel(widget.id, padding,
|
||||
dismissCallback: _menuDismissCallback));
|
||||
}
|
||||
}
|
||||
if (perms['restart'] != false &&
|
||||
@@ -638,21 +920,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
|
||||
if (perms['keyboard'] != false) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Insert Lock'),
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
bind.sessionLockScreen(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
displayMenu.add(RemoteMenuEntry.insertLock(widget.id, padding,
|
||||
dismissCallback: _menuDismissCallback));
|
||||
|
||||
if (pi.platform == kPeerPlatformWindows) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
@@ -670,6 +944,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
if (pi.platform != kPeerPlatformAndroid &&
|
||||
@@ -684,6 +959,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
showConfirmSwitchSidesDialog(widget.id, widget.ffi.dialogManager),
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -699,6 +975,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -720,10 +997,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
// },
|
||||
// padding: padding,
|
||||
// dismissOnClicked: true,
|
||||
// dismissCallback: _menuDismissCallback,
|
||||
// ));
|
||||
// }
|
||||
}
|
||||
|
||||
return displayMenu;
|
||||
}
|
||||
|
||||
@@ -758,33 +1035,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
|
||||
final peer_version = widget.ffi.ffiModel.pi.version;
|
||||
final displayMenu = [
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'),
|
||||
value: kRemoteViewStyleOriginal,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: kRemoteViewStyleAdaptive,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
final viewStyle = await bind.sessionGetViewStyle(id: widget.id) ?? '';
|
||||
widget.state.viewStyle.value = viewStyle;
|
||||
return viewStyle;
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetViewStyle(id: widget.id, value: newValue);
|
||||
widget.state.viewStyle.value = newValue;
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
RemoteMenuEntry.viewStyle(
|
||||
widget.id,
|
||||
widget.ffi,
|
||||
padding,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
rxViewStyle: widget.state.viewStyle,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
@@ -794,21 +1050,26 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Good image quality'),
|
||||
value: kRemoteImageQualityBest,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Balanced'),
|
||||
value: kRemoteImageQualityBalanced,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Optimize reaction time'),
|
||||
value: kRemoteImageQualityLow,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Custom'),
|
||||
value: kRemoteImageQualityCustom,
|
||||
dismissOnClicked: true),
|
||||
text: translate('Custom'),
|
||||
value: kRemoteImageQualityCustom,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async =>
|
||||
// null means peer id is not found, which there's no need to care about
|
||||
@@ -973,12 +1234,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('ScrollAuto'),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'),
|
||||
value: kRemoteScrollStyleBar,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
],
|
||||
@@ -991,6 +1254,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
displayMenu.insert(3, MenuEntryDivider<String>());
|
||||
|
||||
@@ -1061,6 +1325,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1087,11 +1352,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Auto'),
|
||||
value: 'auto',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: 'VP9',
|
||||
value: 'vp9',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
];
|
||||
if (codecs[0]) {
|
||||
@@ -1099,6 +1366,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: 'H264',
|
||||
value: 'h264',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
if (codecs[1]) {
|
||||
@@ -1106,6 +1374,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: 'H265',
|
||||
value: 'h265',
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
return list;
|
||||
@@ -1122,6 +1391,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1129,23 +1399,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
|
||||
/// Show remote cursor
|
||||
if (!widget.ffi.canvasModel.cursorEmbedded) {
|
||||
displayMenu.add(() {
|
||||
final state = ShowRemoteCursorState.find(widget.id);
|
||||
return MenuEntrySwitch2<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-remote-cursor');
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}());
|
||||
displayMenu.add(RemoteMenuEntry.showRemoteCursor(
|
||||
widget.id,
|
||||
padding,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
|
||||
/// Show remote cursor scaling with image
|
||||
@@ -1160,11 +1418,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(id: widget.id, value: opt);
|
||||
state.value =
|
||||
bind.sessionGetToggleOptionSync(id: widget.id, arg: opt);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
);
|
||||
}());
|
||||
}
|
||||
@@ -1184,6 +1444,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
@@ -1192,6 +1453,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
if (perms['audio'] != false) {
|
||||
displayMenu
|
||||
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
|
||||
displayMenu
|
||||
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
|
||||
}
|
||||
|
||||
if (Platform.isWindows &&
|
||||
@@ -1203,8 +1466,11 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
|
||||
if (perms['keyboard'] != false) {
|
||||
if (perms['clipboard'] != false) {
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Disable clipboard', 'disable-clipboard', padding, true));
|
||||
displayMenu.add(RemoteMenuEntry.disableClipboard(
|
||||
widget.id,
|
||||
padding,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Lock after session end', 'lock-after-session-end', padding, true));
|
||||
@@ -1221,6 +1487,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1233,25 +1500,29 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () {
|
||||
List<MenuEntryRadioOption> list = [];
|
||||
List<String> modes = ["legacy"];
|
||||
List<KeyboardModeMenu> modes = [
|
||||
KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'),
|
||||
KeyboardModeMenu(key: 'map', menu: 'Map mode'),
|
||||
KeyboardModeMenu(key: 'translate', menu: 'Translate mode'),
|
||||
];
|
||||
|
||||
if (bind.sessionIsKeyboardModeSupported(id: widget.id, mode: "map")) {
|
||||
modes.add("map");
|
||||
}
|
||||
|
||||
for (String mode in modes) {
|
||||
if (mode == "legacy") {
|
||||
for (KeyboardModeMenu mode in modes) {
|
||||
if (bind.sessionIsKeyboardModeSupported(
|
||||
id: widget.id, mode: mode.key)) {
|
||||
if (mode.key == 'translate') {
|
||||
if (!Platform.isWindows ||
|
||||
widget.ffi.ffiModel.pi.platform != kPeerPlatformWindows) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: translate('Legacy mode'), value: 'legacy'));
|
||||
} else if (mode == "map") {
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: translate('Map mode'), value: 'map'));
|
||||
text: translate(mode.menu), value: mode.key));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetKeyboardMode(id: widget.id) ?? "legacy";
|
||||
return await bind.sessionGetKeyboardMode(id: widget.id) ?? 'legacy';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetKeyboardMode(id: widget.id, value: newValue);
|
||||
@@ -1292,6 +1563,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
_menuDismissCallback();
|
||||
}
|
||||
showKBLayoutTypeChooser(
|
||||
localPlatform, widget.ffi.dialogManager);
|
||||
@@ -1304,6 +1576,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {},
|
||||
padding: EdgeInsets.zero,
|
||||
dismissOnClicked: false,
|
||||
dismissCallback: _menuDismissCallback,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1312,18 +1585,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
|
||||
MenuEntrySwitch<String> _createSwitchMenuEntry(
|
||||
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
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);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
);
|
||||
return RemoteMenuEntry.createSwitchMenuEntry(
|
||||
widget.id, text, option, padding, dismissOnClicked,
|
||||
dismissCallback: _menuDismissCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,3 +1811,10 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardModeMenu {
|
||||
final String key;
|
||||
final String menu;
|
||||
|
||||
KeyboardModeMenu({required this.key, required this.menu});
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/main.dart';
|
||||
import 'package:flutter_hbb/common/shared_state.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/state_model.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
|
||||
import 'package:scroll_pos/scroll_pos.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:bot_toast/bot_toast.dart';
|
||||
|
||||
import '../../utils/multi_window_manager.dart';
|
||||
|
||||
@@ -545,7 +545,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
void onWindowClose() async {
|
||||
// hide window on close
|
||||
if (widget.isMainWindow) {
|
||||
await rustDeskWinManager.unregisterActiveWindow(0);
|
||||
if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) {
|
||||
await rustDeskWinManager.unregisterActiveWindow(kMainWindowId);
|
||||
}
|
||||
// `hide` must be placed after unregisterActiveWindow, because once all windows are hidden,
|
||||
// flutter closes the application on macOS. We should ensure the post-run logic has ran successfully.
|
||||
// e.g.: saving window position.
|
||||
@@ -976,7 +978,7 @@ class _CloseButton extends StatelessWidget {
|
||||
offstage: !visible,
|
||||
child: InkWell(
|
||||
hoverColor: MyTheme.tabbar(context).closeHoverColor,
|
||||
customBorder: const RoundedRectangleBorder(),
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => onClose(),
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
@@ -1099,7 +1101,7 @@ class TabbarTheme extends ThemeExtension<TabbarTheme> {
|
||||
unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
|
||||
dividerColor: Color.fromARGB(255, 238, 238, 238),
|
||||
hoverColor: Color.fromARGB(51, 158, 158, 158),
|
||||
closeHoverColor: Colors.black,
|
||||
closeHoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||
selectedTabBackgroundColor: Color.fromARGB(255, 240, 240, 240));
|
||||
|
||||
static const dark = TabbarTheme(
|
||||
|
||||
Reference in New Issue
Block a user