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>
125 lines
4.0 KiB
C++
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);
|
|
}
|