diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4e6c8ab14..63ab39df2 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1,12 +1,15 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:ffi' hide Size; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:device_info_plus/device_info_plus.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:win32/win32.dart' as win32; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -42,6 +45,7 @@ var isWeb = false; var isWebDesktop = false; var version = ""; int androidVersion = 0; + /// only avaliable for Windows target int windowsBuildNumber = 0; DesktopType? desktopType; @@ -1412,11 +1416,12 @@ Timer periodic_immediate(Duration duration, Future Function() callback) { await callback(); }); } + /// return a human readable windows version WindowsTarget getWindowsTarget(int buildNumber) { if (!Platform.isWindows) { return WindowsTarget.naw; - } + } if (buildNumber >= 22000) { return WindowsTarget.w11; } else if (buildNumber >= 10240) { @@ -1434,3 +1439,47 @@ WindowsTarget getWindowsTarget(int buildNumber) { return WindowsTarget.xp; } } + +/// 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), + void Function(Pointer)>('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 getOSVERSIONINFOEXPointer() { + final pointer = calloc(); + pointer.ref + ..dwOSVersionInfoSize = sizeOf() + ..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); diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 506399e6f..daf9272f7 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -266,7 +266,8 @@ class DesktopTab extends StatelessWidget { Widget build(BuildContext context) { return Column(children: [ Obx(() => Offstage( - offstage: !stateGlobal.showTabBar.isTrue, + offstage: !stateGlobal.showTabBar.isTrue || + (kUseCompatibleUiMode && isHideSingleItem()), child: SizedBox( height: _kTabBarHeight, child: Column( @@ -335,6 +336,15 @@ class DesktopTab extends StatelessWidget { .toList(growable: false)))); } + /// Check whether to show ListView + /// + /// Conditions: + /// - hide single item when only has one item (home) on [DesktopTabPage]. + bool isHideSingleItem() { + return state.value.tabs.length == 1 && + controller.tabType == DesktopTabType.main; + } + Widget _buildBar() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -362,23 +372,26 @@ class DesktopTab extends StatelessWidget { child: const SizedBox( width: 78, )), - Row(children: [ - Offstage( - offstage: !showLogo, - child: SvgPicture.asset( - 'assets/logo.svg', - width: 16, - height: 16, - )), - Offstage( - offstage: !showTitle, - child: const Text( - "RustDesk", - style: TextStyle(fontSize: 13), - ).marginOnly(left: 2)) - ]).marginOnly( - left: 5, - right: 10, + Offstage( + offstage: kUseCompatibleUiMode, + child: Row(children: [ + Offstage( + offstage: !showLogo, + child: SvgPicture.asset( + 'assets/logo.svg', + width: 16, + height: 16, + )), + Offstage( + offstage: !showTitle, + child: const Text( + "RustDesk", + style: TextStyle(fontSize: 13), + ).marginOnly(left: 2)) + ]).marginOnly( + left: 5, + right: 10, + ), ), Expanded( child: Listener( @@ -407,6 +420,7 @@ class DesktopTab extends StatelessWidget { unSelectedTabBackgroundColor))), ], ))), + // hide simulated action buttons when we in compatible ui mode, because of reusing system title bar. WindowActionPanel( isMainWindow: isMainWindow, tabType: tabType, @@ -530,50 +544,59 @@ class WindowActionPanelState extends State children: [ Offstage(offstage: widget.tail == null, child: widget.tail), Offstage( - offstage: !widget.showMinimize, - child: ActionIcon( - message: 'Minimize', - icon: IconFont.min, - onTap: () { - if (widget.isMainWindow) { - windowManager.minimize(); - } else { - WindowController.fromWindowId(windowId!).minimize(); - } - }, - isClose: false, - )), - Offstage( - offstage: !widget.showMaximize, - child: Obx(() => ActionIcon( - message: widget.isMaximized.value ? "Restore" : "Maximize", - icon: widget.isMaximized.value - ? IconFont.restore - : IconFont.max, - onTap: _toggleMaximize, - isClose: false, - ))), - Offstage( - offstage: !widget.showClose, - child: ActionIcon( - message: 'Close', - icon: IconFont.close, - onTap: () async { - final res = await widget.onClose?.call() ?? true; - if (res) { - // hide for all window - // note: the main window can be restored by tray icon - Future.delayed(Duration.zero, () async { - if (widget.isMainWindow) { - await windowManager.close(); - } else { - await WindowController.fromWindowId(windowId!).close(); - } - }); - } - }, - isClose: true, - )), + offstage: kUseCompatibleUiMode, + child: Row( + children: [ + Offstage( + offstage: !widget.showMinimize, + child: ActionIcon( + message: 'Minimize', + icon: IconFont.min, + onTap: () { + if (widget.isMainWindow) { + windowManager.minimize(); + } else { + WindowController.fromWindowId(windowId!).minimize(); + } + }, + isClose: false, + )), + Offstage( + offstage: !widget.showMaximize, + child: Obx(() => ActionIcon( + message: + widget.isMaximized.value ? "Restore" : "Maximize", + icon: widget.isMaximized.value + ? IconFont.restore + : IconFont.max, + onTap: _toggleMaximize, + isClose: false, + ))), + Offstage( + offstage: !widget.showClose, + child: ActionIcon( + message: 'Close', + icon: IconFont.close, + onTap: () async { + final res = await widget.onClose?.call() ?? true; + if (res) { + // hide for all window + // note: the main window can be restored by tray icon + Future.delayed(Duration.zero, () async { + if (widget.isMainWindow) { + await windowManager.close(); + } else { + await WindowController.fromWindowId(windowId!) + .close(); + } + }); + } + }, + isClose: true, + )) + ], + ), + ), ], ); } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 08095e54c..2015c02b2 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -177,8 +177,7 @@ void runMultiWindow( MyTheme.currentThemeMode(), ); // we do not hide titlebar on win7 because of the frame overflow. - if (Platform.isWindows && - const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion)) { + if (kUseCompatibleUiMode) { WindowController.fromWindowId(windowId!).showTitleBar(true); } switch (appType) { @@ -283,8 +282,7 @@ void runInstallPage() async { WindowOptions getHiddenTitleBarWindowOptions({Size? size}) { var defaultTitleBarStyle = TitleBarStyle.hidden; // we do not hide titlebar on win7 because of the frame overflow. - if (Platform.isWindows && - const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion)) { + if (kUseCompatibleUiMode) { defaultTitleBarStyle = TitleBarStyle.normal; } return WindowOptions( diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index be80bb65b..68b85968a 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:win32/win32.dart' as win32; import '../common.dart'; import '../generated_bridge.dart'; @@ -131,7 +132,7 @@ class PlatformFFI { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; name = '${androidInfo.brand}-${androidInfo.model}'; id = androidInfo.id.hashCode.toString(); - androidVersion = androidInfo.version.sdkInt; + androidVersion = androidInfo.version.sdkInt ?? 0; } else if (Platform.isIOS) { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; name = iosInfo.utsname.machine ?? ''; @@ -142,12 +143,14 @@ class PlatformFFI { id = linuxInfo.machineId ?? linuxInfo.id; } else if (Platform.isWindows) { try { + // request windows build number to fix overflow on win7 + windowsBuildNumber = getWindowsTargetBuildNumber(); WindowsDeviceInfo winInfo = await deviceInfo.windowsInfo; name = winInfo.computerName; id = winInfo.computerName; - windowsBuildNumber = winInfo.buildNumber; - } catch (e) { - debugPrint("$e"); + } catch (e, stacktrace) { + debugPrint("get windows device info failed: $e"); + debugPrintStack(stackTrace: stacktrace); name = "unknown"; id = "unknown"; } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index f84cbedcd..8de0be4d6 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: provider: ^6.0.3 tuple: ^2.0.0 wakelock: ^0.6.2 - device_info_plus: ^8.0.0 + device_info_plus: ^4.1.2 #firebase_analytics: ^9.1.5 package_info_plus: ^1.4.2 url_launcher: ^6.0.9 @@ -102,6 +102,8 @@ dependencies: path: ^1.8.1 auto_size_text: ^3.0.0 bot_toast: ^4.0.3 + win32: any + dev_dependencies: icons_launcher: ^2.0.4