From 5820e27aa9742f5aa15ad045c41c92db9aac5805 Mon Sep 17 00:00:00 2001 From: ihmily <114978440+ihmily@users.noreply.github.com> Date: Wed, 27 May 2026 15:26:43 +0800 Subject: [PATCH 1/3] fix: honor hidden startup mode in Windows desktop runners --- client/windows/runner/flutter_window.cpp | 10 ++++++++-- client/windows/runner/utils.cpp | 6 ++++++ client/windows/runner/utils.h | 4 ++++ .../windows/runner/flutter_window.cpp | 10 ++++++++-- .../{{cookiecutter.out_dir}}/windows/runner/utils.cpp | 6 ++++++ .../{{cookiecutter.out_dir}}/windows/runner/utils.h | 4 ++++ 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/client/windows/runner/flutter_window.cpp b/client/windows/runner/flutter_window.cpp index 955ee3038f..5126f5b848 100644 --- a/client/windows/runner/flutter_window.cpp +++ b/client/windows/runner/flutter_window.cpp @@ -3,6 +3,7 @@ #include #include "flutter/generated_plugin_registrant.h" +#include "utils.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} @@ -27,8 +28,13 @@ bool FlutterWindow::OnCreate() { RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); + const bool hide_window_on_start = + HasEnvironmentVariable(L"FLET_HIDE_WINDOW_ON_START"); + flutter_controller_->engine()->SetNextFrameCallback([this, + hide_window_on_start]() { + if (!hide_window_on_start) { + Show(); + } }); // Flutter can complete the first frame before the "show window" callback is diff --git a/client/windows/runner/utils.cpp b/client/windows/runner/utils.cpp index b2b08734db..147ffd4532 100644 --- a/client/windows/runner/utils.cpp +++ b/client/windows/runner/utils.cpp @@ -41,6 +41,12 @@ std::vector GetCommandLineArguments() { return command_line_arguments; } +bool HasEnvironmentVariable(const wchar_t* name) { + ::SetLastError(ERROR_SUCCESS); + const DWORD value_length = ::GetEnvironmentVariableW(name, nullptr, 0); + return value_length > 0 || ::GetLastError() != ERROR_ENVVAR_NOT_FOUND; +} + std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); diff --git a/client/windows/runner/utils.h b/client/windows/runner/utils.h index 3879d54755..7e315e6187 100644 --- a/client/windows/runner/utils.h +++ b/client/windows/runner/utils.h @@ -16,4 +16,8 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string); // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); +// Returns true when the given environment variable exists, even if it is set +// to an empty value. +bool HasEnvironmentVariable(const wchar_t* name); + #endif // RUNNER_UTILS_H_ diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/flutter_window.cpp b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/flutter_window.cpp index 955ee3038f..5126f5b848 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/flutter_window.cpp +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/flutter_window.cpp @@ -3,6 +3,7 @@ #include #include "flutter/generated_plugin_registrant.h" +#include "utils.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} @@ -27,8 +28,13 @@ bool FlutterWindow::OnCreate() { RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); + const bool hide_window_on_start = + HasEnvironmentVariable(L"FLET_HIDE_WINDOW_ON_START"); + flutter_controller_->engine()->SetNextFrameCallback([this, + hide_window_on_start]() { + if (!hide_window_on_start) { + Show(); + } }); // Flutter can complete the first frame before the "show window" callback is diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.cpp b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.cpp index 3a0b46511a..c6bae6a1f9 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.cpp +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.cpp @@ -41,6 +41,12 @@ std::vector GetCommandLineArguments() { return command_line_arguments; } +bool HasEnvironmentVariable(const wchar_t* name) { + ::SetLastError(ERROR_SUCCESS); + const DWORD value_length = ::GetEnvironmentVariableW(name, nullptr, 0); + return value_length > 0 || ::GetLastError() != ERROR_ENVVAR_NOT_FOUND; +} + std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); diff --git a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.h b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.h index 3879d54755..7e315e6187 100644 --- a/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.h +++ b/sdk/python/templates/build/{{cookiecutter.out_dir}}/windows/runner/utils.h @@ -16,4 +16,8 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string); // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); +// Returns true when the given environment variable exists, even if it is set +// to an empty value. +bool HasEnvironmentVariable(const wchar_t* name); + #endif // RUNNER_UTILS_H_ From 740eb6ae73fecf7e8bd329f73c9f83123a089e70 Mon Sep 17 00:00:00 2001 From: ihmily <114978440+ihmily@users.noreply.github.com> Date: Fri, 29 May 2026 14:23:21 +0800 Subject: [PATCH 2/3] fix: honor hidden startup mode in linux desktop runners --- client/linux/my_application.cc | 4 +- packages/flet/lib/src/services/window.dart | 160 ++++++++++++++++----- 2 files changed, 125 insertions(+), 39 deletions(-) diff --git a/client/linux/my_application.cc b/client/linux/my_application.cc index bd4cc2d9ec..7bc5ee4d3d 100644 --- a/client/linux/my_application.cc +++ b/client/linux/my_application.cc @@ -48,7 +48,9 @@ static void my_application_activate(GApplication* application) { } gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); + // Realize the native window without showing it so Dart can decide when to + // display and position the window. + gtk_widget_realize(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); diff --git a/packages/flet/lib/src/services/window.dart b/packages/flet/lib/src/services/window.dart index d5fbd3c0a7..d144032da9 100644 --- a/packages/flet/lib/src/services/window.dart +++ b/packages/flet/lib/src/services/window.dart @@ -5,6 +5,7 @@ import 'package:window_manager/window_manager.dart'; import '../flet_backend.dart'; import '../flet_service.dart'; +import '../models/window_state.dart'; import '../utils/alignment.dart'; import '../utils/colors.dart'; import '../utils/desktop.dart'; @@ -51,6 +52,10 @@ class WindowService extends FletService with WindowListener { bool? _skipTaskBar; double? _progressBar; bool? _ignoreMouseEvents; + Alignment? _deferredAlignmentOnShow; + bool _deferredCenterOnShow = false; + bool _isClosing = false; + bool _applyingDeferredPlacement = false; bool _listenersAttached = false; WindowService({required super.control}); @@ -68,22 +73,7 @@ class WindowService extends FletService with WindowListener { Future _initWindowState() async { try { final windowState = await getWindowState(); - _width = windowState.width; - _height = windowState.height; - _top = windowState.top; - _left = windowState.left; - _opacity = windowState.opacity; - _minimizable = windowState.minimizable; - _maximizable = windowState.maximizable; - _fullScreen = windowState.fullScreen; - _resizable = windowState.resizable; - _alwaysOnTop = windowState.alwaysOnTop; - _preventClose = windowState.preventClose; - _minimized = windowState.minimized; - _maximized = windowState.maximized; - _visible = windowState.visible; - _focused = windowState.focused; - _skipTaskBar = windowState.skipTaskBar; + _cacheWindowState(windowState); if (!_listenersAttached) { windowManager.addListener(this); @@ -130,6 +120,77 @@ class WindowService extends FletService with WindowListener { }); } + bool _shouldDeferPlacementUntilShow() { + return isLinuxDesktop() && (_visible == null || _visible == false); + } + + void _cacheWindowState(WindowState state) { + _width = state.width; + _height = state.height; + _top = state.top; + _left = state.left; + _opacity = state.opacity; + _minimizable = state.minimizable; + _maximizable = state.maximizable; + _fullScreen = state.fullScreen; + _resizable = state.resizable; + _alwaysOnTop = state.alwaysOnTop; + _preventClose = state.preventClose; + _minimized = state.minimized; + _maximized = state.maximized; + _visible = state.visible; + _focused = state.focused; + _skipTaskBar = state.skipTaskBar; + } + + WindowState _snapshotWindowState({bool? visible, bool? focused}) { + return WindowState( + maximized: _maximized ?? false, + minimized: _minimized ?? false, + fullScreen: _fullScreen ?? false, + alwaysOnTop: _alwaysOnTop ?? false, + focused: focused ?? _focused ?? false, + visible: visible ?? _visible ?? false, + minimizable: _minimizable ?? true, + maximizable: _maximizable ?? true, + resizable: _resizable ?? true, + preventClose: _preventClose ?? false, + skipTaskBar: _skipTaskBar ?? false, + width: _width ?? 0, + height: _height ?? 0, + top: _top ?? 0, + left: _left ?? 0, + opacity: _opacity ?? 1, + ); + } + + Future _applyDeferredPlacementAfterShow() async { + if (!isLinuxDesktop() || + _applyingDeferredPlacement || + (_deferredAlignmentOnShow == null && !_deferredCenterOnShow)) { + return; + } + + _applyingDeferredPlacement = true; + final deferredAlignment = _deferredAlignmentOnShow; + final deferredCenter = _deferredCenterOnShow; + _deferredAlignmentOnShow = null; + _deferredCenterOnShow = false; + + try { + if (deferredAlignment != null) { + await setWindowAlignment(deferredAlignment, false); + } + if (deferredCenter) { + await centerWindow(); + } + } catch (e) { + debugPrint("Error applying deferred window placement: $e"); + } finally { + _applyingDeferredPlacement = false; + } + } + Future _updateWindow(FletBackend backend) async { if (!isDesktopPlatform()) { return; @@ -264,7 +325,12 @@ class WindowService extends FletService with WindowListener { } if (alignment != null && alignment != _alignment) { - await setWindowAlignment(alignment); + if (_shouldDeferPlacementUntilShow()) { + _deferredAlignmentOnShow = alignment; + } else { + await setWindowAlignment(alignment); + _deferredAlignmentOnShow = null; + } _alignment = alignment; } @@ -386,12 +452,18 @@ class WindowService extends FletService with WindowListener { break; case "center": await _pendingWindowUpdate; - await centerWindow(); + if (_shouldDeferPlacementUntilShow()) { + _deferredAlignmentOnShow = null; + _deferredCenterOnShow = true; + } else { + await centerWindow(); + } break; case "close": await closeWindow(); break; case "destroy": + _isClosing = true; await destroyWindow(); break; case "start_dragging": @@ -427,25 +499,37 @@ class WindowService extends FletService with WindowListener { if (["resize", "resized", "move"].contains(eventName)) { return; } - getWindowState().then((state) { - _width = state.width; - _height = state.height; - _top = state.top; - _left = state.left; - _opacity = state.opacity; - _minimized = state.minimized; - _maximized = state.maximized; - _minimizable = state.minimizable; - _maximizable = state.maximizable; - _fullScreen = state.fullScreen; - _resizable = state.resizable; - _alwaysOnTop = state.alwaysOnTop; - _preventClose = state.preventClose; - _visible = state.visible; - _focused = state.focused; - _skipTaskBar = state.skipTaskBar; - - control.backend.onWindowEvent(eventName, state); - }); + if (eventName == "close") { + _isClosing = !(_preventClose ?? false); + control.backend.onWindowEvent( + eventName, _snapshotWindowState(focused: false)); + return; + } + if (eventName == "hide") { + _visible = false; + _focused = false; + control.backend.onWindowEvent( + eventName, _snapshotWindowState(visible: false, focused: false)); + return; + } + if (_isClosing) { + control.backend.onWindowEvent(eventName, _snapshotWindowState()); + return; + } + + () async { + try { + if (eventName == "show") { + _visible = true; + await _applyDeferredPlacementAfterShow(); + } + + final state = await getWindowState(); + _cacheWindowState(state); + control.backend.onWindowEvent(eventName, state); + } catch (e) { + debugPrint("Error handling window event $eventName: $e"); + } + }(); } } From 23f3d50b8e5f9575b613754fc41f4ed72e7e9719 Mon Sep 17 00:00:00 2001 From: Hmily <114978440+ihmily@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:51:02 +0800 Subject: [PATCH 3/3] fix: preserve focused state on prevented window close --- packages/flet/lib/src/services/window.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/flet/lib/src/services/window.dart b/packages/flet/lib/src/services/window.dart index d144032da9..075d5aabf8 100644 --- a/packages/flet/lib/src/services/window.dart +++ b/packages/flet/lib/src/services/window.dart @@ -500,9 +500,11 @@ class WindowService extends FletService with WindowListener { return; } if (eventName == "close") { - _isClosing = !(_preventClose ?? false); + final preventClose = _preventClose ?? false; + _isClosing = !preventClose; control.backend.onWindowEvent( - eventName, _snapshotWindowState(focused: false)); + eventName, + _snapshotWindowState(focused: preventClose ? null : false)); return; } if (eventName == "hide") {