433 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:async';
 | 
						|
import 'dart:convert';
 | 
						|
import 'dart:io';
 | 
						|
 | 
						|
import 'package:bot_toast/bot_toast.dart';
 | 
						|
import 'package:desktop_multi_window/desktop_multi_window.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
 | 
						|
import 'package:flutter_hbb/desktop/pages/install_page.dart';
 | 
						|
import 'package:flutter_hbb/desktop/pages/server_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/models/state_model.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: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;
 | 
						|
 | 
						|
/// Uni links.
 | 
						|
StreamSubscription? _uniLinkSubscription;
 | 
						|
 | 
						|
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();
 | 
						|
  // Update the system theme.
 | 
						|
  updateSystemWindowTheme();
 | 
						|
}
 | 
						|
 | 
						|
void runMainApp(bool startService) async {
 | 
						|
  // register uni links
 | 
						|
  await initEnv(kAppTypeMain);
 | 
						|
  // trigger connection status updater
 | 
						|
  await bind.mainCheckConnectStatus();
 | 
						|
  if (startService) {
 | 
						|
    // await windowManager.ensureInitialized();
 | 
						|
    gFFI.serverModel.startService();
 | 
						|
  }
 | 
						|
  gFFI.userModel.refreshCurrentUser();
 | 
						|
  runApp(App());
 | 
						|
  // Set window option.
 | 
						|
  WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
 | 
						|
  windowManager.waitUntilReadyToShow(windowOptions, () async {
 | 
						|
    // 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.
 | 
						|
    final handledByUniLinks = await initUniLinks();
 | 
						|
    final handledByCli = checkArguments();
 | 
						|
    debugPrint(
 | 
						|
        "handled by uni links: $handledByUniLinks, handled by cli: $handledByCli");
 | 
						|
    if (handledByUniLinks || handledByCli) {
 | 
						|
      windowManager.hide();
 | 
						|
    } else {
 | 
						|
      windowManager.show();
 | 
						|
      windowManager.focus();
 | 
						|
      // Move registration of active main window here to prevent from async visible check.
 | 
						|
      rustDeskWinManager.registerActiveWindow(kWindowMainId);
 | 
						|
    }
 | 
						|
    windowManager.setOpacity(1);
 | 
						|
    windowManager.setTitle(getWindowName());
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
void runMobileApp() async {
 | 
						|
  await initEnv(kAppTypeMain);
 | 
						|
  if (isAndroid) androidChannelInit();
 | 
						|
  platformFFI.syncAndroidServiceAppDirConfigPath();
 | 
						|
  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(kAppTypeConnectionManager);
 | 
						|
  await bind.cmStartListenIpcThread();
 | 
						|
  _runApp(
 | 
						|
    '',
 | 
						|
    const DesktopServerPage(),
 | 
						|
    MyTheme.currentThemeMode(),
 | 
						|
  );
 | 
						|
  if (hide) {
 | 
						|
    hideCmWindow();
 | 
						|
  } else {
 | 
						|
    showCmWindow();
 | 
						|
  }
 | 
						|
  // Start the uni links handler and redirect links to Native, not for Flutter.
 | 
						|
  _uniLinkSubscription = listenUniLinks(handleByFlutter: false);
 | 
						|
}
 | 
						|
 | 
						|
void showCmWindow() {
 | 
						|
  WindowOptions windowOptions =
 | 
						|
      getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
 | 
						|
  windowManager.waitUntilReadyToShow(windowOptions, () async {
 | 
						|
    bind.mainHideDocker();
 | 
						|
    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 {
 | 
						|
    bind.mainHideDocker();
 | 
						|
    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(), MyTheme.currentThemeMode());
 | 
						|
  WindowOptions windowOptions =
 | 
						|
      getHiddenTitleBarWindowOptions(size: Size(800, 600), center: true);
 | 
						|
  windowManager.waitUntilReadyToShow(windowOptions, () async {
 | 
						|
    windowManager.show();
 | 
						|
    windowManager.focus();
 | 
						|
    windowManager.setOpacity(1);
 | 
						|
    windowManager.setAlignment(Alignment.center); // ensure
 | 
						|
    windowManager.setTitle(getWindowName());
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
WindowOptions getHiddenTitleBarWindowOptions(
 | 
						|
    {Size? size, bool center = false}) {
 | 
						|
  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: center,
 | 
						|
    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);
 | 
						|
      // Synchronize the window theme of the system.
 | 
						|
      updateSystemWindowTheme();
 | 
						|
      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),
 | 
						|
          ChangeNotifierProvider.value(value: gFFI.peerTabModel),
 | 
						|
        ],
 | 
						|
        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();
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 |