Files
sync_replay_project/apps/desktop/windows/runner/flutter_window.cpp
T
foxyzandClaude Opus 5.5 e16cf50f2c
ci / core (push) Successful in 28s
ci / server (push) Successful in 51s
ci / updater-windows (push) Successful in 6m27s
Windows: every start ended itself after 3 s (1.3.0, 1.3.1)
Win32Window::Create calls Destroy() before it makes the window, which
ran FlutterWindow::OnDestroy and armed the 3 s exit timer at launch: the
window showed, then the process ended with code 0, no crash, no close
lines. The timer is only armed with an engine to tear down. Found and
fixed by Fun Maker's agent on Windows (a debugger caught the exit on the
timer's thread).

The smoke runs missed it (they closed at ~4 s anyway): the app now stays
up 10 s, and the scripts require 'closing' and 'quitting' from its own
close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:04:55 +03:00

125 lines
4.0 KiB
C++

#include "flutter_window.h"
#include <cstdio>
#include <optional>
#include <thread>
#include <flutter/standard_method_codec.h>
#include "flutter/generated_plugin_registrant.h"
namespace {
// Ends the process after |ms| whatever is still running: the app's close
// (lib/src/platform/app_shutdown.dart) is bounded, but a native teardown
// can block for good (media_kit_video's ~VideoOutput waits on a texture
// callback the stopped engine never sends). The process must never stay
// behind a hidden window.
void ExitAfter(DWORD ms) {
std::thread([ms] {
::Sleep(ms);
fflush(nullptr);
::TerminateProcess(::GetCurrentProcess(), 0);
}).detach();
}
} // namespace
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
: project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
window_channel_ = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
flutter_controller_->engine()->messenger(), "starsync/window",
&flutter::StandardMethodCodec::GetInstance());
window_channel_->SetMethodCallHandler(
[this](const flutter::MethodCall<flutter::EncodableValue>& call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (call.method_name() != "attention") {
result->NotImplemented();
return;
}
HWND hwnd = GetHandle();
if (hwnd != nullptr && GetForegroundWindow() != hwnd) {
// Flashes until the window comes to the front.
FLASHWINFO info{sizeof(FLASHWINFO), hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 0, 0};
FlashWindowEx(&info);
}
result->Success();
});
SetChildContent(flutter_controller_->view()->GetNativeWindow());
flutter_controller_->engine()->SetNextFrameCallback([&]() {
this->Show();
});
// Flutter can complete the first frame before the "show window" callback is
// registered. The following call ensures a frame is pending to ensure the
// window is shown. It is a no-op if the first frame hasn't completed yet.
flutter_controller_->ForceRedraw();
return true;
}
void FlutterWindow::OnDestroy() {
// The engine and the plugins go now; the app released its players first.
// Win32Window::Create runs this too, before there is an engine: only a
// real teardown may end the process (else every start dies at 3 s).
if (flutter_controller_) {
ExitAfter(3000);
}
window_channel_ = nullptr;
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// A close request starts the app's close (players, leave), at most a
// few seconds; past 15 s the process ends anyway.
if (message == WM_CLOSE && !close_requested_) {
close_requested_ = true;
ExitAfter(15000);
}
// Give Flutter, including plugins, an opportunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}