
This moves the app template more toward being a more generic starting point for any Flutter application, eliminating some hard-code assumptions about there being a single window/engine pair that is directly bound to the life of the application: - Moves the runloop into its own class, making it capable of servicing any number of engine instances. - Moves the logic for setting up a window containing only a Flutter view into a window subclass for ease of re-use. - Makes quit-on-window-close an optional property. (Long term this should be even more generic, like a quit-when-last-window-closes option, but this is a short-term improvement that removes the binding between the runloop and the window). - Allows for multiple instances of Win32Window to exist without issues relating to the window class registration. Since there are getting to be a non-trivial number of files associated with the runner, this moves the source into a runner/ directory, as is already done on some other platforms. Note that creating multiple Flutter windows at the same time still doesn't work correctly even with this change, but this addresses some of the known issues, and makes it easier to test in the future (e.g., for debugging engine-level issues with multiple instances). Fixes #45397
250 lines
7.0 KiB
C++
250 lines
7.0 KiB
C++
#include "win32_window.h"
|
|
|
|
#include <flutter_windows.h>
|
|
|
|
#include "resource.h"
|
|
|
|
namespace {
|
|
|
|
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
|
|
|
// The number of Win32Window objects that currently exist.
|
|
static int g_active_window_count = 0;
|
|
|
|
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
|
|
|
// Scale helper to convert logical scaler values to physical using passed in
|
|
// scale factor
|
|
int Scale(int source, double scale_factor) {
|
|
return static_cast<int>(source * scale_factor);
|
|
}
|
|
|
|
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
|
// This API is only needed for PerMonitor V1 awareness mode.
|
|
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
|
HMODULE user32_module = LoadLibraryA("User32.dll");
|
|
if (!user32_module) {
|
|
return;
|
|
}
|
|
auto enable_non_client_dpi_scaling =
|
|
reinterpret_cast<EnableNonClientDpiScaling*>(
|
|
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
|
if (enable_non_client_dpi_scaling != nullptr) {
|
|
enable_non_client_dpi_scaling(hwnd);
|
|
FreeLibrary(user32_module);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Manages the Win32Window's window class registration.
|
|
class WindowClassRegistrar {
|
|
public:
|
|
~WindowClassRegistrar() = default;
|
|
|
|
// Returns the singleton registar instance.
|
|
static WindowClassRegistrar* GetInstance() {
|
|
if (!instance_) {
|
|
instance_ = new WindowClassRegistrar();
|
|
}
|
|
return instance_;
|
|
}
|
|
|
|
// Returns the name of the window class, registering the class if it hasn't
|
|
// previously been registered.
|
|
const wchar_t* GetWindowClass();
|
|
|
|
// Unregisters the window class. Should only be called if there are no
|
|
// instances of the window.
|
|
void UnregisterWindowClass();
|
|
|
|
private:
|
|
WindowClassRegistrar() = default;
|
|
|
|
static WindowClassRegistrar* instance_;
|
|
|
|
bool class_registered_ = false;
|
|
};
|
|
|
|
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
|
|
|
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
|
if (!class_registered_) {
|
|
WNDCLASS window_class{};
|
|
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
window_class.lpszClassName = kWindowClassName;
|
|
window_class.style = CS_HREDRAW | CS_VREDRAW;
|
|
window_class.cbClsExtra = 0;
|
|
window_class.cbWndExtra = 0;
|
|
window_class.hInstance = GetModuleHandle(nullptr);
|
|
window_class.hIcon =
|
|
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
|
window_class.hbrBackground = 0;
|
|
window_class.lpszMenuName = nullptr;
|
|
window_class.lpfnWndProc = Win32Window::WndProc;
|
|
RegisterClass(&window_class);
|
|
class_registered_ = true;
|
|
}
|
|
return kWindowClassName;
|
|
}
|
|
|
|
void WindowClassRegistrar::UnregisterWindowClass() {
|
|
UnregisterClass(kWindowClassName, nullptr);
|
|
class_registered_ = false;
|
|
}
|
|
|
|
Win32Window::Win32Window() {
|
|
++g_active_window_count;
|
|
}
|
|
|
|
Win32Window::~Win32Window() {
|
|
--g_active_window_count;
|
|
Destroy();
|
|
}
|
|
|
|
bool Win32Window::CreateAndShow(const std::wstring& title,
|
|
const Point& origin,
|
|
const Size& size) {
|
|
Destroy();
|
|
|
|
const wchar_t* window_class =
|
|
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
|
|
|
const POINT target_point = {static_cast<LONG>(origin.x),
|
|
static_cast<LONG>(origin.y)};
|
|
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
|
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
|
double scale_factor = dpi / 96.0;
|
|
|
|
HWND window = CreateWindow(
|
|
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
|
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
|
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
|
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
|
|
|
OnCreate();
|
|
|
|
return window != nullptr;
|
|
}
|
|
|
|
// static
|
|
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
|
UINT const message,
|
|
WPARAM const wparam,
|
|
LPARAM const lparam) noexcept {
|
|
if (message == WM_NCCREATE) {
|
|
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
|
SetWindowLongPtr(window, GWLP_USERDATA,
|
|
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
|
|
|
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
|
EnableFullDpiSupportIfAvailable(window);
|
|
that->window_handle_ = window;
|
|
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
|
return that->MessageHandler(window, message, wparam, lparam);
|
|
}
|
|
|
|
return DefWindowProc(window, message, wparam, lparam);
|
|
}
|
|
|
|
LRESULT
|
|
Win32Window::MessageHandler(HWND hwnd,
|
|
UINT const message,
|
|
WPARAM const wparam,
|
|
LPARAM const lparam) noexcept {
|
|
auto window =
|
|
reinterpret_cast<Win32Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
|
|
|
if (window == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
switch (message) {
|
|
case WM_DESTROY:
|
|
window_handle_ = nullptr;
|
|
Destroy();
|
|
if (quit_on_close_) {
|
|
PostQuitMessage(0);
|
|
}
|
|
return 0;
|
|
|
|
case WM_DPICHANGED: {
|
|
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
|
LONG newWidth = newRectSize->right - newRectSize->left;
|
|
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
|
|
|
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
|
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
|
|
return 0;
|
|
}
|
|
case WM_SIZE:
|
|
RECT rect;
|
|
GetClientRect(hwnd, &rect);
|
|
if (child_content_ != nullptr) {
|
|
// Size and position the child window.
|
|
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
|
|
rect.bottom - rect.top, TRUE);
|
|
}
|
|
return 0;
|
|
|
|
case WM_ACTIVATE:
|
|
if (child_content_ != nullptr) {
|
|
SetFocus(child_content_);
|
|
}
|
|
return 0;
|
|
|
|
// Messages that are directly forwarded to embedding.
|
|
case WM_FONTCHANGE:
|
|
SendMessage(child_content_, WM_FONTCHANGE, NULL, NULL);
|
|
return 0;
|
|
}
|
|
|
|
return DefWindowProc(window_handle_, message, wparam, lparam);
|
|
}
|
|
|
|
void Win32Window::Destroy() {
|
|
OnDestroy();
|
|
|
|
if (window_handle_) {
|
|
DestroyWindow(window_handle_);
|
|
window_handle_ = nullptr;
|
|
}
|
|
if (g_active_window_count == 0) {
|
|
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
|
}
|
|
}
|
|
|
|
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
|
return reinterpret_cast<Win32Window*>(
|
|
GetWindowLongPtr(window, GWLP_USERDATA));
|
|
}
|
|
|
|
void Win32Window::SetChildContent(HWND content) {
|
|
child_content_ = content;
|
|
SetParent(content, window_handle_);
|
|
RECT frame;
|
|
GetClientRect(window_handle_, &frame);
|
|
|
|
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
|
|
frame.bottom - frame.top, true);
|
|
|
|
SetFocus(child_content_);
|
|
}
|
|
|
|
HWND Win32Window::GetHandle() {
|
|
return window_handle_;
|
|
}
|
|
|
|
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
|
quit_on_close_ = quit_on_close;
|
|
}
|
|
|
|
void Win32Window::OnCreate() {
|
|
// No-op; provided for subclasses.
|
|
}
|
|
|
|
void Win32Window::OnDestroy() {
|
|
// No-op; provided for subclasses.
|
|
}
|