2022-09-16 14:43:28 +03:00
import ' dart:convert ' ;
2022-09-08 18:25:19 +03:00
import ' dart:io ' ;
2022-09-13 17:24:06 +03:00
import ' dart:math ' as math ;
2022-10-09 14:27:30 +03:00
import ' dart:ui ' as ui ;
2022-09-08 18:25:19 +03:00
2022-08-26 18:28:08 +03:00
import ' package:flutter/material.dart ' ;
import ' package:flutter/services.dart ' ;
import ' package:flutter_hbb/models/chat_model.dart ' ;
2022-11-01 12:01:43 +03:00
import ' package:flutter_hbb/models/state_model.dart ' ;
2022-11-24 06:19:16 +03:00
import ' package:flutter_hbb/consts.dart ' ;
2022-12-01 08:52:12 +03:00
import ' package:flutter_hbb/utils/multi_window_manager.dart ' ;
2022-08-26 18:28:08 +03:00
import ' package:get/get.dart ' ;
2022-09-15 12:31:28 +03:00
import ' package:provider/provider.dart ' ;
2022-12-07 10:13:24 +03:00
import ' package:debounce_throttle/debounce_throttle.dart ' ;
2022-10-08 12:27:30 +03:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-10-09 14:27:30 +03:00
import ' package:window_size/window_size.dart ' as window_size ;
2022-08-26 18:28:08 +03:00
import ' ../../common.dart ' ;
import ' ../../mobile/widgets/dialog.dart ' ;
import ' ../../models/model.dart ' ;
import ' ../../models/platform_model.dart ' ;
2022-08-29 13:48:12 +03:00
import ' ../../common/shared_state.dart ' ;
2022-08-26 18:28:08 +03:00
import ' ./popup_menu.dart ' ;
2022-08-28 16:55:16 +03:00
import ' ./material_mod_popup_menu.dart ' as mod_menu ;
2022-12-27 11:45:13 +03:00
import ' ./kb_layout_type_chooser.dart ' ;
2022-08-26 18:28:08 +03:00
2022-11-10 09:32:22 +03:00
class MenubarState {
2022-11-17 13:52:27 +03:00
final kStoreKey = ' remoteMenubarState ' ;
2022-11-10 09:32:22 +03:00
late RxBool show ;
late RxBool _pin ;
2022-11-24 06:19:16 +03:00
RxString viewStyle = RxString ( kRemoteViewStyleOriginal ) ;
2022-11-10 09:32:22 +03:00
MenubarState ( ) {
2022-11-10 16:25:12 +03:00
final s = bind . getLocalFlutterConfig ( k: kStoreKey ) ;
if ( s . isEmpty ) {
2022-11-10 09:32:22 +03:00
_initSet ( false , false ) ;
2022-11-10 16:25:12 +03:00
return ;
2022-11-10 09:32:22 +03:00
}
2022-11-10 16:25:12 +03:00
2022-11-10 09:32:22 +03:00
try {
2022-11-10 16:25:12 +03:00
final m = jsonDecode ( s ) ;
2022-11-10 09:32:22 +03:00
if ( m = = null ) {
_initSet ( false , false ) ;
} else {
_initSet ( m [ ' pin ' ] ? ? false , m [ ' pin ' ] ? ? false ) ;
}
} catch ( e ) {
debugPrint ( ' Failed to decode menubar state ${ e . toString ( ) } ' ) ;
_initSet ( false , false ) ;
}
}
_initSet ( bool s , bool p ) {
2022-11-16 13:07:58 +03:00
// Show remubar when connection is established.
show = RxBool ( true ) ;
2022-11-10 09:32:22 +03:00
_pin = RxBool ( p ) ;
}
bool get pin = > _pin . value ;
switchShow ( ) async {
show . value = ! show . value ;
}
setShow ( bool v ) async {
if ( show . value ! = v ) {
show . value = v ;
}
}
switchPin ( ) async {
_pin . value = ! _pin . value ;
// Save everytime changed, as this func will not be called frequently
2022-11-24 06:19:16 +03:00
await _savePin ( ) ;
2022-11-10 09:32:22 +03:00
}
setPin ( bool v ) async {
if ( _pin . value ! = v ) {
_pin . value = v ;
// Save everytime changed, as this func will not be called frequently
2022-11-24 06:19:16 +03:00
await _savePin ( ) ;
2022-11-10 09:32:22 +03:00
}
}
2022-11-24 06:19:16 +03:00
_savePin ( ) async {
2022-11-10 16:25:12 +03:00
bind . setLocalFlutterConfig (
k: kStoreKey , v: jsonEncode ( { ' pin ' : _pin . value } ) ) ;
2022-11-10 09:32:22 +03:00
}
2022-11-24 06:19:16 +03:00
save ( ) async {
await _savePin ( ) ;
}
2022-11-10 09:32:22 +03:00
}
2022-08-26 18:28:08 +03:00
class _MenubarTheme {
static const Color commonColor = MyTheme . accent ;
2022-08-29 13:48:12 +03:00
// kMinInteractiveDimension
2022-09-23 07:20:40 +03:00
static const double height = 20.0 ;
2022-08-29 13:48:12 +03:00
static const double dividerHeight = 12.0 ;
2022-08-26 18:28:08 +03:00
}
class RemoteMenubar extends StatefulWidget {
final String id ;
final FFI ffi ;
2022-11-10 09:32:22 +03:00
final MenubarState state ;
2022-09-14 05:10:55 +03:00
final Function ( Function ( bool ) ) onEnterOrLeaveImageSetter ;
final Function ( ) onEnterOrLeaveImageCleaner ;
2022-08-26 18:28:08 +03:00
const RemoteMenubar ( {
Key ? key ,
required this . id ,
required this . ffi ,
2022-11-10 09:32:22 +03:00
required this . state ,
2022-09-14 05:10:55 +03:00
required this . onEnterOrLeaveImageSetter ,
required this . onEnterOrLeaveImageCleaner ,
2022-08-26 18:28:08 +03:00
} ) : super ( key: key ) ;
@ override
State < RemoteMenubar > createState ( ) = > _RemoteMenubarState ( ) ;
}
class _RemoteMenubarState extends State < RemoteMenubar > {
2022-12-07 10:13:24 +03:00
late Debouncer < int > _debouncerHide ;
2022-09-13 16:59:06 +03:00
bool _isCursorOverImage = false ;
2022-10-09 14:27:30 +03:00
window_size . Screen ? _screen ;
2022-12-07 10:13:24 +03:00
final _fractionX = 0.5 . obs ;
final _dragging = false . obs ;
2022-08-26 18:28:08 +03:00
2022-11-01 12:01:43 +03:00
int get windowId = > stateGlobal . windowId ;
bool get isFullscreen = > stateGlobal . fullscreen ;
2022-09-13 16:59:06 +03:00
void _setFullscreen ( bool v ) {
2022-11-01 12:01:43 +03:00
stateGlobal . setFullscreen ( v ) ;
setState ( ( ) { } ) ;
2022-08-26 18:28:08 +03:00
}
2022-11-10 09:32:22 +03:00
RxBool get show = > widget . state . show ;
bool get pin = > widget . state . pin ;
2022-11-03 16:58:25 +03:00
2022-09-13 16:59:06 +03:00
@ override
2022-09-14 05:10:55 +03:00
initState ( ) {
2022-09-13 16:59:06 +03:00
super . initState ( ) ;
2022-12-07 10:13:24 +03:00
_debouncerHide = Debouncer < int > (
Duration ( milliseconds: 5000 ) ,
onChanged: _debouncerHideProc ,
initialValue: 0 ,
) ;
2022-09-14 05:10:55 +03:00
widget . onEnterOrLeaveImageSetter ( ( enter ) {
2022-09-13 16:59:06 +03:00
if ( enter ) {
2022-12-07 10:13:24 +03:00
_debouncerHide . value = 0 ;
2022-09-13 16:59:06 +03:00
_isCursorOverImage = true ;
} else {
_isCursorOverImage = false ;
}
} ) ;
2022-12-07 10:13:24 +03:00
}
2022-09-13 16:59:06 +03:00
2022-12-07 10:13:24 +03:00
_debouncerHideProc ( int v ) {
if ( ! pin & & show . isTrue & & _isCursorOverImage & & _dragging . isFalse ) {
show . value = false ;
}
2022-09-13 16:59:06 +03:00
}
2022-09-14 05:10:55 +03:00
@ override
dispose ( ) {
super . dispose ( ) ;
widget . onEnterOrLeaveImageCleaner ( ) ;
}
2022-08-26 18:28:08 +03:00
@ override
Widget build ( BuildContext context ) {
2022-12-23 18:10:34 +03:00
// No need to use future builder here.
_updateScreen ( ) ;
2022-08-26 18:28:08 +03:00
return Align (
alignment: Alignment . topCenter ,
2022-12-07 10:13:24 +03:00
child: Obx ( ( ) = > show . value
? _buildMenubar ( context )
: _buildDraggableShowHide ( context ) ) ,
2022-08-26 18:28:08 +03:00
) ;
}
2022-12-07 10:13:24 +03:00
Widget _buildDraggableShowHide ( BuildContext context ) {
return Obx ( ( ) {
if ( show . isTrue & & _dragging . isFalse ) {
_debouncerHide . value = 1 ;
}
return Align (
alignment: FractionalOffset ( _fractionX . value , 0 ) ,
child: Offstage (
offstage: _dragging . isTrue ,
child: _DraggableShowHide (
dragging: _dragging ,
fractionX: _fractionX ,
show: show ,
) ,
) ,
) ;
} ) ;
2022-08-26 18:28:08 +03:00
}
2022-10-09 14:27:30 +03:00
_updateScreen ( ) async {
2022-12-01 08:52:12 +03:00
final v = await rustDeskWinManager . call (
WindowType . Main , kWindowGetWindowInfo , ' ' ) ;
2022-10-09 14:27:30 +03:00
final String valueStr = v ;
if ( valueStr . isEmpty ) {
_screen = null ;
} else {
final screenMap = jsonDecode ( valueStr ) ;
_screen = window_size . Screen (
Rect . fromLTRB ( screenMap [ ' frame ' ] [ ' l ' ] , screenMap [ ' frame ' ] [ ' t ' ] ,
screenMap [ ' frame ' ] [ ' r ' ] , screenMap [ ' frame ' ] [ ' b ' ] ) ,
Rect . fromLTRB (
screenMap [ ' visibleFrame ' ] [ ' l ' ] ,
screenMap [ ' visibleFrame ' ] [ ' t ' ] ,
screenMap [ ' visibleFrame ' ] [ ' r ' ] ,
screenMap [ ' visibleFrame ' ] [ ' b ' ] ) ,
screenMap [ ' scaleFactor ' ] ) ;
}
}
2022-08-26 18:28:08 +03:00
Widget _buildMenubar ( BuildContext context ) {
final List < Widget > menubarItems = [ ] ;
if ( ! isWebDesktop ) {
2022-09-13 16:59:06 +03:00
menubarItems . add ( _buildPinMenubar ( context ) ) ;
2022-08-26 18:28:08 +03:00
menubarItems . add ( _buildFullscreen ( context ) ) ;
2022-09-08 17:18:02 +03: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 18:28:08 +03:00
}
menubarItems . add ( _buildMonitor ( context ) ) ;
menubarItems . add ( _buildControl ( context ) ) ;
menubarItems . add ( _buildDisplay ( context ) ) ;
2022-09-05 17:18:29 +03:00
menubarItems . add ( _buildKeyboard ( context ) ) ;
2022-08-26 18:28:08 +03:00
if ( ! isWeb ) {
menubarItems . add ( _buildChat ( context ) ) ;
}
2022-09-15 12:31:28 +03:00
menubarItems . add ( _buildRecording ( context ) ) ;
2022-08-26 18:28:08 +03:00
menubarItems . add ( _buildClose ( context ) ) ;
return PopupMenuTheme (
2022-08-28 16:55:16 +03:00
data: const PopupMenuThemeData (
2022-08-26 18:28:08 +03:00
textStyle: TextStyle ( color: _MenubarTheme . commonColor ) ) ,
child: Column ( mainAxisSize: MainAxisSize . min , children: [
Container (
2022-10-30 13:26:00 +03:00
decoration: BoxDecoration (
color: Colors . white ,
border: Border . all ( color: MyTheme . border ) ,
) ,
2022-08-26 18:28:08 +03:00
child: Row (
mainAxisSize: MainAxisSize . min ,
children: menubarItems ,
) ) ,
2022-12-07 10:13:24 +03:00
_buildDraggableShowHide ( context ) ,
2022-08-26 18:28:08 +03:00
] ) ) ;
}
2022-09-13 16:59:06 +03:00
Widget _buildPinMenubar ( BuildContext context ) {
2022-09-13 17:24:06 +03:00
return Obx ( ( ) = > IconButton (
2022-11-10 09:32:22 +03:00
tooltip: translate ( pin ? ' Unpin menubar ' : ' Pin menubar ' ) ,
2022-09-13 17:24:06 +03:00
onPressed: ( ) {
2022-11-10 09:32:22 +03:00
widget . state . switchPin ( ) ;
2022-09-13 17:24:06 +03:00
} ,
icon: Obx ( ( ) = > Transform . rotate (
2022-11-10 09:32:22 +03:00
angle: pin ? math . pi / 4 : 0 ,
2022-09-13 17:24:06 +03:00
child: Icon (
Icons . push_pin ,
2022-11-10 09:32:22 +03:00
color: pin ? _MenubarTheme . commonColor : Colors . grey ,
2022-09-13 17:24:06 +03:00
) ) ) ,
) ) ;
2022-09-13 16:59:06 +03:00
}
2022-08-26 18:28:08 +03:00
Widget _buildFullscreen ( BuildContext context ) {
return IconButton (
tooltip: translate ( isFullscreen ? ' Exit Fullscreen ' : ' Fullscreen ' ) ,
onPressed: ( ) {
2022-09-13 16:59:06 +03:00
_setFullscreen ( ! isFullscreen ) ;
2022-08-26 18:28:08 +03:00
} ,
2022-11-01 12:01:43 +03:00
icon: isFullscreen
2022-08-28 16:55:16 +03:00
? const Icon (
2022-08-26 18:28:08 +03:00
Icons . fullscreen_exit ,
color: _MenubarTheme . commonColor ,
)
2022-08-28 16:55:16 +03:00
: const Icon (
2022-08-26 18:28:08 +03:00
Icons . fullscreen ,
color: _MenubarTheme . commonColor ,
2022-11-01 12:01:43 +03:00
) ,
2022-08-26 18:28:08 +03:00
) ;
}
Widget _buildChat ( BuildContext context ) {
return IconButton (
tooltip: translate ( ' Chat ' ) ,
onPressed: ( ) {
widget . ffi . chatModel . changeCurrentID ( ChatModel . clientModeID ) ;
widget . ffi . chatModel . toggleChatOverlay ( ) ;
} ,
2022-08-28 16:55:16 +03:00
icon: const Icon (
2022-08-26 18:28:08 +03:00
Icons . message ,
color: _MenubarTheme . commonColor ,
) ,
) ;
}
Widget _buildMonitor ( BuildContext context ) {
final pi = widget . ffi . ffiModel . pi ;
2022-08-28 16:55:16 +03:00
return mod_menu . PopupMenuButton (
2022-08-26 18:28:08 +03:00
tooltip: translate ( ' Select Monitor ' ) ,
padding: EdgeInsets . zero ,
2022-08-28 16:55:16 +03:00
position: mod_menu . PopupMenuPosition . under ,
2022-08-26 18:28:08 +03:00
icon: Stack (
alignment: Alignment . center ,
children: [
2022-08-28 16:55:16 +03:00
const Icon (
2022-08-26 18:28:08 +03:00
Icons . personal_video ,
color: _MenubarTheme . commonColor ,
) ,
Padding (
2022-08-28 16:55:16 +03:00
padding: const EdgeInsets . only ( bottom: 3.9 ) ,
2022-08-26 18:28:08 +03:00
child: Obx ( ( ) {
RxInt display = CurrentDisplayState . find ( widget . id ) ;
return Text (
2022-11-17 13:52:27 +03:00
' ${ display . value + 1 } / ${ pi . displays . length } ' ,
2022-08-28 16:55:16 +03:00
style: const TextStyle (
color: _MenubarTheme . commonColor , fontSize: 8 ) ,
2022-08-26 18:28:08 +03:00
) ;
} ) ,
)
] ,
) ,
itemBuilder: ( BuildContext context ) {
final List < Widget > rowChildren = [ ] ;
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
2022-08-29 13:48:12 +03:00
rowChildren . add (
Stack (
2022-08-26 18:28:08 +03:00
alignment: Alignment . center ,
children: [
2022-08-28 16:55:16 +03:00
const Icon (
2022-08-26 18:28:08 +03:00
Icons . personal_video ,
color: _MenubarTheme . commonColor ,
) ,
TextButton (
child: Container (
alignment: AlignmentDirectional . center ,
constraints:
2022-08-28 16:55:16 +03:00
const BoxConstraints ( minHeight: _MenubarTheme . height ) ,
2022-08-26 18:28:08 +03:00
child: Padding (
2022-08-28 16:55:16 +03:00
padding: const EdgeInsets . only ( bottom: 2.5 ) ,
2022-08-26 18:28:08 +03:00
child: Text (
( i + 1 ) . toString ( ) ,
2022-08-28 16:55:16 +03:00
style:
const TextStyle ( color: _MenubarTheme . commonColor ) ,
2022-08-26 18:28:08 +03:00
) ,
) ) ,
onPressed: ( ) {
RxInt display = CurrentDisplayState . find ( widget . id ) ;
if ( display . value ! = i ) {
bind . sessionSwitchDisplay ( id: widget . id , value: i ) ;
}
} ,
)
] ,
) ,
2022-08-29 13:48:12 +03:00
) ;
2022-08-26 18:28:08 +03:00
}
2022-08-28 16:55:16 +03:00
return < mod_menu . PopupMenuEntry < String > > [
mod_menu . PopupMenuItem < String > (
2022-08-26 18:28:08 +03:00
height: _MenubarTheme . height ,
padding: EdgeInsets . zero ,
child: Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: rowChildren ) ,
)
] ;
} ,
) ;
}
Widget _buildControl ( BuildContext context ) {
2022-08-28 16:55:16 +03:00
return mod_menu . PopupMenuButton (
2022-08-26 18:28:08 +03:00
padding: EdgeInsets . zero ,
2022-08-28 16:55:16 +03:00
icon: const Icon (
2022-08-26 18:28:08 +03:00
Icons . bolt ,
color: _MenubarTheme . commonColor ,
) ,
tooltip: translate ( ' Control Actions ' ) ,
2022-08-28 16:55:16 +03:00
position: mod_menu . PopupMenuPosition . under ,
2022-09-08 10:35:19 +03:00
itemBuilder: ( BuildContext context ) = > _getControlMenu ( context )
2022-08-26 18:28:08 +03:00
. map ( ( entry ) = > entry . build (
context ,
2022-08-28 16:55:16 +03:00
const MenuConfig (
2022-08-26 18:28:08 +03:00
commonColor: _MenubarTheme . commonColor ,
2022-08-29 13:48:12 +03:00
height: _MenubarTheme . height ,
dividerHeight: _MenubarTheme . dividerHeight ,
2022-08-26 18:28:08 +03:00
) ) )
2022-08-30 12:20:25 +03:00
. expand ( ( i ) = > i )
2022-08-26 18:28:08 +03:00
. toList ( ) ,
) ;
}
Widget _buildDisplay ( BuildContext context ) {
2022-09-16 14:43:28 +03:00
return FutureBuilder ( future: ( ) async {
2022-11-24 06:19:16 +03:00
widget . state . viewStyle . value =
await bind . sessionGetViewStyle ( id: widget . id ) ? ? ' ' ;
2022-09-16 14:43:28 +03:00
final supportedHwcodec =
await bind . sessionSupportedHwcodec ( id: widget . id ) ;
return { ' supportedHwcodec ' : supportedHwcodec } ;
} ( ) , builder: ( context , snapshot ) {
if ( snapshot . hasData ) {
2022-10-08 12:27:30 +03: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 14:43:28 +03:00
} else {
return const Offstage ( ) ;
}
} ) ;
2022-08-26 18:28:08 +03:00
}
2022-09-05 17:18:29 +03:00
Widget _buildKeyboard ( BuildContext context ) {
2022-10-16 07:32:52 +03:00
FfiModel ffiModel = Provider . of < FfiModel > ( context ) ;
if ( ffiModel . permissions [ ' keyboard ' ] = = false ) {
return Offstage ( ) ;
}
2022-09-05 17:18:29 +03:00
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 12:31:28 +03:00
Widget _buildRecording ( BuildContext context ) {
2022-09-22 04:55:34 +03: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 12:31:28 +03:00
}
2022-08-26 18:28:08 +03:00
Widget _buildClose ( BuildContext context ) {
return IconButton (
tooltip: translate ( ' Close ' ) ,
onPressed: ( ) {
2022-11-15 11:49:55 +03:00
clientClose ( widget . id , widget . ffi . dialogManager ) ;
2022-08-26 18:28:08 +03:00
} ,
2022-08-28 16:55:16 +03:00
icon: const Icon (
2022-08-26 18:28:08 +03:00
Icons . close ,
color: _MenubarTheme . commonColor ,
) ,
) ;
}
2022-09-08 10:35:19 +03:00
List < MenuEntryBase < String > > _getControlMenu ( BuildContext context ) {
2022-08-26 18:28:08 +03:00
final pi = widget . ffi . ffiModel . pi ;
final perms = widget . ffi . ffiModel . permissions ;
2022-09-23 07:20:40 +03:00
const EdgeInsets padding = EdgeInsets . only ( left: 14.0 , right: 5.0 ) ;
2022-08-26 18:28:08 +03:00
final List < MenuEntryBase < String > > displayMenu = [ ] ;
2022-09-08 10:35:19 +03:00
displayMenu . addAll ( [
MenuEntryButton < String > (
2022-09-23 07:20:40 +03: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 10:35:19 +03:00
proc: ( ) {
showSetOSPassword ( widget . id , false , widget . ffi . dialogManager ) ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-09-08 10:35:19 +03:00
dismissOnClicked: true ,
) ,
MenuEntryButton < String > (
2022-08-26 18:28:08 +03:00
childBuilder: ( TextStyle ? style ) = > Text (
2022-09-08 10:35:19 +03:00
translate ( ' Transfer File ' ) ,
2022-08-26 18:28:08 +03:00
style: style ,
) ,
proc: ( ) {
2022-09-08 10:35:19 +03:00
connect ( context , widget . id , isFileTransfer: true ) ;
2022-08-26 18:28:08 +03:00
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-08-31 13:41:55 +03:00
dismissOnClicked: true ,
2022-08-26 18:28:08 +03:00
) ,
2022-09-08 10:35:19 +03:00
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' TCP Tunneling ' ) ,
style: style ,
) ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-09-08 10:35:19 +03:00
proc: ( ) {
connect ( context , widget . id , isTcpTunneling: true ) ;
} ,
dismissOnClicked: true ,
) ,
] ) ;
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
2022-12-18 11:17:10 +03:00
final auditServer =
bind . sessionGetAuditServerSync ( id: widget . id , typ: " conn " ) ;
2022-09-08 18:25:19 +03: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 07:20:40 +03:00
padding: padding ,
2022-09-08 18:25:19 +03:00
dismissOnClicked: true ,
2022-08-26 18:28:08 +03:00
) ,
2022-09-08 18:25:19 +03:00
) ;
}
2022-09-08 10:35:19 +03:00
displayMenu . add ( MenuEntryDivider ( ) ) ;
2022-08-26 18:28:08 +03:00
if ( perms [ ' keyboard ' ] ! = false ) {
2023-01-10 12:13:40 +03:00
if ( pi . platform = = kPeerPlatformLinux | | pi . sasEnabled ) {
2022-08-26 18:28:08 +03:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
2022-08-28 16:55:16 +03:00
' ${ translate ( " Insert " ) } Ctrl + Alt + Del ' ,
2022-08-26 18:28:08 +03:00
style: style ,
) ,
proc: ( ) {
bind . sessionCtrlAltDel ( id: widget . id ) ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-08-31 13:41:55 +03:00
dismissOnClicked: true ,
2022-08-26 18:28:08 +03:00
) ) ;
}
2022-09-08 10:35:19 +03:00
}
2022-11-17 13:52:27 +03:00
if ( perms [ ' restart ' ] ! = false & &
2023-01-10 12:13:40 +03:00
( pi . platform = = kPeerPlatformLinux | |
pi . platform = = kPeerPlatformWindows | |
pi . platform = = kPeerPlatformMacOS ) ) {
2022-09-08 10:35:19 +03:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Restart Remote Device ' ) ,
style: style ,
) ,
proc: ( ) {
showRestartRemoteDevice ( pi , widget . id , gFFI . dialogManager ) ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-09-08 10:35:19 +03:00
dismissOnClicked: true ,
) ) ;
}
2022-08-26 18:28:08 +03:00
2022-09-08 10:35:19 +03:00
if ( perms [ ' keyboard ' ] ! = false ) {
2022-08-26 18:28:08 +03:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
translate ( ' Insert Lock ' ) ,
style: style ,
) ,
proc: ( ) {
bind . sessionLockScreen ( id: widget . id ) ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-08-31 13:41:55 +03:00
dismissOnClicked: true ,
2022-08-26 18:28:08 +03:00
) ) ;
2023-01-10 12:13:40 +03:00
if ( pi . platform = = kPeerPlatformWindows ) {
2022-08-26 18:28:08 +03:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Obx ( ( ) = > Text (
translate (
2022-11-17 13:52:27 +03:00
' ${ BlockInputState . find ( widget . id ) . value ? ' Unb ' : ' B ' } lock user input ' ) ,
2022-08-26 18:28:08 +03:00
style: style ,
) ) ,
proc: ( ) {
RxBool blockInput = BlockInputState . find ( widget . id ) ;
bind . sessionToggleOption (
id: widget . id ,
2022-11-17 13:52:27 +03:00
value: ' ${ blockInput . value ? ' un ' : ' ' } block-input ' ) ;
2022-08-26 18:28:08 +03:00
blockInput . value = ! blockInput . value ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-08-31 13:41:55 +03:00
dismissOnClicked: true ,
2022-08-26 18:28:08 +03:00
) ) ;
}
}
2022-09-08 10:35:19 +03:00
if ( pi . version . isNotEmpty ) {
2022-08-26 18:28:08 +03:00
displayMenu . add ( MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Text (
2022-09-08 10:35:19 +03:00
translate ( ' Refresh ' ) ,
2022-08-26 18:28:08 +03:00
style: style ,
) ,
proc: ( ) {
2022-09-08 10:35:19 +03:00
bind . sessionRefresh ( id: widget . id ) ;
} ,
2022-09-23 07:20:40 +03:00
padding: padding ,
2022-09-08 10:35:19 +03: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) {
2022-11-17 13:52:27 +03:00
// bind.sessionInputString(id: widget.id, value: data.text ?? '');
2022-09-08 10:35:19 +03:00
// }
// }();
// },
2022-09-23 07:20:40 +03:00
// padding: padding,
2022-09-08 10:35:19 +03:00
// dismissOnClicked: true,
// ));
// }
2022-08-26 18:28:08 +03:00
}
return displayMenu ;
}
2022-10-08 12:27:30 +03:00
bool _isWindowCanBeAdjusted ( int remoteCount ) {
2022-10-09 14:27:30 +03:00
if ( remoteCount ! = 1 ) {
return false ;
}
if ( _screen = = null ) {
return false ;
}
2023-01-06 13:14:31 +03:00
final scale = kIgnoreDpi ? 1.0 : _screen ! . scaleFactor ;
2022-12-26 09:34:35 +03:00
double selfWidth = _screen ! . visibleFrame . width ;
double selfHeight = _screen ! . visibleFrame . height ;
2022-10-10 04:56:27 +03:00
if ( isFullscreen ) {
2022-12-26 09:34:35 +03:00
selfWidth = _screen ! . frame . width ;
selfHeight = _screen ! . frame . height ;
2022-10-09 14:27:30 +03:00
}
final canvasModel = widget . ffi . canvasModel ;
final displayWidth = canvasModel . getDisplayWidth ( ) ;
final displayHeight = canvasModel . getDisplayHeight ( ) ;
final requiredWidth = displayWidth +
( canvasModel . tabBarHeight + canvasModel . windowBorderWidth * 2 ) ;
final requiredHeight = displayHeight +
( canvasModel . tabBarHeight + canvasModel . windowBorderWidth * 2 ) ;
return selfWidth > ( requiredWidth * scale ) & &
selfHeight > ( requiredHeight * scale ) ;
2022-10-08 12:27:30 +03:00
}
List < MenuEntryBase < String > > _getDisplayMenu (
dynamic futureData , int remoteCount ) {
2022-09-23 07:20:40 +03:00
const EdgeInsets padding = EdgeInsets . only ( left: 18.0 , right: 8.0 ) ;
2022-08-26 18:28:08 +03:00
final displayMenu = [
2022-08-30 12:20:25 +03:00
MenuEntryRadios < String > (
2022-09-23 07:20:40 +03:00
text: translate ( ' Ratio ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' Scale original ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteViewStyleOriginal ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Scale adaptive ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteViewStyleAdaptive ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ,
) ,
] ,
2022-11-24 06:19:16 +03:00
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 ;
} ,
2022-09-23 07:20:40 +03:00
optionSetter: ( String oldValue , String newValue ) async {
2022-11-17 13:52:27 +03:00
await bind . sessionSetViewStyle ( id: widget . id , value: newValue ) ;
2022-11-24 06:19:16 +03:00
widget . state . viewStyle . value = newValue ;
2022-09-23 07:20:40 +03:00
widget . ffi . canvasModel . updateViewStyle ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ,
2022-08-30 12:20:25 +03:00
MenuEntryDivider < String > ( ) ,
MenuEntryRadios < String > (
2022-09-23 07:20:40 +03:00
text: translate ( ' Image Quality ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' Good image quality ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteImageQualityBest ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Balanced ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteImageQualityBalanced ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Optimize reaction time ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteImageQualityLow ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ,
) ,
MenuEntryRadioOption (
text: translate ( ' Custom ' ) ,
2022-11-24 06:19:16 +03:00
value: kRemoteImageQualityCustom ,
2022-09-23 07:20:40 +03:00
dismissOnClicked: true ) ,
] ,
2022-11-17 13:52:27 +03:00
curOptionGetter: ( ) async = >
// null means peer id is not found, which there's no need to care about
await bind . sessionGetImageQuality ( id: widget . id ) ? ? ' ' ,
2022-09-23 07:20:40 +03:00
optionSetter: ( String oldValue , String newValue ) async {
if ( oldValue ! = newValue ) {
await bind . sessionSetImageQuality ( id: widget . id , value: newValue ) ;
}
2022-10-19 05:19:49 +03:00
double qualityInitValue = 50 ;
double fpsInitValue = 30 ;
bool qualitySet = false ;
bool fpsSet = false ;
setCustomValues ( { double ? quality , double ? fps } ) async {
if ( quality ! = null ) {
qualitySet = true ;
await bind . sessionSetCustomImageQuality (
id: widget . id , value: quality . toInt ( ) ) ;
}
if ( fps ! = null ) {
fpsSet = true ;
await bind . sessionSetCustomFps ( id: widget . id , fps: fps . toInt ( ) ) ;
}
if ( ! qualitySet ) {
qualitySet = true ;
await bind . sessionSetCustomImageQuality (
id: widget . id , value: qualityInitValue . toInt ( ) ) ;
}
if ( ! fpsSet ) {
fpsSet = true ;
await bind . sessionSetCustomFps (
id: widget . id , fps: fpsInitValue . toInt ( ) ) ;
}
}
2022-11-24 06:19:16 +03:00
if ( newValue = = kRemoteImageQualityCustom ) {
2023-01-15 14:46:16 +03:00
final btnClose = dialogButton ( ' Close ' , onPressed: ( ) async {
2022-10-19 05:19:49 +03:00
await setCustomValues ( ) ;
2022-09-23 07:20:40 +03:00
widget . ffi . dialogManager . dismissAll ( ) ;
} ) ;
2022-10-19 05:19:49 +03:00
// quality
2022-09-23 07:20:40 +03:00
final quality =
await bind . sessionGetCustomImageQuality ( id: widget . id ) ;
2022-10-19 05:19:49 +03:00
qualityInitValue = quality ! = null & & quality . isNotEmpty
2022-09-23 07:20:40 +03:00
? quality [ 0 ] . toDouble ( )
: 50.0 ;
2022-10-19 05:19:49 +03:00
const qualityMinValue = 10.0 ;
const qualityMaxValue = 100.0 ;
if ( qualityInitValue < qualityMinValue ) {
qualityInitValue = qualityMinValue ;
2022-08-31 13:41:55 +03:00
}
2022-10-19 05:19:49 +03:00
if ( qualityInitValue > qualityMaxValue ) {
qualityInitValue = qualityMaxValue ;
2022-08-31 13:41:55 +03:00
}
2022-10-19 05:19:49 +03:00
final RxDouble qualitySliderValue = RxDouble ( qualityInitValue ) ;
2022-12-21 11:38:46 +03:00
final debouncerQuality = Debouncer < double > (
2022-12-07 10:13:24 +03:00
Duration ( milliseconds: 1000 ) ,
onChanged: ( double v ) {
setCustomValues ( quality: v ) ;
} ,
initialValue: qualityInitValue ,
) ;
2022-10-19 05:19:49 +03:00
final qualitySlider = Obx ( ( ) = > Row (
children: [
Slider (
value: qualitySliderValue . value ,
min: qualityMinValue ,
max: qualityMaxValue ,
divisions: 90 ,
onChanged: ( double value ) {
qualitySliderValue . value = value ;
2022-12-21 11:38:46 +03:00
debouncerQuality . value = value ;
2022-10-19 05:19:49 +03:00
} ,
) ,
SizedBox (
width: 90 ,
child: Obx ( ( ) = > Text (
' ${ qualitySliderValue . value . round ( ) } % Bitrate ' ,
style: const TextStyle ( fontSize: 15 ) ,
) ) )
] ,
) ) ;
// fps
final fpsOption =
await bind . sessionGetOption ( id: widget . id , arg: ' custom-fps ' ) ;
fpsInitValue =
fpsOption = = null ? 30 : double . tryParse ( fpsOption ) ? ? 30 ;
if ( fpsInitValue < 10 | | fpsInitValue > 120 ) {
fpsInitValue = 30 ;
}
final RxDouble fpsSliderValue = RxDouble ( fpsInitValue ) ;
2022-12-07 10:13:24 +03:00
final debouncerFps = Debouncer < double > (
Duration ( milliseconds: 1000 ) ,
onChanged: ( double v ) {
setCustomValues ( fps: v ) ;
} ,
initialValue: qualityInitValue ,
) ;
2022-10-19 05:19:49 +03:00
bool ? direct ;
try {
direct = ConnectionTypeState . find ( widget . id ) . direct . value = =
ConnectionType . strDirect ;
} catch ( _ ) { }
final fpsSlider = Offstage (
offstage:
( await bind . mainIsUsingPublicServer ( ) & & direct ! = true ) | |
( await bind . versionToNumber (
v: widget . ffi . ffiModel . pi . version ) <
await bind . versionToNumber ( v: ' 1.2.0 ' ) ) ,
child: Row (
children: [
Obx ( ( ( ) = > Slider (
value: fpsSliderValue . value ,
min: 10 ,
max: 120 ,
divisions: 22 ,
onChanged: ( double value ) {
fpsSliderValue . value = value ;
2022-12-07 10:13:24 +03:00
debouncerFps . value = value ;
2022-10-19 05:19:49 +03:00
} ,
) ) ) ,
SizedBox (
width: 90 ,
child: Obx ( ( ) {
final fps = fpsSliderValue . value . round ( ) ;
String text ;
if ( fps < 100 ) {
text = ' $ fps FPS ' ;
} else {
text = ' $ fps FPS ' ;
}
return Text (
text ,
2022-09-23 07:20:40 +03:00
style: const TextStyle ( fontSize: 15 ) ,
2022-10-19 05:19:49 +03:00
) ;
} ) )
] ,
) ,
) ;
final content = Column (
children: [ qualitySlider , fpsSlider ] ,
2022-09-23 07:20:40 +03:00
) ;
msgBoxCommon ( widget . ffi . dialogManager , ' Custom Image Quality ' ,
2022-10-19 05:19:49 +03:00
content , [ btnClose ] ) ;
2022-09-23 07:20:40 +03:00
}
} ,
padding: padding ,
) ,
2022-08-30 12:20:25 +03:00
MenuEntryDivider < String > ( ) ,
2022-09-16 14:43:28 +03:00
] ;
2022-11-26 05:46:57 +03:00
if ( widget . state . viewStyle . value = = kRemoteViewStyleOriginal ) {
displayMenu . insert (
2 ,
MenuEntryRadios < String > (
text: translate ( ' Scroll Style ' ) ,
optionsGetter: ( ) = > [
MenuEntryRadioOption (
text: translate ( ' ScrollAuto ' ) ,
value: kRemoteScrollStyleAuto ,
dismissOnClicked: true ,
2023-01-06 15:25:18 +03:00
enabled: widget . ffi . canvasModel . imageOverflow ,
2022-11-26 05:46:57 +03:00
) ,
MenuEntryRadioOption (
text: translate ( ' Scrollbar ' ) ,
value: kRemoteScrollStyleBar ,
dismissOnClicked: true ,
2023-01-06 15:25:18 +03:00
enabled: widget . ffi . canvasModel . imageOverflow ,
2022-11-26 05:46:57 +03:00
) ,
] ,
curOptionGetter: ( ) async = >
// null means peer id is not found, which there's no need to care about
await bind . sessionGetScrollStyle ( id: widget . id ) ? ? ' ' ,
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionSetScrollStyle ( id: widget . id , value: newValue ) ;
widget . ffi . canvasModel . updateScrollStyle ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ) ;
displayMenu . insert ( 3 , MenuEntryDivider < String > ( ) ) ;
2023-01-10 11:07:48 +03:00
if ( _isWindowCanBeAdjusted ( remoteCount ) ) {
displayMenu . insert (
0 ,
MenuEntryDivider < String > ( ) ,
) ;
displayMenu . insert (
0 ,
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Container (
child: Text (
translate ( ' Adjust Window ' ) ,
style: style ,
) ) ,
proc: ( ) {
( ) async {
await _updateScreen ( ) ;
if ( _screen ! = null ) {
_setFullscreen ( false ) ;
double scale = _screen ! . scaleFactor ;
final wndRect =
await WindowController . fromWindowId ( windowId ) . getFrame ( ) ;
final mediaSize = MediaQueryData . fromWindow ( ui . window ) . size ;
// On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect.
// https://stackoverflow.com/a/7561083
double magicWidth =
wndRect . right - wndRect . left - mediaSize . width * scale ;
double magicHeight =
wndRect . bottom - wndRect . top - mediaSize . height * scale ;
2022-10-09 14:27:30 +03:00
2023-01-10 11:07:48 +03:00
final canvasModel = widget . ffi . canvasModel ;
final width =
( canvasModel . getDisplayWidth ( ) * canvasModel . scale +
canvasModel . windowBorderWidth * 2 ) *
scale +
magicWidth ;
final height =
( canvasModel . getDisplayHeight ( ) * canvasModel . scale +
canvasModel . tabBarHeight +
canvasModel . windowBorderWidth * 2 ) *
scale +
magicHeight ;
double left = wndRect . left + ( wndRect . width - width ) / 2 ;
double top = wndRect . top + ( wndRect . height - height ) / 2 ;
2022-10-09 14:27:30 +03:00
2023-01-10 11:07:48 +03:00
Rect frameRect = _screen ! . frame ;
if ( ! isFullscreen ) {
frameRect = _screen ! . visibleFrame ;
}
if ( left < frameRect . left ) {
left = frameRect . left ;
}
if ( top < frameRect . top ) {
top = frameRect . top ;
}
if ( ( left + width ) > frameRect . right ) {
left = frameRect . right - width ;
}
if ( ( top + height ) > frameRect . bottom ) {
top = frameRect . bottom - height ;
}
await WindowController . fromWindowId ( windowId )
. setFrame ( Rect . fromLTWH ( left , top , width , height ) ) ;
2022-10-09 14:27:30 +03:00
}
2023-01-10 11:07:48 +03:00
} ( ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ,
) ;
}
2022-10-08 12:27:30 +03:00
}
2022-09-16 14:43:28 +03: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 ) ;
2022-12-20 17:55:54 +03:00
} catch ( e ) {
debugPrint ( " Show Codec Preference err= $ e " ) ;
}
2022-09-16 14:43:28 +03:00
if ( codecs . length = = 2 & & ( codecs [ 0 ] | | codecs [ 1 ] ) ) {
displayMenu . add ( MenuEntryRadios < String > (
2022-09-23 07:20:40 +03: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 ;
} ,
2022-11-17 13:52:27 +03:00
curOptionGetter: ( ) async = >
// null means peer id is not found, which there's no need to care about
await bind . sessionGetOption (
id: widget . id , arg: ' codec-preference ' ) ? ?
' ' ,
2022-09-23 07:20:40 +03:00
optionSetter: ( String oldValue , String newValue ) async {
await bind . sessionPeerOption (
2022-11-17 13:52:27 +03:00
id: widget . id , name: ' codec-preference ' , value: newValue ) ;
2022-09-23 07:20:40 +03:00
bind . sessionChangePreferCodec ( id: widget . id ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ) ;
2022-09-16 14:43:28 +03:00
}
}
/// Show remote cursor
2022-12-31 16:41:16 +03:00
if ( ! widget . ffi . canvasModel . cursorEmbedded ) {
2022-11-29 11:36:35 +03:00
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 ,
) ;
} ( ) ) ;
}
2022-09-16 14:43:28 +03:00
2022-11-24 06:19:16 +03:00
/// Show remote cursor scaling with image
if ( widget . state . viewStyle . value ! = kRemoteViewStyleOriginal ) {
displayMenu . add ( ( ) {
final opt = ' zoom-cursor ' ;
final state = PeerBoolOption . find ( widget . id , opt ) ;
return MenuEntrySwitch2 < String > (
switchType: SwitchType . scheckbox ,
text: translate ( ' Zoom cursor ' ) ,
getter: ( ) {
return state ;
} ,
setter: ( bool v ) async {
state . value = v ;
await bind . sessionToggleOption ( id: widget . id , value: opt ) ;
} ,
padding: padding ,
dismissOnClicked: true ,
) ;
} ( ) ) ;
}
2022-11-16 13:07:58 +03:00
2022-09-16 14:43:28 +03:00
/// Show quality monitor
displayMenu . add ( MenuEntrySwitch < String > (
2022-09-23 07:20:40 +03: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 18:28:08 +03:00
final perms = widget . ffi . ffiModel . permissions ;
final pi = widget . ffi . ffiModel . pi ;
if ( perms [ ' audio ' ] ! = false ) {
2022-09-23 07:20:40 +03:00
displayMenu
. add ( _createSwitchMenuEntry ( ' Mute ' , ' disable-audio ' , padding , true ) ) ;
2022-08-26 18:28:08 +03:00
}
2022-09-08 18:25:19 +03:00
if ( Platform . isWindows & &
2023-01-10 12:13:40 +03:00
pi . platform = = kPeerPlatformWindows & &
2022-09-08 18:25:19 +03:00
perms [ ' file ' ] ! = false ) {
displayMenu . add ( _createSwitchMenuEntry (
2022-09-23 07:20:40 +03:00
' Allow file copy and paste ' , ' enable-file-transfer ' , padding , true ) ) ;
2022-09-08 18:25:19 +03:00
}
2022-08-26 18:28:08 +03:00
if ( perms [ ' keyboard ' ] ! = false ) {
if ( perms [ ' clipboard ' ] ! = false ) {
2022-09-23 07:20:40 +03:00
displayMenu . add ( _createSwitchMenuEntry (
' Disable clipboard ' , ' disable-clipboard ' , padding , true ) ) ;
2022-08-26 18:28:08 +03:00
}
displayMenu . add ( _createSwitchMenuEntry (
2022-09-23 07:20:40 +03:00
' Lock after session end ' , ' lock-after-session-end ' , padding , true ) ) ;
2022-12-02 16:34:20 +03:00
if ( pi . features . privacyMode ) {
2022-08-26 18:28:08 +03:00
displayMenu . add ( MenuEntrySwitch2 < String > (
2022-09-23 07:20:40 +03: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 18:28:08 +03:00
}
}
return displayMenu ;
}
2022-09-05 17:18:29 +03:00
List < MenuEntryBase < String > > _getKeyboardMenu ( ) {
2022-12-27 11:45:13 +03:00
final List < MenuEntryBase < String > > keyboardMenu = [
2022-09-05 17:18:29 +03:00
MenuEntryRadios < String > (
2022-09-23 07:20:40 +03:00
text: translate ( ' Ratio ' ) ,
2022-12-26 13:30:25 +03:00
optionsGetter: ( ) {
List < MenuEntryRadioOption > list = [ ] ;
List < String > modes = [ " legacy " ] ;
if ( bind . sessionIsKeyboardModeSupported ( id: widget . id , mode: " map " ) ) {
modes . add ( " map " ) ;
}
for ( String mode in modes ) {
if ( mode = = " legacy " ) {
list . add ( MenuEntryRadioOption (
text: translate ( ' Legacy mode ' ) , value: ' legacy ' ) ) ;
} else if ( mode = = " map " ) {
list . add ( MenuEntryRadioOption (
text: translate ( ' Map mode ' ) , value: ' map ' ) ) ;
}
}
return list ;
} ,
2022-11-16 10:09:29 +03:00
curOptionGetter: ( ) async {
return await bind . sessionGetKeyboardMode ( id: widget . id ) ? ? " legacy " ;
} ,
2022-09-23 07:20:40 +03:00
optionSetter: ( String oldValue , String newValue ) async {
2022-11-16 10:09:29 +03:00
await bind . sessionSetKeyboardMode ( id: widget . id , value: newValue ) ;
2022-09-23 07:20:40 +03:00
} ,
)
2022-09-05 17:18:29 +03:00
] ;
2022-12-27 11:45:13 +03:00
final localPlatform =
getLocalPlatformForKBLayoutType ( widget . ffi . ffiModel . pi . platform ) ;
if ( localPlatform ! = ' ' ) {
keyboardMenu . add ( MenuEntryDivider ( ) ) ;
keyboardMenu . add (
MenuEntryButton < String > (
childBuilder: ( TextStyle ? style ) = > Container (
alignment: AlignmentDirectional . center ,
height: _MenubarTheme . height ,
child: Row (
children: [
Obx ( ( ) = > RichText (
text: TextSpan (
text: ' ${ translate ( ' Local keyboard type ' ) } : ' ,
style: DefaultTextStyle . of ( context ) . style ,
children: < TextSpan > [
TextSpan (
text: KBLayoutType . value ,
style: TextStyle ( fontWeight: FontWeight . bold ) ,
) ,
] ,
) ,
) ) ,
Expanded (
child: Align (
alignment: Alignment . centerRight ,
child: Transform . scale (
scale: 0.8 ,
child: IconButton (
padding: EdgeInsets . zero ,
icon: const Icon ( Icons . settings ) ,
onPressed: ( ) {
if ( Navigator . canPop ( context ) ) {
Navigator . pop ( context ) ;
}
showKBLayoutTypeChooser (
localPlatform , widget . ffi . dialogManager ) ;
} ,
) ,
) ,
) )
] ,
) ) ,
proc: ( ) { } ,
padding: EdgeInsets . zero ,
dismissOnClicked: false ,
) ,
) ;
}
2022-09-05 17:18:29 +03:00
return keyboardMenu ;
}
2022-09-23 07:20:40 +03:00
MenuEntrySwitch < String > _createSwitchMenuEntry (
String text , String option , EdgeInsets ? padding , bool dismissOnClicked ) {
2022-08-26 18:28:08 +03:00
return MenuEntrySwitch < String > (
2022-09-23 07:20:40 +03: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 18:28:08 +03:00
}
}
void showSetOSPassword (
String id , bool login , OverlayDialogManager dialogManager ) async {
final controller = TextEditingController ( ) ;
2022-11-17 13:52:27 +03:00
var password = await bind . sessionGetOption ( id: id , arg: ' os-password ' ) ? ? ' ' ;
var autoLogin = await bind . sessionGetOption ( id: id , arg: ' auto-login ' ) ! = ' ' ;
2022-08-26 18:28:08 +03:00
controller . text = password ;
dialogManager . show ( ( setState , close ) {
2022-09-03 13:19:50 +03:00
submit ( ) {
var text = controller . text . trim ( ) ;
2022-11-17 13:52:27 +03:00
bind . sessionPeerOption ( id: id , name: ' os-password ' , value: text ) ;
2022-09-03 13:19:50 +03:00
bind . sessionPeerOption (
2022-11-17 13:52:27 +03:00
id: id , name: ' auto-login ' , value: autoLogin ? ' Y ' : ' ' ) ;
if ( text ! = ' ' & & login ) {
2022-09-03 13:19:50 +03:00
bind . sessionInputOsPassword ( id: id , value: text ) ;
}
close ( ) ;
}
2022-08-26 18:28:08 +03:00
return CustomAlertDialog (
2022-09-03 13:19:50 +03: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 18:28:08 +03:00
) ,
2022-09-03 13:19:50 +03:00
value: autoLogin ,
onChanged: ( v ) {
if ( v = = null ) return ;
setState ( ( ) = > autoLogin = v ) ;
} ,
) ,
] ) ,
actions: [
2023-01-15 14:46:16 +03:00
dialogButton ( ' Cancel ' , onPressed: close , isOutline: true ) ,
dialogButton ( ' OK ' , onPressed: submit ) ,
2022-09-03 13:19:50 +03:00
] ,
onSubmit: submit ,
onCancel: close ,
) ;
2022-08-26 18:28:08 +03:00
} ) ;
}
2022-09-08 10:35:19 +03:00
void showAuditDialog ( String id , dialogManager ) async {
final controller = TextEditingController ( ) ;
dialogManager . show ( ( setState , close ) {
submit ( ) {
var text = controller . text . trim ( ) ;
2022-11-17 13:52:27 +03:00
if ( text ! = ' ' ) {
2022-09-08 10:35:19 +03:00
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 (
2022-11-17 13:52:27 +03:00
hintText: ' input note here ' ,
2022-09-08 10:35:19 +03:00
) ,
// inputFormatters: [
// LengthLimitingTextInputFormatter(16),
2022-11-17 13:52:27 +03:00
// // FilteringTextInputFormatter(RegExp(r'[a-zA-z][a-zA-z0-9\_]*'), allow: true)
2022-09-08 10:35:19 +03:00
// ],
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 ,
) ;
} ) ;
}
2022-12-07 10:13:24 +03:00
class _DraggableShowHide extends StatefulWidget {
final RxDouble fractionX ;
final RxBool dragging ;
final RxBool show ;
const _DraggableShowHide ( {
Key ? key ,
required this . fractionX ,
required this . dragging ,
required this . show ,
} ) : super ( key: key ) ;
@ override
2023-01-04 11:41:05 +03:00
State < _DraggableShowHide > createState ( ) = > _DraggableShowHideState ( ) ;
2022-12-07 10:13:24 +03:00
}
2023-01-04 11:41:05 +03:00
class _DraggableShowHideState extends State < _DraggableShowHide > {
2022-12-07 10:13:24 +03:00
Offset position = Offset . zero ;
Size size = Size . zero ;
Widget _buildDraggable ( BuildContext context ) {
return Draggable (
axis: Axis . horizontal ,
child: Icon (
Icons . drag_indicator ,
2023-01-03 18:42:40 +03:00
size: 20 ,
color: Colors . grey ,
2022-12-07 10:13:24 +03:00
) ,
feedback: widget ,
onDragStarted: ( ( ) {
final RenderObject ? renderObj = context . findRenderObject ( ) ;
if ( renderObj ! = null ) {
final RenderBox renderBox = renderObj as RenderBox ;
size = renderBox . size ;
position = renderBox . localToGlobal ( Offset . zero ) ;
}
widget . dragging . value = true ;
} ) ,
onDragEnd: ( details ) {
final mediaSize = MediaQueryData . fromWindow ( ui . window ) . size ;
widget . fractionX . value + =
( details . offset . dx - position . dx ) / ( mediaSize . width - size . width ) ;
if ( widget . fractionX . value < 0.35 ) {
widget . fractionX . value = 0.35 ;
}
if ( widget . fractionX . value > 0.65 ) {
widget . fractionX . value = 0.65 ;
}
widget . dragging . value = false ;
} ,
) ;
}
@ override
Widget build ( BuildContext context ) {
final ButtonStyle buttonStyle = ButtonStyle (
minimumSize: MaterialStateProperty . all ( const Size ( 0 , 0 ) ) ,
padding: MaterialStateProperty . all ( EdgeInsets . zero ) ,
) ;
final child = Row (
mainAxisSize: MainAxisSize . min ,
children: [
_buildDraggable ( context ) ,
TextButton (
onPressed: ( ) = > setState ( ( ) {
widget . show . value = ! widget . show . value ;
} ) ,
child: Obx ( ( ( ) = > Icon (
widget . show . isTrue ? Icons . expand_less : Icons . expand_more ,
2023-01-03 18:42:40 +03:00
size: 20 ,
2022-12-07 10:13:24 +03:00
) ) ) ,
) ,
] ,
) ;
return TextButtonTheme (
data: TextButtonThemeData ( style: buttonStyle ) ,
child: Container (
decoration: BoxDecoration (
color: Colors . white ,
border: Border . all ( color: MyTheme . border ) ,
) ,
child: SizedBox (
2023-01-03 18:42:40 +03:00
height: 20 ,
2022-12-07 10:13:24 +03:00
child: child ,
) ,
) ,
) ;
}
}