rustdesk/flutter/lib/main.dart

412 lines
12 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/desktop/pages/server_page.dart';
import 'package:flutter_hbb/desktop/pages/install_page.dart';
import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
import 'package:bot_toast/bot_toast.dart';
// import 'package:window_manager/window_manager.dart';
import 'common.dart';
import 'consts.dart';
import 'mobile/pages/home_page.dart';
import 'mobile/pages/server_page.dart';
import 'models/platform_model.dart';
/// Basic window and launch properties.
int? kWindowId;
WindowType? kWindowType;
late List<String> kBootArgs;
Future<void> main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
debugPrint("launch args: $args");
kBootArgs = List.from(args);
if (!isDesktop) {
runMobileApp();
return;
}
// main window
if (args.isNotEmpty && args.first == 'multi_window') {
kWindowId = int.parse(args[1]);
stateGlobal.setWindowId(kWindowId!);
if (!Platform.isMacOS) {
WindowController.fromWindowId(kWindowId!).showTitleBar(false);
}
final argument = args[2].isEmpty
? <String, dynamic>{}
: jsonDecode(args[2]) as Map<String, dynamic>;
int type = argument['type'] ?? -1;
// to-do: No need to parse window id ?
// Because stateGlobal.windowId is a global value.
argument['windowId'] = kWindowId;
kWindowType = type.windowType;
final windowName = getWindowName();
switch (kWindowType) {
case WindowType.RemoteDesktop:
desktopType = DesktopType.remote;
runMultiWindow(
argument,
kAppTypeDesktopRemote,
windowName,
);
break;
case WindowType.FileTransfer:
desktopType = DesktopType.fileTransfer;
runMultiWindow(
argument,
kAppTypeDesktopFileTransfer,
windowName,
);
break;
case WindowType.PortForward:
desktopType = DesktopType.portForward;
runMultiWindow(
argument,
kAppTypeDesktopPortForward,
windowName,
);
break;
default:
break;
}
} else if (args.isNotEmpty && args.first == '--cm') {
debugPrint("--cm started");
desktopType = DesktopType.cm;
await windowManager.ensureInitialized();
runConnectionManagerScreen(args.contains('--hide'));
} else if (args.contains('--install')) {
runInstallPage();
} else {
desktopType = DesktopType.main;
await windowManager.ensureInitialized();
windowManager.setPreventClose(true);
runMainApp(true);
}
}
Future<void> initEnv(String appType) async {
// global shared preference
await platformFFI.init(appType);
// global FFI, use this **ONLY** for global configuration
// for convenience, use global FFI on mobile platform
// focus on multi-ffi on desktop first
await initGlobalFFI();
// await Firebase.initializeApp();
_registerEventHandler();
}
void runMainApp(bool startService) async {
// register uni links
initUniLinks();
await initEnv(kAppTypeMain);
// trigger connection status updater
await bind.mainCheckConnectStatus();
if (startService) {
// await windowManager.ensureInitialized();
gFFI.serverModel.startService();
}
gFFI.userModel.refreshCurrentUser();
runApp(App());
// restore the location of the main window before window hide or show
await restoreWindowPosition(WindowType.Main);
// check the startup argument, if we successfully handle the argument, we keep the main window hidden.
if (checkArguments()) {
windowManager.hide();
} else {
windowManager.show();
windowManager.focus();
// move registration of active main window here to prevent async visible check.
rustDeskWinManager.registerActiveWindow(kWindowMainId);
}
// set window option
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
windowManager.waitUntilReadyToShow(windowOptions, () async {
windowManager.setOpacity(1);
});
windowManager.setTitle(getWindowName());
}
void runMobileApp() async {
await initEnv(kAppTypeMain);
if (isAndroid) androidChannelInit();
runApp(App());
}
void runMultiWindow(
Map<String, dynamic> argument,
String appType,
String title,
) async {
await initEnv(appType);
// set prevent close to true, we handle close event manually
WindowController.fromWindowId(kWindowId!).setPreventClose(true);
late Widget widget;
switch (appType) {
case kAppTypeDesktopRemote:
widget = DesktopRemoteScreen(
params: argument,
);
break;
case kAppTypeDesktopFileTransfer:
widget = DesktopFileTransferScreen(
params: argument,
);
break;
case kAppTypeDesktopPortForward:
widget = DesktopPortForwardScreen(
params: argument,
);
break;
default:
// no such appType
exit(0);
}
_runApp(
title,
widget,
MyTheme.currentThemeMode(),
);
// we do not hide titlebar on win7 because of the frame overflow.
if (kUseCompatibleUiMode) {
WindowController.fromWindowId(kWindowId!).showTitleBar(true);
}
switch (appType) {
case kAppTypeDesktopRemote:
await restoreWindowPosition(WindowType.RemoteDesktop,
windowId: kWindowId!);
break;
case kAppTypeDesktopFileTransfer:
await restoreWindowPosition(WindowType.FileTransfer,
windowId: kWindowId!);
break;
case kAppTypeDesktopPortForward:
await restoreWindowPosition(WindowType.PortForward, windowId: kWindowId!);
break;
default:
// no such appType
exit(0);
}
// show window from hidden status
WindowController.fromWindowId(kWindowId!).show();
}
void runConnectionManagerScreen(bool hide) async {
await initEnv(kAppTypeMain);
_runApp(
'',
const DesktopServerPage(),
MyTheme.currentThemeMode(),
);
if (hide) {
hideCmWindow();
} else {
showCmWindow();
}
}
void showCmWindow() {
WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await Future.wait([windowManager.focus(), windowManager.setOpacity(1)]);
// ensure initial window size to be changed
await windowManager.setSizeAlignment(
kConnectionManagerWindowSize, Alignment.topRight);
});
}
void hideCmWindow() {
WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
windowManager.setOpacity(0);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.hide();
});
}
void _runApp(
String title,
Widget home,
ThemeMode themeMode,
) {
final botToastBuilder = BotToastInit();
runApp(RefreshWrapper(
builder: (context) => GetMaterialApp(
navigatorKey: globalKey,
debugShowCheckedModeBanner: false,
title: title,
theme: MyTheme.lightTheme,
darkTheme: MyTheme.darkTheme,
themeMode: themeMode,
home: home,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
navigatorObservers: [
// FirebaseAnalyticsObserver(analytics: analytics),
BotToastNavigatorObserver(),
],
builder: (context, child) {
child = _keepScaleBuilder(context, child);
child = botToastBuilder(context, child);
return child;
},
),
));
}
void runInstallPage() async {
await windowManager.ensureInitialized();
await initEnv(kAppTypeMain);
_runApp('', const InstallPage(), ThemeMode.light);
windowManager.waitUntilReadyToShow(
WindowOptions(size: Size(800, 600), center: true), () async {
windowManager.show();
windowManager.focus();
windowManager.setOpacity(1);
windowManager.setAlignment(Alignment.center); // ensure
});
}
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
var defaultTitleBarStyle = TitleBarStyle.hidden;
// we do not hide titlebar on win7 because of the frame overflow.
if (kUseCompatibleUiMode) {
defaultTitleBarStyle = TitleBarStyle.normal;
}
return WindowOptions(
size: size,
center: false,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: defaultTitleBarStyle,
);
}
class App extends StatefulWidget {
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.window.onPlatformBrightnessChanged = () {
final userPreference = MyTheme.getThemeModePreference();
if (userPreference != ThemeMode.system) return;
WidgetsBinding.instance.handlePlatformBrightnessChanged();
final systemIsDark =
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
final ThemeMode to;
if (systemIsDark) {
to = ThemeMode.dark;
} else {
to = ThemeMode.light;
}
Get.changeThemeMode(to);
if (desktopType == DesktopType.main) {
bind.mainChangeTheme(dark: to.toShortString());
}
};
}
@override
Widget build(BuildContext context) {
// final analytics = FirebaseAnalytics.instance;
final botToastBuilder = BotToastInit();
return RefreshWrapper(builder: (context) {
return MultiProvider(
providers: [
// global configuration
// use session related FFI when in remote control or file transfer page
ChangeNotifierProvider.value(value: gFFI.ffiModel),
ChangeNotifierProvider.value(value: gFFI.imageModel),
ChangeNotifierProvider.value(value: gFFI.cursorModel),
ChangeNotifierProvider.value(value: gFFI.canvasModel),
],
child: GetMaterialApp(
navigatorKey: globalKey,
debugShowCheckedModeBanner: false,
title: 'RustDesk',
theme: MyTheme.lightTheme,
darkTheme: MyTheme.darkTheme,
themeMode: MyTheme.currentThemeMode(),
home: isDesktop
? const DesktopTabPage()
: !isAndroid
? WebHomePage()
: HomePage(),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
navigatorObservers: [
// FirebaseAnalyticsObserver(analytics: analytics),
BotToastNavigatorObserver(),
],
builder: isAndroid
? (context, child) => AccessibilityListener(
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: 1.0,
),
child: child ?? Container(),
),
)
: (context, child) {
child = _keepScaleBuilder(context, child);
child = botToastBuilder(context, child);
return child;
},
),
);
});
}
}
Widget _keepScaleBuilder(BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor: 1.0,
),
child: child ?? Container(),
);
}
_registerEventHandler() {
if (isDesktop && desktopType != DesktopType.main) {
platformFFI.registerEventHandler('theme', 'theme', (evt) async {
String? dark = evt['dark'];
if (dark != null) {
MyTheme.changeDarkMode(MyTheme.themeModeFromString(dark));
}
});
platformFFI.registerEventHandler('language', 'language', (_) async {
reloadAllWindows();
});
}
}