halkyon/apps/train/halkyon_train_flutter/windows/runner/flutter_window.cpp
Tizian 「ティツィアーン」 7b5fbee913
feat(train): Halkyon Train's shell, Todo's copied and renamed (ADR 0030, D12)
apps/train with halkyon_train_server, _client and _flutter, as Todo's:
halkyon_base configured by trainBase(), its own register alone and a
member of the instance's where the operator names one, the app on
halkyon_app's gates with Train's frame, account sheet and About, texts
in British English and German that speak of workouts, app.halkyon.train
on Android, iOS, Linux, macOS and Windows.

Where it differs from Todo: development ports of its own (8180 to 8190),
so it runs beside Learn's server; one initial migration with the schema
Todo's migrations end at (the same tables, compared line by line), not
Todo's seventeen; and Todo's icon as a placeholder until the icon tool
of ADR 0031 draws Train's bike. The workspace, the licences, the READMEs
and AGENTS.md name it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-30 15:52:45 +01:00

140 lines
4.5 KiB
C++

#include "flutter_window.h"
#include <flutter/standard_method_codec.h>
#include <optional>
#include "flutter/generated_plugin_registrant.h"
// Kept out of captures altogether (Windows 10 2004 and later); older SDKs
// don't name it.
#ifndef WDA_EXCLUDEFROMCAPTURE
#define WDA_EXCLUDEFROMCAPTURE 0x00000011
#endif
namespace {
// Whether this Windows keeps a window out of captures with
// WDA_EXCLUDEFROMCAPTURE: Windows 10 2004 (build 19041) and later. Before,
// it takes the value without failing and acts as WDA_MONITOR, so the
// version decides, not a failure. RtlGetVersion tells the real version;
// GetVersionEx tells what the app's manifest claims.
bool ExcludesFromCapture() {
using RtlGetVersionFunction = LONG(WINAPI*)(PRTL_OSVERSIONINFOW);
const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
return false;
}
const auto rtl_get_version = reinterpret_cast<RtlGetVersionFunction>(
GetProcAddress(ntdll, "RtlGetVersion"));
if (!rtl_get_version) {
return false;
}
RTL_OSVERSIONINFOW version = {};
version.dwOSVersionInfoSize = sizeof(version);
if (rtl_get_version(&version) != 0) {
return false;
}
return version.dwMajorVersion > 10 ||
(version.dwMajorVersion == 10 && version.dwBuildNumber >= 19041);
}
} // 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());
GuardScreen();
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::GuardScreen() {
screen_guard_ =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
flutter_controller_->engine()->messenger(),
"app.halkyon/screen_guard",
&flutter::StandardMethodCodec::GetInstance());
screen_guard_->SetMethodCallHandler(
[this](const flutter::MethodCall<flutter::EncodableValue>& call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>
result) {
if (call.method_name() == "hold") {
secrets_shown_++;
// Before Windows 10 2004 the window shows black in captures
// instead.
static const bool excludes = ExcludesFromCapture();
SetWindowDisplayAffinity(
GetHandle(), excludes ? WDA_EXCLUDEFROMCAPTURE : WDA_MONITOR);
result->Success();
} else if (call.method_name() == "release") {
secrets_shown_ = secrets_shown_ > 0 ? secrets_shown_ - 1 : 0;
if (secrets_shown_ == 0) {
SetWindowDisplayAffinity(GetHandle(), WDA_NONE);
}
result->Success();
} else {
result->NotImplemented();
}
});
}
void FlutterWindow::OnDestroy() {
screen_guard_ = nullptr;
if (flutter_controller_) {
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// 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);
}