From 888e993534e1dfbcb7a9a17d28b8e0b9630b1795 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 12 Jul 2024 22:04:11 +0800 Subject: [PATCH] fix: unable to close on fullscreen (#8690) Signed-off-by: fufesou --- .../lib/desktop/widgets/tabbar_widget.dart | 401 ++++++++++-------- 1 file changed, 222 insertions(+), 179 deletions(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index c7d9e5493..513441113 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -230,8 +230,7 @@ typedef LabelGetter = Rx Function(String key); int _lastClickTime = DateTime.now().millisecondsSinceEpoch - bind.getDoubleClickTime() - 1000; -// ignore: must_be_immutable -class DesktopTab extends StatelessWidget { +class DesktopTab extends StatefulWidget { final bool showLogo; final bool showTitle; final bool showMinimize; @@ -252,12 +251,8 @@ class DesktopTab extends StatelessWidget { final DesktopTabController controller; - Rx get state => controller.state; final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50)); - late final DesktopTabType tabType; - late final bool isMainWindow; - final RxList invisibleTabKeys = RxList.empty(); DesktopTab({ @@ -279,18 +274,232 @@ class DesktopTab extends StatelessWidget { this.unSelectedTabBackgroundColor, this.selectedBorderColor, this.blockTab, - }) : super(key: key) { - tabType = controller.tabType; - isMainWindow = tabType == DesktopTabType.main || - tabType == DesktopTabType.cm || - tabType == DesktopTabType.install; - } + }) : super(key: key); static RxString tablabelGetter(String peerId) { final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias'); return RxString(getDesktopTabLabel(peerId, alias)); } + @override + State createState() { + return _DesktopTabState(); + } +} + +// ignore: must_be_immutable +class _DesktopTabState extends State + with MultiWindowListener, WindowListener { + final _saveFrameDebounce = Debouncer(delay: Duration(seconds: 1)); + Timer? _macOSCheckRestoreTimer; + int _macOSCheckRestoreCounter = 0; + + bool get showLogo => widget.showLogo; + bool get showTitle => widget.showTitle; + bool get showMinimize => widget.showMinimize; + bool get showMaximize => widget.showMaximize; + bool get showClose => widget.showClose; + Widget Function(Widget pageView)? get pageViewBuilder => + widget.pageViewBuilder; + TabMenuBuilder? get tabMenuBuilder => widget.tabMenuBuilder; + Widget? get tail => widget.tail; + Future Function()? get onWindowCloseButton => + widget.onWindowCloseButton; + TabBuilder? get tabBuilder => widget.tabBuilder; + LabelGetter? get labelGetter => widget.labelGetter; + double? get maxLabelWidth => widget.maxLabelWidth; + Color? get selectedTabBackgroundColor => widget.selectedTabBackgroundColor; + Color? get unSelectedTabBackgroundColor => + widget.unSelectedTabBackgroundColor; + Color? get selectedBorderColor => widget.selectedBorderColor; + RxBool? get blockTab => widget.blockTab; + DesktopTabController get controller => widget.controller; + RxList get invisibleTabKeys => widget.invisibleTabKeys; + Debouncer get _scrollDebounce => widget._scrollDebounce; + + Rx get state => controller.state; + + DesktopTabType get tabType => controller.tabType; + bool get isMainWindow => + tabType == DesktopTabType.main || + tabType == DesktopTabType.cm || + tabType == DesktopTabType.install; + + _DesktopTabState() : super(); + + static RxString tablabelGetter(String peerId) { + final alias = bind.mainGetPeerOptionSync(id: peerId, key: 'alias'); + return RxString(getDesktopTabLabel(peerId, alias)); + } + + @override + void initState() { + super.initState(); + DesktopMultiWindow.addListener(this); + windowManager.addListener(this); + + Future.delayed(Duration(milliseconds: 500), () { + if (isMainWindow) { + windowManager.isMaximized().then((maximized) { + if (stateGlobal.isMaximized.value != maximized) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => stateGlobal.setMaximized(maximized))); + } + }); + } else { + final wc = WindowController.fromWindowId(kWindowId!); + wc.isMaximized().then((maximized) { + debugPrint("isMaximized $maximized"); + if (stateGlobal.isMaximized.value != maximized) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => stateGlobal.setMaximized(maximized))); + } + }); + } + }); + } + + @override + void dispose() { + DesktopMultiWindow.removeListener(this); + windowManager.removeListener(this); + _macOSCheckRestoreTimer?.cancel(); + super.dispose(); + } + + void _setMaximized(bool maximize) { + stateGlobal.setMaximized(maximize); + _saveFrameDebounce.call(_saveFrame); + setState(() {}); + } + + @override + void onWindowFocus() { + stateGlobal.isFocused.value = true; + } + + @override + void onWindowBlur() { + stateGlobal.isFocused.value = false; + } + + @override + void onWindowMinimize() { + stateGlobal.setMinimized(true); + stateGlobal.setMaximized(false); + super.onWindowMinimize(); + } + + @override + void onWindowMaximize() { + stateGlobal.setMinimized(false); + _setMaximized(true); + super.onWindowMaximize(); + } + + @override + void onWindowUnmaximize() { + stateGlobal.setMinimized(false); + _setMaximized(false); + super.onWindowUnmaximize(); + } + + _saveFrame() async { + if (tabType == DesktopTabType.main) { + await saveWindowPosition(WindowType.Main); + } else if (kWindowType != null && kWindowId != null) { + await saveWindowPosition(kWindowType!, windowId: kWindowId); + } + } + + @override + void onWindowMoved() { + _saveFrameDebounce.call(_saveFrame); + super.onWindowMoved(); + } + + @override + void onWindowResized() { + _saveFrameDebounce.call(_saveFrame); + super.onWindowMoved(); + } + + @override + void onWindowClose() async { + mainWindowClose() async => await windowManager.hide(); + notMainWindowClose(WindowController windowController) async { + if (controller.length != 0) { + debugPrint("close not empty multiwindow from taskbar"); + if (isWindows) { + await windowController.show(); + await windowController.focus(); + final res = await onWindowCloseButton?.call() ?? true; + if (!res) return; + } + controller.clear(); + } + await windowController.hide(); + await rustDeskWinManager + .call(WindowType.Main, kWindowEventHide, {"id": kWindowId!}); + } + + macOSWindowClose( + Future Function() checkFullscreen, + Future Function() closeFunc, + ) async { + _macOSCheckRestoreCounter = 0; + _macOSCheckRestoreTimer = + Timer.periodic(Duration(milliseconds: 30), (timer) async { + _macOSCheckRestoreCounter++; + if (!await checkFullscreen() || _macOSCheckRestoreCounter >= 30) { + _macOSCheckRestoreTimer?.cancel(); + _macOSCheckRestoreTimer = null; + Timer(Duration(milliseconds: 700), () async => await closeFunc()); + } + }); + } + + // hide window on close + if (isMainWindow) { + if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) { + await rustDeskWinManager.unregisterActiveWindow(kMainWindowId); + } + // macOS specific workaround, the window is not hiding when in fullscreen. + if (isMacOS && await windowManager.isFullScreen()) { + await windowManager.setFullScreen(false); + await macOSWindowClose( + () async => await windowManager.isFullScreen(), + mainWindowClose, + ); + } else { + await mainWindowClose(); + } + } else { + // it's safe to hide the subwindow + final controller = WindowController.fromWindowId(kWindowId!); + if (isMacOS) { + // onWindowClose() maybe called multiple times because of loopCloseWindow() in remote_tab_page.dart. + // use ??= to make sure the value is set on first call. + + if (await onWindowCloseButton?.call() ?? true) { + if (await controller.isFullScreen()) { + await controller.setFullscreen(false); + stateGlobal.setFullscreen(false, procWnd: false); + await macOSWindowClose( + () async => await controller.isFullScreen(), + () async => await notMainWindowClose(controller), + ); + } else { + await notMainWindowClose(controller); + } + } + } else { + await notMainWindowClose(controller); + } + } + super.onWindowClose(); + } + @override Widget build(BuildContext context) { return Column(children: [ @@ -468,7 +677,6 @@ class DesktopTab extends StatelessWidget { // hide simulated action buttons when we in compatible ui mode, because of reusing system title bar. WindowActionPanel( isMainWindow: isMainWindow, - tabType: tabType, state: state, tabController: controller, invisibleTabKeys: invisibleTabKeys, @@ -486,7 +694,6 @@ class DesktopTab extends StatelessWidget { class WindowActionPanel extends StatefulWidget { final bool isMainWindow; - final DesktopTabType tabType; final Rx state; final DesktopTabController tabController; @@ -502,7 +709,6 @@ class WindowActionPanel extends StatefulWidget { const WindowActionPanel( {Key? key, required this.isMainWindow, - required this.tabType, required this.state, required this.tabController, required this.invisibleTabKeys, @@ -520,180 +726,17 @@ class WindowActionPanel extends StatefulWidget { } } -class WindowActionPanelState extends State - with MultiWindowListener, WindowListener { - final _saveFrameDebounce = Debouncer(delay: Duration(seconds: 1)); - Timer? _macOSCheckRestoreTimer; - int _macOSCheckRestoreCounter = 0; - +class WindowActionPanelState extends State { @override void initState() { super.initState(); - DesktopMultiWindow.addListener(this); - windowManager.addListener(this); - - Future.delayed(Duration(milliseconds: 500), () { - if (widget.isMainWindow) { - windowManager.isMaximized().then((maximized) { - if (stateGlobal.isMaximized.value != maximized) { - WidgetsBinding.instance.addPostFrameCallback( - (_) => setState(() => stateGlobal.setMaximized(maximized))); - } - }); - } else { - final wc = WindowController.fromWindowId(kWindowId!); - wc.isMaximized().then((maximized) { - debugPrint("isMaximized $maximized"); - if (stateGlobal.isMaximized.value != maximized) { - WidgetsBinding.instance.addPostFrameCallback( - (_) => setState(() => stateGlobal.setMaximized(maximized))); - } - }); - } - }); } @override void dispose() { - DesktopMultiWindow.removeListener(this); - windowManager.removeListener(this); - _macOSCheckRestoreTimer?.cancel(); super.dispose(); } - void _setMaximized(bool maximize) { - stateGlobal.setMaximized(maximize); - _saveFrameDebounce.call(_saveFrame); - setState(() {}); - } - - @override - void onWindowFocus() { - stateGlobal.isFocused.value = true; - } - - @override - void onWindowBlur() { - stateGlobal.isFocused.value = false; - } - - @override - void onWindowMinimize() { - stateGlobal.setMinimized(true); - stateGlobal.setMaximized(false); - super.onWindowMinimize(); - } - - @override - void onWindowMaximize() { - stateGlobal.setMinimized(false); - _setMaximized(true); - super.onWindowMaximize(); - } - - @override - void onWindowUnmaximize() { - stateGlobal.setMinimized(false); - _setMaximized(false); - super.onWindowUnmaximize(); - } - - _saveFrame() async { - if (widget.tabType == DesktopTabType.main) { - await saveWindowPosition(WindowType.Main); - } else if (kWindowType != null && kWindowId != null) { - await saveWindowPosition(kWindowType!, windowId: kWindowId); - } - } - - @override - void onWindowMoved() { - _saveFrameDebounce.call(_saveFrame); - super.onWindowMoved(); - } - - @override - void onWindowResized() { - _saveFrameDebounce.call(_saveFrame); - super.onWindowMoved(); - } - - @override - void onWindowClose() async { - mainWindowClose() async => await windowManager.hide(); - notMainWindowClose(WindowController controller) async { - if (widget.tabController.length != 0) { - debugPrint("close not empty multiwindow from taskbar"); - if (isWindows) { - await controller.show(); - await controller.focus(); - final res = await widget.onClose?.call() ?? true; - if (!res) return; - } - widget.tabController.clear(); - } - await controller.hide(); - await rustDeskWinManager - .call(WindowType.Main, kWindowEventHide, {"id": kWindowId!}); - } - - macOSWindowClose( - Future Function() checkFullscreen, - Future Function() closeFunc, - ) async { - _macOSCheckRestoreCounter = 0; - _macOSCheckRestoreTimer = - Timer.periodic(Duration(milliseconds: 30), (timer) async { - _macOSCheckRestoreCounter++; - if (!await checkFullscreen() || _macOSCheckRestoreCounter >= 30) { - _macOSCheckRestoreTimer?.cancel(); - _macOSCheckRestoreTimer = null; - Timer(Duration(milliseconds: 700), () async => await closeFunc()); - } - }); - } - - // hide window on close - if (widget.isMainWindow) { - if (rustDeskWinManager.getActiveWindows().contains(kMainWindowId)) { - await rustDeskWinManager.unregisterActiveWindow(kMainWindowId); - } - // macOS specific workaround, the window is not hiding when in fullscreen. - if (isMacOS && await windowManager.isFullScreen()) { - await windowManager.setFullScreen(false); - await macOSWindowClose( - () async => await windowManager.isFullScreen(), - mainWindowClose, - ); - } else { - await mainWindowClose(); - } - } else { - // it's safe to hide the subwindow - final controller = WindowController.fromWindowId(kWindowId!); - if (isMacOS) { - // onWindowClose() maybe called multiple times because of loopCloseWindow() in remote_tab_page.dart. - // use ??= to make sure the value is set on first call. - - if (await widget.onClose?.call() ?? true) { - if (await controller.isFullScreen()) { - await controller.setFullscreen(false); - stateGlobal.setFullscreen(false, procWnd: false); - await macOSWindowClose( - () async => await controller.isFullScreen(), - () async => await notMainWindowClose(controller), - ); - } else { - await notMainWindowClose(controller); - } - } - } else { - await notMainWindowClose(controller); - } - } - super.onWindowClose(); - } - bool showTabDowndown() { return widget.tabController.state.value.tabs.length > 1 && (widget.tabController.tabType == DesktopTabType.remoteScreen ||