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-08-17 21:28:36 +08:00
import ' dart:typed_data ' ;
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 ' ;
import ' package:win32/win32.dart ' as win32 ;
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 ' ;
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 ' ;
2022-08-27 00:45:09 +08:00
import ' package:flutter_hbb/models/peer_model.dart ' ;
2022-09-16 12:14:03 +08:00
import ' package:flutter_hbb/utils/multi_window_manager.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 ' ;
import ' package:uni_links_desktop/uni_links_desktop.dart ' ;
2022-08-09 09:01:06 +08:00
import ' package:window_manager/window_manager.dart ' ;
2022-09-27 18:34:05 +08:00
import ' package:flutter_svg/flutter_svg.dart ' ;
2022-10-09 19:27:30 +08:00
import ' package:window_size/window_size.dart ' as window_size ;
2022-10-14 11:19:49 +08:00
import ' package:url_launcher/url_launcher.dart ' ;
2022-04-19 13:07:45 +08:00
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-10-09 19:27:30 +08:00
import ' ../consts.dart ' ;
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 ;
2022-03-17 21:03:52 +08:00
var version = " " ;
2022-03-24 17:58:33 +08:00
int androidVersion = 0 ;
2022-11-30 13:56:02 +08:00
2022-11-29 23:03:16 +08:00
/// only avaliable for Windows target
int windowsBuildNumber = 0 ;
2022-09-15 16:36:52 +08:00
DesktopType ? desktopType ;
2022-03-17 21:03:52 +08:00
2022-10-20 23:05:34 +09: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-10 19:50:48 -07:00
typedef StreamEventHandler = Future < void > Function ( Map < String , dynamic > ) ;
2022-10-20 10:31:31 +09:00
final iconKeyboard = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-08-17 21:28:36 +08:00
" iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA0Gd/0y8ILZgbJffDPUwV2nvzt+TMqZxyU7CMb1pYQyzsvKunkXE4AwJnNC24AAAA+0lEQVQ4y83O2U7DMBCF4ZMxk9rZk26kpQs7nPd/QJy4EiLbLf01N5Y/2YP/qxDFQvGB5NPC/ZpVnfJx4b5xyGfF95rkHvNCWH1u+N6J6T0sC7gqRy8uGPfBLEbozPXUjlkQKwGaFPNizwQbwkx0TDvhCii34ExZCSQVBdzIOEOyeclSHgBGXkpeygXSQgStACtWx4Z8rr8COHOvfEP/IbbsQAToFUAAV1M408IIjIGYAPoCSNRP7DQutfQTqxuAiH7UUg1FaJR2AGrrx52sK2ye28LZ0wBAEyR6y8X+NADhm1B4fgiiHXbRrTrxpwEY9RdM9wsepnvFHfUDwYEeiwAJr/gAAAAASUVORK5CYII= " ) ) ) ;
2022-10-20 10:31:31 +09:00
final iconClipboard = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-08-17 21:28:36 +08:00
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAjVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8DizOFAAAALnRSTlMAnIsyZy8YZF3NSAuabRL34cq6trCScyZ4qI9CQDwV+fPl2tnTwzkeB+m/pIFK/Xx0ewAAAQlJREFUOMudktduhDAQRWep69iY3tle0+7/f16Qg7MsJUQ5Dwh8jzRzhemJPIaf3GiW7eFQfOwDPp1ek/iMnKgBi5PrhJAhZAa1lCxE9pw5KWMswOMAQXuQOvqTB7tLFJ36wimKLrufZTzUaoRtdthqRA2vEwS+tR4qguiElRKk1YMrYfUQRkwLmwVBYDMvJKF8R0o3V2MOhNrfo+hXSYYjPn1L/S+n438t8gWh+q1F+cYFBMm1Jh8Ia7y2OWXQxMMRLqr2eTc1crSD84cWfEGwYM4LlaACEee2ZjsQXJxR3qmYb+GpC8ZfNM5oh3yxxbxgQE7lEkb3ZvvH1BiRHn1bu02ICcKGWr4AudUkyYxmvywAAAAASUVORK5CYII= ' ) ) ) ;
2022-10-20 10:31:31 +09:00
final iconAudio = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-08-17 21:28:36 +08:00
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAk1BMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ROyVeAAAAMHRSTlMAgfz08DDqCAThvraZjEcoGA751JxzbGdfTRP25NrIpaGTcEM+HAvMuKinhXhWNx9Yzm/gAAABFUlEQVQ4y82S2XLCMAxFheMsQNghCQFalkL39vz/11V4GpNk0r629+Va1pmxPFfyh1ravOP2Y1ydJmBO0lYP3r+PyQ62s2Y7fgF6VRXOYdToT++ogIuoVhCUtX7YpwJG3F8f6V8rr3WABwwUahlEvr8y3IBniGKdKYBQ5OGQpukQakBpIVcfwptIhJcf8hWGakdndAAhBInIGHbdQGJg6jjbDUgEE5EpmB+AAM4uj6gb+AQT6wdhITLvAHJ4VCtgoAlG1tpNA0gWON/f4ioHdSADc1bfgt+PZFkDlD6ojWF+kVoaHlhvFjPHuVRrefohY1GdcFm1N8JvwEyrJ/X2Th2rIoVgIi3Fo6Xf0z5k8psKu5f/oi+nHjjI92o36AAAAABJRU5ErkJggg== ' ) ) ) ;
2022-10-20 10:31:31 +09:00
final iconFile = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-08-17 21:28:36 +08:00
' iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg== ' ) ) ) ;
2022-10-20 10:31:31 +09:00
final iconRestart = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-08-18 19:49:41 +08:00
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC ' ) ) ) ;
2022-10-20 10:31:31 +09:00
final iconRecording = MemoryImage ( Uint8List . fromList ( base64Decode (
2022-09-22 09:55:34 +08:00
' iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAANpJREFUWEftltENAiEMhtsJ1NcynG6gI+gGugEOR591gppeQoIYSDBILxEeydH/57u2FMF4obE+TAOTwLoIhBDOAHBExG2n6rgR0akW640AM0sn4SWMiDycc7s8JjN7Ijro/k8NqAAR5RoeAPZxv2ggP9hCJiWZxtGbq3hqbJiBVHy4gVx8qAER8Yi4JFy6huVAKXemgb8icI+1b5KEitq0DOO/Nm1EEX1TK27p/bVvv36MOhl4EtHHbFF7jq8AoG1z08OAiFycczrkFNe6RrIet26NMQlMAuYEXiayryF/QQktAAAAAElFTkSuQmCC ' ) ) ) ;
2022-10-20 10:31:31 +09: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 ) ;
2022-08-20 19:57:16 +08:00
}
class ColorThemeExtension extends ThemeExtension < ColorThemeExtension > {
const ColorThemeExtension ( {
required this . border ,
} ) ;
final Color ? border ;
static const light = ColorThemeExtension (
border: Color ( 0xFFCCCCCC ) ,
) ;
static const dark = ColorThemeExtension (
border: Color ( 0xFF555555 ) ,
) ;
@ override
2022-09-23 16:31:50 +08:00
ThemeExtension < ColorThemeExtension > copyWith ( { Color ? border } ) {
2022-08-20 19:57:16 +08:00
return ColorThemeExtension (
border: border ? ? this . border ,
) ;
}
@ override
ThemeExtension < ColorThemeExtension > lerp (
ThemeExtension < ColorThemeExtension > ? other , double t ) {
if ( other is ! ColorThemeExtension ) {
return this ;
}
return ColorThemeExtension (
border: Color . lerp ( border , other . border , t ) ,
) ;
}
}
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
2020-11-16 01:13:26 +08:00
static const Color grayBg = Color ( 0xFFEEEEEE ) ;
static const Color white = Color ( 0xFFFFFFFF ) ;
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 ) ;
static const Color darkGray = Color ( 0xFFB9BABC ) ;
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 ) ;
2022-07-29 16:47:24 +08:00
static ThemeData lightTheme = ThemeData (
brightness: Brightness . light ,
2022-09-23 16:31:50 +08:00
backgroundColor: Color ( 0xFFFFFFFF ) ,
scaffoldBackgroundColor: Color ( 0xFFEEEEEE ) ,
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 ) ) ,
2022-09-23 16:31:50 +08:00
hintColor: Color ( 0xFFAAAAAA ) ,
2022-07-29 16:47:24 +08:00
primarySwatch: Colors . blue ,
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 ,
) ,
2022-08-22 17:58:48 +08:00
splashColor: Colors . transparent ,
highlightColor: Colors . transparent ,
2022-09-23 12:20:40 +08:00
splashFactory: isDesktop ? NoSplash . splashFactory : null ,
textButtonTheme: isDesktop
? TextButtonThemeData (
style: ButtonStyle ( splashFactory: NoSplash . splashFactory ) ,
)
: null ,
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 ,
2022-09-23 16:31:50 +08:00
backgroundColor: Color ( 0xFF252525 ) ,
scaffoldBackgroundColor: Color ( 0xFF141414 ) ,
textTheme: const TextTheme (
titleLarge: TextStyle ( fontSize: 19 ) ,
2022-09-27 19:42:05 +08:00
titleSmall: TextStyle ( fontSize: 14 ) ,
2022-09-26 11:21:40 +08:00
bodySmall: TextStyle ( fontSize: 12 , height: 1.25 ) ,
2022-09-29 13:07:20 +08:00
bodyMedium: TextStyle ( fontSize: 14 , height: 1.25 ) ,
labelLarge: TextStyle (
fontSize: 16.0 , fontWeight: FontWeight . bold , color: accent80 ) ) ,
2022-09-23 17:16:25 +08:00
cardColor: Color ( 0xFF252525 ) ,
2022-08-20 19:57:16 +08:00
primarySwatch: Colors . blue ,
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 ,
) ,
2022-08-22 17:58:48 +08:00
splashColor: Colors . transparent ,
highlightColor: Colors . transparent ,
2022-09-23 12:20:40 +08:00
splashFactory: isDesktop ? NoSplash . splashFactory : null ,
textButtonTheme: isDesktop
? TextButtonThemeData (
style: ButtonStyle ( splashFactory: NoSplash . splashFactory ) ,
)
: null ,
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
}
static void changeDarkMode ( ThemeMode mode ) {
final preference = getThemeModePreference ( ) ;
if ( preference ! = mode ) {
if ( mode = = ThemeMode . system ) {
2022-11-10 21:25:12 +08:00
bind . mainSetLocalOption ( key: kCommConfKeyTheme , value: ' ' ) ;
2022-09-21 23:32:59 +08:00
} else {
2022-11-10 21:25:12 +08:00
bind . mainSetLocalOption (
key: kCommConfKeyTheme , value: mode . toShortString ( ) ) ;
2022-09-21 23:32:59 +08:00
}
Get . changeThemeMode ( mode ) ;
2022-09-07 18:57:49 +08:00
if ( desktopType = = DesktopType . main ) {
2022-09-21 23:32:59 +08:00
bind . mainChangeTheme ( dark: currentThemeMode ( ) . toShortString ( ) ) ;
2022-09-07 18:57:49 +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 [
// specify CN/TW to fix CJK issue in flutter
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 ' ) ,
Locale ( ' en ' , ' US ' ) ,
] ;
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 ) {
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
}
2022-08-09 19:32:19 +08:00
void window_on_top ( int ? id ) {
if ( id = = null ) {
// main window
windowManager . restore ( ) ;
windowManager . show ( ) ;
windowManager . focus ( ) ;
2022-11-26 11:40:13 +08:00
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 (
2022-04-19 13:07:45 +08:00
StateSetter setState , void Function ( [ dynamic ] ) close ) ;
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
}
}
}
class OverlayDialogManager {
OverlayState ? _overlayState ;
2022-09-08 22:18:02 +08:00
final Map < String , Dialog > _dialogs = { } ;
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 ;
2022-08-12 18:42:02 +08:00
/// By default OverlayDialogManager use global overlay
OverlayDialogManager ( ) {
_overlayState = globalKey . currentState ? . overlay ;
2022-02-28 18:29:25 +08:00
}
2022-03-13 00:32:44 +08:00
2022-08-12 18:42:02 +08:00
void setOverlayState ( OverlayState ? overlayState ) {
_overlayState = overlayState ;
}
void dismissAll ( ) {
_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 =
forceGlobal ? globalKey . currentState ? . overlay : _overlayState ;
if ( overlayState = = null ) {
return Future . error (
" [OverlayDialogManager] Failed to show dialog, _overlayState is null, call [setOverlayState] first " ) ;
}
2022-10-09 19:57:38 +09:00
final String dialogTag ;
2022-04-19 13:07:45 +08:00
if ( tag ! = null ) {
2022-10-09 19:57:38 +09:00
dialogTag = tag ;
2022-04-19 13:07:45 +08:00
} else {
2022-10-09 19:57:38 +09: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 19:57:38 +09:00
_dialogs [ dialogTag ] = dialog ;
2022-08-12 18:42:02 +08:00
2022-10-09 19:57:38 +09:00
close ( [ res ] ) {
_dialogs . remove ( dialogTag ) ;
2022-08-12 18:42:02 +08:00
dialog . complete ( res ) ;
2022-10-09 19:57:38 +09:00
BackButtonInterceptor . removeByName ( dialogTag ) ;
}
2022-08-12 18:42:02 +08:00
dialog . entry = OverlayEntry ( builder: ( _ ) {
2022-08-15 14:39:31 +08:00
bool innerClicked = false ;
return Listener (
onPointerUp: ( _ ) {
if ( ! innerClicked & & clickMaskDismiss ) {
close ( ) ;
}
innerClicked = false ;
} ,
child: Container (
color: Colors . black12 ,
child: StatefulBuilder ( builder: ( context , setState ) {
return Listener (
onPointerUp: ( _ ) = > innerClicked = true ,
child: builder ( setState , close ) ,
) ;
} ) ) ) ;
2022-08-12 18:42:02 +08:00
} ) ;
overlayState . insert ( dialog . entry ! ) ;
BackButtonInterceptor . add ( ( stopDefaultButtonEvent , routeInfo ) {
if ( backDismiss ) {
close ( ) ;
}
return true ;
2022-10-09 19:57:38 +09:00
} , name: dialogTag ) ;
2022-08-12 18:42:02 +08:00
return dialog . completer . future ;
}
2022-10-09 19:57:38 +09:00
String showLoading ( String text ,
2022-08-15 14:39:31 +08:00
{ bool clickMaskDismiss = false ,
bool showCancel = true ,
VoidCallback ? onCancel } ) {
2022-10-09 19:57:38 +09:00
final tag = _tagCount . toString ( ) ;
_tagCount + + ;
2022-09-03 18:19:50 +08:00
show ( ( setState , close ) {
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 (
child: TextButton (
style: flatButtonStyle ,
2022-09-03 18:19:50 +08:00
onPressed: cancel ,
2022-08-15 14:39:31 +08:00
child: Text ( translate ( ' Cancel ' ) ,
2022-09-03 18:19:50 +08:00
style:
const TextStyle ( color: MyTheme . accent ) ) ) ) )
] ) ) ,
onCancel: showCancel ? cancel : null ,
) ;
2022-10-09 19:57:38 +09: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 ;
if ( _overlayState = = null ) return ;
// 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 ( ) ,
) ;
} ) ;
_overlayState ! . insert ( overlay ) ;
_mobileActionsOverlayEntry = overlay ;
}
void hideMobileActionsOverlay ( ) {
if ( _mobileActionsOverlayEntry ! = null ) {
_mobileActionsOverlayEntry ! . remove ( ) ;
_mobileActionsOverlayEntry = null ;
return ;
}
}
void toggleMobileActionsOverlay ( { FFI ? ffi } ) {
if ( _mobileActionsOverlayEntry = = null ) {
showMobileActionsOverlay ( ffi: ffi ) ;
} else {
hideMobileActionsOverlay ( ) ;
}
}
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 ,
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
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 ,
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 ;
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 ) {
2022-09-03 18:19:50 +08:00
FocusNode focusNode = FocusNode ( ) ;
// request focus if there is no focused FocusNode in the dialog
Future . delayed ( Duration . zero , ( ) {
if ( ! focusNode . hasFocus ) focusNode . requestFocus ( ) ;
} ) ;
return Focus (
focusNode: focusNode ,
autofocus: true ,
onKey: ( node , key ) {
if ( key . logicalKey = = LogicalKeyboardKey . escape ) {
if ( key is RawKeyDownEvent ) {
onCancel ? . call ( ) ;
}
return KeyEventResult . handled ; // avoid TextField exception on escape
} else if ( onSubmit ! = null & &
key . logicalKey = = LogicalKeyboardKey . enter ) {
if ( key is RawKeyDownEvent ) onSubmit ? . call ( ) ;
return KeyEventResult . handled ;
}
return KeyEventResult . ignored ;
} ,
child: AlertDialog (
scrollable: true ,
title: title ,
contentPadding: EdgeInsets . symmetric (
horizontal: contentPadding ? ? 25 , vertical: 10 ) ,
2022-09-29 21:09:40 +08:00
content:
ConstrainedBox ( constraints: contentBoxConstraints , child: content ) ,
2022-09-03 18:19:50 +08:00
actions: actions ,
) ,
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
2022-11-15 16:49:55 +08:00
void msgBox ( String id , String type , String title , String text , String link ,
2022-10-14 11:19:49 +08:00
OverlayDialogManager dialogManager ,
2022-08-12 18:42:02 +08:00
{ bool ? hasCancel } ) {
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 ;
buttons . insert ( 0 , msgBoxButton ( translate ( ' OK ' ) , 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 ) {
2022-09-03 18:19:50 +08:00
buttons . insert ( 0 , msgBoxButton ( translate ( ' Cancel ' ) , cancel ) ) ;
2020-11-29 01:36:10 +08:00
}
2022-08-08 22:00:01 +08:00
// TODO: test this button
2022-08-31 18:41:55 +08:00
if ( type . contains ( " hasclose " ) ) {
2022-08-08 22:00:01 +08:00
buttons . insert (
0 ,
2022-08-31 23:02:02 -07:00
msgBoxButton ( translate ( ' Close ' ) , ( ) {
2022-08-12 18:42:02 +08:00
dialogManager . dismissAll ( ) ;
2022-08-08 22:00:01 +08:00
} ) ) ;
}
2022-10-14 11:19:49 +08:00
if ( link . isNotEmpty ) {
buttons . insert ( 0 , msgBoxButton ( translate ( ' JumpLink ' ) , jumplink ) ) ;
}
2022-11-15 16:49:55 +08:00
dialogManager . show (
( setState , close ) = > CustomAlertDialog (
title: _msgBoxTitle ( title ) ,
content:
SelectableText ( translate ( text ) , style: const TextStyle ( fontSize: 15 ) ) ,
actions: buttons ,
onSubmit: hasOk ? submit : null ,
onCancel: hasCancel = = true ? cancel : null ,
) ,
tag: ' $ id - $ type - $ title - $ text - $ link ' ,
) ;
2020-11-21 14:40:28 +08:00
}
2020-11-25 18:33:09 +08:00
2022-08-31 23:02:02 -07:00
Widget msgBoxButton ( String text , void Function ( ) onPressed ) {
2022-08-31 18:41:55 +08:00
return ButtonTheme (
padding: EdgeInsets . symmetric ( horizontal: 20 , vertical: 10 ) ,
materialTapTargetSize: MaterialTapTargetSize . shrinkWrap ,
//limits the touch area to the button area
minWidth: 0 ,
//wraps child's width
height: 0 ,
child: TextButton (
style: flatButtonStyle ,
onPressed: onPressed ,
child:
Text ( translate ( text ) , style: TextStyle ( color: MyTheme . accent ) ) ) ) ;
}
2022-09-03 18:19:50 +08:00
Widget _msgBoxTitle ( String title ) = >
Text ( translate ( title ) , style: TextStyle ( fontSize: 21 ) ) ;
2022-08-31 23:02:02 -07: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 } ) {
2022-08-31 18:41:55 +08:00
dialogManager . dismissAll ( ) ;
dialogManager . show ( ( setState , close ) = > CustomAlertDialog (
2022-09-03 18:19:50 +08:00
title: _msgBoxTitle ( title ) ,
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
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-19 17:15:37 +01: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-19 17:15:37 +01: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
class PermissionManager {
static Completer < bool > ? _completer ;
static Timer ? _timer ;
static var _current = " " ;
2022-07-16 22:31:44 +08:00
static final permissions = [
" audio " ,
" file " ,
" ignore_battery_optimizations " ,
" application_details_settings "
] ;
2022-04-20 22:37:47 +08:00
static bool isWaitingFile ( ) {
if ( _completer ! = null ) {
return ! _completer ! . isCompleted & & _current = = " file " ;
}
return false ;
}
static Future < bool > check ( String type ) {
2022-08-18 00:34:04 +08:00
if ( isDesktop ) {
return Future . value ( true ) ;
}
2022-09-20 19:31:32 +08:00
if ( ! permissions . contains ( type ) ) {
2022-04-20 22:37:47 +08:00
return Future . error ( " Wrong permission! $ type " ) ;
2022-09-20 19:31:32 +08:00
}
2022-06-13 21:07:26 +08:00
return gFFI . invokeMethod ( " check_permission " , type ) ;
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-09-20 19:31:32 +08:00
if ( ! permissions . contains ( type ) ) {
2022-04-20 22:37:47 +08:00
return Future . error ( " Wrong permission! $ type " ) ;
2022-09-20 19:31:32 +08:00
}
2022-04-20 22:37:47 +08:00
2022-08-01 14:33:08 +08:00
gFFI . invokeMethod ( " request_permission " , type ) ;
2022-07-14 17:44:37 +08:00
if ( type = = " ignore_battery_optimizations " ) {
return Future . value ( false ) ;
}
2022-04-20 22:37:47 +08:00
_current = type ;
_completer = Completer < bool > ( ) ;
2022-06-13 21:07:26 +08:00
gFFI . invokeMethod ( " request_permission " , type ) ;
2022-04-20 22:37:47 +08:00
// timeout
_timer ? . cancel ( ) ;
_timer = Timer ( Duration ( seconds: 60 ) , ( ) {
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
2022-07-29 18:34:25 +08:00
RadioListTile < T > getRadio < T > (
2022-09-27 22:56:18 +08:00
String name , T toValue , T curValue , void Function ( T ? ) onChange ,
{ EdgeInsetsGeometry ? contentPadding } ) {
2022-07-29 18:34:25 +08:00
return RadioListTile < T > (
2022-09-27 22:56:18 +08:00
contentPadding: contentPadding ,
2022-07-29 18:34:25 +08:00
controlAffinity: ListTileControlAffinity . trailing ,
title: Text ( translate ( name ) ) ,
value: toValue ,
groupValue: curValue ,
onChanged: onChange ,
dense: true ,
) ;
}
2022-08-01 10:44:05 +08:00
2022-08-05 20:29:43 +08:00
CheckboxListTile getToggle (
2022-08-15 20:26:20 +08:00
String id , void Function ( void Function ( ) ) setState , option , name ,
{ FFI ? ffi } ) {
2022-08-16 15:22:57 +08:00
final opt = bind . sessionGetToggleOptionSync ( id: id , arg: option ) ;
2022-08-05 20:29:43 +08:00
return CheckboxListTile (
value: opt ,
onChanged: ( v ) {
setState ( ( ) {
bind . sessionToggleOption ( id: id , value: option ) ;
} ) ;
if ( option = = " show-quality-monitor " ) {
2022-08-15 20:26:20 +08:00
( ffi ? ? gFFI ) . qualityMonitorModel . checkShowQualityMonitor ( id ) ;
2022-08-05 20:29:43 +08:00
}
} ,
dense: true ,
title: Text ( translate ( name ) ) ) ;
}
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 " ) ;
2022-06-13 21:07:26 +08:00
_globalFFI = FFI ( ) ;
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 " | |
option = = " stop-rendezvous-service " ) {
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 " | |
option = = " stop-rendezvous-service " ) {
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
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 ;
}
final alias = await bind . mainGetPeerOption ( id: peer . id , key: ' alias ' ) ;
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 } ) {
platform = platform . toLowerCase ( ) ;
if ( platform = = ' mac os ' ) {
platform = ' mac ' ;
} else if ( platform ! = ' linux ' & & platform ! = ' android ' ) {
platform = ' win ' ;
}
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 19:48:41 +09:00
bool ? isMaximized ;
2022-09-16 12:14:03 +08:00
2022-10-14 19:48:41 +09:00
LastWindowPosition ( this . width , this . height , this . offsetWidth ,
this . offsetHeight , this . isMaximized ) ;
2022-09-16 12:14:03 +08:00
Map < String , dynamic > toJson ( ) {
return < String , dynamic > {
" width " : width ,
" height " : height ,
" offsetWidth " : offsetWidth ,
2022-10-14 19:48:41 +09:00
" offsetHeight " : offsetHeight ,
" isMaximized " : isMaximized ,
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 19:48:41 +09:00
return LastWindowPosition ( m [ " width " ] , m [ " height " ] , m [ " offsetWidth " ] ,
m [ " offsetHeight " ] , m [ " isMaximized " ] ) ;
2022-09-16 12:14:03 +08:00
} catch ( e ) {
debugPrint ( e . toString ( ) ) ;
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 " ) ;
}
switch ( type ) {
case WindowType . Main:
2022-10-14 19:48:41 +09:00
final position = await windowManager . getPosition ( ) ;
final sz = await windowManager . getSize ( ) ;
final isMaximized = await windowManager . isMaximized ( ) ;
final pos = LastWindowPosition (
sz . width , sz . height , position . dx , position . dy , isMaximized ) ;
2022-11-10 21:25:12 +08:00
await bind . setLocalFlutterConfig (
k: kWindowPrefix + type . name , v: pos . toString ( ) ) ;
2022-09-16 12:14:03 +08:00
break ;
default :
2022-10-14 19:48:41 +09:00
final wc = WindowController . fromWindowId ( windowId ! ) ;
final frame = await wc . getFrame ( ) ;
final position = frame . topLeft ;
final sz = frame . size ;
final isMaximized = await wc . isMaximized ( ) ;
final pos = LastWindowPosition (
sz . width , sz . height , position . dx , position . dy , isMaximized ) ;
2022-11-10 21:25:12 +08:00
debugPrint (
" saving frame: $ windowId : ${ pos . width } / ${ pos . height } , offset: ${ pos . offsetWidth } / ${ pos . offsetHeight } " ) ;
await bind . setLocalFlutterConfig (
k: kWindowPrefix + type . name , v: pos . toString ( ) ) ;
2022-09-16 12:14:03 +08:00
break ;
}
}
2022-10-14 19:48:41 +09:00
Future < Size > _adjustRestoreMainWindowSize ( double ? width , double ? height ) async {
2022-10-09 19:27:30 +08:00
const double minWidth = 600 ;
const double minHeight = 100 ;
2022-10-10 09:56:27 +08:00
double maxWidth = ( ( ( isDesktop | | isWebDesktop )
? kDesktopMaxDisplayWidth
: kMobileMaxDisplayWidth ) )
. toDouble ( ) ;
2022-10-09 19:27:30 +08:00
double maxHeight = ( ( isDesktop | | isWebDesktop )
2022-10-10 09:56:27 +08:00
? kDesktopMaxDisplayHeight
: kMobileMaxDisplayHeight )
. toDouble ( ) ;
2022-10-09 19:27:30 +08:00
if ( isDesktop | | isWebDesktop ) {
final screen = ( await window_size . getWindowInfo ( ) ) . screen ;
if ( screen ! = null ) {
maxWidth = screen . visibleFrame . width ;
maxHeight = screen . visibleFrame . height ;
}
}
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 ) {
restoreWidth = minWidth ;
}
if ( restoreHeight < minHeight ) {
restoreHeight = minHeight ;
}
if ( restoreWidth > maxWidth ) {
restoreWidth = maxWidth ;
}
if ( restoreHeight > maxHeight ) {
2022-11-09 15:14:11 +08:00
restoreHeight = maxHeight ;
2022-10-09 19:27:30 +08:00
}
2022-10-14 19:48:41 +09:00
return Size ( restoreWidth , restoreHeight ) ;
2022-10-09 19:27:30 +08:00
}
2022-10-14 19:48:41 +09:00
/// return null means center
Future < Offset ? > _adjustRestoreMainWindowOffset (
double ? left , double ? top ) async {
2022-10-09 19:27:30 +08:00
if ( left = = null | | top = = null ) {
await windowManager . center ( ) ;
} else {
2022-11-09 15:14:11 +08:00
double windowLeft = max ( 0.0 , left ) ;
double windowTop = max ( 0.0 , top ) ;
2022-10-09 19:27:30 +08:00
2022-11-09 15:14:11 +08:00
double frameLeft = double . infinity ;
double frameTop = double . infinity ;
2022-10-09 19:27:30 +08:00
double frameRight = ( ( isDesktop | | isWebDesktop )
2022-10-10 09:56:27 +08:00
? kDesktopMaxDisplayWidth
: kMobileMaxDisplayWidth )
. toDouble ( ) ;
2022-10-09 19:27:30 +08:00
double frameBottom = ( ( isDesktop | | isWebDesktop )
2022-10-10 09:56:27 +08:00
? kDesktopMaxDisplayHeight
: kMobileMaxDisplayHeight )
. toDouble ( ) ;
2022-10-09 19:27:30 +08:00
if ( isDesktop | | isWebDesktop ) {
2022-11-10 21:25:12 +08:00
for ( final screen in await window_size . getScreenList ( ) ) {
2022-11-09 15:14:11 +08:00
frameLeft = min ( screen . visibleFrame . left , frameLeft ) ;
frameTop = min ( screen . visibleFrame . top , frameTop ) ;
frameRight = max ( screen . visibleFrame . right , frameRight ) ;
frameBottom = max ( screen . visibleFrame . bottom , frameBottom ) ;
2022-10-09 19:27:30 +08:00
}
}
if ( windowLeft < frameLeft | |
windowLeft > frameRight | |
windowTop < frameTop | |
windowTop > frameBottom ) {
2022-10-14 19:48:41 +09:00
return null ;
2022-10-09 19:27:30 +08:00
} else {
2022-10-14 19:48:41 +09:00
return Offset ( windowLeft , windowTop ) ;
2022-10-09 19:27:30 +08:00
}
}
2022-10-14 19:48:41 +09:00
return null ;
2022-10-09 19:27:30 +08:00
}
2022-10-14 19:48:41 +09: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
Future < bool > restoreWindowPosition ( WindowType type , { int ? windowId } ) async {
if ( type ! = WindowType . Main & & windowId = = null ) {
debugPrint (
" Error: windowId cannot be null when saving positions for sub window " ) ;
}
2022-11-10 21:25:12 +08:00
final pos = bind . getLocalFlutterConfig ( k: kWindowPrefix + type . name ) ;
2022-10-14 19:48:41 +09:00
var lpos = LastWindowPosition . loadFromString ( pos ) ;
if ( lpos = = null ) {
debugPrint ( " window position saved, but cannot be parsed " ) ;
return false ;
}
2022-09-16 12:14:03 +08:00
switch ( type ) {
case WindowType . Main:
2022-10-14 19:48:41 +09:00
if ( lpos . isMaximized = = true ) {
await windowManager . maximize ( ) ;
} else {
final size =
await _adjustRestoreMainWindowSize ( lpos . width , lpos . height ) ;
final offset = await _adjustRestoreMainWindowOffset (
lpos . offsetWidth , lpos . offsetHeight ) ;
await windowManager . setSize ( size ) ;
if ( offset = = null ) {
await windowManager . center ( ) ;
} else {
await windowManager . setPosition ( offset ) ;
}
2022-09-16 12:14:03 +08:00
}
return true ;
default :
2022-10-14 19:48:41 +09:00
final wc = WindowController . fromWindowId ( windowId ! ) ;
if ( lpos . isMaximized = = true ) {
await wc . maximize ( ) ;
} else {
final size =
await _adjustRestoreMainWindowSize ( lpos . width , lpos . height ) ;
final offset = await _adjustRestoreMainWindowOffset (
lpos . offsetWidth , lpos . offsetHeight ) ;
2022-11-10 21:25:12 +08:00
debugPrint (
" restore lpos: ${ size . width } / ${ size . height } , offset: ${ offset ? . dx } / ${ offset ? . dy } " ) ;
2022-10-14 19:48:41 +09:00
if ( offset = = null ) {
await wc . center ( ) ;
} else {
final frame =
Rect . fromLTWH ( offset . dx , offset . dy , size . width , size . height ) ;
await wc . setFrame ( frame ) ;
}
}
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.
Future < void > initUniLinks ( ) async {
if ( ! Platform . isWindows & & ! Platform . isMacOS ) {
return ;
}
if ( Platform . isWindows ) {
registerProtocol ( ' rustdesk ' ) ;
}
// check cold boot
try {
final initialLink = await getInitialLink ( ) ;
2022-10-19 09:54:04 +08:00
if ( initialLink = = null ) {
return ;
}
parseRustdeskUri ( initialLink ) ;
} catch ( err ) {
debugPrint ( " $ err " ) ;
2022-10-18 10:29:33 +08:00
}
}
2022-10-31 16:35:14 +08:00
StreamSubscription ? listenUniLinks ( ) {
if ( ! ( Platform . isWindows | | Platform . isMacOS ) ) {
return null ;
2022-10-18 10:29:33 +08:00
}
2022-10-31 16:35:14 +08:00
final sub = uriLinkStream . listen ( ( Uri ? uri ) {
if ( uri ! = null ) {
callUniLinksUriHandler ( uri ) ;
} 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
}
2022-11-26 11:40:13 +08:00
/// Returns true if we successfully handle the startup arguments.
bool checkArguments ( ) {
2022-10-11 19:52:03 +08:00
// check connect args
final connectIndex = bootArgs . indexOf ( " --connect " ) ;
if ( connectIndex = = - 1 ) {
2022-11-26 11:40:13 +08:00
return false ;
2022-10-11 19:52:03 +08:00
}
2022-10-12 21:57:19 +08:00
String ? arg =
bootArgs . length < connectIndex + 1 ? null : bootArgs [ connectIndex + 1 ] ;
if ( arg ! = null ) {
if ( arg . startsWith ( kUniLinksPrefix ) ) {
2022-11-26 11:40:13 +08:00
return parseRustdeskUri ( arg ) ;
2022-10-12 21:57:19 +08:00
} else {
2022-11-26 11:40:13 +08:00
// remove "--connect xxx" in the `bootArgs` array
2022-10-12 21:57:19 +08:00
bootArgs . removeAt ( connectIndex ) ;
bootArgs . removeAt ( connectIndex ) ;
2022-11-26 11:40:13 +08:00
// fallback to peer id
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newRemoteDesktop ( arg ) ;
} ) ;
return true ;
2022-10-12 21:57:19 +08:00
}
}
2022-11-26 11:40:13 +08:00
return false ;
2022-10-12 21:57:19 +08:00
}
/// Parse `rustdesk://` unilinks
2022-10-14 19:48:41 +09:00
///
2022-11-26 11:40:13 +08:00
/// Returns true if we successfully handle the uri provided.
2022-10-12 21:57:19 +08:00
/// [Functions]
/// 1. New Connection: rustdesk://connection/new/your_peer_id
2022-11-26 11:40:13 +08:00
bool parseRustdeskUri ( String uriPath ) {
2022-10-12 21:57:19 +08:00
final uri = Uri . tryParse ( uriPath ) ;
if ( uri = = null ) {
print ( " uri is not valid: $ uriPath " ) ;
2022-11-26 11:40:13 +08:00
return false ;
2022-10-12 21:57:19 +08:00
}
2022-11-26 11:40:13 +08:00
return callUniLinksUriHandler ( uri ) ;
2022-10-18 10:29:33 +08:00
}
/// uri handler
2022-11-26 11:40:13 +08:00
///
/// Returns true if we successfully handle the uri provided.
bool callUniLinksUriHandler ( Uri uri ) {
2022-10-19 09:54:04 +08:00
debugPrint ( " uni links called: $ uri " ) ;
2022-10-12 21:57:19 +08:00
// new connection
if ( uri . authority = = " connection " & & uri . path . startsWith ( " /new/ " ) ) {
final peerId = uri . path . substring ( " /new/ " . length ) ;
Future . delayed ( Duration . zero , ( ) {
rustDeskWinManager . newRemoteDesktop ( peerId ) ;
} ) ;
2022-11-26 11:40:13 +08:00
return true ;
2022-10-11 19:52:03 +08:00
}
2022-11-26 11:40:13 +08:00
return false ;
2022-10-11 19:52:03 +08:00
}
2022-12-01 13:52:12 +08:00
connectMainDesktop ( String id ,
{ required bool isFileTransfer ,
required bool isTcpTunneling ,
required bool isRDP } ) async {
if ( isFileTransfer ) {
await rustDeskWinManager . newFileTransfer ( id ) ;
} else if ( isTcpTunneling | | isRDP ) {
await rustDeskWinManager . newPortForward ( id , isRDP ) ;
} else {
await rustDeskWinManager . newRemoteDesktop ( id ) ;
}
}
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.
2022-12-01 13:52:12 +08:00
connect ( BuildContext context , String id ,
2022-09-19 20:26:39 +08:00
{ bool isFileTransfer = false ,
bool isTcpTunneling = false ,
bool isRDP = false } ) async {
if ( id = = ' ' ) return ;
id = id . replaceAll ( ' ' , ' ' ) ;
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 ) {
await connectMainDesktop (
id ,
isFileTransfer: isFileTransfer ,
isTcpTunneling: isTcpTunneling ,
isRDP: isRDP ,
) ;
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 ,
} ) ;
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 ) {
if ( ! await PermissionManager . check ( " file " ) ) {
if ( ! await PermissionManager . request ( " file " ) ) {
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
Future < Map < String , String > > getHttpHeaders ( ) async {
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-03 22:03:49 -07:00
// Simple wrapper of built-in types for refrence use.
class SimpleWrapper < T > {
2022-11-03 21:58:25 +08:00
T value ;
SimpleWrapper ( this . value ) ;
2022-10-03 22:03:49 -07: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]
/// Portable build is only avaliable on Windows.
bool isRunningInPortableMode ( ) {
if ( ! Platform . isWindows ) {
return false ;
}
return bool . hasEnvironment ( kEnvPortableExecutable ) ;
}
/// Window status callback
void onActiveWindowChanged ( ) async {
print (
" [MultiWindowHandler] active window changed: ${ rustDeskWinManager . getActiveWindows ( ) } " ) ;
if ( rustDeskWinManager . getActiveWindows ( ) . isEmpty ) {
// close all sub windows
try {
await Future . wait ( [
saveWindowPosition ( WindowType . Main ) ,
rustDeskWinManager . closeAllSubWindows ( )
] ) ;
} catch ( err ) {
debugPrint ( " $ err " ) ;
} finally {
await windowManager . setPreventClose ( false ) ;
await windowManager . close ( ) ;
}
}
}
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 ) ;