2022-09-16 19:43:28 +08:00
import ' dart:convert ' ;
2022-09-08 08:25:19 -07:00
import ' dart:io ' ;
2022-09-13 07:24:06 -07:00
import ' dart:math ' as math ;
2022-09-08 08:25:19 -07:00
2022-08-26 23:28:08 +08:00
import ' package:flutter/material.dart ' ;
import ' package:flutter/services.dart ' ;
import ' package:flutter_hbb/models/chat_model.dart ' ;
import ' package:get/get.dart ' ;
2022-09-15 17:31:28 +08:00
import ' package:provider/provider.dart ' ;
2022-08-31 23:02:02 -07:00
import ' package:rxdart/rxdart.dart ' as rxdart ;
2022-10-08 17:27:30 +08:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-08-26 23:28:08 +08:00
import ' ../../common.dart ' ;
import ' ../../mobile/widgets/dialog.dart ' ;
import ' ../../models/model.dart ' ;
import ' ../../models/platform_model.dart ' ;
2022-08-29 18:48:12 +08:00
import ' ../../common/shared_state.dart ' ;
2022-08-26 23:28:08 +08:00
import ' ./popup_menu.dart ' ;
2022-08-28 21:55:16 +08:00
import ' ./material_mod_popup_menu.dart ' as mod_menu ;
2022-08-26 23:28:08 +08:00
class _MenubarTheme {
static const Color commonColor = MyTheme . accent ;
2022-08-29 18:48:12 +08:00
// kMinInteractiveDimension
2022-09-23 12:20:40 +08:00
static const double height = 20.0 ;
2022-08-29 18:48:12 +08:00
static const double dividerHeight = 12.0 ;
2022-08-26 23:28:08 +08:00
}
class RemoteMenubar extends StatefulWidget {
final String id ;
2022-10-08 17:27:30 +08:00
final int windowId ;
2022-08-26 23:28:08 +08:00
final FFI ffi ;
2022-09-13 19:10:55 -07:00
final Function ( Function ( bool ) ) onEnterOrLeaveImageSetter ;
final Function ( ) onEnterOrLeaveImageCleaner ;
2022-08-26 23:28:08 +08:00
const RemoteMenubar ( {
Key ? key ,
required this . id ,
2022-10-08 17:27:30 +08:00
required this . windowId ,
2022-08-26 23:28:08 +08:00
required this . ffi ,
2022-09-13 19:10:55 -07:00
required this . onEnterOrLeaveImageSetter ,
required this . onEnterOrLeaveImageCleaner ,
2022-08-26 23:28:08 +08:00
} ) : 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 ;
2022-09-13 06:59:06 -07:00
final _rxHideReplay = rxdart . ReplaySubject < int > ( ) ;
final _pinMenubar = false . obs ;
bool _isCursorOverImage = false ;
2022-08-26 23:28:08 +08:00
bool get isFullscreen = > Get . find < RxBool > ( tag: ' fullscreen ' ) . isTrue ;
2022-09-13 06:59:06 -07:00
void _setFullscreen ( bool v ) {
2022-08-26 23:28:08 +08:00
Get . find < RxBool > ( tag: ' fullscreen ' ) . value = v ;
}
2022-09-13 06:59:06 -07:00
@ override
2022-09-13 19:10:55 -07:00
initState ( ) {
2022-09-13 06:59:06 -07:00
super . initState ( ) ;
2022-09-13 19:10:55 -07:00
widget . onEnterOrLeaveImageSetter ( ( enter ) {
2022-09-13 06:59:06 -07:00
if ( enter ) {
_rxHideReplay . add ( 0 ) ;
_isCursorOverImage = true ;
} else {
_isCursorOverImage = false ;
}
} ) ;
_rxHideReplay
. throttleTime ( const Duration ( milliseconds: 5000 ) ,
trailing: true , leading: false )
. listen ( ( int v ) {
if ( _pinMenubar . isFalse & & _show . isTrue & & _isCursorOverImage ) {
_show . value = false ;
}
} ) ;
}
2022-09-13 19:10:55 -07:00
@ override
dispose ( ) {
super . dispose ( ) ;
widget . onEnterOrLeaveImageCleaner ( ) ;
}
2022-08-26 23:28:08 +08:00
@ override
Widget build ( BuildContext context ) {
return Align (
alignment: Alignment . topCenter ,
child: Obx (
( ) = > _show . value ? _buildMenubar ( context ) : _buildShowHide ( context ) ) ,
) ;
}
Widget _buildShowHide ( BuildContext context ) {
2022-08-28 21:55:16 +08:00
return Obx ( ( ) = > Tooltip (
2022-09-13 19:10:55 -07:00
message: translate ( _show . value ? " Hide Menubar " : " Show Menubar " ) ,
child: SizedBox (
width: 100 ,
height: 13 ,
child: TextButton (
onHover: ( bool v ) {
_hideColor . value = v ? Colors . white60 : Colors . white24 ;
} ,
onPressed: ( ) {
_show . value = ! _show . value ;
} ,
child: Obx ( ( ) = > Container (
color: _hideColor . value ,
) . marginOnly ( bottom: 8.0 ) ) ) ) ) ) ;
2022-08-26 23:28:08 +08:00
}
Widget _buildMenubar ( BuildContext context ) {
final List < Widget > menubarItems = [ ] ;
if ( ! isWebDesktop ) {
2022-09-13 06:59:06 -07:00
menubarItems . add ( _buildPinMenubar ( context ) ) ;
2022-08-26 23:28:08 +08:00
menubarItems . add ( _buildFullscreen ( context ) ) ;
2022-09-08 22:18:02 +08:00
if ( widget . ffi . ffiModel . isPeerAndroid ) {
menubarItems . add ( IconButton (
tooltip: translate ( ' Mobile Actions ' ) ,
color: _MenubarTheme . commonColor ,
icon: const Icon ( Icons . build ) ,
onPressed: ( ) {
widget . ffi . dialogManager
. toggleMobileActionsOverlay ( ffi: widget . ffi ) ;
} ,
) ) ;
}
2022-08-26 23:28:08 +08:00
}
menubarItems . add ( _buildMonitor ( context ) ) ;
menubarItems . add ( _buildControl ( context ) ) ;
menubarItems . add ( _buildDisplay ( context ) ) ;
2022-09-05 10:18:29 -04:00
menubarItems . add ( _buildKeyboard ( context ) ) ;
2022-08-26 23:28:08 +08:00
if ( ! isWeb ) {
menubarItems . add ( _buildChat ( context ) ) ;
}
2022-09-15 17:31:28 +08:00
menubarItems . add ( _buildRecording ( context ) ) ;
2022-08-26 23:28:08 +08:00
menubarItems . add ( _buildClose ( context ) ) ;
return PopupMenuTheme (
2022-08-28 21:55:16 +08:00
data: const PopupMenuThemeData (
2022-08-26 23:28:08 +08:00
textStyle: TextStyle ( color: _MenubarTheme . commonColor ) ) ,
child: Column ( mainAxisSize: MainAxisSize . min , children: [
Container (
color: Colors . white ,
child: Row (
mainAxisSize: MainAxisSize . min ,
children: menubarItems ,
) ) ,
_buildShowHide ( context ) ,
] ) ) ;
}
2022-09-13 06:59:06 -07:00
Widget _buildPinMenubar ( BuildContext context ) {
2022-09-13 07:24:06 -07:00
return Obx ( ( ) = > IconButton (
tooltip:
translate ( _pinMenubar . isTrue ? ' Unpin menubar ' : ' Pin menubar ' ) ,
onPressed: ( ) {
_pinMenubar . value = ! _pinMenubar . value ;
} ,
icon: Obx ( ( ) = > Transform . rotate (
angle: _pinMenubar . isTrue ? math . pi / 4 : 0 ,
child: Icon (
Icons . push_pin ,
color: _pinMenubar . isTrue
? _MenubarTheme . commonColor
: Colors . grey ,
) ) ) ,
) ) ;
2022-09-13 06:59:06 -07:00
}
2022-08-26 23:28:08 +08:00
Widget _buildFullscreen ( BuildContext context ) {
return IconButton (
tooltip: translate ( isFullscreen ? ' Exit Fullscreen ' : ' Fullscreen ' ) ,
onPressed: ( ) {
2022-09-13 06:59:06 -07:00
_setFullscreen ( ! isFullscreen ) ;
2022-08-26 23:28:08 +08:00
} ,
icon: Obx ( ( ) = > isFullscreen
2022-08-28 21:55:16 +08:00
? const Icon (
2022-08-26 23:28:08 +08:00
Icons . fullscreen_exit ,
color: _MenubarTheme . commonColor ,
)
2022-08-28 21:55:16 +08:00
: const Icon (
2022-08-26 23:28:08 +08:00
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 ( ) ;
} ,
2022-08-28 21:55:16 +08:00
icon: const Icon (
2022-08-26 23:28:08 +08:00
Icons . message ,
color: _MenubarTheme . commonColor ,
) ,
) ;
}
Widget _buildMonitor ( BuildContext context ) {
final pi = widget . ffi . ffiModel . pi ;
2022-08-28 21:55:16 +08:00
return mod_menu . PopupMenuButton (
2022-08-26 23:28:08 +08:00
tooltip: translate ( ' Select Monitor ' ) ,
padding: EdgeInsets . zero ,
2022-08-28 21:55:16 +08:00
position: mod_menu . PopupMenuPosition . under ,
2022-08-26 23:28:08 +08:00
icon: Stack (
alignment: Alignment . center ,
children: [
2022-08-28 21:55:16 +08:00
const Icon (
2022-08-26 23:28:08 +08:00
Icons . personal_video ,
color: _MenubarTheme . commonColor ,
) ,
Padding (
2022-08-28 21:55:16 +08:00
padding: const EdgeInsets . only ( bottom: 3.9 ) ,
2022-08-26 23:28:08 +08:00
child: Obx ( ( ) {
RxInt display = CurrentDisplayState . find ( widget . id ) ;
return Text (
" ${ display . value + 1 } / ${ pi . displays . length } " ,
2022-08-28 21:55:16 +08:00
style: const TextStyle (
color: _MenubarTheme . commonColor , fontSize: 8 ) ,
2022-08-26 23:28:08 +08:00
) ;
} ) ,
)
] ,
) ,
itemBuilder: ( BuildContext context ) {
final List < Widget > rowChildren = [ ] ;
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
2022-08-29 18:48:12 +08:00
rowChildren . add (
Stack (
2022-08-26 23:28:08 +08:00
alignment: Alignment . center ,
children: [
2022-08-28 21:55:16 +08:00
const Icon (
2022-08-26 23:28:08 +08:00
Icons . personal_video ,
color: _MenubarTheme . commonColor ,
) ,
TextButton (
child: Container (
alignment: AlignmentDirectional . center ,
constraints:
2022-08-28 21:55:16 +08:00
const BoxConstraints ( minHeight: _MenubarTheme . height ) ,
2022-08-26 23:28:08 +08:00
child: Padding (
2022-08-28 21:55:16 +08:00
padding: const EdgeInsets . only ( bottom: 2.5 ) ,
2022-08-26 23:28:08 +08:00
child: Text (
( i + 1 ) . toString ( ) ,
2022-08-28 21:55:16 +08:00
style:
const TextStyle ( color: _MenubarTheme . commonColor ) ,
2022-08-26 23:28:08 +08:00
) ,
) ) ,
onPressed: ( ) {
RxInt display = CurrentDisplayState . find ( widget . id ) ;
if ( display . value ! = i ) {
bind . sessionSwitchDisplay ( id: widget . id , value: i ) ;
pi . currentDisplay = i ;
display . value = i ;
}
} ,
)
] ,
) ,
2022-08-29 18:48:12 +08:00
) ;
2022-08-26 23:28:08 +08:00
}
2022-08-28 21:55:16 +08:00
return < mod_menu . PopupMenuEntry < String > > [
mod_menu . PopupMenuItem < String > (
2022-08-26 23:28:08 +08:00
height: _MenubarTheme . height ,
padding: EdgeInsets . zero ,
child: Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: rowChildren ) ,
)
] ;
} ,
) ;
}
Widget _buildControl ( BuildContext context ) {
2022-08-28 21:55:16 +08:00
return mod_menu . PopupMenuButton (
2022-08-26 23:28:08 +08:00
padding: EdgeInsets . zero ,
2022-08-28 21:55:16 +08:00
icon: const Icon (
2022-08-26 23:28:08 +08:00
Icons . bolt ,
color: _MenubarTheme . commonColor ,
) ,
tooltip: translate ( ' Control Actions ' ) ,
2022-08-28 21:55:16 +08:00
position: mod_menu . PopupMenuPosition . under ,
2022-09-08 00:35:19 -07:00
itemBuilder: ( BuildContext context ) = > _getControlMenu ( context )
2022-08-26 23:28:08 +08:00
. map ( ( entry ) = > entry . build (
context ,
2022-08-28 21:55:16 +08:00
const MenuConfig (
2022-08-26 23:28:08 +08:00
commonColor: _MenubarTheme . commonColor ,
2022-08-29 18:48:12 +08:00
height: _MenubarTheme . height ,
dividerHeight: _MenubarTheme . dividerHeight ,
2022-08-26 23:28:08 +08:00
) ) )
2022-08-30 17:20:25 +08:00
. expand ( ( i ) = > i )
2022-08-26 23:28:08 +08:00
. toList ( ) ,
) ;
}
Widget _buildDisplay ( BuildContext context ) {
2022-09-16 19:43:28 +08:00
return FutureBuilder ( future: ( ) async {
final supportedHwcodec =
await bind . sessionSupportedHwcodec ( id: widget . id ) ;
return { ' supportedHwcodec ' : supportedHwcodec } ;
} ( ) , builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
2022-10-08 17:27:30 +08:00
return Obx ( ( ) {
final remoteCount = RemoteCountState . find ( ) . value ;
return mod_menu . PopupMenuButton (
padding: EdgeInsets . zero ,
icon: const Icon (
Icons . tv ,
color: _MenubarTheme . commonColor ,
) ,
tooltip: translate ( ' Display Settings ' ) ,
position: mod_menu . PopupMenuPosition . under ,
itemBuilder: ( BuildContext context ) = >
_getDisplayMenu ( snapshot . data ! , remoteCount )
. map ( ( entry ) = > entry . build (
context ,
const MenuConfig (
commonColor: _MenubarTheme . commonColor ,
height: _MenubarTheme . height ,
dividerHeight: _MenubarTheme . dividerHeight ,
) ) )
. expand ( ( i ) = > i )
. toList ( ) ,
) ;
} ) ;
2022-09-16 19:43:28 +08:00
} else {
return const Offstage ( ) ;
}
} ) ;
2022-08-26 23:28:08 +08:00
}
2022-09-05 10:18:29 -04:00
Widget _buildKeyboard ( BuildContext context ) {
return mod_menu . PopupMenuButton (
padding: EdgeInsets . zero ,
icon: const Icon (
Icons . keyboard ,
color: _MenubarTheme . commonColor ,
) ,
tooltip: translate ( ' Keyboard Settings ' ) ,
position: mod_menu . PopupMenuPosition . under ,
itemBuilder: ( BuildContext context ) = > _getKeyboardMenu ( )
. map ( ( entry ) = > entry . build (
context ,
const MenuConfig (
commonColor: _MenubarTheme . commonColor ,
height: _MenubarTheme . height ,
dividerHeight: _MenubarTheme . dividerHeight ,
) ) )
. expand ( ( i ) = > i )
. toList ( ) ,
) ;
}
2022-09-15 17:31:28 +08:00
Widget _buildRecording ( BuildContext context ) {
2022-09-22 09:55:34 +08:00
return Consumer < FfiModel > ( builder: ( ( context , value , child ) {
if ( value . permissions [ ' recording ' ] ! = false ) {
return Consumer < RecordingModel > (
builder: ( context , value , child ) = > IconButton (
tooltip: value . start
? 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 ,
) ,
) ) ;
} else {
return Offstage ( ) ;
}
} ) ) ;
2022-09-15 17:31:28 +08:00
}
2022-08-26 23:28:08 +08:00
Widget _buildClose ( BuildContext context ) {
return IconButton (
tooltip: translate ( ' Close ' ) ,
onPressed: ( ) {
clientClose ( widget . ffi . dialogManager ) ;
} ,
2022-08-28 21:55:16 +08:00
icon: const Icon (
2022-08-26 23:28:08 +08:00
Icons . close ,
color: _MenubarTheme . commonColor ,
) ,
) ;
}
2022-09-08 00:35:19 -07:00
List < MenuEntryBase < String > > _getControlMenu ( BuildContext context ) {
2022-08-26 23:28:08 +08:00
final pi = widget . ffi . ffiModel . pi ;
final perms = widget . ffi . ffiModel . permissions ;
2022-09-23 12:20:40 +08:00
const EdgeInsets padding = EdgeInsets . only ( left: 14.0 , right: 5.0 ) ;
2022-08-26 23:28:08 +08:00
final List < MenuEntryBase < String > > displayMenu = [ ] ;
2022-09-08 00:35:19 -07:00
displayMenu . addAll ( [
MenuEntryButton < String > (
2022-09-23 12:20:40 +08:00
childBuilder: ( TextStyle ? style ) = > Container (
alignment: AlignmentDirectional . center ,
height: _MenubarTheme . height ,
child: Row (
children: [
Text (
translate ( ' OS Password ' ) ,
style: style ,
) ,
Expanded (
child: Align (
alignment: Alignment . centerRight ,
child: Transform . scale (
scale: 0.8 ,
child: IconButton (
padding: EdgeInsets . zero ,
icon: const Icon ( Icons . edit ) ,
onPressed: ( ) {
if ( Navigator . canPop ( context ) ) {
Navigator . pop ( context ) ;
}
showSetOSPassword (
widget . id , false , widget . ffi . dialogManager ) ;
} ) ) ,
) )
] ,
) ) ,
2022-09-08 00:35:19 -07:00
proc: ( ) {
showSetOSPassword ( widget . id , false , widget . ffi . dialogManager ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-09-08 00:35:19 -07:00
dismissOnClicked: true ,
) ,
MenuEntryButton < String > (
2022-08-26 23:28:08 +08:00
childBuilder: ( TextStyle ? style ) = > Text (
2022-09-08 00:35:19 -07:00
translate ( ' Transfer File ' ) ,
2022-08-26 23:28:08 +08:00
style: style ,
) ,
proc: ( ) {
2022-09-08 00:35:19 -07:00
connect ( context , widget . id , isFileTransfer: true ) ;
2022-08-26 23:28:08 +08:00
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-08-31 18:41:55 +08:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ,
2022-09-08 00:35:19 -07:00
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' TCP Tunneling ' ) ,
style: style ,
) ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-09-08 00:35:19 -07:00
proc: ( ) {
connect ( context , widget . id , isTcpTunneling: true ) ;
} ,
dismissOnClicked: true ,
) ,
] ) ;
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
final auditServer = bind . sessionGetAuditServerSync ( id: widget . id ) ;
2022-09-08 08:25:19 -07:00
if ( auditServer . isNotEmpty ) {
displayMenu . add (
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Note ' ) ,
style: style ,
) ,
proc: ( ) {
showAuditDialog ( widget . id , widget . ffi . dialogManager ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-09-08 08:25:19 -07:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ,
2022-09-08 08:25:19 -07:00
) ;
}
2022-09-08 00:35:19 -07:00
displayMenu . add ( MenuEntryDivider ( ) ) ;
2022-08-26 23:28:08 +08:00
if ( perms [ ' keyboard ' ] ! = false ) {
if ( pi . platform = = ' Linux ' | | pi . sasEnabled ) {
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
2022-08-28 21:55:16 +08:00
' ${ translate ( " Insert " ) } Ctrl + Alt + Del ' ,
2022-08-26 23:28:08 +08:00
style: style ,
) ,
proc: ( ) {
bind . sessionCtrlAltDel ( id: widget . id ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-08-31 18:41:55 +08:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ) ;
}
2022-09-08 00:35:19 -07:00
}
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: ( ) {
showRestartRemoteDevice ( pi , widget . id , gFFI . dialogManager ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-09-08 00:35:19 -07:00
dismissOnClicked: true ,
) ) ;
}
2022-08-26 23:28:08 +08:00
2022-09-08 00:35:19 -07:00
if ( perms [ ' keyboard ' ] ! = false ) {
2022-08-26 23:28:08 +08:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Insert Lock ' ) ,
style: style ,
) ,
proc: ( ) {
bind . sessionLockScreen ( id: widget . id ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-08-31 18:41:55 +08:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ) ;
if ( pi . platform = = ' Windows ' ) {
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Obx ( ( ) = > Text (
translate (
2022-08-28 21:55:16 +08:00
' ${ BlockInputState . find ( widget . id ) . value ? " Unb " : " B " } lock user input ' ) ,
2022-08-26 23:28:08 +08:00
style: style ,
) ) ,
proc: ( ) {
RxBool blockInput = BlockInputState . find ( widget . id ) ;
bind . sessionToggleOption (
id: widget . id ,
2022-08-28 21:55:16 +08:00
value: ' ${ blockInput . value ? " un " : " " } block-input ' ) ;
2022-08-26 23:28:08 +08:00
blockInput . value = ! blockInput . value ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-08-31 18:41:55 +08:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ) ;
}
}
2022-09-08 00:35:19 -07:00
if ( pi . version . isNotEmpty ) {
2022-08-26 23:28:08 +08:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
2022-09-08 00:35:19 -07:00
translate ( ' Refresh ' ) ,
2022-08-26 23:28:08 +08:00
style: style ,
) ,
proc: ( ) {
2022-09-08 00:35:19 -07:00
bind . sessionRefresh ( id: widget . id ) ;
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-09-08 00:35:19 -07:00
dismissOnClicked: true ,
) ) ;
}
if ( ! isWebDesktop ) {
// if (perms['keyboard'] != false && perms['clipboard'] != false) {
// displayMenu.add(MenuEntryButton<String>(
// childBuilder: (TextStyle? style) => Text(
// translate('Paste'),
// style: style,
// ),
// proc: () {
// () async {
// ClipboardData? data =
// await Clipboard.getData(Clipboard.kTextPlain);
// if (data != null && data.text != null) {
// bind.sessionInputString(id: widget.id, value: data.text ?? "");
// }
// }();
// },
2022-09-23 12:20:40 +08:00
// padding: padding,
2022-09-08 00:35:19 -07:00
// dismissOnClicked: true,
// ));
// }
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Reset canvas ' ) ,
style: style ,
) ,
proc: ( ) {
widget . ffi . cursorModel . reset ( ) ;
2022-08-26 23:28:08 +08:00
} ,
2022-09-23 12:20:40 +08:00
padding: padding ,
2022-08-31 18:41:55 +08:00
dismissOnClicked: true ,
2022-08-26 23:28:08 +08:00
) ) ;
}
return displayMenu ;
}
2022-10-08 17:27:30 +08:00
bool _isWindowCanBeAdjusted ( int remoteCount ) {
final RxBool fullscreen = Get . find ( tag: ' fullscreen ' ) ;
return remoteCount = = 1 & &
fullscreen . isFalse & &
widget . ffi . canvasModel . scale > 1.0 ;
}
List < MenuEntryBase < String > > _getDisplayMenu (
dynamic futureData , int remoteCount ) {
2022-09-23 12:20:40 +08:00
const EdgeInsets padding = EdgeInsets . only ( left: 18.0 , right: 8.0 ) ;
2022-08-26 23:28:08 +08:00
final displayMenu = [
2022-08-30 17:20:25 +08:00
MenuEntryRadios < String > (
2022-09-23 12:20:40 +08:00
text: translate ( ' Ratio ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' Scale original ' ) ,
value: ' original ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Scale adaptive ' ) ,
value: ' adaptive ' ,
dismissOnClicked: true ,
) ,
] ,
curOptionGetter: ( ) async {
return await bind . sessionGetOption (
id: widget . id , arg: ' view-style ' ) ? ?
' adaptive ' ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionPeerOption (
id: widget . id , name: " view-style " , value: newValue ) ;
widget . ffi . canvasModel . updateViewStyle ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ,
2022-08-30 17:20:25 +08:00
MenuEntryDivider < String > ( ) ,
MenuEntryRadios < String > (
2022-09-23 12:20:40 +08:00
text: translate ( ' Scroll Style ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' ScrollAuto ' ) ,
value: ' scrollauto ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Scrollbar ' ) ,
value: ' scrollbar ' ,
dismissOnClicked: true ,
) ,
] ,
curOptionGetter: ( ) async {
return await bind . sessionGetOption (
id: widget . id , arg: ' scroll-style ' ) ? ?
' ' ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionPeerOption (
id: widget . id , name: " scroll-style " , value: newValue ) ;
widget . ffi . canvasModel . updateScrollStyle ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ,
2022-08-30 17:20:25 +08:00
MenuEntryDivider < String > ( ) ,
MenuEntryRadios < String > (
2022-09-23 12:20:40 +08:00
text: translate ( ' Image Quality ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' Good image quality ' ) ,
value: ' best ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Balanced ' ) ,
value: ' balanced ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Optimize reaction time ' ) ,
value: ' low ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Custom ' ) ,
value: ' custom ' ,
dismissOnClicked: true ) ,
] ,
curOptionGetter: ( ) async {
String quality =
await bind . sessionGetImageQuality ( id: widget . id ) ? ? ' balanced ' ;
if ( quality = = ' ' ) quality = ' balanced ' ;
return quality ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
if ( oldValue ! = newValue ) {
await bind . sessionSetImageQuality ( id: widget . id , value: newValue ) ;
}
if ( newValue = = ' custom ' ) {
final btnCancel = msgBoxButton ( translate ( ' Close ' ) , ( ) {
widget . ffi . dialogManager . dismissAll ( ) ;
} ) ;
final quality =
await bind . sessionGetCustomImageQuality ( id: widget . id ) ;
double initValue = quality ! = null & & quality . isNotEmpty
? quality [ 0 ] . toDouble ( )
: 50.0 ;
const minValue = 10.0 ;
const maxValue = 100.0 ;
if ( initValue < minValue ) {
initValue = minValue ;
2022-08-31 18:41:55 +08:00
}
2022-09-23 12:20:40 +08:00
if ( initValue > maxValue ) {
initValue = maxValue ;
2022-08-31 18:41:55 +08:00
}
2022-09-23 12:20:40 +08:00
final RxDouble sliderValue = RxDouble ( initValue ) ;
final rxReplay = rxdart . ReplaySubject < double > ( ) ;
rxReplay
. throttleTime ( const Duration ( milliseconds: 1000 ) ,
trailing: true , leading: false )
. listen ( ( double v ) {
( ) async {
await bind . sessionSetCustomImageQuality (
id: widget . id , value: v . toInt ( ) ) ;
} ( ) ;
} ) ;
final slider = Obx ( ( ) {
return Slider (
value: sliderValue . value ,
min: minValue ,
max: maxValue ,
divisions: 90 ,
onChanged: ( double value ) {
sliderValue . value = value ;
rxReplay . add ( value ) ;
} ,
) ;
} ) ;
final content = Row (
children: [
slider ,
SizedBox (
width: 90 ,
child: Obx ( ( ) = > Text (
' ${ sliderValue . value . round ( ) } % Bitrate ' ,
style: const TextStyle ( fontSize: 15 ) ,
) ) )
] ,
) ;
msgBoxCommon ( widget . ffi . dialogManager , ' Custom Image Quality ' ,
content , [ btnCancel ] ) ;
}
} ,
padding: padding ,
) ,
2022-08-30 17:20:25 +08:00
MenuEntryDivider < String > ( ) ,
2022-09-16 19:43:28 +08:00
] ;
2022-10-08 17:27:30 +08:00
if ( _isWindowCanBeAdjusted ( remoteCount ) ) {
displayMenu . insert (
0 ,
MenuEntryDivider < String > ( ) ,
) ;
displayMenu . insert (
0 ,
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Container (
alignment: AlignmentDirectional . center ,
height: _MenubarTheme . height ,
child: Text (
translate ( ' Adjust Window ' ) ,
style: style ,
) ) ,
proc: ( ) {
( ) async {
final wndRect =
await WindowController . fromWindowId ( widget . windowId )
. getFrame ( ) ;
final canvasModel = widget . ffi . canvasModel ;
final width =
canvasModel . size . width + canvasModel . windowBorderWidth * 2 ;
final height = canvasModel . size . height +
canvasModel . tabBarHeight +
canvasModel . windowBorderWidth * 2 ;
await WindowController . fromWindowId ( widget . windowId ) . setFrame (
Rect . fromLTWH ( wndRect . left , wndRect . top , width , height ) ) ;
} ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ,
) ;
}
2022-09-16 19:43:28 +08:00
/// Show Codec Preference
if ( bind . mainHasHwcodec ( ) ) {
final List < bool > codecs = [ ] ;
try {
final Map codecsJson = jsonDecode ( futureData [ ' supportedHwcodec ' ] ) ;
final h264 = codecsJson [ ' h264 ' ] ? ? false ;
final h265 = codecsJson [ ' h265 ' ] ? ? false ;
codecs . add ( h264 ) ;
codecs . add ( h265 ) ;
} finally { }
if ( codecs . length = = 2 & & ( codecs [ 0 ] | | codecs [ 1 ] ) ) {
displayMenu . add ( MenuEntryRadios < String > (
2022-09-23 12:20:40 +08:00
text: translate ( ' Codec Preference ' ) ,
optionsGetter: ( ) {
final list = [
MenuEntryRadioOption (
text: translate ( ' Auto ' ) ,
value: ' auto ' ,
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: ' VP9 ' ,
value: ' vp9 ' ,
dismissOnClicked: true ,
) ,
] ;
if ( codecs [ 0 ] ) {
list . add ( MenuEntryRadioOption (
text: ' H264 ' ,
value: ' h264 ' ,
dismissOnClicked: true ,
) ) ;
}
if ( codecs [ 1 ] ) {
list . add ( MenuEntryRadioOption (
text: ' H265 ' ,
value: ' h265 ' ,
dismissOnClicked: true ,
) ) ;
}
return list ;
} ,
curOptionGetter: ( ) async {
return await bind . sessionGetOption (
id: widget . id , arg: ' codec-preference ' ) ? ?
' auto ' ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionPeerOption (
id: widget . id , name: " codec-preference " , value: newValue ) ;
bind . sessionChangePreferCodec ( id: widget . id ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ) ;
2022-09-16 19:43:28 +08:00
}
}
/// Show remote cursor
displayMenu . add ( ( ) {
final state = ShowRemoteCursorState . find ( widget . id ) ;
return MenuEntrySwitch2 < String > (
2022-09-23 12:20:40 +08:00
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 ,
) ;
2022-09-16 19:43:28 +08:00
} ( ) ) ;
/// Show quality monitor
displayMenu . add ( MenuEntrySwitch < String > (
2022-09-23 12:20:40 +08:00
switchType: SwitchType . scheckbox ,
text: translate ( ' Show quality monitor ' ) ,
getter: ( ) async {
return 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 ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ) ;
2022-08-26 23:28:08 +08:00
final perms = widget . ffi . ffiModel . permissions ;
final pi = widget . ffi . ffiModel . pi ;
if ( perms [ ' audio ' ] ! = false ) {
2022-09-23 12:20:40 +08:00
displayMenu
. add ( _createSwitchMenuEntry ( ' Mute ' , ' disable-audio ' , padding , true ) ) ;
2022-08-26 23:28:08 +08:00
}
2022-09-08 08:25:19 -07:00
if ( Platform . isWindows & &
pi . platform = = ' Windows ' & &
perms [ ' file ' ] ! = false ) {
displayMenu . add ( _createSwitchMenuEntry (
2022-09-23 12:20:40 +08:00
' Allow file copy and paste ' , ' enable-file-transfer ' , padding , true ) ) ;
2022-09-08 08:25:19 -07:00
}
2022-08-26 23:28:08 +08:00
if ( perms [ ' keyboard ' ] ! = false ) {
if ( perms [ ' clipboard ' ] ! = false ) {
2022-09-23 12:20:40 +08:00
displayMenu . add ( _createSwitchMenuEntry (
' Disable clipboard ' , ' disable-clipboard ' , padding , true ) ) ;
2022-08-26 23:28:08 +08:00
}
displayMenu . add ( _createSwitchMenuEntry (
2022-09-23 12:20:40 +08:00
' Lock after session end ' , ' lock-after-session-end ' , padding , true ) ) ;
2022-08-26 23:28:08 +08:00
if ( pi . platform = = ' Windows ' ) {
displayMenu . add ( MenuEntrySwitch2 < String > (
2022-09-23 12:20:40 +08:00
switchType: SwitchType . scheckbox ,
text: translate ( ' Privacy mode ' ) ,
getter: ( ) {
return PrivacyModeState . find ( widget . id ) ;
} ,
setter: ( bool v ) async {
await bind . sessionToggleOption (
id: widget . id , value: ' privacy-mode ' ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ) ;
2022-08-26 23:28:08 +08:00
}
}
return displayMenu ;
}
2022-09-05 10:18:29 -04:00
List < MenuEntryBase < String > > _getKeyboardMenu ( ) {
final keyboardMenu = [
MenuEntryRadios < String > (
2022-09-23 12:20:40 +08:00
text: translate ( ' Ratio ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption ( text: translate ( ' Legacy mode ' ) , value: ' legacy ' ) ,
MenuEntryRadioOption ( text: translate ( ' Map mode ' ) , value: ' map ' ) ,
] ,
curOptionGetter: ( ) async {
return await bind . sessionGetKeyboardName ( id: widget . id ) ;
} ,
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionSetKeyboardMode (
id: widget . id , keyboardMode: newValue ) ;
widget . ffi . canvasModel . updateViewStyle ( ) ;
} ,
)
2022-09-05 10:18:29 -04:00
] ;
return keyboardMenu ;
}
2022-09-23 12:20:40 +08:00
MenuEntrySwitch < String > _createSwitchMenuEntry (
String text , String option , EdgeInsets ? padding , bool dismissOnClicked ) {
2022-08-26 23:28:08 +08:00
return MenuEntrySwitch < String > (
2022-09-23 12:20:40 +08:00
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 ,
) ;
2022-08-26 23:28:08 +08:00
}
}
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 ) {
2022-09-03 18:19:50 +08:00
submit ( ) {
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 ( ) ;
}
2022-08-26 23:28:08 +08:00
return CustomAlertDialog (
2022-09-03 18:19:50 +08:00
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 ' ) ,
2022-08-26 23:28:08 +08:00
) ,
2022-09-03 18:19:50 +08:00
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: submit ,
child: Text ( translate ( ' OK ' ) ) ,
) ,
] ,
onSubmit: submit ,
onCancel: close ,
) ;
2022-08-26 23:28:08 +08:00
} ) ;
}
2022-09-08 00:35:19 -07:00
void showAuditDialog ( String id , dialogManager ) async {
final controller = TextEditingController ( ) ;
dialogManager . show ( ( setState , close ) {
submit ( ) {
var text = controller . text . trim ( ) ;
if ( text ! = " " ) {
bind . sessionSendNote ( id: id , note: text ) ;
}
close ( ) ;
}
late final focusNode = FocusNode (
onKey: ( FocusNode node , RawKeyEvent evt ) {
if ( evt . logicalKey . keyLabel = = ' Enter ' ) {
if ( evt is RawKeyDownEvent ) {
int pos = controller . selection . base . offset ;
controller . text =
' ${ controller . text . substring ( 0 , pos ) } \n ${ controller . text . substring ( pos ) } ' ;
controller . selection =
TextSelection . fromPosition ( TextPosition ( offset: pos + 1 ) ) ;
}
return KeyEventResult . handled ;
}
if ( evt . logicalKey . keyLabel = = ' Esc ' ) {
if ( evt is RawKeyDownEvent ) {
close ( ) ;
}
return KeyEventResult . handled ;
} else {
return KeyEventResult . ignored ;
}
} ,
) ;
return CustomAlertDialog (
title: Text ( translate ( ' Note ' ) ) ,
content: SizedBox (
width: 250 ,
height: 120 ,
child: TextField (
autofocus: true ,
keyboardType: TextInputType . multiline ,
textInputAction: TextInputAction . newline ,
decoration: const InputDecoration . collapsed (
hintText: " input note here " ,
) ,
// inputFormatters: [
// LengthLimitingTextInputFormatter(16),
// // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
// ],
maxLines: null ,
maxLength: 256 ,
controller: controller ,
focusNode: focusNode ,
) ) ,
actions: [
TextButton (
style: flatButtonStyle ,
onPressed: close ,
child: Text ( translate ( ' Cancel ' ) ) ,
) ,
TextButton (
style: flatButtonStyle ,
onPressed: submit ,
child: Text ( translate ( ' OK ' ) ) ,
) ,
] ,
onSubmit: submit ,
onCancel: close ,
) ;
} ) ;
}