2022-05-30 13:25:06 +08:00
import ' dart:async ' ;
2022-08-17 21:28:36 +08:00
import ' dart:convert ' ;
2022-11-30 13:56:02 +08:00
import ' dart:ffi ' hide Size ;
2022-08-03 22:03:31 +08:00
import ' dart:io ' ;
2022-11-09 15:14:11 +08:00
import ' dart:math ' ;
2022-05-30 13:25:06 +08:00
2022-08-17 21:28:36 +08:00
import ' package:back_button_interceptor/back_button_interceptor.dart ' ;
2022-08-09 19:32:19 +08:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-11-30 13:56:02 +08:00
import ' package:ffi/ffi.dart ' ;
import ' package:flutter/foundation.dart ' ;
2022-04-14 15:37:47 +08:00
import ' package:flutter/gestures.dart ' ;
2020-11-15 20:04:05 +08:00
import ' package:flutter/material.dart ' ;
2022-09-03 18:19:50 +08:00
import ' package:flutter/services.dart ' ;
2023-08-08 17:22:25 +08:00
import ' package:flutter_hbb/common/formatter/id_formatter.dart ' ;
2022-10-26 14:39:13 +08:00
import ' package:flutter_hbb/desktop/widgets/refresh_wrapper.dart ' ;
2022-08-16 21:27:21 +08:00
import ' package:flutter_hbb/desktop/widgets/tabbar_widget.dart ' ;
2022-10-11 19:52:03 +08:00
import ' package:flutter_hbb/main.dart ' ;
2023-10-17 00:30:34 +08:00
import ' package:flutter_hbb/models/desktop_render_texture.dart ' ;
2022-08-27 00:45:09 +08:00
import ' package:flutter_hbb/models/peer_model.dart ' ;
2023-08-11 15:53:47 +08:00
import ' package:flutter_hbb/models/state_model.dart ' ;
2022-09-16 12:14:03 +08:00
import ' package:flutter_hbb/utils/multi_window_manager.dart ' ;
2023-02-03 17:08:40 +08:00
import ' package:flutter_hbb/utils/platform_channel.dart ' ;
import ' package:flutter_svg/flutter_svg.dart ' ;
2022-08-20 19:57:16 +08:00
import ' package:get/get.dart ' ;
2022-10-18 10:29:33 +08:00
import ' package:uni_links/uni_links.dart ' ;
2023-02-03 17:08:40 +08:00
import ' package:url_launcher/url_launcher.dart ' ;
2023-06-06 07:39:44 +08:00
import ' package:uuid/uuid.dart ' ;
2023-02-03 17:08:40 +08:00
import ' package:win32/win32.dart ' as win32 ;
2022-08-09 09:01:06 +08:00
import ' package:window_manager/window_manager.dart ' ;
2022-10-09 19:27:30 +08:00
import ' package:window_size/window_size.dart ' as window_size ;
2022-04-19 13:07:45 +08:00
2023-02-03 17:08:40 +08:00
import ' ../consts.dart ' ;
2022-09-13 09:03:34 +08:00
import ' common/widgets/overlay.dart ' ;
2022-09-22 16:45:14 +08:00
import ' mobile/pages/file_manager_page.dart ' ;
import ' mobile/pages/remote_page.dart ' ;
2022-09-27 20:35:02 +08:00
import ' models/input_model.dart ' ;
2022-04-19 13:07:45 +08:00
import ' models/model.dart ' ;
2022-08-03 22:03:31 +08:00
import ' models/platform_model.dart ' ;
2021-08-02 20:54:56 +08:00
2022-03-07 22:54:34 +08:00
final globalKey = GlobalKey < NavigatorState > ( ) ;
2022-04-12 22:38:39 +08:00
final navigationBarKey = GlobalKey ( ) ;
2022-03-07 22:54:34 +08:00
2022-08-23 15:25:18 +08:00
final isAndroid = Platform . isAndroid ;
final isIOS = Platform . isIOS ;
final isDesktop = Platform . isWindows | | Platform . isMacOS | | Platform . isLinux ;
2022-03-17 21:03:52 +08:00
var isWeb = false ;
2022-05-23 16:02:37 +08:00
var isWebDesktop = false ;
2023-04-12 09:41:13 +08:00
var isMobile = isAndroid | | isIOS ;
2022-03-17 21:03:52 +08:00
var version = " " ;
2022-03-24 17:58:33 +08:00
int androidVersion = 0 ;
2023-02-23 23:49:31 +08:00
2022-12-26 01:21:13 +08:00
/// only available for Windows target
2022-11-29 23:03:16 +08:00
int windowsBuildNumber = 0 ;
2022-09-15 16:36:52 +08:00
DesktopType ? desktopType ;
2022-03-17 21:03:52 +08:00
2023-10-17 00:30:34 +08:00
bool get isMainDesktopWindow = >
desktopType = = DesktopType . main | | desktopType = = DesktopType . cm ;
2023-02-10 21:18:55 +08:00
/// Check if the app is running with single view mode.
bool isSingleViewApp ( ) {
return desktopType = = DesktopType . cm ;
}
2022-10-20 22:05:34 +08:00
/// * debug or test only, DO NOT enable in release build
bool isTest = false ;
2021-08-02 20:54:56 +08:00
typedef F = String Function ( String ) ;
2022-02-10 02:07:53 +08:00
typedef FMethod = String Function ( String , dynamic ) ;
2021-08-02 20:54:56 +08:00
2022-09-11 10:50:48 +08:00
typedef StreamEventHandler = Future < void > Function ( Map < String , dynamic > ) ;
2023-06-06 07:39:44 +08:00
typedef SessionID = UuidValue ;
2022-10-20 09:31:31 +08:00
final iconHardDrive = MemoryImage ( Uint8List . fromList ( base64Decode (
' iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAmVBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjHWqVAAAAMnRSTlMAv0BmzLJNXlhiUu2fxXDgu7WuSUUe29LJvpqUjX53VTstD7ilNujCqTEk5IYH+vEoFjKvAagAAAPpSURBVHja7d0JbhpBEIXhB3jYzb5vBgzYgO04df/DJXGUKMwU9ECmZ6pQfSfw028LCXW3YYwxxhhjjDHGGGOM0eZ9VV1MckdKWLM1bRQ/35GW/WxHHu1me6ShuyHvNl34VhlTKsYVeDWj1EzgUZ1S1DrAk/UDparZgxd9Sl0BHnxSBhpI3jfKQG2FpLUpE69I2ILikv1nsvygjBwPSNKYMlNHggqUoSKS80AZCnwHqQ1zCRvW+CRegwRFeFAMKKrtM8gTPJlzSfwFgT9dJom3IDN4VGaSeAryAK8m0SSeghTg1ZYiql6CjBDhO8mzlyAVhKhIwgXxrh5NojGIhyRckEdwpCdhgpSQgiWTRGMQNonGIGySp0SDvMDBX5KWxiB8Eo1BgE00SYJBykhNnkmSWJAcLpGaJNMgfJKyxiDAK4WNEwryhMtkJsk8CJtEYxA+icYgQIfCcgkEqcJNXhIRQdgkGoPwSTQG+e8khdu/7JOVREwQIKCwF41B2CQljUH4JLcH6SI+OUlEBQHa0SQag/BJNAbhkjxqDMIn0RgEeI4muSlID9eSkERgEKAVTaIxCJ9EYxA2ydVB8hCASVLRGAQYR5NoDMIn0RgEyFHYSGMQPonGII4kziCNvBgNJonEk4u3GAk8Sprk6eYaqbMDY0oKvUm5jfC/viGiSypV7+M3i2iDsAGpNEDYjlTa3W8RdR/r544g50ilnA0RxoZIE2NIXqQbhkAkGyKNDZHGhkhjQ6SxIdLYEGlsiDQ2JGTVeD0264U9zipPh7XOooffpA6pfNCXjxl4/c3pUzlChwzor53zwYYVfpI5pOV6LWFF/2jiJ5FDSs5jdY/0rwUAkUMeXWdBqnSqD0DikBqdqCHsjTvELm9In0IOri/0pwAEDtlSyNaRjAIAAoesKWTtuusxByBwCJp0oomwBXcYUuCQgE50ENajE4OvZAKHLB1/68Br5NqiyCGYOY8YRd77kTkEb64n7lZN+mOIX4QOwb5FX0ZVx3uOxwW+SB0CbBubemWP8/rlaaeRX+M3uUOuZENsiA25zIbYkPsZElBIHwL13U/PTjJ/cyOOEoVM3I+hziDQlELm7pPxw3eI8/7gPh1fpLA6xGnEeDDgO0UcIAzzM35HxLPIq5SXe9BLzOsj9eUaQqyXzxS1QFSfWM2cCANiHcAISJ0AnCKpUwTuIkkA3EeSInAXSQKcs1V18e24wlllUmQp9v9zXKeHi+akRAMOPVKhAqdPBZeUmnnEsO6QcJ0+4qmOSbBxFfGVRiTUqITrdKcCbyYO3/K4wX4+aQ+FfNjXhu3JfAVjjDHGGGOMMcYYY4xIPwCgfqT6TbhCLAAAAABJRU5ErkJggg== ' ) ) ) ;
2022-08-17 21:28:36 +08:00
2022-09-05 16:01:53 +08:00
enum DesktopType {
main ,
remote ,
fileTransfer ,
cm ,
portForward ,
}
2022-08-20 19:57:16 +08:00
class IconFont {
2022-08-24 11:01:58 +08:00
static const _family1 = ' Tabbar ' ;
static const _family2 = ' PeerSearchbar ' ;
2022-08-20 19:57:16 +08:00
IconFont . _ ( ) ;
2022-08-24 11:01:58 +08:00
static const IconData max = IconData ( 0xe606 , fontFamily: _family1 ) ;
static const IconData restore = IconData ( 0xe607 , fontFamily: _family1 ) ;
static const IconData close = IconData ( 0xe668 , fontFamily: _family1 ) ;
static const IconData min = IconData ( 0xe609 , fontFamily: _family1 ) ;
static const IconData add = IconData ( 0xe664 , fontFamily: _family1 ) ;
static const IconData menu = IconData ( 0xe628 , fontFamily: _family1 ) ;
static const IconData search = IconData ( 0xe6a4 , fontFamily: _family2 ) ;
2022-11-03 21:58:25 +08:00
static const IconData roundClose = IconData ( 0xe6ed , fontFamily: _family2 ) ;
2023-06-21 16:04:52 +08:00
static const IconData addressBook =
IconData ( 0xe602 , fontFamily: " AddressBook " ) ;
2022-08-20 19:57:16 +08:00
}
class ColorThemeExtension extends ThemeExtension < ColorThemeExtension > {
const ColorThemeExtension ( {
required this . border ,
2023-03-02 01:00:56 +08:00
required this . border2 ,
2022-12-11 21:40:35 +08:00
required this . highlight ,
2023-03-11 19:53:19 +08:00
required this . drag_indicator ,
2023-03-15 16:57:24 +08:00
required this . shadow ,
2023-09-11 11:18:26 +08:00
required this . errorBannerBg ,
2023-09-26 09:26:53 +08:00
required this . me ,
2022-08-20 19:57:16 +08:00
} ) ;
final Color ? border ;
2023-03-02 01:00:56 +08:00
final Color ? border2 ;
2022-12-11 21:40:35 +08:00
final Color ? highlight ;
2023-03-11 19:53:19 +08:00
final Color ? drag_indicator ;
2023-03-15 16:57:24 +08:00
final Color ? shadow ;
2023-09-11 11:18:26 +08:00
final Color ? errorBannerBg ;
2023-09-26 09:26:53 +08:00
final Color ? me ;
2022-08-20 19:57:16 +08:00
2023-03-11 19:53:19 +08:00
static final light = ColorThemeExtension (
2022-08-20 19:57:16 +08:00
border: Color ( 0xFFCCCCCC ) ,
2023-03-02 01:00:56 +08:00
border2: Color ( 0xFFBBBBBB ) ,
2022-12-11 21:40:35 +08:00
highlight: Color ( 0xFFE5E5E5 ) ,
2023-03-11 19:53:19 +08:00
drag_indicator: Colors . grey [ 800 ] ,
2023-03-15 16:57:24 +08:00
shadow: Colors . black ,
2023-09-11 11:18:26 +08:00
errorBannerBg: Color ( 0xFFFDEEEB ) ,
2023-09-26 09:26:53 +08:00
me: Colors . green ,
2022-08-20 19:57:16 +08:00
) ;
2023-03-11 19:53:19 +08:00
static final dark = ColorThemeExtension (
2022-08-20 19:57:16 +08:00
border: Color ( 0xFF555555 ) ,
2023-03-02 01:00:56 +08:00
border2: Color ( 0xFFE5E5E5 ) ,
2022-12-11 21:40:35 +08:00
highlight: Color ( 0xFF3F3F3F ) ,
2023-03-11 19:53:19 +08:00
drag_indicator: Colors . grey ,
2023-03-15 16:57:24 +08:00
shadow: Colors . grey ,
2023-09-11 11:18:26 +08:00
errorBannerBg: Color ( 0xFF470F2D ) ,
2023-09-26 09:26:53 +08:00
me: Colors . greenAccent ,
2022-08-20 19:57:16 +08:00
) ;
@ override
2023-03-15 16:57:24 +08:00
ThemeExtension < ColorThemeExtension > copyWith ( {
Color ? border ,
Color ? border2 ,
Color ? highlight ,
Color ? drag_indicator ,
Color ? shadow ,
2023-09-11 11:18:26 +08:00
Color ? errorBannerBg ,
2023-09-26 09:26:53 +08:00
Color ? me ,
2023-03-15 16:57:24 +08:00
} ) {
2022-08-20 19:57:16 +08:00
return ColorThemeExtension (
2023-03-15 16:57:24 +08:00
border: border ? ? this . border ,
border2: border2 ? ? this . border2 ,
highlight: highlight ? ? this . highlight ,
drag_indicator: drag_indicator ? ? this . drag_indicator ,
shadow: shadow ? ? this . shadow ,
2023-09-11 11:18:26 +08:00
errorBannerBg: errorBannerBg ? ? this . errorBannerBg ,
2023-09-26 09:26:53 +08:00
me: me ? ? this . me ,
2023-03-15 16:57:24 +08:00
) ;
2022-08-20 19:57:16 +08:00
}
@ override
ThemeExtension < ColorThemeExtension > lerp (
ThemeExtension < ColorThemeExtension > ? other , double t ) {
if ( other is ! ColorThemeExtension ) {
return this ;
}
return ColorThemeExtension (
border: Color . lerp ( border , other . border , t ) ,
2023-03-02 01:00:56 +08:00
border2: Color . lerp ( border2 , other . border2 , t ) ,
2022-12-11 21:40:35 +08:00
highlight: Color . lerp ( highlight , other . highlight , t ) ,
2023-03-11 19:53:19 +08:00
drag_indicator: Color . lerp ( drag_indicator , other . drag_indicator , t ) ,
2023-03-15 16:57:24 +08:00
shadow: Color . lerp ( shadow , other . shadow , t ) ,
2023-09-11 11:18:26 +08:00
errorBannerBg: Color . lerp ( shadow , other . errorBannerBg , t ) ,
2023-09-26 09:26:53 +08:00
me: Color . lerp ( shadow , other . me , t ) ,
2022-08-20 19:57:16 +08:00
) ;
}
}
2020-11-16 01:13:26 +08:00
class MyTheme {
2020-11-20 16:37:48 +08:00
MyTheme . _ ( ) ;
2022-02-02 17:25:56 +08:00
2023-03-04 14:28:43 +08:00
static const Color grayBg = Color ( 0xFFEFEFF2 ) ;
2020-11-16 22:00:09 +08:00
static const Color accent = Color ( 0xFF0071FF ) ;
2020-11-19 17:22:42 +08:00
static const Color accent50 = Color ( 0x770071FF ) ;
2020-11-27 02:14:27 +08:00
static const Color accent80 = Color ( 0xAA0071FF ) ;
2020-11-19 18:41:37 +08:00
static const Color canvasColor = Color ( 0xFF212121 ) ;
2020-11-20 13:06:52 +08:00
static const Color border = Color ( 0xFFCCCCCC ) ;
2022-01-31 16:22:05 +08:00
static const Color idColor = Color ( 0xFF00B6F0 ) ;
2023-02-23 05:13:21 +08:00
static const Color darkGray = Color . fromARGB ( 255 , 148 , 148 , 148 ) ;
2022-08-17 21:28:36 +08:00
static const Color cmIdColor = Color ( 0xFF21790B ) ;
2022-05-30 13:25:06 +08:00
static const Color dark = Colors . black87 ;
2022-08-22 17:58:48 +08:00
static const Color button = Color ( 0xFF2C8CFF ) ;
static const Color hoverBorder = Color ( 0xFF999999 ) ;
2023-07-09 03:53:07 +08:00
static const Color bordDark = Colors . white24 ;
static const Color bordLight = Colors . black26 ;
static const Color dividerDark = Colors . white38 ;
static const Color dividerLight = Colors . black38 ;
2022-07-29 16:47:24 +08:00
2023-03-30 06:03:15 +08:00
// ListTile
static const ListTileThemeData listTileTheme = ListTileThemeData (
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . all (
Radius . circular ( 5 ) ,
) ,
) ,
) ;
2023-06-06 20:53:05 +08:00
static SwitchThemeData switchTheme ( ) {
return SwitchThemeData ( splashRadius: isDesktop ? 0 : kRadialReactionRadius ) ;
}
static RadioThemeData radioTheme ( ) {
return RadioThemeData ( splashRadius: isDesktop ? 0 : kRadialReactionRadius ) ;
}
2023-05-20 21:12:52 +08:00
2023-03-30 06:03:15 +08:00
// Checkbox
static const CheckboxThemeData checkboxTheme = CheckboxThemeData (
splashRadius: 0 ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . all (
Radius . circular ( 5 ) ,
) ,
) ,
) ;
2023-03-29 22:14:55 +08:00
// TextButton
// Value is used to calculate "dialog.actionsPadding"
static const double mobileTextButtonPaddingLR = 20 ;
// TextButton on mobile needs a fixed padding, otherwise small buttons
// like "OK" has a larger left/right padding.
2023-03-30 06:03:15 +08:00
static TextButtonThemeData mobileTextButtonTheme = TextButtonThemeData (
style: TextButton . styleFrom (
padding: EdgeInsets . symmetric ( horizontal: mobileTextButtonPaddingLR ) ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8.0 ) ,
) ,
) ,
) ;
2023-03-29 22:14:55 +08:00
2023-08-10 21:02:02 +08:00
//tooltip
static TooltipThemeData tooltipTheme ( ) {
return TooltipThemeData (
2023-08-11 12:01:57 +08:00
waitDuration: Duration ( seconds: 1 , milliseconds: 500 ) ,
2023-08-10 21:02:02 +08:00
) ;
}
2023-03-29 22:14:55 +08:00
// Dialogs
static const double dialogPadding = 24 ;
2023-03-30 06:03:15 +08:00
// padding bottom depends on content (some dialogs has no content)
2023-03-29 22:14:55 +08:00
static EdgeInsets dialogTitlePadding ( { bool content = true } ) {
final double p = dialogPadding ;
return EdgeInsets . fromLTRB ( p , p , p , content ? 0 : p ) ;
}
2023-03-30 06:03:15 +08:00
// padding bottom depends on actions (mobile has dialogs without actions)
2023-03-29 22:14:55 +08:00
static EdgeInsets dialogContentPadding ( { bool actions = true } ) {
final double p = dialogPadding ;
return isDesktop
? EdgeInsets . fromLTRB ( p , p , p , actions ? ( p - 4 ) : p )
: EdgeInsets . fromLTRB ( p , p , p , actions ? ( p / 2 ) : p ) ;
}
static EdgeInsets dialogActionsPadding ( ) {
final double p = dialogPadding ;
return isDesktop
? EdgeInsets . fromLTRB ( p , 0 , p , ( p - 4 ) )
: EdgeInsets . fromLTRB ( p , 0 , ( p - mobileTextButtonPaddingLR ) , ( p / 2 ) ) ;
}
static EdgeInsets dialogButtonPadding = isDesktop
? EdgeInsets . only ( left: dialogPadding )
: EdgeInsets . only ( left: dialogPadding / 3 ) ;
2023-09-13 21:02:21 +08:00
static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData (
2023-09-26 19:58:51 +08:00
thickness: MaterialStateProperty . all ( 6 ) ,
thumbColor: MaterialStateProperty . resolveWith < Color ? > ( ( states ) {
if ( states . contains ( MaterialState . dragged ) ) {
return Colors . grey [ 900 ] ;
} else if ( states . contains ( MaterialState . hovered ) ) {
return Colors . grey [ 700 ] ;
} else {
return Colors . grey [ 500 ] ;
}
} ) ,
crossAxisMargin: 4 ,
2023-09-13 21:02:21 +08:00
) ;
static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme . copyWith (
2023-09-26 19:58:51 +08:00
thumbColor: MaterialStateProperty . resolveWith < Color ? > ( ( states ) {
if ( states . contains ( MaterialState . dragged ) ) {
return Colors . grey [ 100 ] ;
} else if ( states . contains ( MaterialState . hovered ) ) {
return Colors . grey [ 300 ] ;
} else {
return Colors . grey [ 500 ] ;
}
} ) ,
2023-09-13 21:02:21 +08:00
) ;
2022-07-29 16:47:24 +08:00
static ThemeData lightTheme = ThemeData (
brightness: Brightness . light ,
2023-02-23 05:13:21 +08:00
hoverColor: Color . fromARGB ( 255 , 224 , 224 , 224 ) ,
2023-03-04 14:28:43 +08:00
scaffoldBackgroundColor: Colors . white ,
dialogBackgroundColor: Colors . white ,
2023-02-27 16:44:52 +08:00
dialogTheme: DialogTheme (
2023-02-28 21:08:55 +08:00
elevation: 15 ,
2023-02-27 16:44:52 +08:00
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 18.0 ) ,
2023-02-28 21:08:55 +08:00
side: BorderSide (
width: 1 ,
2023-03-04 14:28:43 +08:00
color: grayBg ,
2023-02-28 21:08:55 +08:00
) ,
2023-02-27 16:44:52 +08:00
) ,
) ,
2023-09-13 21:02:21 +08:00
scrollbarTheme: scrollbarTheme ,
2023-03-17 19:44:07 +08:00
inputDecorationTheme: isDesktop
? InputDecorationTheme (
fillColor: grayBg ,
filled: true ,
isDense: true ,
2023-03-17 23:42:49 +08:00
border: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 8 ) ,
2023-03-17 19:44:07 +08:00
) ,
)
: null ,
2022-09-23 16:31:50 +08:00
textTheme: const TextTheme (
2022-09-29 13:07:20 +08:00
titleLarge: TextStyle ( fontSize: 19 , color: Colors . black87 ) ,
titleSmall: TextStyle ( fontSize: 14 , color: Colors . black87 ) ,
bodySmall: TextStyle ( fontSize: 12 , color: Colors . black87 , height: 1.25 ) ,
bodyMedium:
TextStyle ( fontSize: 14 , color: Colors . black87 , height: 1.25 ) ,
labelLarge: TextStyle ( fontSize: 16.0 , color: MyTheme . accent80 ) ) ,
2023-03-04 14:28:43 +08:00
cardColor: grayBg ,
2022-09-23 16:31:50 +08:00
hintColor: Color ( 0xFFAAAAAA ) ,
2022-07-29 16:47:24 +08:00
visualDensity: VisualDensity . adaptivePlatformDensity ,
2022-09-04 11:03:16 +08:00
tabBarTheme: const TabBarTheme (
2022-08-20 19:57:16 +08:00
labelColor: Colors . black87 ,
) ,
2023-08-10 21:02:02 +08:00
tooltipTheme: tooltipTheme ( ) ,
2023-06-29 10:02:19 +08:00
splashColor: isDesktop ? Colors . transparent : null ,
highlightColor: isDesktop ? Colors . transparent : null ,
2022-09-23 12:20:40 +08:00
splashFactory: isDesktop ? NoSplash . splashFactory : null ,
textButtonTheme: isDesktop
? TextButtonThemeData (
2023-03-02 01:00:56 +08:00
style: TextButton . styleFrom (
2023-02-28 03:56:45 +08:00
splashFactory: NoSplash . splashFactory ,
2023-03-02 01:00:56 +08:00
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 18.0 ) ,
2023-02-28 03:56:45 +08:00
) ,
) ,
2022-09-23 12:20:40 +08:00
)
2023-03-30 06:03:15 +08:00
: mobileTextButtonTheme ,
2023-02-28 03:56:45 +08:00
elevatedButtonTheme: ElevatedButtonThemeData (
2023-03-02 01:00:56 +08:00
style: ElevatedButton . styleFrom (
backgroundColor: MyTheme . accent ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8.0 ) ,
2023-03-01 21:50:50 +08:00
) ,
) ,
) ,
outlinedButtonTheme: OutlinedButtonThemeData (
2023-03-02 01:00:56 +08:00
style: OutlinedButton . styleFrom (
2023-03-04 14:28:43 +08:00
backgroundColor: grayBg ,
2023-03-02 01:00:56 +08:00
foregroundColor: Colors . black87 ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8.0 ) ,
2023-02-28 03:56:45 +08:00
) ,
) ,
) ,
2023-06-06 20:53:05 +08:00
switchTheme: switchTheme ( ) ,
radioTheme: radioTheme ( ) ,
2023-03-30 06:03:15 +08:00
checkboxTheme: checkboxTheme ,
listTileTheme: listTileTheme ,
2023-03-11 19:29:26 +08:00
menuBarTheme: MenuBarThemeData (
style:
MenuStyle ( backgroundColor: MaterialStatePropertyAll ( Colors . white ) ) ) ,
2023-03-10 21:12:28 +08:00
colorScheme: ColorScheme . light (
primary: Colors . blue , secondary: accent , background: grayBg ) ,
2022-08-20 19:57:16 +08:00
) . copyWith (
extensions: < ThemeExtension < dynamic > > [
ColorThemeExtension . light ,
2022-09-04 11:03:16 +08:00
TabbarTheme . light ,
2022-08-20 19:57:16 +08:00
] ,
2022-07-29 16:47:24 +08:00
) ;
static ThemeData darkTheme = ThemeData (
2022-08-20 19:57:16 +08:00
brightness: Brightness . dark ,
2023-02-23 05:13:21 +08:00
hoverColor: Color . fromARGB ( 255 , 45 , 46 , 53 ) ,
scaffoldBackgroundColor: Color ( 0xFF18191E ) ,
2023-02-27 16:44:52 +08:00
dialogBackgroundColor: Color ( 0xFF18191E ) ,
dialogTheme: DialogTheme (
2023-02-28 21:08:55 +08:00
elevation: 15 ,
2023-02-27 16:44:52 +08:00
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 18.0 ) ,
2023-02-28 21:08:55 +08:00
side: BorderSide (
width: 1 ,
color: Color ( 0xFF24252B ) ,
) ,
2023-02-27 16:44:52 +08:00
) ,
) ,
2023-09-13 21:02:21 +08:00
scrollbarTheme: scrollbarThemeDark ,
2023-03-17 19:44:07 +08:00
inputDecorationTheme: isDesktop
? InputDecorationTheme (
fillColor: Color ( 0xFF24252B ) ,
filled: true ,
isDense: true ,
2023-03-17 23:42:49 +08:00
border: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 8 ) ,
2023-03-17 19:44:07 +08:00
) ,
)
: null ,
2022-09-23 16:31:50 +08:00
textTheme: const TextTheme (
2023-05-20 21:12:52 +08:00
titleLarge: TextStyle ( fontSize: 19 ) ,
titleSmall: TextStyle ( fontSize: 14 ) ,
bodySmall: TextStyle ( fontSize: 12 , height: 1.25 ) ,
bodyMedium: TextStyle ( fontSize: 14 , height: 1.25 ) ,
labelLarge: TextStyle (
fontSize: 16.0 ,
fontWeight: FontWeight . bold ,
color: accent80 ,
) ,
) ,
2023-02-23 05:13:21 +08:00
cardColor: Color ( 0xFF24252B ) ,
2022-08-20 19:57:16 +08:00
visualDensity: VisualDensity . adaptivePlatformDensity ,
2022-09-04 11:03:16 +08:00
tabBarTheme: const TabBarTheme (
2022-08-20 19:57:16 +08:00
labelColor: Colors . white70 ,
) ,
2023-08-10 21:02:02 +08:00
tooltipTheme: tooltipTheme ( ) ,
2023-06-29 10:02:19 +08:00
splashColor: isDesktop ? Colors . transparent : null ,
highlightColor: isDesktop ? Colors . transparent : null ,
2022-09-23 12:20:40 +08:00
splashFactory: isDesktop ? NoSplash . splashFactory : null ,
textButtonTheme: isDesktop
? TextButtonThemeData (
2023-03-02 01:00:56 +08:00
style: TextButton . styleFrom (
2023-02-28 03:56:45 +08:00
splashFactory: NoSplash . splashFactory ,
2023-03-02 01:00:56 +08:00
disabledForegroundColor: Colors . white70 ,
foregroundColor: Colors . white70 ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 18.0 ) ,
2023-02-28 03:56:45 +08:00
) ,
) ,
2022-09-23 12:20:40 +08:00
)
2023-03-30 06:03:15 +08:00
: mobileTextButtonTheme ,
2023-02-28 03:56:45 +08:00
elevatedButtonTheme: ElevatedButtonThemeData (
2023-03-02 01:00:56 +08:00
style: ElevatedButton . styleFrom (
backgroundColor: MyTheme . accent ,
2023-03-10 21:12:28 +08:00
foregroundColor: Colors . white ,
2023-03-02 01:00:56 +08:00
disabledForegroundColor: Colors . white70 ,
disabledBackgroundColor: Colors . white10 ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8.0 ) ,
2023-03-01 21:50:50 +08:00
) ,
) ,
) ,
outlinedButtonTheme: OutlinedButtonThemeData (
2023-03-02 01:00:56 +08:00
style: OutlinedButton . styleFrom (
backgroundColor: Color ( 0xFF24252B ) ,
side: BorderSide ( color: Colors . white12 , width: 0.5 ) ,
disabledForegroundColor: Colors . white70 ,
foregroundColor: Colors . white70 ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8.0 ) ,
2023-02-28 03:56:45 +08:00
) ,
) ,
) ,
2023-06-06 20:53:05 +08:00
switchTheme: switchTheme ( ) ,
radioTheme: radioTheme ( ) ,
2023-03-30 06:03:15 +08:00
checkboxTheme: checkboxTheme ,
listTileTheme: listTileTheme ,
2023-03-11 19:29:26 +08:00
menuBarTheme: MenuBarThemeData (
style: MenuStyle (
backgroundColor: MaterialStatePropertyAll ( Color ( 0xFF121212 ) ) ) ) ,
2023-03-06 10:55:34 +08:00
colorScheme: ColorScheme . dark (
primary: Colors . blue ,
2023-03-10 21:12:28 +08:00
secondary: accent ,
2023-02-26 18:49:22 +08:00
background: Color ( 0xFF24252B ) ,
) ,
2022-08-20 19:57:16 +08:00
) . copyWith (
extensions: < ThemeExtension < dynamic > > [
ColorThemeExtension . dark ,
2022-09-04 11:03:16 +08:00
TabbarTheme . dark ,
2022-08-20 19:57:16 +08:00
] ,
) ;
2022-09-21 23:32:59 +08:00
static ThemeMode getThemeModePreference ( ) {
2022-11-10 21:25:12 +08:00
return themeModeFromString ( bind . mainGetLocalOption ( key: kCommConfKeyTheme ) ) ;
2022-09-21 23:32:59 +08:00
}
2023-02-02 14:03:50 +08:00
static void changeDarkMode ( ThemeMode mode ) async {
2022-12-03 16:32:22 +08:00
Get . changeThemeMode ( mode ) ;
2023-03-06 22:37:43 +08:00
if ( desktopType = = DesktopType . main | | isAndroid | | isIOS ) {
2022-09-21 23:32:59 +08:00
if ( mode = = ThemeMode . system ) {
2023-02-02 14:03:50 +08:00
await bind . mainSetLocalOption ( key: kCommConfKeyTheme , value: ' ' ) ;
2022-09-21 23:32:59 +08:00
} else {
2023-02-02 14:03:50 +08:00
await bind . mainSetLocalOption (
2022-11-10 21:25:12 +08:00
key: kCommConfKeyTheme , value: mode . toShortString ( ) ) ;
2022-09-21 23:32:59 +08:00
}
2023-02-02 14:03:50 +08:00
await bind . mainChangeTheme ( dark: mode . toShortString ( ) ) ;
// Synchronize the window theme of the system.
updateSystemWindowTheme ( ) ;
2023-02-02 13:57:20 +08:00
}
2022-09-06 22:34:01 +08:00
}
2022-09-21 23:32:59 +08:00
static ThemeMode currentThemeMode ( ) {
final preference = getThemeModePreference ( ) ;
if ( preference = = ThemeMode . system ) {
if ( WidgetsBinding . instance . platformDispatcher . platformBrightness = =
Brightness . light ) {
return ThemeMode . light ;
} else {
return ThemeMode . dark ;
}
2022-09-06 22:34:01 +08:00
} else {
2022-09-21 23:32:59 +08:00
return preference ;
2022-09-06 22:34:01 +08:00
}
2022-09-07 18:57:49 +08:00
}
2022-08-20 19:57:16 +08:00
static ColorThemeExtension color ( BuildContext context ) {
return Theme . of ( context ) . extension < ColorThemeExtension > ( ) ! ;
}
2022-09-04 11:03:16 +08:00
static TabbarTheme tabbar ( BuildContext context ) {
return Theme . of ( context ) . extension < TabbarTheme > ( ) ! ;
}
2022-09-21 23:32:59 +08:00
static ThemeMode themeModeFromString ( String v ) {
switch ( v ) {
case " light " :
return ThemeMode . light ;
case " dark " :
return ThemeMode . dark ;
default :
return ThemeMode . system ;
}
}
2022-07-29 16:47:24 +08:00
}
2022-09-21 23:32:59 +08:00
extension ParseToString on ThemeMode {
String toShortString ( ) {
return toString ( ) . split ( ' . ' ) . last ;
}
2020-11-16 01:13:26 +08:00
}
2021-08-02 20:54:56 +08:00
final ButtonStyle flatButtonStyle = TextButton . styleFrom (
2022-05-18 15:47:07 +08:00
minimumSize: Size ( 0 , 36 ) ,
padding: EdgeInsets . symmetric ( horizontal: 16.0 , vertical: 10.0 ) ,
2021-08-02 20:54:56 +08:00
shape: const RoundedRectangleBorder (
borderRadius: BorderRadius . all ( Radius . circular ( 2.0 ) ) ,
) ,
) ;
2022-09-19 15:46:09 +08:00
List < Locale > supportedLocales = const [
2023-03-09 06:30:39 +08:00
Locale ( ' en ' , ' US ' ) ,
2022-09-19 15:46:09 +08:00
Locale ( ' zh ' , ' CN ' ) ,
Locale ( ' zh ' , ' TW ' ) ,
2022-09-19 16:06:03 +08:00
Locale ( ' zh ' , ' SG ' ) ,
2022-09-19 15:46:09 +08:00
Locale ( ' fr ' ) ,
Locale ( ' de ' ) ,
Locale ( ' it ' ) ,
Locale ( ' ja ' ) ,
Locale ( ' cs ' ) ,
Locale ( ' pl ' ) ,
Locale ( ' ko ' ) ,
Locale ( ' hu ' ) ,
Locale ( ' pt ' ) ,
Locale ( ' ru ' ) ,
Locale ( ' sk ' ) ,
Locale ( ' id ' ) ,
Locale ( ' da ' ) ,
Locale ( ' eo ' ) ,
Locale ( ' tr ' ) ,
Locale ( ' vi ' ) ,
Locale ( ' pl ' ) ,
Locale ( ' kz ' ) ,
2023-03-09 06:30:39 +08:00
Locale ( ' es ' ) ,
2022-09-19 15:46:09 +08:00
] ;
2022-08-18 00:34:04 +08:00
String formatDurationToTime ( Duration duration ) {
var totalTime = duration . inSeconds ;
final secs = totalTime % 60 ;
totalTime = ( totalTime - secs ) ~ / 60 ;
final mins = totalTime % 60 ;
totalTime = ( totalTime - mins ) ~ / 60 ;
return " ${ totalTime . toString ( ) . padLeft ( 2 , " 0 " ) } : ${ mins . toString ( ) . padLeft ( 2 , " 0 " ) } : ${ secs . toString ( ) . padLeft ( 2 , " 0 " ) } " ;
}
2022-08-16 21:27:21 +08:00
closeConnection ( { String ? id } ) {
2022-08-12 18:42:02 +08:00
if ( isAndroid | | isIOS ) {
2023-05-29 09:46:16 +08:00
gFFI . chatModel . hideChatOverlay ( ) ;
2022-08-12 18:42:02 +08:00
Navigator . popUntil ( globalKey . currentContext ! , ModalRoute . withName ( " / " ) ) ;
} else {
2022-08-24 20:56:42 +08:00
final controller = Get . find < DesktopTabController > ( ) ;
controller . closeBy ( id ) ;
2022-08-12 18:42:02 +08:00
}
2022-03-13 23:07:52 +08:00
}
2023-10-12 23:11:11 +08:00
Future < void > windowOnTop ( int ? id ) async {
2023-02-16 10:58:27 +08:00
if ( ! isDesktop ) {
return ;
}
2023-08-11 15:53:47 +08:00
print ( " Bring window ' $ id ' on top " ) ;
2022-08-09 19:32:19 +08:00
if ( id = = null ) {
// main window
2023-08-11 19:03:09 +08:00
if ( stateGlobal . isMinimized ) {
2023-08-11 15:53:47 +08:00
await windowManager . restore ( ) ;
}
await windowManager . show ( ) ;
await windowManager . focus ( ) ;
await rustDeskWinManager . registerActiveWindow ( kWindowMainId ) ;
2022-08-09 19:32:19 +08:00
} else {
2022-08-09 21:12:55 +08:00
WindowController . fromWindowId ( id )
. . focus ( )
. . show ( ) ;
2022-11-05 23:41:22 +08:00
rustDeskWinManager . call ( WindowType . Main , kWindowEventShow , { " id " : id } ) ;
2022-08-09 19:32:19 +08:00
}
2022-08-09 09:01:06 +08:00
}
2022-03-13 00:32:44 +08:00
typedef DialogBuilder = CustomAlertDialog Function (
2023-05-08 12:34:19 +08:00
StateSetter setState , void Function ( [ dynamic ] ) close , BuildContext context ) ;
2022-03-13 00:32:44 +08:00
2022-08-12 18:42:02 +08:00
class Dialog < T > {
OverlayEntry ? entry ;
2022-08-15 19:31:58 +08:00
Completer < T ? > completer = Completer < T ? > ( ) ;
2022-08-12 18:42:02 +08:00
Dialog ( ) ;
void complete ( T ? res ) {
try {
if ( ! completer . isCompleted ) {
completer . complete ( res ) ;
}
} catch ( e ) {
debugPrint ( " Dialog complete catch error: $ e " ) ;
2022-08-15 19:31:58 +08:00
} finally {
entry ? . remove ( ) ;
2022-08-12 18:42:02 +08:00
}
}
}
2023-02-08 21:01:15 +08:00
class OverlayKeyState {
final _overlayKey = GlobalKey < OverlayState > ( ) ;
/// use global overlay by default
OverlayState ? get state = >
_overlayKey . currentState ? ? globalKey . currentState ? . overlay ;
GlobalKey < OverlayState > ? get key = > _overlayKey ;
}
2022-08-12 18:42:02 +08:00
class OverlayDialogManager {
2022-09-08 22:18:02 +08:00
final Map < String , Dialog > _dialogs = { } ;
2023-02-08 21:01:15 +08:00
var _overlayKeyState = OverlayKeyState ( ) ;
2022-08-12 18:42:02 +08:00
int _tagCount = 0 ;
2022-03-13 00:32:44 +08:00
2022-09-08 22:18:02 +08:00
OverlayEntry ? _mobileActionsOverlayEntry ;
2023-09-04 14:17:54 +08:00
RxBool mobileActionsOverlayVisible = false . obs ;
2022-09-08 22:18:02 +08:00
2023-02-08 21:01:15 +08:00
void setOverlayState ( OverlayKeyState overlayKeyState ) {
_overlayKeyState = overlayKeyState ;
2022-08-12 18:42:02 +08:00
}
2023-06-08 16:42:57 +08:00
void dismissAll ( ) {
2022-08-12 18:42:02 +08:00
_dialogs . forEach ( ( key , value ) {
value . complete ( null ) ;
BackButtonInterceptor . removeByName ( key ) ;
} ) ;
_dialogs . clear ( ) ;
}
void dismissByTag ( String tag ) {
_dialogs [ tag ] ? . complete ( null ) ;
_dialogs . remove ( tag ) ;
BackButtonInterceptor . removeByName ( tag ) ;
}
Future < T ? > show < T > ( DialogBuilder builder ,
2022-04-20 23:43:19 +08:00
{ bool clickMaskDismiss = false ,
2022-08-03 22:03:31 +08:00
bool backDismiss = false ,
String ? tag ,
2022-08-12 18:42:02 +08:00
bool useAnimation = true ,
bool forceGlobal = false } ) {
final overlayState =
2023-02-08 21:01:15 +08:00
forceGlobal ? globalKey . currentState ? . overlay : _overlayKeyState . state ;
2022-08-12 18:42:02 +08:00
if ( overlayState = = null ) {
return Future . error (
" [OverlayDialogManager] Failed to show dialog, _overlayState is null, call [setOverlayState] first " ) ;
}
2022-10-09 18:57:38 +08:00
final String dialogTag ;
2022-04-19 13:07:45 +08:00
if ( tag ! = null ) {
2022-10-09 18:57:38 +08:00
dialogTag = tag ;
2022-04-19 13:07:45 +08:00
} else {
2022-10-09 18:57:38 +08:00
dialogTag = _tagCount . toString ( ) ;
2022-08-12 18:42:02 +08:00
_tagCount + + ;
2022-04-19 13:07:45 +08:00
}
2022-08-12 18:42:02 +08:00
final dialog = Dialog < T > ( ) ;
2022-10-09 18:57:38 +08:00
_dialogs [ dialogTag ] = dialog ;
2022-08-12 18:42:02 +08:00
2022-10-09 18:57:38 +08:00
close ( [ res ] ) {
_dialogs . remove ( dialogTag ) ;
2022-08-12 18:42:02 +08:00
dialog . complete ( res ) ;
2022-10-09 18:57:38 +08:00
BackButtonInterceptor . removeByName ( dialogTag ) ;
}
2023-02-28 21:08:55 +08:00
dialog . entry = OverlayEntry ( builder: ( context ) {
2022-08-15 14:39:31 +08:00
bool innerClicked = false ;
return Listener (
onPointerUp: ( _ ) {
if ( ! innerClicked & & clickMaskDismiss ) {
close ( ) ;
}
innerClicked = false ;
} ,
child: Container (
2023-02-28 21:08:55 +08:00
color: Theme . of ( context ) . brightness = = Brightness . light
? Colors . black12
: Colors . black45 ,
2022-08-15 14:39:31 +08:00
child: StatefulBuilder ( builder: ( context , setState ) {
return Listener (
onPointerUp: ( _ ) = > innerClicked = true ,
2023-05-08 12:34:19 +08:00
child: builder ( setState , close , overlayState . context ) ,
2022-08-15 14:39:31 +08:00
) ;
} ) ) ) ;
2022-08-12 18:42:02 +08:00
} ) ;
overlayState . insert ( dialog . entry ! ) ;
BackButtonInterceptor . add ( ( stopDefaultButtonEvent , routeInfo ) {
if ( backDismiss ) {
close ( ) ;
}
return true ;
2022-10-09 18:57:38 +08:00
} , name: dialogTag ) ;
2022-08-12 18:42:02 +08:00
return dialog . completer . future ;
}
2022-10-09 18:57:38 +08:00
String showLoading ( String text ,
2022-08-15 14:39:31 +08:00
{ bool clickMaskDismiss = false ,
bool showCancel = true ,
2023-08-05 21:40:15 +08:00
VoidCallback ? onCancel ,
String ? tag } ) {
if ( tag = = null ) {
tag = _tagCount . toString ( ) ;
_tagCount + + ;
}
2023-05-08 12:34:19 +08:00
show ( ( setState , close , context ) {
2022-09-03 18:19:50 +08:00
cancel ( ) {
dismissAll ( ) ;
if ( onCancel ! = null ) {
onCancel ( ) ;
}
}
return CustomAlertDialog (
2022-08-12 18:42:02 +08:00
content: Container (
2022-09-03 18:19:50 +08:00
constraints: const BoxConstraints ( maxWidth: 240 ) ,
2022-08-12 18:42:02 +08:00
child: Column (
mainAxisSize: MainAxisSize . min ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
2022-09-03 18:19:50 +08:00
const SizedBox ( height: 30 ) ,
const Center ( child: CircularProgressIndicator ( ) ) ,
const SizedBox ( height: 20 ) ,
2022-08-12 18:42:02 +08:00
Center (
child: Text ( translate ( text ) ,
2022-09-03 18:19:50 +08:00
style: const TextStyle ( fontSize: 15 ) ) ) ,
const SizedBox ( height: 20 ) ,
2022-08-15 14:39:31 +08:00
Offstage (
offstage: ! showCancel ,
child: Center (
2023-02-15 23:41:34 +08:00
child: isDesktop
? dialogButton ( ' Cancel ' , onPressed: cancel )
: TextButton (
style: flatButtonStyle ,
onPressed: cancel ,
child: Text ( translate ( ' Cancel ' ) ,
style: const TextStyle (
color: MyTheme . accent ) ) ) ) )
2022-09-03 18:19:50 +08:00
] ) ) ,
onCancel: showCancel ? cancel : null ,
) ;
2022-10-09 18:57:38 +08:00
} , tag: tag ) ;
return tag ;
2022-08-12 18:42:02 +08:00
}
2022-09-08 22:18:02 +08:00
void resetMobileActionsOverlay ( { FFI ? ffi } ) {
if ( _mobileActionsOverlayEntry = = null ) return ;
hideMobileActionsOverlay ( ) ;
showMobileActionsOverlay ( ffi: ffi ) ;
}
void showMobileActionsOverlay ( { FFI ? ffi } ) {
if ( _mobileActionsOverlayEntry ! = null ) return ;
2023-02-08 21:01:15 +08:00
final overlayState = _overlayKeyState . state ;
if ( overlayState = = null ) return ;
2022-09-08 22:18:02 +08:00
// compute overlay position
final screenW = MediaQuery . of ( globalKey . currentContext ! ) . size . width ;
final screenH = MediaQuery . of ( globalKey . currentContext ! ) . size . height ;
const double overlayW = 200 ;
const double overlayH = 45 ;
final left = ( screenW - overlayW ) / 2 ;
final top = screenH - overlayH - 80 ;
final overlay = OverlayEntry ( builder: ( context ) {
final session = ffi ? ? gFFI ;
return DraggableMobileActions (
position: Offset ( left , top ) ,
width: overlayW ,
height: overlayH ,
2022-09-27 20:35:02 +08:00
onBackPressed: ( ) = > session . inputModel . tap ( MouseButtons . right ) ,
onHomePressed: ( ) = > session . inputModel . tap ( MouseButtons . wheel ) ,
2022-09-08 22:18:02 +08:00
onRecentPressed: ( ) async {
2022-09-27 20:35:02 +08:00
session . inputModel . sendMouse ( ' down ' , MouseButtons . wheel ) ;
2022-09-08 22:18:02 +08:00
await Future . delayed ( const Duration ( milliseconds: 500 ) ) ;
2022-09-27 20:35:02 +08:00
session . inputModel . sendMouse ( ' up ' , MouseButtons . wheel ) ;
2022-09-08 22:18:02 +08:00
} ,
onHidePressed: ( ) = > hideMobileActionsOverlay ( ) ,
) ;
} ) ;
2023-02-08 21:01:15 +08:00
overlayState . insert ( overlay ) ;
2022-09-08 22:18:02 +08:00
_mobileActionsOverlayEntry = overlay ;
2023-09-04 14:17:54 +08:00
mobileActionsOverlayVisible . value = true ;
2022-09-08 22:18:02 +08:00
}
void hideMobileActionsOverlay ( ) {
if ( _mobileActionsOverlayEntry ! = null ) {
_mobileActionsOverlayEntry ! . remove ( ) ;
_mobileActionsOverlayEntry = null ;
2023-09-04 14:17:54 +08:00
mobileActionsOverlayVisible . value = false ;
2022-09-08 22:18:02 +08:00
return ;
}
}
void toggleMobileActionsOverlay ( { FFI ? ffi } ) {
if ( _mobileActionsOverlayEntry = = null ) {
showMobileActionsOverlay ( ffi: ffi ) ;
} else {
hideMobileActionsOverlay ( ) ;
}
}
2023-01-12 21:03:05 +08:00
bool existing ( String tag ) {
return _dialogs . keys . contains ( tag ) ;
}
2022-08-15 14:39:31 +08:00
}
2022-08-12 18:42:02 +08:00
2022-08-15 14:39:31 +08:00
void showToast ( String text , { Duration timeout = const Duration ( seconds: 2 ) } ) {
final overlayState = globalKey . currentState ? . overlay ;
if ( overlayState = = null ) return ;
final entry = OverlayEntry ( builder: ( _ ) {
return IgnorePointer (
child: Align (
2022-09-03 18:19:50 +08:00
alignment: const Alignment ( 0.0 , 0.8 ) ,
2022-08-15 14:39:31 +08:00
child: Container (
decoration: BoxDecoration (
color: Colors . black . withOpacity ( 0.6 ) ,
2022-09-03 18:19:50 +08:00
borderRadius: const BorderRadius . all (
2022-08-15 14:39:31 +08:00
Radius . circular ( 20 ) ,
) ,
) ,
2022-09-03 18:19:50 +08:00
padding: const EdgeInsets . symmetric ( horizontal: 20 , vertical: 5 ) ,
2022-08-15 14:39:31 +08:00
child: Text (
text ,
2023-04-19 14:39:22 +08:00
textAlign: TextAlign . center ,
2022-09-03 18:19:50 +08:00
style: const TextStyle (
2022-08-15 14:39:31 +08:00
decoration: TextDecoration . none ,
fontWeight: FontWeight . w300 ,
fontSize: 18 ,
color: Colors . white ) ,
) ,
) ) ) ;
} ) ;
overlayState . insert ( entry ) ;
Future . delayed ( timeout , ( ) {
entry . remove ( ) ;
} ) ;
2022-02-28 16:11:21 +08:00
}
2022-02-02 17:25:56 +08:00
2023-03-29 22:14:55 +08:00
// TODO
// - Remove argument "contentPadding", no need for it, all should look the same.
// - Remove "required" for argument "content". See simple confirm dialog "delete peer", only title and actions are used. No need to "content: SizedBox.shrink()".
// - Make dead code alive, transform arguments "onSubmit" and "onCancel" into correspondenting buttons "ConfirmOkButton", "CancelButton".
2022-03-13 00:32:44 +08:00
class CustomAlertDialog extends StatelessWidget {
2022-09-03 18:19:50 +08:00
const CustomAlertDialog (
{ Key ? key ,
this . title ,
2023-07-01 12:54:19 +08:00
this . titlePadding ,
2022-09-03 18:19:50 +08:00
required this . content ,
this . actions ,
this . contentPadding ,
2022-09-29 21:09:40 +08:00
this . contentBoxConstraints = const BoxConstraints ( maxWidth: 500 ) ,
2022-09-03 18:19:50 +08:00
this . onSubmit ,
this . onCancel } )
: super ( key: key ) ;
2020-11-18 12:49:43 +08:00
2022-08-12 18:42:02 +08:00
final Widget ? title ;
2023-07-01 12:54:19 +08:00
final EdgeInsetsGeometry ? titlePadding ;
2022-03-13 00:32:44 +08:00
final Widget content ;
2022-08-12 18:42:02 +08:00
final List < Widget > ? actions ;
2022-03-13 00:32:44 +08:00
final double ? contentPadding ;
2022-09-29 21:09:40 +08:00
final BoxConstraints contentBoxConstraints ;
2022-09-03 18:19:50 +08:00
final Function ( ) ? onSubmit ;
final Function ( ) ? onCancel ;
2022-03-13 00:32:44 +08:00
@ override
Widget build ( BuildContext context ) {
2023-01-28 17:10:40 +08:00
// request focus
FocusScopeNode scopeNode = FocusScopeNode ( ) ;
2022-09-03 18:19:50 +08:00
Future . delayed ( Duration . zero , ( ) {
2023-01-28 17:10:40 +08:00
if ( ! scopeNode . hasFocus ) scopeNode . requestFocus ( ) ;
2022-09-03 18:19:50 +08:00
} ) ;
2023-02-16 15:16:54 +08:00
bool tabTapped = false ;
2023-06-07 22:46:20 +08:00
if ( isAndroid ) gFFI . invokeMethod ( " enable_soft_keyboard " , true ) ;
2023-03-29 22:14:55 +08:00
2023-01-16 20:58:42 +08:00
return FocusScope (
node: scopeNode ,
2022-09-03 18:19:50 +08:00
autofocus: true ,
onKey: ( node , key ) {
if ( key . logicalKey = = LogicalKeyboardKey . escape ) {
if ( key is RawKeyDownEvent ) {
onCancel ? . call ( ) ;
}
return KeyEventResult . handled ; // avoid TextField exception on escape
2023-02-16 15:16:54 +08:00
} else if ( ! tabTapped & &
onSubmit ! = null & &
2022-09-03 18:19:50 +08:00
key . logicalKey = = LogicalKeyboardKey . enter ) {
if ( key is RawKeyDownEvent ) onSubmit ? . call ( ) ;
return KeyEventResult . handled ;
2023-01-16 20:58:42 +08:00
} else if ( key . logicalKey = = LogicalKeyboardKey . tab ) {
if ( key is RawKeyDownEvent ) {
scopeNode . nextFocus ( ) ;
2023-02-16 15:16:54 +08:00
tabTapped = true ;
2023-01-16 20:58:42 +08:00
}
return KeyEventResult . handled ;
2022-09-03 18:19:50 +08:00
}
return KeyEventResult . ignored ;
} ,
child: AlertDialog (
2023-03-29 22:14:55 +08:00
scrollable: true ,
title: title ,
content: ConstrainedBox (
constraints: contentBoxConstraints ,
child: content ,
) ,
actions: actions ,
2023-07-01 12:54:19 +08:00
titlePadding: titlePadding ? ? MyTheme . dialogTitlePadding ( ) ,
2023-03-29 22:14:55 +08:00
contentPadding:
MyTheme . dialogContentPadding ( actions: actions is List ) ,
actionsPadding: MyTheme . dialogActionsPadding ( ) ,
buttonPadding: MyTheme . dialogButtonPadding ) ,
2022-04-21 10:02:47 +08:00
) ;
2022-03-13 00:32:44 +08:00
}
2020-11-16 22:00:09 +08:00
}
2020-11-16 22:12:32 +08:00
2023-06-06 07:39:44 +08:00
void msgBox ( SessionID sessionId , String type , String title , String text ,
String link , OverlayDialogManager dialogManager ,
2023-10-21 15:25:01 +08:00
{ bool ? hasCancel , ReconnectHandle ? reconnect , int ? reconnectTimeout } ) {
2022-08-12 18:42:02 +08:00
dialogManager . dismissAll ( ) ;
2022-08-08 22:00:01 +08:00
List < Widget > buttons = [ ] ;
2022-09-03 18:19:50 +08:00
bool hasOk = false ;
submit ( ) {
dialogManager . dismissAll ( ) ;
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
2022-09-08 17:22:24 +08:00
if ( ! type . contains ( " custom " ) & & desktopType ! = DesktopType . portForward ) {
2022-09-03 18:19:50 +08:00
closeConnection ( ) ;
}
}
cancel ( ) {
dialogManager . dismissAll ( ) ;
}
2022-10-14 11:19:49 +08:00
jumplink ( ) {
if ( link . startsWith ( ' http ' ) ) {
launchUrl ( Uri . parse ( link ) ) ;
}
}
2022-08-31 18:41:55 +08:00
if ( type ! = " connecting " & & type ! = " success " & & ! type . contains ( " nook " ) ) {
2022-09-03 18:19:50 +08:00
hasOk = true ;
2023-01-15 19:46:16 +08:00
buttons . insert ( 0 , dialogButton ( ' OK ' , onPressed: submit ) ) ;
2022-08-08 22:00:01 +08:00
}
2022-08-31 18:41:55 +08:00
hasCancel ? ? = ! type . contains ( " error " ) & &
! type . contains ( " nocancel " ) & &
type ! = " restarting " ;
2020-11-29 01:36:10 +08:00
if ( hasCancel ) {
2023-01-15 19:46:16 +08:00
buttons . insert (
0 , dialogButton ( ' Cancel ' , onPressed: cancel , isOutline: true ) ) ;
2020-11-29 01:36:10 +08:00
}
2022-08-31 18:41:55 +08:00
if ( type . contains ( " hasclose " ) ) {
2022-08-08 22:00:01 +08:00
buttons . insert (
0 ,
2023-01-15 19:46:16 +08:00
dialogButton ( ' Close ' , onPressed: ( ) {
2022-08-12 18:42:02 +08:00
dialogManager . dismissAll ( ) ;
2022-08-08 22:00:01 +08:00
} ) ) ;
}
2023-10-21 15:25:01 +08:00
if ( reconnect ! = null & &
title = = " Connection Error " & &
reconnectTimeout ! = null ) {
2023-09-30 14:21:11 +08:00
// `enabled` is used to disable the dialog button once the button is clicked.
2023-09-29 21:42:49 +08:00
final enabled = true . obs ;
2023-10-21 15:25:01 +08:00
final button = Obx ( ( ) = > _ReconnectCountDownButton (
second: reconnectTimeout ,
onPressed: enabled . isTrue
? ( ) {
// Disable the button
enabled . value = false ;
reconnect ( dialogManager , sessionId , false ) ;
}
: null ,
) ) ;
2023-09-29 21:42:49 +08:00
buttons . insert ( 0 , button ) ;
2023-02-16 14:54:13 +08:00
}
2022-10-14 11:19:49 +08:00
if ( link . isNotEmpty ) {
2023-01-15 19:46:16 +08:00
buttons . insert ( 0 , dialogButton ( ' JumpLink ' , onPressed: jumplink ) ) ;
2022-10-14 11:19:49 +08:00
}
2022-11-15 16:49:55 +08:00
dialogManager . show (
2023-05-08 12:34:19 +08:00
( setState , close , context ) = > CustomAlertDialog (
2023-01-30 17:56:35 +08:00
title: null ,
2023-02-05 16:56:13 +08:00
content: SelectionArea ( child: msgboxContent ( type , title , text ) ) ,
2022-11-15 16:49:55 +08:00
actions: buttons ,
onSubmit: hasOk ? submit : null ,
onCancel: hasCancel = = true ? cancel : null ,
) ,
2023-06-06 07:39:44 +08:00
tag: ' $ sessionId - $ type - $ title - $ text - $ link ' ,
2022-11-15 16:49:55 +08:00
) ;
2020-11-21 14:40:28 +08:00
}
2020-11-25 18:33:09 +08:00
2023-01-30 17:56:35 +08:00
Color ? _msgboxColor ( String type ) {
if ( type = = " input-password " | | type = = " custom-os-password " ) {
return Color ( 0xFFAD448E ) ;
}
if ( type . contains ( " success " ) ) {
return Color ( 0xFF32bea6 ) ;
}
if ( type . contains ( " error " ) | | type = = " re-input-password " ) {
return Color ( 0xFFE04F5F ) ;
}
return Color ( 0xFF2C8CFF ) ;
}
Widget msgboxIcon ( String type ) {
IconData ? iconData ;
if ( type . contains ( " error " ) | | type = = " re-input-password " ) {
iconData = Icons . cancel ;
}
if ( type . contains ( " success " ) ) {
iconData = Icons . check_circle ;
}
if ( type = = " wait-uac " | | type = = " wait-remote-accept-nook " ) {
iconData = Icons . hourglass_top ;
}
if ( type = = ' on-uac ' | | type = = ' on-foreground-elevated ' ) {
iconData = Icons . admin_panel_settings ;
}
2023-10-08 21:44:54 +08:00
if ( type . contains ( ' info ' ) ) {
2023-01-30 17:56:35 +08:00
iconData = Icons . info ;
}
if ( iconData ! = null ) {
return Icon ( iconData , size: 50 , color: _msgboxColor ( type ) )
. marginOnly ( right: 16 ) ;
}
return Offstage ( ) ;
2022-08-31 18:41:55 +08:00
}
2023-01-30 17:56:35 +08:00
// title should be null
Widget msgboxContent ( String type , String title , String text ) {
2023-06-29 13:47:55 +08:00
String translateText ( String text ) {
2023-06-29 22:48:51 +08:00
if ( text . indexOf ( ' Failed ' ) = = 0 & & text . indexOf ( ' : ' ) > 0 ) {
2023-06-29 22:25:01 +08:00
List < String > words = text . split ( ' : ' ) ;
for ( var i = 0 ; i < words . length ; + + i ) {
words [ i ] = translate ( words [ i ] ) ;
}
text = words . join ( ' : ' ) ;
2023-06-29 13:47:55 +08:00
} else {
2023-06-29 22:44:15 +08:00
List < String > words = text . split ( ' ' ) ;
if ( words . length > 1 & & words [ 0 ] . endsWith ( ' _tip ' ) ) {
words [ 0 ] = translate ( words [ 0 ] ) ;
final rest = text . substring ( words [ 0 ] . length + 1 ) ;
text = ' ${ words [ 0 ] } ${ translate ( rest ) } ' ;
} else {
text = translate ( text ) ;
}
2023-06-29 13:47:55 +08:00
}
2023-06-29 22:25:01 +08:00
return text ;
2023-06-29 13:47:55 +08:00
}
2023-01-30 17:56:35 +08:00
return Row (
children: [
msgboxIcon ( type ) ,
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
translate ( title ) ,
style: TextStyle ( fontSize: 21 ) ,
) . marginOnly ( bottom: 10 ) ,
2023-06-29 13:47:55 +08:00
Text ( translateText ( text ) , style: const TextStyle ( fontSize: 15 ) ) ,
2023-01-30 17:56:35 +08:00
] ,
) ,
) ,
] ,
2023-02-05 16:56:13 +08:00
) . marginOnly ( bottom: 12 ) ;
2023-01-30 17:56:35 +08:00
}
2022-09-01 14:02:02 +08:00
2022-08-31 18:41:55 +08:00
void msgBoxCommon ( OverlayDialogManager dialogManager , String title ,
2022-09-03 18:19:50 +08:00
Widget content , List < Widget > buttons ,
{ bool hasCancel = true } ) {
2023-05-08 12:34:19 +08:00
dialogManager . show ( ( setState , close , context ) = > CustomAlertDialog (
2023-01-30 17:56:35 +08:00
title: Text (
translate ( title ) ,
style: TextStyle ( fontSize: 21 ) ,
) ,
2022-09-03 18:19:50 +08:00
content: content ,
actions: buttons ,
onCancel: hasCancel ? close : null ,
) ) ;
2022-08-31 18:41:55 +08:00
}
2020-11-25 18:33:09 +08:00
Color str2color ( String str , [ alpha = 0xFF ] ) {
var hash = 160 < < 16 + 114 < < 8 + 91 ;
for ( var i = 0 ; i < str . length ; i + = 1 ) {
hash = str . codeUnitAt ( i ) + ( ( hash < < 5 ) - hash ) ;
}
2021-08-14 14:14:01 +08:00
hash = hash % 16777216 ;
return Color ( ( hash & 0xFF7FFF ) | ( alpha < < 24 ) ) ;
2020-11-25 18:33:09 +08:00
}
2022-02-02 17:25:56 +08:00
2023-08-22 19:07:01 +08:00
Color str2color2 ( String str , { List < int > existing = const [ ] } ) {
2023-08-15 09:52:02 +08:00
Map < String , Color > colorMap = {
" red " : Colors . red ,
" green " : Colors . green ,
" blue " : Colors . blue ,
" orange " : Colors . orange ,
" purple " : Colors . purple ,
" grey " : Colors . grey ,
" cyan " : Colors . cyan ,
" lime " : Colors . lime ,
" teal " : Colors . teal ,
" pink " : Colors . pink [ 200 ] ! ,
" indigo " : Colors . indigo ,
" brown " : Colors . brown ,
} ;
final color = colorMap [ str . toLowerCase ( ) ] ;
if ( color ! = null ) {
2023-08-22 19:07:01 +08:00
return color . withAlpha ( 0xFF ) ;
2023-08-15 09:52:02 +08:00
}
if ( str . toLowerCase ( ) = = ' yellow ' ) {
2023-08-22 19:07:01 +08:00
return Colors . yellow . withAlpha ( 0xFF ) ;
2023-08-15 09:52:02 +08:00
}
2023-08-13 18:13:06 +08:00
var hash = 0 ;
for ( var i = 0 ; i < str . length ; i + + ) {
hash + = str . codeUnitAt ( i ) ;
}
2023-08-15 09:52:02 +08:00
List < Color > colorList = colorMap . values . toList ( ) ;
2023-08-13 18:13:06 +08:00
hash = hash % colorList . length ;
2023-08-22 19:07:01 +08:00
var result = colorList [ hash ] . withAlpha ( 0xFF ) ;
if ( existing . contains ( result . value ) ) {
Color ? notUsed =
colorList . firstWhereOrNull ( ( e ) = > ! existing . contains ( e . value ) ) ;
if ( notUsed ! = null ) {
result = notUsed ;
}
}
return result ;
2023-08-13 18:13:06 +08:00
}
2022-03-17 21:03:52 +08:00
const K = 1024 ;
const M = K * K ;
const G = M * K ;
String readableFileSize ( double size ) {
if ( size < K ) {
2022-08-31 18:41:55 +08:00
return " ${ size . toStringAsFixed ( 2 ) } B " ;
2022-03-17 21:03:52 +08:00
} else if ( size < M ) {
2022-08-31 18:41:55 +08:00
return " ${ ( size / K ) . toStringAsFixed ( 2 ) } KB " ;
2022-03-17 21:03:52 +08:00
} else if ( size < G ) {
2022-08-31 18:41:55 +08:00
return " ${ ( size / M ) . toStringAsFixed ( 2 ) } MB " ;
2022-03-17 21:03:52 +08:00
} else {
2022-08-31 18:41:55 +08:00
return " ${ ( size / G ) . toStringAsFixed ( 2 ) } GB " ;
2022-03-17 21:03:52 +08:00
}
}
2022-04-14 15:37:47 +08:00
/// Flutter can't not catch PointerMoveEvent when size is 1
/// This will happen in Android AccessibilityService Input
/// android can't init dispatching size yet ,see: https://stackoverflow.com/questions/59960451/android-accessibility-dispatchgesture-is-it-possible-to-specify-pressure-for-a
/// use this temporary solution until flutter or android fixes the bug
class AccessibilityListener extends StatelessWidget {
final Widget ? child ;
static final offset = 100 ;
AccessibilityListener ( { this . child } ) ;
@ override
Widget build ( BuildContext context ) {
return Listener (
onPointerDown: ( evt ) {
2022-05-26 18:25:16 +08:00
if ( evt . size = = 1 ) {
GestureBinding . instance . handlePointerEvent ( PointerAddedEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset , position: evt . position ) ) ;
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerDownEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
position: evt . position ) ) ;
}
} ,
onPointerUp: ( evt ) {
2022-06-20 00:15:37 +08:00
if ( evt . size = = 1 ) {
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerUpEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
position: evt . position ) ) ;
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerRemovedEvent (
2022-04-15 17:45:48 +08:00
pointer: evt . pointer + offset , position: evt . position ) ) ;
2022-04-14 15:37:47 +08:00
}
} ,
onPointerMove: ( evt ) {
2022-06-20 00:15:37 +08:00
if ( evt . size = = 1 ) {
2022-05-26 18:25:16 +08:00
GestureBinding . instance . handlePointerEvent ( PointerMoveEvent (
2022-04-14 15:37:47 +08:00
pointer: evt . pointer + offset ,
size: 0.1 ,
delta: evt . delta ,
position: evt . position ) ) ;
}
} ,
child: child ) ;
}
}
2022-04-20 22:37:47 +08:00
2023-03-02 01:00:56 +08:00
class AndroidPermissionManager {
2022-04-20 22:37:47 +08:00
static Completer < bool > ? _completer ;
static Timer ? _timer ;
static var _current = " " ;
static bool isWaitingFile ( ) {
if ( _completer ! = null ) {
2023-03-02 01:00:56 +08:00
return ! _completer ! . isCompleted & & _current = = kManageExternalStorage ;
2022-04-20 22:37:47 +08:00
}
return false ;
}
static Future < bool > check ( String type ) {
2022-08-18 00:34:04 +08:00
if ( isDesktop ) {
return Future . value ( true ) ;
}
2022-06-13 21:07:26 +08:00
return gFFI . invokeMethod ( " check_permission " , type ) ;
2022-04-20 22:37:47 +08:00
}
2023-03-02 01:00:56 +08:00
// startActivity goto Android Setting's page to request permission manually by user
static void startAction ( String action ) {
gFFI . invokeMethod ( AndroidChannel . kStartAction , action ) ;
}
/// We use XXPermissions to request permissions,
/// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java
2022-04-20 22:37:47 +08:00
static Future < bool > request ( String type ) {
2022-08-18 00:34:04 +08:00
if ( isDesktop ) {
return Future . value ( true ) ;
}
2022-04-20 22:37:47 +08:00
2022-08-01 14:33:08 +08:00
gFFI . invokeMethod ( " request_permission " , type ) ;
2023-03-02 01:00:56 +08:00
// clear last task
if ( _completer ? . isCompleted = = false ) {
_completer ? . complete ( false ) ;
2022-07-14 17:44:37 +08:00
}
2023-03-02 01:00:56 +08:00
_timer ? . cancel ( ) ;
2022-04-20 22:37:47 +08:00
_current = type ;
_completer = Completer < bool > ( ) ;
2023-03-02 01:00:56 +08:00
_timer = Timer ( Duration ( seconds: 120 ) , ( ) {
2022-04-20 22:37:47 +08:00
if ( _completer = = null ) return ;
if ( ! _completer ! . isCompleted ) {
_completer ! . complete ( false ) ;
}
_completer = null ;
_current = " " ;
} ) ;
return _completer ! . future ;
}
static complete ( String type , bool res ) {
if ( type ! = _current ) {
res = false ;
}
_timer ? . cancel ( ) ;
_completer ? . complete ( res ) ;
_current = " " ;
}
}
2022-06-13 21:07:26 +08:00
2023-03-30 08:33:04 +08:00
// TODO move this to mobile/widgets.
// Used only for mobile, pages remote, settings, dialog
// TODO remove argument contentPadding, it’ s not used, getToggle() has not
2022-07-29 18:34:25 +08:00
RadioListTile < T > getRadio < T > (
2023-04-12 09:41:13 +08:00
Widget title , T toValue , T curValue , ValueChanged < T ? > ? onChange ,
2023-06-11 16:32:22 +08:00
{ EdgeInsetsGeometry ? contentPadding , bool ? dense } ) {
2022-07-29 18:34:25 +08:00
return RadioListTile < T > (
2023-03-30 08:33:04 +08:00
contentPadding: contentPadding ? ? EdgeInsets . zero ,
visualDensity: VisualDensity . compact ,
2022-07-29 18:34:25 +08:00
controlAffinity: ListTileControlAffinity . trailing ,
2023-04-12 09:41:13 +08:00
title: title ,
2022-07-29 18:34:25 +08:00
value: toValue ,
groupValue: curValue ,
onChanged: onChange ,
2023-06-11 16:32:22 +08:00
dense: dense ,
2022-07-29 18:34:25 +08:00
) ;
}
2022-08-01 10:44:05 +08:00
2022-06-13 21:07:26 +08:00
/// find ffi, tag is Remote ID
/// for session specific usage
FFI ffi ( String ? tag ) {
return Get . find < FFI > ( tag: tag ) ;
}
/// Global FFI object
late FFI _globalFFI ;
FFI get gFFI = > _globalFFI ;
Future < void > initGlobalFFI ( ) async {
2022-08-04 17:24:02 +08:00
debugPrint ( " _globalFFI init " ) ;
2023-08-03 23:14:40 +08:00
_globalFFI = FFI ( null ) ;
2022-08-04 17:24:02 +08:00
debugPrint ( " _globalFFI init end " ) ;
2022-06-13 21:07:26 +08:00
// after `put`, can also be globally found by Get.find<FFI>();
Get . put ( _globalFFI , permanent: true ) ;
2022-08-03 22:03:31 +08:00
}
2022-08-08 17:53:51 +08:00
String translate ( String name ) {
if ( name . startsWith ( ' Failed to ' ) & & name . contains ( ' : ' ) ) {
return name . split ( ' : ' ) . map ( ( x ) = > translate ( x ) ) . join ( ' : ' ) ;
}
return platformFFI . translate ( name , localeName ) ;
}
2022-08-13 12:43:35 +08:00
2022-08-15 11:08:42 +08:00
bool option2bool ( String option , String value ) {
2022-08-13 12:43:35 +08:00
bool res ;
2022-08-15 11:08:42 +08:00
if ( option . startsWith ( " enable- " ) ) {
2022-08-13 12:43:35 +08:00
res = value ! = " N " ;
2022-08-15 11:08:42 +08:00
} else if ( option . startsWith ( " allow- " ) | |
option = = " stop-service " | |
option = = " direct-server " | |
2022-11-28 18:16:29 +08:00
option = = " stop-rendezvous-service " | |
2023-08-08 12:12:35 +08:00
option = = kOptionForceAlwaysRelay ) {
2022-08-13 12:43:35 +08:00
res = value = = " Y " ;
} else {
assert ( false ) ;
res = value ! = " N " ;
}
return res ;
}
2022-08-15 11:08:42 +08:00
String bool2option ( String option , bool b ) {
2022-08-13 12:43:35 +08:00
String res ;
2022-08-15 11:08:42 +08:00
if ( option . startsWith ( ' enable- ' ) ) {
res = b ? ' ' : ' N ' ;
} else if ( option . startsWith ( ' allow- ' ) | |
option = = " stop-service " | |
option = = " direct-server " | |
2022-11-28 18:16:29 +08:00
option = = " stop-rendezvous-service " | |
2023-08-08 12:12:35 +08:00
option = = kOptionForceAlwaysRelay ) {
2022-08-15 11:08:42 +08:00
res = b ? ' Y ' : ' ' ;
2022-08-13 12:43:35 +08:00
} else {
assert ( false ) ;
2022-08-15 11:08:42 +08:00
res = b ? ' Y ' : ' N ' ;
2022-08-13 12:43:35 +08:00
}
return res ;
}
2022-08-27 00:45:09 +08:00
2023-07-23 17:07:30 +08:00
mainSetBoolOption ( String key , bool value ) async {
String v = bool2option ( key , value ) ;
await bind . mainSetOption ( key: key , value: v ) ;
}
Future < bool > mainGetBoolOption ( String key ) async {
return option2bool ( key , await bind . mainGetOption ( key: key ) ) ;
}
bool mainGetBoolOptionSync ( String key ) {
return option2bool ( key , bind . mainGetOptionSync ( key: key ) ) ;
}
2023-07-30 12:16:00 +08:00
mainSetLocalBoolOption ( String key , bool value ) async {
String v = bool2option ( key , value ) ;
await bind . mainSetLocalOption ( key: key , value: v ) ;
}
bool mainGetLocalBoolOptionSync ( String key ) {
return option2bool ( key , bind . mainGetLocalOption ( key: key ) ) ;
}
2023-08-08 12:12:35 +08:00
bool mainGetPeerBoolOptionSync ( String id , String key ) {
return option2bool ( key , bind . mainGetPeerOptionSync ( id: id , key: key ) ) ;
}
mainSetPeerBoolOptionSync ( String id , String key , bool v ) {
bind . mainSetPeerOptionSync ( id: id , key: key , value: bool2option ( key , v ) ) ;
}
2022-08-27 00:45:09 +08:00
Future < bool > matchPeer ( String searchText , Peer peer ) async {
if ( searchText . isEmpty ) {
return true ;
}
if ( peer . id . toLowerCase ( ) . contains ( searchText ) ) {
return true ;
}
if ( peer . hostname . toLowerCase ( ) . contains ( searchText ) | |
peer . username . toLowerCase ( ) . contains ( searchText ) ) {
return true ;
}
2023-08-17 17:20:08 +08:00
final alias = peer . alias ;
2022-08-27 00:45:09 +08:00
if ( alias . isEmpty ) {
return false ;
}
return alias . toLowerCase ( ) . contains ( searchText ) ;
}
2022-09-13 21:36:38 +08:00
/// Get the image for the current [platform].
Widget getPlatformImage ( String platform , { double size = 50 } ) {
2023-06-23 17:05:48 +08:00
if ( platform . isEmpty ) {
return Container ( width: size , height: size ) ;
}
2023-01-10 17:13:40 +08:00
if ( platform = = kPeerPlatformMacOS ) {
2022-09-13 21:36:38 +08:00
platform = ' mac ' ;
2023-01-10 17:13:40 +08:00
} else if ( platform ! = kPeerPlatformLinux & &
platform ! = kPeerPlatformAndroid ) {
2022-09-13 21:36:38 +08:00
platform = ' win ' ;
2023-01-11 18:41:45 +08:00
} else {
2023-01-12 21:03:05 +08:00
platform = platform . toLowerCase ( ) ;
2022-09-13 21:36:38 +08:00
}
2022-09-27 18:34:05 +08:00
return SvgPicture . asset ( ' assets/ $ platform .svg ' , height: size , width: size ) ;
2022-09-13 21:36:38 +08:00
}
2022-09-16 12:14:03 +08:00
class LastWindowPosition {
double ? width ;
double ? height ;
double ? offsetWidth ;
double ? offsetHeight ;
2022-10-14 18:48:41 +08:00
bool ? isMaximized ;
2023-09-07 13:39:20 +08:00
bool ? isFullscreen ;
2022-09-16 12:14:03 +08:00
2022-10-14 18:48:41 +08:00
LastWindowPosition ( this . width , this . height , this . offsetWidth ,
2023-09-07 13:39:20 +08:00
this . offsetHeight , this . isMaximized , this . isFullscreen ) ;
2022-09-16 12:14:03 +08:00
Map < String , dynamic > toJson ( ) {
return < String , dynamic > {
" width " : width ,
" height " : height ,
" offsetWidth " : offsetWidth ,
2022-10-14 18:48:41 +08:00
" offsetHeight " : offsetHeight ,
" isMaximized " : isMaximized ,
2023-09-07 13:39:20 +08:00
" isFullscreen " : isFullscreen ,
2022-09-16 12:14:03 +08:00
} ;
}
@ override
String toString ( ) {
return jsonEncode ( toJson ( ) ) ;
}
static LastWindowPosition ? loadFromString ( String content ) {
if ( content . isEmpty ) {
return null ;
}
try {
final m = jsonDecode ( content ) ;
2022-10-14 18:48:41 +08:00
return LastWindowPosition ( m [ " width " ] , m [ " height " ] , m [ " offsetWidth " ] ,
2023-09-07 13:39:20 +08:00
m [ " offsetHeight " ] , m [ " isMaximized " ] , m [ " isFullscreen " ] ) ;
2022-09-16 12:14:03 +08:00
} catch ( e ) {
2023-07-26 21:29:35 +08:00
debugPrintStack (
label:
' Failed to load LastWindowPosition " $ content " ${ e . toString ( ) } ' ) ;
2022-09-16 12:14:03 +08:00
return null ;
}
}
}
/// Save window position and size on exit
/// Note that windowId must be provided if it's subwindow
Future < void > saveWindowPosition ( WindowType type , { int ? windowId } ) async {
if ( type ! = WindowType . Main & & windowId = = null ) {
debugPrint (
" Error: windowId cannot be null when saving positions for sub window " ) ;
}
2023-07-26 07:52:40 +08:00
late Offset position ;
late Size sz ;
late bool isMaximized ;
2023-10-17 14:29:14 +08:00
bool isFullscreen = stateGlobal . fullscreen . isTrue | |
2023-10-20 09:15:53 +08:00
( Platform . isMacOS & & stateGlobal . closeOnFullscreen = = true ) ;
2023-08-11 12:01:57 +08:00
setFrameIfMaximized ( ) {
if ( isMaximized ) {
final pos = bind . getLocalFlutterOption ( k: kWindowPrefix + type . name ) ;
var lpos = LastWindowPosition . loadFromString ( pos ) ;
position = Offset (
lpos ? . offsetWidth ? ? position . dx , lpos ? . offsetHeight ? ? position . dy ) ;
sz = Size ( lpos ? . width ? ? sz . width , lpos ? . height ? ? sz . height ) ;
}
}
2022-09-16 12:14:03 +08:00
switch ( type ) {
case WindowType . Main:
2023-08-11 12:01:57 +08:00
isMaximized = await windowManager . isMaximized ( ) ;
2023-07-26 07:52:40 +08:00
position = await windowManager . getPosition ( ) ;
sz = await windowManager . getSize ( ) ;
2023-08-11 12:01:57 +08:00
setFrameIfMaximized ( ) ;
2022-09-16 12:14:03 +08:00
break ;
default :
2022-10-14 18:48:41 +08:00
final wc = WindowController . fromWindowId ( windowId ! ) ;
2023-08-11 12:01:57 +08:00
isMaximized = await wc . isMaximized ( ) ;
2023-06-21 00:30:34 +08:00
final Rect frame ;
try {
frame = await wc . getFrame ( ) ;
} catch ( e ) {
debugPrint ( " Failed to get frame of window $ windowId , it may be hidden " ) ;
return ;
}
2023-07-26 07:52:40 +08:00
position = frame . topLeft ;
sz = frame . size ;
2023-08-11 12:01:57 +08:00
setFrameIfMaximized ( ) ;
2022-09-16 12:14:03 +08:00
break ;
}
2023-07-27 20:45:29 +08:00
if ( Platform . isWindows ) {
const kMinOffset = - 10000 ;
2023-07-27 22:19:38 +08:00
const kMaxOffset = 10000 ;
if ( position . dx < kMinOffset | |
position . dy < kMinOffset | |
position . dx > kMaxOffset | |
position . dy > kMaxOffset ) {
2023-07-27 20:45:29 +08:00
debugPrint ( " Invalid position: $ position , ignore saving position " ) ;
return ;
}
}
2023-07-26 07:52:40 +08:00
final pos = LastWindowPosition (
2023-09-07 21:50:03 +08:00
sz . width , sz . height , position . dx , position . dy , isMaximized , isFullscreen ) ;
2023-07-26 07:52:40 +08:00
debugPrint (
2023-09-07 13:39:20 +08:00
" Saving frame: $ windowId : ${ pos . width } / ${ pos . height } , offset: ${ pos . offsetWidth } / ${ pos . offsetHeight } , isMaximized: ${ pos . isMaximized } , isFullscreen: ${ pos . isFullscreen } " ) ;
2023-08-02 22:02:18 +08:00
2023-08-10 22:27:35 +08:00
await bind . setLocalFlutterOption (
2023-07-26 07:52:40 +08:00
k: kWindowPrefix + type . name , v: pos . toString ( ) ) ;
2023-08-02 22:02:18 +08:00
if ( type = = WindowType . RemoteDesktop & & windowId ! = null ) {
2023-09-07 13:39:20 +08:00
await _saveSessionWindowPosition (
2023-09-07 21:50:03 +08:00
type , windowId , isMaximized , isFullscreen , pos ) ;
2023-08-02 22:02:18 +08:00
}
}
2023-08-11 12:01:57 +08:00
Future _saveSessionWindowPosition ( WindowType windowType , int windowId ,
2023-09-07 13:39:20 +08:00
bool isMaximized , bool isFullscreen , LastWindowPosition pos ) async {
2023-08-02 22:02:18 +08:00
final remoteList = await DesktopMultiWindow . invokeMethod (
windowId , kWindowEventGetRemoteList , null ) ;
2023-08-11 12:01:57 +08:00
getPeerPos ( String peerId ) {
if ( isMaximized ) {
final peerPos = bind . mainGetPeerFlutterOptionSync (
id: peerId , k: kWindowPrefix + windowType . name ) ;
var lpos = LastWindowPosition . loadFromString ( peerPos ) ;
return LastWindowPosition (
lpos ? . width ? ? pos . offsetWidth ,
lpos ? . height ? ? pos . offsetHeight ,
lpos ? . offsetWidth ? ? pos . offsetWidth ,
lpos ? . offsetHeight ? ? pos . offsetHeight ,
2023-09-07 13:39:20 +08:00
isMaximized ,
isFullscreen )
2023-08-11 12:01:57 +08:00
. toString ( ) ;
} else {
return pos . toString ( ) ;
}
}
2023-08-02 22:02:18 +08:00
if ( remoteList ! = null ) {
for ( final peerId in remoteList . split ( ' , ' ) ) {
2023-08-10 22:27:35 +08:00
bind . mainSetPeerFlutterOptionSync (
2023-08-11 12:01:57 +08:00
id: peerId ,
k: kWindowPrefix + windowType . name ,
v: getPeerPos ( peerId ) ) ;
2023-08-02 22:02:18 +08:00
}
}
2022-09-16 12:14:03 +08:00
}
2022-10-14 18:48:41 +08:00
Future < Size > _adjustRestoreMainWindowSize ( double ? width , double ? height ) async {
2023-08-03 18:15:50 +08:00
const double minWidth = 1 ;
const double minHeight = 1 ;
const double maxWidth = 6480 ;
const double maxHeight = 6480 ;
2022-10-09 19:27:30 +08:00
2022-10-10 09:56:27 +08:00
final defaultWidth =
( ( isDesktop | | isWebDesktop ) ? 1280 : kMobileDefaultDisplayWidth )
. toDouble ( ) ;
final defaultHeight =
( ( isDesktop | | isWebDesktop ) ? 720 : kMobileDefaultDisplayHeight )
. toDouble ( ) ;
2022-10-09 19:27:30 +08:00
double restoreWidth = width ? ? defaultWidth ;
double restoreHeight = height ? ? defaultHeight ;
if ( restoreWidth < minWidth ) {
2023-07-26 21:29:35 +08:00
restoreWidth = defaultWidth ;
2022-10-09 19:27:30 +08:00
}
if ( restoreHeight < minHeight ) {
2023-07-26 21:29:35 +08:00
restoreHeight = defaultHeight ;
2022-10-09 19:27:30 +08:00
}
if ( restoreWidth > maxWidth ) {
2023-07-26 21:29:35 +08:00
restoreWidth = defaultWidth ;
2022-10-09 19:27:30 +08:00
}
if ( restoreHeight > maxHeight ) {
2023-07-26 21:29:35 +08:00
restoreHeight = defaultHeight ;
2022-10-09 19:27:30 +08:00
}
2022-10-14 18:48:41 +08:00
return Size ( restoreWidth , restoreHeight ) ;
2022-10-09 19:27:30 +08:00
}
2022-10-14 18:48:41 +08:00
/// return null means center
Future < Offset ? > _adjustRestoreMainWindowOffset (
2023-07-26 21:29:35 +08:00
double ? left ,
double ? top ,
double ? width ,
double ? height ,
) async {
if ( left = = null | | top = = null | | width = = null | | height = = null ) {
return null ;
}
2022-10-09 19:27:30 +08:00
2023-07-26 21:29:35 +08:00
double ? frameLeft ;
double ? frameTop ;
double ? frameRight ;
double ? frameBottom ;
2022-10-09 19:27:30 +08:00
2023-07-26 21:29:35 +08:00
if ( isDesktop | | isWebDesktop ) {
for ( final screen in await window_size . getScreenList ( ) ) {
frameLeft = frameLeft = = null
? screen . visibleFrame . left
: min ( screen . visibleFrame . left , frameLeft ) ;
frameTop = frameTop = = null
? screen . visibleFrame . top
: min ( screen . visibleFrame . top , frameTop ) ;
frameRight = frameRight = = null
? screen . visibleFrame . right
: max ( screen . visibleFrame . right , frameRight ) ;
frameBottom = frameBottom = = null
? screen . visibleFrame . bottom
: max ( screen . visibleFrame . bottom , frameBottom ) ;
2022-10-09 19:27:30 +08:00
}
}
2023-07-26 21:29:35 +08:00
if ( frameLeft = = null ) {
frameLeft = 0.0 ;
frameTop = 0.0 ;
frameRight = ( ( isDesktop | | isWebDesktop )
? kDesktopMaxDisplaySize
: kMobileMaxDisplaySize )
. toDouble ( ) ;
frameBottom = ( ( isDesktop | | isWebDesktop )
? kDesktopMaxDisplaySize
: kMobileMaxDisplaySize )
. toDouble ( ) ;
}
2023-07-27 09:11:43 +08:00
final minWidth = 10.0 ;
2023-07-27 09:13:39 +08:00
if ( ( left + minWidth ) > frameRight ! | |
( top + minWidth ) > frameBottom ! | |
( left + width - minWidth ) < frameLeft | |
2023-08-11 12:01:57 +08:00
top < frameTop ! ) {
2023-07-26 21:29:35 +08:00
return null ;
} else {
return Offset ( left , top ) ;
}
2022-10-09 19:27:30 +08:00
}
2022-10-14 18:48:41 +08:00
/// Restore window position and size on start
2022-09-16 12:14:03 +08:00
/// Note that windowId must be provided if it's subwindow
2023-10-19 07:50:59 +08:00
//
// display is used to set the offset of the window in individual display mode.
2023-08-02 23:10:31 +08:00
Future < bool > restoreWindowPosition ( WindowType type ,
2023-10-19 07:50:59 +08:00
{ int ? windowId , String ? peerId , int ? display } ) async {
2023-06-27 13:52:29 +08:00
if ( bind
. mainGetEnv ( key: " DISABLE_RUSTDESK_RESTORE_WINDOW_POSITION " )
. isNotEmpty ) {
2023-06-27 13:26:55 +08:00
return false ;
}
2022-09-16 12:14:03 +08:00
if ( type ! = WindowType . Main & & windowId = = null ) {
debugPrint (
" Error: windowId cannot be null when saving positions for sub window " ) ;
2023-08-02 22:02:18 +08:00
return false ;
}
2023-08-03 18:03:56 +08:00
bool isRemotePeerPos = false ;
2023-08-02 22:02:18 +08:00
String ? pos ;
2023-08-09 18:37:09 +08:00
// No need to check mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs)
2023-08-11 12:01:57 +08:00
// Though "open in tabs" is true and the new window restore peer position, it's ok.
2023-08-02 22:02:18 +08:00
if ( type = = WindowType . RemoteDesktop & & windowId ! = null & & peerId ! = null ) {
2023-08-08 21:34:46 +08:00
// If the restore position is called by main window, and the peer id is not null
// then we may need to get the position by reading the peer config.
// Because the session may not be read at this time.
2023-08-08 18:14:01 +08:00
if ( desktopType = = DesktopType . main ) {
2023-08-10 22:27:35 +08:00
pos = bind . mainGetPeerFlutterOptionSync (
2023-08-08 18:14:01 +08:00
id: peerId , k: kWindowPrefix + type . name ) ;
} else {
2023-08-10 22:27:35 +08:00
pos = await bind . sessionGetFlutterOptionByPeerId (
2023-08-08 19:58:30 +08:00
id: peerId , k: kWindowPrefix + type . name ) ;
2023-08-08 18:14:01 +08:00
}
2023-08-03 18:03:56 +08:00
isRemotePeerPos = pos ! = null ;
2022-09-16 12:14:03 +08:00
}
2023-08-10 22:27:35 +08:00
pos ? ? = bind . getLocalFlutterOption ( k: kWindowPrefix + type . name ) ;
2023-08-02 23:10:31 +08:00
2022-10-14 18:48:41 +08:00
var lpos = LastWindowPosition . loadFromString ( pos ) ;
if ( lpos = = null ) {
2022-12-09 10:49:47 +08:00
debugPrint ( " no window position saved, ignoring position restoration " ) ;
2022-10-14 18:48:41 +08:00
return false ;
}
2023-10-19 07:50:59 +08:00
if ( type = = WindowType . RemoteDesktop ) {
if ( ! isRemotePeerPos & & windowId ! = null ) {
if ( lpos . offsetWidth ! = null ) {
2023-10-19 12:21:35 +08:00
lpos . offsetWidth = lpos . offsetWidth ! + windowId * kNewWindowOffset ;
2023-10-19 07:50:59 +08:00
}
if ( lpos . offsetHeight ! = null ) {
2023-10-19 12:21:35 +08:00
lpos . offsetHeight = lpos . offsetHeight ! + windowId * kNewWindowOffset ;
2023-10-19 07:50:59 +08:00
}
2023-08-03 18:03:56 +08:00
}
2023-10-19 07:50:59 +08:00
if ( display ! = null ) {
if ( lpos . offsetWidth ! = null ) {
2023-10-19 12:21:35 +08:00
lpos . offsetWidth = lpos . offsetWidth ! + display * kNewWindowOffset ;
2023-10-19 07:50:59 +08:00
}
if ( lpos . offsetHeight ! = null ) {
2023-10-19 12:21:35 +08:00
lpos . offsetHeight = lpos . offsetHeight ! + display * kNewWindowOffset ;
2023-10-19 07:50:59 +08:00
}
2023-08-03 18:03:56 +08:00
}
}
2022-10-14 18:48:41 +08:00
2023-08-11 12:01:57 +08:00
final size = await _adjustRestoreMainWindowSize ( lpos . width , lpos . height ) ;
final offset = await _adjustRestoreMainWindowOffset (
lpos . offsetWidth ,
lpos . offsetHeight ,
size . width ,
size . height ,
) ;
debugPrint (
" restore lpos: ${ size . width } / ${ size . height } , offset: ${ offset ? . dx } / ${ offset ? . dy } " ) ;
2022-09-16 12:14:03 +08:00
switch ( type ) {
case WindowType . Main:
2023-08-11 12:01:57 +08:00
restorePos ( ) async {
2022-10-14 18:48:41 +08:00
if ( offset = = null ) {
await windowManager . center ( ) ;
} else {
await windowManager . setPosition ( offset ) ;
}
2022-09-16 12:14:03 +08:00
}
2023-08-11 12:01:57 +08:00
if ( lpos . isMaximized = = true ) {
await restorePos ( ) ;
await windowManager . maximize ( ) ;
} else {
await windowManager . setSize ( size ) ;
await restorePos ( ) ;
}
2022-09-16 12:14:03 +08:00
return true ;
default :
2022-10-14 18:48:41 +08:00
final wc = WindowController . fromWindowId ( windowId ! ) ;
2023-08-11 12:01:57 +08:00
restoreFrame ( ) async {
2022-10-14 18:48:41 +08:00
if ( offset = = null ) {
await wc . center ( ) ;
} else {
final frame =
Rect . fromLTWH ( offset . dx , offset . dy , size . width , size . height ) ;
await wc . setFrame ( frame ) ;
}
}
2023-09-07 13:39:20 +08:00
if ( lpos . isFullscreen = = true ) {
await restoreFrame ( ) ;
// An duration is needed to avoid the window being restored after fullscreen.
Future . delayed ( Duration ( milliseconds: 300 ) , ( ) async {
stateGlobal . setFullscreen ( true ) ;
} ) ;
} else if ( lpos . isMaximized = = true ) {
2023-08-11 12:01:57 +08:00
await restoreFrame ( ) ;
2023-09-03 22:27:42 +08:00
// An duration is needed to avoid the window being restored after maximized.
Future . delayed ( Duration ( milliseconds: 300 ) , ( ) async {
await wc . maximize ( ) ;
} ) ;
2023-08-11 12:01:57 +08:00
} else {
await restoreFrame ( ) ;
}
2022-09-16 12:14:03 +08:00
break ;
}
return false ;
}
2022-09-19 20:26:39 +08:00
2022-10-18 10:29:33 +08:00
/// Initialize uni links for macos/windows
///
/// [Availability]
/// initUniLinks should only be used on macos/windows.
/// we use dbus for linux currently.
2023-02-03 18:52:22 +08:00
Future < bool > initUniLinks ( ) async {
2023-02-03 17:08:40 +08:00
if ( Platform . isLinux ) {
2023-02-03 18:52:22 +08:00
return false ;
2022-10-18 10:29:33 +08:00
}
// check cold boot
try {
final initialLink = await getInitialLink ( ) ;
2022-10-19 09:54:04 +08:00
if ( initialLink = = null ) {
2023-02-03 18:52:22 +08:00
return false ;
2022-10-19 09:54:04 +08:00
}
2023-07-07 12:22:39 +08:00
return handleUriLink ( uriString: initialLink ) ;
2022-10-19 09:54:04 +08:00
} catch ( err ) {
2022-12-09 10:49:47 +08:00
debugPrintStack ( label: " $ err " ) ;
2023-02-03 18:52:22 +08:00
return false ;
2022-10-18 10:29:33 +08:00
}
}
2023-02-07 01:31:11 +08:00
/// Listen for uni links.
///
2023-02-07 01:35:38 +08:00
/// * handleByFlutter: Should uni links be handled by Flutter.
2023-02-07 01:31:11 +08:00
///
/// Returns a [StreamSubscription] which can listen the uni links.
StreamSubscription ? listenUniLinks ( { handleByFlutter = true } ) {
if ( Platform . isLinux ) {
2022-10-31 16:35:14 +08:00
return null ;
2022-10-18 10:29:33 +08:00
}
2022-10-31 16:35:14 +08:00
final sub = uriLinkStream . listen ( ( Uri ? uri ) {
2023-08-11 15:01:42 +08:00
debugPrint ( " A uri was received: $ uri . handleByFlutter $ handleByFlutter " ) ;
2022-10-31 16:35:14 +08:00
if ( uri ! = null ) {
2023-02-07 01:31:11 +08:00
if ( handleByFlutter ) {
2023-07-07 12:22:39 +08:00
handleUriLink ( uri: uri ) ;
2023-02-07 01:31:11 +08:00
} else {
bind . sendUrlScheme ( url: uri . toString ( ) ) ;
}
2022-10-31 16:35:14 +08:00
} else {
print ( " uni listen error: uri is empty. " ) ;
}
} , onError: ( err ) {
print ( " uni links error: $ err " ) ;
} ) ;
return sub ;
2022-10-18 10:29:33 +08:00
}
2023-07-07 12:22:39 +08:00
enum UriLinkType {
remoteDesktop ,
fileTransfer ,
portForward ,
rdp ,
}
// uri link handler
bool handleUriLink ( { List < String > ? cmdArgs , Uri ? uri , String ? uriString } ) {
List < String > ? args ;
2023-10-12 23:11:11 +08:00
if ( cmdArgs ! = null & & cmdArgs . isNotEmpty ) {
2023-07-07 12:22:39 +08:00
args = cmdArgs ;
2023-07-07 18:35:15 +08:00
// rustdesk <uri link>
2023-10-12 23:11:11 +08:00
if ( args [ 0 ] . startsWith ( kUniLinksPrefix ) ) {
2023-07-07 12:22:39 +08:00
final uri = Uri . tryParse ( args [ 0 ] ) ;
if ( uri ! = null ) {
args = urlLinkToCmdArgs ( uri ) ;
}
}
} else if ( uri ! = null ) {
args = urlLinkToCmdArgs ( uri ) ;
} else if ( uriString ! = null ) {
final uri = Uri . tryParse ( uriString ) ;
if ( uri ! = null ) {
args = urlLinkToCmdArgs ( uri ) ;
2023-02-04 01:22:40 +08:00
}
}
2023-08-11 15:01:42 +08:00
if ( args = = null ) {
return false ;
}
if ( args . isEmpty ) {
windowOnTop ( null ) ;
return true ;
}
2023-07-07 12:22:39 +08:00
UriLinkType ? type ;
String ? id ;
String ? password ;
String ? switchUuid ;
bool ? forceRelay ;
for ( int i = 0 ; i < args . length ; i + + ) {
switch ( args [ i ] ) {
case ' --connect ' :
case ' --play ' :
type = UriLinkType . remoteDesktop ;
id = args [ i + 1 ] ;
i + + ;
break ;
case ' --file-transfer ' :
type = UriLinkType . fileTransfer ;
id = args [ i + 1 ] ;
i + + ;
break ;
case ' --port-forward ' :
type = UriLinkType . portForward ;
id = args [ i + 1 ] ;
i + + ;
break ;
case ' --rdp ' :
type = UriLinkType . rdp ;
id = args [ i + 1 ] ;
i + + ;
break ;
case ' --password ' :
password = args [ i + 1 ] ;
i + + ;
break ;
case ' --switch_uuid ' :
switchUuid = args [ i + 1 ] ;
i + + ;
break ;
case ' --relay ' :
forceRelay = true ;
break ;
default :
break ;
}
2022-10-11 19:52:03 +08:00
}
2023-07-07 12:22:39 +08:00
if ( type ! = null & & id ! = null ) {
switch ( type ) {
case UriLinkType . remoteDesktop:
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newRemoteDesktop ( id ! ,
password: password ,
2023-08-01 22:19:38 +08:00
switchUuid: switchUuid ,
2023-07-07 12:22:39 +08:00
forceRelay: forceRelay ) ;
} ) ;
break ;
case UriLinkType . fileTransfer:
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newFileTransfer ( id ! ,
password: password , forceRelay: forceRelay ) ;
} ) ;
break ;
case UriLinkType . portForward:
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newPortForward ( id ! , false ,
password: password , forceRelay: forceRelay ) ;
} ) ;
break ;
case UriLinkType . rdp:
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newPortForward ( id ! , true ,
password: password , forceRelay: forceRelay ) ;
} ) ;
break ;
2022-10-12 21:57:19 +08:00
}
2023-07-07 12:22:39 +08:00
return true ;
2022-10-12 21:57:19 +08:00
}
2023-07-07 12:22:39 +08:00
2022-11-26 11:40:13 +08:00
return false ;
2022-10-12 21:57:19 +08:00
}
2023-07-07 12:22:39 +08:00
List < String > ? urlLinkToCmdArgs ( Uri uri ) {
String ? command ;
String ? id ;
2023-08-11 15:01:42 +08:00
if ( uri . authority . isEmpty & &
uri . path . split ( ' ' ) . every ( ( char ) = > char = = ' / ' ) ) {
return [ ] ;
} else if ( uri . authority = = " connection " & & uri . path . startsWith ( " /new/ " ) ) {
2023-07-07 12:22:39 +08:00
// For compatibility
command = ' --connect ' ;
id = uri . path . substring ( " /new/ " . length ) ;
} else if ( [ ' connect ' , " play " , ' file-transfer ' , ' port-forward ' , ' rdp ' ]
. contains ( uri . authority ) ) {
command = ' -- ${ uri . authority } ' ;
if ( uri . path . length > 1 ) {
id = uri . path . substring ( 1 ) ;
}
2023-07-07 18:35:15 +08:00
} else if ( uri . authority . length > 2 & & uri . path . length < = 1 ) {
// rustdesk://<connect-id>
command = ' --connect ' ;
id = uri . authority ;
2022-10-12 21:57:19 +08:00
}
2022-10-18 10:29:33 +08:00
2023-07-07 12:22:39 +08:00
List < String > args = List . empty ( growable: true ) ;
if ( command ! = null & & id ! = null ) {
args . add ( command ) ;
args . add ( id ) ;
var param = uri . queryParameters ;
String ? password = param [ " password " ] ;
if ( password ! = null ) args . addAll ( [ ' --password ' , password ] ) ;
String ? switch_uuid = param [ " switch_uuid " ] ;
if ( switch_uuid ! = null ) args . addAll ( [ ' --switch_uuid ' , switch_uuid ] ) ;
if ( param [ " relay " ] ! = null ) args . add ( " --relay " ) ;
return args ;
2022-10-11 19:52:03 +08:00
}
2023-07-07 12:22:39 +08:00
return null ;
2022-10-11 19:52:03 +08:00
}
2023-08-02 23:10:31 +08:00
connectMainDesktop (
String id , {
required bool isFileTransfer ,
required bool isTcpTunneling ,
required bool isRDP ,
bool ? forceRelay ,
} ) async {
2022-12-01 13:52:12 +08:00
if ( isFileTransfer ) {
2023-02-13 16:40:24 +08:00
await rustDeskWinManager . newFileTransfer ( id , forceRelay: forceRelay ) ;
2022-12-01 13:52:12 +08:00
} else if ( isTcpTunneling | | isRDP ) {
2023-02-13 16:40:24 +08:00
await rustDeskWinManager . newPortForward ( id , isRDP , forceRelay: forceRelay ) ;
2022-12-01 13:52:12 +08:00
} else {
2023-08-08 12:12:35 +08:00
await rustDeskWinManager . newRemoteDesktop ( id , forceRelay: forceRelay ) ;
2022-12-01 13:52:12 +08:00
}
}
2022-09-19 20:26:39 +08:00
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
/// If [isTcpTunneling], starts a session only for tcp tunneling.
/// If [isRDP], starts a session only for rdp.
2023-08-02 23:10:31 +08:00
connect (
BuildContext context ,
String id , {
bool isFileTransfer = false ,
bool isTcpTunneling = false ,
bool isRDP = false ,
} ) async {
2022-09-19 20:26:39 +08:00
if ( id = = ' ' ) return ;
2023-08-08 17:22:25 +08:00
if ( ! isDesktop | | desktopType = = DesktopType . main ) {
try {
if ( Get . isRegistered < IDTextEditingController > ( ) ) {
final idController = Get . find < IDTextEditingController > ( ) ;
idController . text = formatID ( id ) ;
}
2023-10-25 05:06:23 +08:00
if ( Get . isRegistered < TextEditingController > ( ) ) {
final fieldTextEditingController = Get . find < TextEditingController > ( ) ;
fieldTextEditingController . text = formatID ( id ) ;
}
2023-08-08 17:22:25 +08:00
} catch ( _ ) { }
}
2022-09-19 20:26:39 +08:00
id = id . replaceAll ( ' ' , ' ' ) ;
2023-02-26 11:23:43 +08:00
final oldId = id ;
id = await bind . mainHandleRelayId ( id: id ) ;
final forceRelay = id ! = oldId ;
2022-09-19 20:26:39 +08:00
assert ( ! ( isFileTransfer & & isTcpTunneling & & isRDP ) ,
" more than one connect type " ) ;
2022-09-22 16:45:14 +08:00
if ( isDesktop ) {
2022-12-01 13:52:12 +08:00
if ( desktopType = = DesktopType . main ) {
2023-08-02 23:10:31 +08:00
await connectMainDesktop (
id ,
isFileTransfer: isFileTransfer ,
isTcpTunneling: isTcpTunneling ,
isRDP: isRDP ,
forceRelay: forceRelay ,
) ;
2022-09-22 16:45:14 +08:00
} else {
2022-12-01 13:52:12 +08:00
await rustDeskWinManager . call ( WindowType . Main , kWindowConnect , {
' id ' : id ,
' isFileTransfer ' : isFileTransfer ,
' isTcpTunneling ' : isTcpTunneling ,
' isRDP ' : isRDP ,
2023-08-02 23:10:31 +08:00
' forceRelay ' : forceRelay ,
2022-12-01 13:52:12 +08:00
} ) ;
2022-09-22 16:45:14 +08:00
}
2022-09-19 20:26:39 +08:00
} else {
2022-09-22 16:45:14 +08:00
if ( isFileTransfer ) {
2023-03-02 01:00:56 +08:00
if ( ! await AndroidPermissionManager . check ( kManageExternalStorage ) ) {
if ( ! await AndroidPermissionManager . request ( kManageExternalStorage ) ) {
2022-09-22 16:45:14 +08:00
return ;
}
}
Navigator . push (
context ,
MaterialPageRoute (
builder: ( BuildContext context ) = > FileManagerPage ( id: id ) ,
) ,
) ;
} else {
Navigator . push (
context ,
MaterialPageRoute (
builder: ( BuildContext context ) = > RemotePage ( id: id ) ,
) ,
) ;
}
2022-09-19 20:26:39 +08:00
}
2022-09-22 16:45:14 +08:00
FocusScopeNode currentFocus = FocusScope . of ( context ) ;
2022-09-19 20:26:39 +08:00
if ( ! currentFocus . hasPrimaryFocus ) {
currentFocus . unfocus ( ) ;
}
}
2022-09-27 17:52:36 +08:00
2023-01-06 18:26:19 +08:00
Map < String , String > getHttpHeaders ( ) {
2022-09-27 17:52:36 +08:00
return {
2022-11-10 21:25:12 +08:00
' Authorization ' : ' Bearer ${ bind . mainGetLocalOption ( key: ' access_token ' ) } '
2022-09-27 17:52:36 +08:00
} ;
}
2022-10-04 13:03:49 +08:00
2022-12-26 01:21:13 +08:00
// Simple wrapper of built-in types for reference use.
2022-10-04 13:03:49 +08:00
class SimpleWrapper < T > {
2022-11-03 21:58:25 +08:00
T value ;
SimpleWrapper ( this . value ) ;
2022-10-04 13:03:49 +08:00
}
2022-10-26 14:39:13 +08:00
/// call this to reload current window.
///
/// [Note]
/// Must have [RefreshWrapper] on the top of widget tree.
void reloadCurrentWindow ( ) {
if ( Get . context ! = null ) {
// reload self window
RefreshWrapper . of ( Get . context ! ) ? . rebuild ( ) ;
} else {
debugPrint (
" reload current window failed, global BuildContext does not exist " ) ;
}
}
/// call this to reload all windows, including main + all sub windows.
Future < void > reloadAllWindows ( ) async {
reloadCurrentWindow ( ) ;
try {
final ids = await DesktopMultiWindow . getAllSubWindowIds ( ) ;
for ( final id in ids ) {
DesktopMultiWindow . invokeMethod ( id , kWindowActionRebuild ) ;
}
} on AssertionError {
// ignore
}
}
2022-11-05 23:41:22 +08:00
/// Indicate the flutter app is running in portable mode.
///
/// [Note]
2022-12-26 01:21:13 +08:00
/// Portable build is only available on Windows.
2022-11-05 23:41:22 +08:00
bool isRunningInPortableMode ( ) {
if ( ! Platform . isWindows ) {
return false ;
}
return bool . hasEnvironment ( kEnvPortableExecutable ) ;
}
/// Window status callback
2023-01-07 12:40:29 +08:00
Future < void > onActiveWindowChanged ( ) async {
2022-11-05 23:41:22 +08:00
print (
" [MultiWindowHandler] active window changed: ${ rustDeskWinManager . getActiveWindows ( ) } " ) ;
if ( rustDeskWinManager . getActiveWindows ( ) . isEmpty ) {
// close all sub windows
try {
2023-07-26 07:52:40 +08:00
if ( Platform . isLinux ) {
await Future . wait ( [
saveWindowPosition ( WindowType . Main ) ,
rustDeskWinManager . closeAllSubWindows ( )
] ) ;
} else {
await rustDeskWinManager . closeAllSubWindows ( ) ;
}
2022-11-05 23:41:22 +08:00
} catch ( err ) {
2022-12-09 10:49:47 +08:00
debugPrintStack ( label: " $ err " ) ;
2022-11-05 23:41:22 +08:00
} finally {
2023-02-03 17:08:40 +08:00
debugPrint ( " Start closing RustDesk... " ) ;
2022-11-05 23:41:22 +08:00
await windowManager . setPreventClose ( false ) ;
await windowManager . close ( ) ;
2023-02-03 17:08:40 +08:00
if ( Platform . isMacOS ) {
RdPlatformChannel . instance . terminate ( ) ;
}
2022-11-05 23:41:22 +08:00
}
}
}
2022-11-29 22:36:35 +08:00
Timer periodic_immediate ( Duration duration , Future < void > Function ( ) callback ) {
Future . delayed ( Duration . zero , callback ) ;
return Timer . periodic ( duration , ( timer ) async {
await callback ( ) ;
} ) ;
}
2022-11-30 13:56:02 +08:00
2022-11-29 23:03:16 +08:00
/// return a human readable windows version
WindowsTarget getWindowsTarget ( int buildNumber ) {
if ( ! Platform . isWindows ) {
return WindowsTarget . naw ;
2022-11-30 13:56:02 +08:00
}
2022-11-29 23:03:16 +08:00
if ( buildNumber > = 22000 ) {
return WindowsTarget . w11 ;
} else if ( buildNumber > = 10240 ) {
return WindowsTarget . w10 ;
} else if ( buildNumber > = 9600 ) {
return WindowsTarget . w8_1 ;
} else if ( buildNumber > = 9200 ) {
return WindowsTarget . w8 ;
} else if ( buildNumber > = 7601 ) {
return WindowsTarget . w7 ;
} else if ( buildNumber > = 6002 ) {
return WindowsTarget . vista ;
} else {
// minimum support
return WindowsTarget . xp ;
}
}
2022-11-30 13:56:02 +08:00
/// Get windows target build number.
///
/// [Note]
/// Please use this function wrapped with `Platform.isWindows`.
int getWindowsTargetBuildNumber ( ) {
final rtlGetVersion = DynamicLibrary . open ( ' ntdll.dll ' ) . lookupFunction <
Void Function ( Pointer < win32 . OSVERSIONINFOEX > ) ,
void Function ( Pointer < win32 . OSVERSIONINFOEX > ) > ( ' RtlGetVersion ' ) ;
final osVersionInfo = getOSVERSIONINFOEXPointer ( ) ;
rtlGetVersion ( osVersionInfo ) ;
int buildNumber = osVersionInfo . ref . dwBuildNumber ;
calloc . free ( osVersionInfo ) ;
return buildNumber ;
}
/// Get Windows OS version pointer
///
/// [Note]
/// Please use this function wrapped with `Platform.isWindows`.
Pointer < win32 . OSVERSIONINFOEX > getOSVERSIONINFOEXPointer ( ) {
final pointer = calloc < win32 . OSVERSIONINFOEX > ( ) ;
pointer . ref
. . dwOSVersionInfoSize = sizeOf < win32 . OSVERSIONINFOEX > ( )
. . dwBuildNumber = 0
. . dwMajorVersion = 0
. . dwMinorVersion = 0
. . dwPlatformId = 0
. . szCSDVersion = ' '
. . wServicePackMajor = 0
. . wServicePackMinor = 0
. . wSuiteMask = 0
. . wProductType = 0
. . wReserved = 0 ;
return pointer ;
}
/// Indicating we need to use compatible ui mode.
///
/// [Conditions]
/// - Windows 7, window will overflow when we use frameless ui.
bool get kUseCompatibleUiMode = >
Platform . isWindows & &
const [ WindowsTarget . w7 ] . contains ( windowsBuildNumber . windowsVersion ) ;
2022-12-21 14:14:43 +08:00
class ServerConfig {
late String idServer ;
late String relayServer ;
late String apiServer ;
late String key ;
ServerConfig (
{ String ? idServer , String ? relayServer , String ? apiServer , String ? key } ) {
this . idServer = idServer ? . trim ( ) ? ? ' ' ;
this . relayServer = relayServer ? . trim ( ) ? ? ' ' ;
this . apiServer = apiServer ? . trim ( ) ? ? ' ' ;
this . key = key ? . trim ( ) ? ? ' ' ;
}
2022-12-21 15:24:01 +08:00
/// decode from shared string (from user shared or rustdesk-server generated)
/// also see [encode]
/// throw when decoding failure
2022-12-21 14:14:43 +08:00
ServerConfig . decode ( String msg ) {
2023-03-19 16:33:33 +08:00
var json = { } ;
try {
2023-03-19 16:41:51 +08:00
// back compatible
2023-03-19 16:33:33 +08:00
json = jsonDecode ( msg ) ;
} catch ( err ) {
final input = msg . split ( ' ' ) . reversed . join ( ' ' ) ;
final bytes = base64Decode ( base64 . normalize ( input ) ) ;
json = jsonDecode ( utf8 . decode ( bytes ) ) ;
}
2022-12-21 14:14:43 +08:00
idServer = json [ ' host ' ] ? ? ' ' ;
relayServer = json [ ' relay ' ] ? ? ' ' ;
apiServer = json [ ' api ' ] ? ? ' ' ;
key = json [ ' key ' ] ? ? ' ' ;
}
2022-12-21 15:24:01 +08:00
/// encode to shared string
/// also see [ServerConfig.decode]
2022-12-21 14:14:43 +08:00
String encode ( ) {
Map < String , String > config = { } ;
config [ ' host ' ] = idServer . trim ( ) ;
config [ ' relay ' ] = relayServer . trim ( ) ;
config [ ' api ' ] = apiServer . trim ( ) ;
config [ ' key ' ] = key . trim ( ) ;
return base64Encode ( Uint8List . fromList ( jsonEncode ( config ) . codeUnits ) )
. split ( ' ' )
. reversed
. join ( ) ;
}
2022-12-21 15:24:01 +08:00
/// from local options
ServerConfig . fromOptions ( Map < String , dynamic > options )
: idServer = options [ ' custom-rendezvous-server ' ] ? ? " " ,
relayServer = options [ ' relay-server ' ] ? ? " " ,
apiServer = options [ ' api-server ' ] ? ? " " ,
key = options [ ' key ' ] ? ? " " ;
2022-12-21 14:14:43 +08:00
}
2023-01-15 19:46:16 +08:00
Widget dialogButton ( String text ,
{ required VoidCallback ? onPressed ,
bool isOutline = false ,
2023-03-01 21:50:50 +08:00
Widget ? icon ,
2023-01-30 17:56:35 +08:00
TextStyle ? style ,
ButtonStyle ? buttonStyle } ) {
2023-01-15 19:46:16 +08:00
if ( isDesktop ) {
if ( isOutline ) {
2023-03-01 21:50:50 +08:00
return icon = = null
? OutlinedButton (
onPressed: onPressed ,
child: Text ( translate ( text ) , style: style ) ,
)
: OutlinedButton . icon (
icon: icon ,
onPressed: onPressed ,
label: Text ( translate ( text ) , style: style ) ,
) ;
2023-01-15 19:46:16 +08:00
} else {
2023-03-01 21:50:50 +08:00
return icon = = null
? ElevatedButton (
style: ElevatedButton . styleFrom ( elevation: 0 ) . merge ( buttonStyle ) ,
onPressed: onPressed ,
child: Text ( translate ( text ) , style: style ) ,
)
: ElevatedButton . icon (
icon: icon ,
style: ElevatedButton . styleFrom ( elevation: 0 ) . merge ( buttonStyle ) ,
onPressed: onPressed ,
label: Text ( translate ( text ) , style: style ) ,
) ;
2023-01-15 19:46:16 +08:00
}
} else {
return TextButton (
2023-03-01 21:50:50 +08:00
onPressed: onPressed ,
child: Text (
translate ( text ) ,
style: style ,
) ,
) ;
2023-01-15 19:46:16 +08:00
}
}
2023-01-17 13:28:33 +08:00
2023-10-08 21:44:54 +08:00
int versionCmp ( String v1 , String v2 ) {
2023-01-19 21:21:28 +08:00
return bind . versionToNumber ( v: v1 ) - bind . versionToNumber ( v: v2 ) ;
2023-01-17 13:28:33 +08:00
}
2023-01-23 22:07:50 +08:00
String getWindowName ( { WindowType ? overrideType } ) {
switch ( overrideType ? ? kWindowType ) {
case WindowType . Main:
return " RustDesk " ;
case WindowType . FileTransfer:
return " File Transfer - RustDesk " ;
case WindowType . PortForward:
return " Port Forward - RustDesk " ;
case WindowType . RemoteDesktop:
return " Remote Desktop - RustDesk " ;
default :
break ;
}
return " RustDesk " ;
}
String getWindowNameWithId ( String id , { WindowType ? overrideType } ) {
2023-09-26 15:11:31 +08:00
return " ${ DesktopTab . tablabelGetter ( id ) . value } - ${ getWindowName ( overrideType: overrideType ) } " ;
2023-01-23 22:07:50 +08:00
}
2023-02-02 13:57:20 +08:00
2023-02-02 14:03:50 +08:00
Future < void > updateSystemWindowTheme ( ) async {
// Set system window theme for macOS.
2023-02-02 13:57:20 +08:00
final userPreference = MyTheme . getThemeModePreference ( ) ;
if ( userPreference ! = ThemeMode . system ) {
if ( Platform . isMacOS ) {
2023-02-02 14:03:50 +08:00
await RdPlatformChannel . instance . changeSystemWindowTheme (
2023-02-02 13:57:20 +08:00
userPreference = = ThemeMode . light
? SystemWindowTheme . light
: SystemWindowTheme . dark ) ;
}
}
2023-02-08 21:01:15 +08:00
}
2023-02-13 16:18:46 +08:00
2023-01-31 13:32:10 +08:00
/// macOS only
///
/// Note: not found a general solution for rust based AVFoundation bingding.
/// [AVFoundation] crate has compile error.
const kMacOSPermChannel = MethodChannel ( " org.rustdesk.rustdesk/macos " ) ;
enum PermissionAuthorizeType {
undetermined ,
authorized ,
denied , // and restricted
}
Future < PermissionAuthorizeType > osxCanRecordAudio ( ) async {
int res = await kMacOSPermChannel . invokeMethod ( " canRecordAudio " ) ;
print ( res ) ;
if ( res > 0 ) {
return PermissionAuthorizeType . authorized ;
} else if ( res = = 0 ) {
return PermissionAuthorizeType . undetermined ;
} else {
return PermissionAuthorizeType . denied ;
}
}
Future < bool > osxRequestAudio ( ) async {
return await kMacOSPermChannel . invokeMethod ( " requestRecordAudio " ) ;
}
2023-02-13 16:18:46 +08:00
class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that does not let the user scroll.
const DraggableNeverScrollableScrollPhysics ( { super . parent } ) ;
@ override
DraggableNeverScrollableScrollPhysics applyTo ( ScrollPhysics ? ancestor ) {
return DraggableNeverScrollableScrollPhysics ( parent: buildParent ( ancestor ) ) ;
}
@ override
bool shouldAcceptUserOffset ( ScrollMetrics position ) {
// TODO: find a better solution to check if the offset change is caused by the scrollbar.
// Workaround: when dragging with the scrollbar, it always triggers an [IdleScrollActivity].
if ( position is ScrollPositionWithSingleContext ) {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
return position . activity is IdleScrollActivity ;
}
return false ;
}
@ override
bool get allowImplicitScrolling = > false ;
}
2023-02-23 14:30:29 +08:00
Widget futureBuilder (
{ required Future ? future , required Widget Function ( dynamic data ) hasData } ) {
return FutureBuilder (
future: future ,
builder: ( BuildContext context , AsyncSnapshot snapshot ) {
if ( snapshot . hasData ) {
return hasData ( snapshot . data ! ) ;
} else {
if ( snapshot . hasError ) {
debugPrint ( snapshot . error . toString ( ) ) ;
}
return Container ( ) ;
}
} ) ;
}
2023-04-19 14:39:22 +08:00
void onCopyFingerprint ( String value ) {
if ( value . isNotEmpty ) {
Clipboard . setData ( ClipboardData ( text: value ) ) ;
showToast ( ' $ value \n ${ translate ( " Copied " ) } ' ) ;
} else {
showToast ( translate ( " no fingerprints " ) ) ;
}
}
2023-06-06 21:51:40 +08:00
2023-08-13 17:50:19 +08:00
Future < bool > callMainCheckSuperUserPermission ( ) async {
bool checked = await bind . mainCheckSuperUserPermission ( ) ;
if ( Platform . isMacOS ) {
await windowManager . show ( ) ;
}
return checked ;
}
2023-06-06 21:51:40 +08:00
Future < void > start_service ( bool is_start ) async {
bool checked = ! bind . mainIsInstalled ( ) | |
! Platform . isMacOS | |
2023-08-13 17:50:19 +08:00
await callMainCheckSuperUserPermission ( ) ;
2023-06-06 21:51:40 +08:00
if ( checked ) {
bind . mainSetOption ( key: " stop-service " , value: is_start ? " " : " Y " ) ;
}
}
2023-07-05 17:35:37 +08:00
typedef Future < bool > WhetherUseRemoteBlock ( ) ;
Widget buildRemoteBlock ( { required Widget child , WhetherUseRemoteBlock ? use } ) {
var block = false . obs ;
return Obx ( ( ) = > MouseRegion (
onEnter: ( _ ) async {
if ( use ! = null & & ! await use ( ) ) {
block . value = false ;
return ;
}
var time0 = DateTime . now ( ) . millisecondsSinceEpoch ;
await bind . mainCheckMouseTime ( ) ;
Timer ( const Duration ( milliseconds: 120 ) , ( ) async {
var d = time0 - await bind . mainGetMouseTime ( ) ;
if ( d < 120 ) {
block . value = true ;
}
} ) ;
} ,
onExit: ( event ) = > block . value = false ,
child: Stack ( children: [
child ,
Offstage (
offstage: ! block . value ,
child: Container (
color: Colors . black . withOpacity ( 0.5 ) ,
) ) ,
] ) ,
) ) ;
}
2023-07-06 09:40:03 +08:00
2023-07-10 09:40:41 +08:00
Widget unreadMessageCountBuilder ( RxInt ? count ,
2023-07-10 21:03:35 +08:00
{ double ? size , double ? fontSize } ) {
2023-07-06 09:40:03 +08:00
return Obx ( ( ) = > Offstage (
offstage: ! ( ( count ? . value ? ? 0 ) > 0 ) ,
child: Container (
2023-07-10 09:40:41 +08:00
width: size ? ? 16 ,
height: size ? ? 16 ,
2023-07-06 09:40:03 +08:00
decoration: BoxDecoration (
color: Colors . red ,
shape: BoxShape . circle ,
) ,
child: Center (
child: Text ( " ${ count ? . value ? ? 0 } " ,
2023-07-10 09:40:41 +08:00
maxLines: 1 ,
style: TextStyle ( color: Colors . white , fontSize: fontSize ? ? 10 ) ) ,
2023-07-06 09:40:03 +08:00
) ,
2023-07-10 21:03:35 +08:00
) ) ) ;
}
2023-07-11 10:05:31 +08:00
Widget unreadTopRightBuilder ( RxInt ? count , { Widget ? icon } ) {
2023-07-10 21:03:35 +08:00
return Stack (
children: [
icon ? ? Icon ( Icons . chat ) ,
Positioned (
top: 0 ,
right: 0 ,
child: unreadMessageCountBuilder ( count , size: 12 , fontSize: 8 ) )
] ,
) ;
2023-07-06 09:40:03 +08:00
}
2023-07-20 08:05:38 +08:00
String toCapitalized ( String s ) {
if ( s . isEmpty ) {
return s ;
}
return s . substring ( 0 , 1 ) . toUpperCase ( ) + s . substring ( 1 ) ;
}
2023-09-14 10:17:03 +08:00
Widget buildErrorBanner ( BuildContext context ,
{ required RxBool loading ,
required RxString err ,
required Function ? retry ,
required Function close } ) {
const double height = 25 ;
return Obx ( ( ) = > Offstage (
offstage: ! ( ! loading . value & & err . value . isNotEmpty ) ,
child: Center (
child: Container (
height: height ,
color: MyTheme . color ( context ) . errorBannerBg ,
child: Row (
mainAxisAlignment: MainAxisAlignment . center ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
FittedBox (
child: Icon (
Icons . info ,
color: Color . fromARGB ( 255 , 249 , 81 , 81 ) ,
) ,
) . marginAll ( 4 ) ,
Flexible (
child: Align (
alignment: Alignment . centerLeft ,
child: Tooltip (
message: translate ( err . value ) ,
child: Text (
translate ( err . value ) ,
overflow: TextOverflow . ellipsis ,
) ,
) ) . marginSymmetric ( vertical: 2 ) ,
) ,
if ( retry ! = null )
InkWell (
onTap: ( ) {
retry . call ( ) ;
} ,
child: Text (
translate ( " Retry " ) ,
style: TextStyle ( color: MyTheme . accent ) ,
) ) . marginSymmetric ( horizontal: 5 ) ,
FittedBox (
child: InkWell (
onTap: ( ) {
close . call ( ) ;
} ,
child: Icon ( Icons . close ) . marginSymmetric ( horizontal: 5 ) ,
) ,
) . marginAll ( 4 )
] ,
) ,
) ) . marginOnly ( bottom: 14 ) ,
) ) ;
}
2023-09-26 15:11:31 +08:00
String getDesktopTabLabel ( String peerId , String alias ) {
String label = alias . isEmpty ? peerId : alias ;
try {
String peer = bind . mainGetPeerSync ( id: peerId ) ;
Map < String , dynamic > config = jsonDecode ( peer ) ;
if ( config [ ' info ' ] [ ' hostname ' ] is String ) {
String hostname = config [ ' info ' ] [ ' hostname ' ] ;
if ( hostname . isNotEmpty & &
! label . toLowerCase ( ) . contains ( hostname . toLowerCase ( ) ) ) {
label + = " @ $ hostname " ;
}
}
} catch ( e ) {
debugPrint ( " Failed to get hostname: $ e " ) ;
}
return label ;
}
2023-10-08 21:44:54 +08:00
sessionRefreshVideo ( SessionID sessionId , PeerInfo pi ) async {
if ( pi . currentDisplay = = kAllDisplayValue ) {
for ( int i = 0 ; i < pi . displays . length ; i + + ) {
await bind . sessionRefresh ( sessionId: sessionId , display: i ) ;
}
} else {
await bind . sessionRefresh ( sessionId: sessionId , display: pi . currentDisplay ) ;
}
}
2023-10-09 17:22:22 +08:00
bool isChooseDisplayToOpenInNewWindow ( PeerInfo pi , SessionID sessionId ) = >
pi . isSupportMultiDisplay & &
2023-10-16 07:26:55 +08:00
useTextureRender & &
2023-10-09 17:22:22 +08:00
bind . sessionGetDisplaysAsIndividualWindows ( sessionId: sessionId ) = = ' Y ' ;
2023-10-17 00:30:34 +08:00
Future < List < Rect > > getScreenListWayland ( ) async {
final screenRectList = < Rect > [ ] ;
if ( isMainDesktopWindow ) {
for ( var screen in await window_size . getScreenList ( ) ) {
final scale = kIgnoreDpi ? 1.0 : screen . scaleFactor ;
double l = screen . frame . left ;
double t = screen . frame . top ;
double r = screen . frame . right ;
double b = screen . frame . bottom ;
final rect = Rect . fromLTRB ( l / scale , t / scale , r / scale , b / scale ) ;
screenRectList . add ( rect ) ;
}
} else {
final screenList = await rustDeskWinManager . call (
WindowType . Main , kWindowGetScreenList , ' ' ) ;
try {
for ( var screen in jsonDecode ( screenList . result ) as List < dynamic > ) {
final scale = kIgnoreDpi ? 1.0 : screen [ ' scaleFactor ' ] ;
double l = screen [ ' frame ' ] [ ' l ' ] ;
double t = screen [ ' frame ' ] [ ' t ' ] ;
double r = screen [ ' frame ' ] [ ' r ' ] ;
double b = screen [ ' frame ' ] [ ' b ' ] ;
final rect = Rect . fromLTRB ( l / scale , t / scale , r / scale , b / scale ) ;
screenRectList . add ( rect ) ;
}
} catch ( e ) {
debugPrint ( ' Failed to parse screenList: $ e ' ) ;
}
}
return screenRectList ;
}
Future < List < Rect > > getScreenListNotWayland ( ) async {
final screenRectList = < Rect > [ ] ;
final displays = bind . mainGetDisplays ( ) ;
if ( displays . isEmpty ) {
return screenRectList ;
}
try {
for ( var display in jsonDecode ( displays ) as List < dynamic > ) {
// to-do: scale factor ?
// final scale = kIgnoreDpi ? 1.0 : screen.scaleFactor;
double l = display [ ' x ' ] . toDouble ( ) ;
double t = display [ ' y ' ] . toDouble ( ) ;
double r = ( display [ ' x ' ] + display [ ' w ' ] ) . toDouble ( ) ;
double b = ( display [ ' y ' ] + display [ ' h ' ] ) . toDouble ( ) ;
screenRectList . add ( Rect . fromLTRB ( l , t , r , b ) ) ;
}
} catch ( e ) {
debugPrint ( ' Failed to parse displays: $ e ' ) ;
}
return screenRectList ;
}
Future < List < Rect > > getScreenRectList ( ) async {
return bind . mainCurrentIsWayland ( )
? await getScreenListWayland ( )
: await getScreenListNotWayland ( ) ;
}
openMonitorInTheSameTab ( int i , FFI ffi , PeerInfo pi ) {
final displays = i = = kAllDisplayValue
? List . generate ( pi . displays . length , ( index ) = > index )
: [ i ] ;
bind . sessionSwitchDisplay (
sessionId: ffi . sessionId , value: Int32List . fromList ( displays ) ) ;
ffi . ffiModel . switchToNewDisplay ( i , ffi . sessionId , ffi . id ) ;
}
// Open new tab or window to show this monitor.
// For now just open new window.
//
// screenRect is used to move the new window to the specified screen and set fullscreen.
openMonitorInNewTabOrWindow ( int i , String peerId , PeerInfo pi ,
{ Rect ? screenRect } ) {
final args = {
' window_id ' : stateGlobal . windowId ,
' peer_id ' : peerId ,
' display ' : i ,
' display_count ' : pi . displays . length ,
} ;
if ( screenRect ! = null ) {
args [ ' screen_rect ' ] = {
' l ' : screenRect . left ,
' t ' : screenRect . top ,
' r ' : screenRect . right ,
' b ' : screenRect . bottom ,
} ;
}
DesktopMultiWindow . invokeMethod (
kMainWindowId , kWindowEventOpenMonitorSession , jsonEncode ( args ) ) ;
}
tryMoveToScreenAndSetFullscreen ( Rect ? screenRect ) async {
if ( screenRect = = null ) {
return ;
}
2023-10-17 07:43:12 +08:00
final wc = WindowController . fromWindowId ( stateGlobal . windowId ) ;
final curFrame = await wc . getFrame ( ) ;
2023-10-17 00:30:34 +08:00
final frame =
Rect . fromLTWH ( screenRect . left + 30 , screenRect . top + 30 , 600 , 400 ) ;
2023-10-17 14:29:14 +08:00
if ( stateGlobal . fullscreen . isTrue & &
2023-10-17 07:43:12 +08:00
curFrame . left < = frame . left & &
curFrame . top < = frame . top & &
curFrame . width > = frame . width & &
curFrame . height > = frame . height ) {
return ;
}
await wc . setFrame ( frame ) ;
2023-10-17 00:30:34 +08:00
// An duration is needed to avoid the window being restored after fullscreen.
Future . delayed ( Duration ( milliseconds: 300 ) , ( ) async {
stateGlobal . setFullscreen ( true ) ;
} ) ;
}
2023-10-17 09:36:08 +08:00
parseParamScreenRect ( Map < String , dynamic > params ) {
Rect ? screenRect ;
if ( params [ ' screen_rect ' ] ! = null ) {
double l = params [ ' screen_rect ' ] [ ' l ' ] ;
double t = params [ ' screen_rect ' ] [ ' t ' ] ;
double r = params [ ' screen_rect ' ] [ ' r ' ] ;
double b = params [ ' screen_rect ' ] [ ' b ' ] ;
screenRect = Rect . fromLTRB ( l , t , r , b ) ;
}
return screenRect ;
}
2023-10-21 15:25:01 +08:00
class _ReconnectCountDownButton extends StatefulWidget {
_ReconnectCountDownButton ( {
Key ? key ,
required this . second ,
required this . onPressed ,
} ) : super ( key: key ) ;
final VoidCallback ? onPressed ;
final int second ;
@ override
State < _ReconnectCountDownButton > createState ( ) = >
_ReconnectCountDownButtonState ( ) ;
}
class _ReconnectCountDownButtonState extends State < _ReconnectCountDownButton > {
late int _countdownSeconds = widget . second ;
Timer ? _timer ;
@ override
void initState ( ) {
super . initState ( ) ;
_startCountdownTimer ( ) ;
}
@ override
void dispose ( ) {
_timer ? . cancel ( ) ;
super . dispose ( ) ;
}
void _startCountdownTimer ( ) {
_timer = Timer . periodic ( Duration ( seconds: 1 ) , ( timer ) {
if ( _countdownSeconds < = 0 ) {
timer . cancel ( ) ;
} else {
setState ( ( ) {
_countdownSeconds - - ;
} ) ;
}
} ) ;
}
@ override
Widget build ( BuildContext context ) {
return dialogButton (
' ${ translate ( ' Reconnect ' ) } ( ${ _countdownSeconds } s) ' ,
onPressed: widget . onPressed ,
isOutline: true ,
) ;
}
}