aboutsummaryrefslogtreecommitdiff
path: root/windows/runner
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.com>2023-09-11 01:00:14 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.com>2023-09-11 01:00:14 +0530
commit7651d5200767a339089dd4021bf77a86b6adcf80 (patch)
tree3fef13a7f0140b22716bfbc9a7c6e842782b853c /windows/runner
first commitv0.0.1
Diffstat (limited to 'windows/runner')
-rw-r--r--windows/runner/CMakeLists.txt40
-rw-r--r--windows/runner/Runner.rc121
-rw-r--r--windows/runner/flutter_window.cpp71
-rw-r--r--windows/runner/flutter_window.h33
-rw-r--r--windows/runner/main.cpp43
-rw-r--r--windows/runner/resource.h16
-rw-r--r--windows/runner/resources/app_icon.icobin0 -> 33772 bytes
-rw-r--r--windows/runner/runner.exe.manifest20
-rw-r--r--windows/runner/utils.cpp65
-rw-r--r--windows/runner/utils.h19
-rw-r--r--windows/runner/win32_window.cpp288
-rw-r--r--windows/runner/win32_window.h102
12 files changed, 818 insertions, 0 deletions
diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..394917c
--- /dev/null
+++ b/windows/runner/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+ "flutter_window.cpp"
+ "main.cpp"
+ "utils.cpp"
+ "win32_window.cpp"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+ "Runner.rc"
+ "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc
new file mode 100644
index 0000000..ba7f872
--- /dev/null
+++ b/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON ICON "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
+#else
+#define VERSION_AS_NUMBER 1,0,0,0
+#endif
+
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "com.example" "\0"
+ VALUE "FileDescription", "openbillsclient" "\0"
+ VALUE "FileVersion", VERSION_AS_STRING "\0"
+ VALUE "InternalName", "openbillsclient" "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0"
+ VALUE "OriginalFilename", "openbillsclient.exe" "\0"
+ VALUE "ProductName", "openbillsclient" "\0"
+ VALUE "ProductVersion", VERSION_AS_STRING "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..955ee30
--- /dev/null
+++ b/windows/runner/flutter_window.cpp
@@ -0,0 +1,71 @@
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+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());
+ 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() {
+ 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);
+}
diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h
new file mode 100644
index 0000000..6da0652
--- /dev/null
+++ b/windows/runner/flutter_window.h
@@ -0,0 +1,33 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+ // Creates a new FlutterWindow hosting a Flutter view running |project|.
+ explicit FlutterWindow(const flutter::DartProject& project);
+ virtual ~FlutterWindow();
+
+ protected:
+ // Win32Window:
+ bool OnCreate() override;
+ void OnDestroy() override;
+ LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+ LPARAM const lparam) noexcept override;
+
+ private:
+ // The project to run.
+ flutter::DartProject project_;
+
+ // The Flutter instance hosted by this window.
+ std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif // RUNNER_FLUTTER_WINDOW_H_
diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp
new file mode 100644
index 0000000..159a8c4
--- /dev/null
+++ b/windows/runner/main.cpp
@@ -0,0 +1,43 @@
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+ _In_ wchar_t *command_line, _In_ int show_command) {
+ // Attach to console when present (e.g., 'flutter run') or create a
+ // new console when running with a debugger.
+ if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+ CreateAndAttachConsole();
+ }
+
+ // Initialize COM, so that it is available for use in the library and/or
+ // plugins.
+ ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ flutter::DartProject project(L"data");
+
+ std::vector<std::string> command_line_arguments =
+ GetCommandLineArguments();
+
+ project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+ FlutterWindow window(project);
+ Win32Window::Point origin(10, 10);
+ Win32Window::Size size(1280, 720);
+ if (!window.Create(L"openbillsclient", origin, size)) {
+ return EXIT_FAILURE;
+ }
+ window.SetQuitOnClose(true);
+
+ ::MSG msg;
+ while (::GetMessage(&msg, nullptr, 0, 0)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+
+ ::CoUninitialize();
+ return EXIT_SUCCESS;
+}
diff --git a/windows/runner/resource.h b/windows/runner/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..a42ea76
--- /dev/null
+++ b/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+ </windowsSettings>
+ </application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 and Windows 11 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp
new file mode 100644
index 0000000..b2b0873
--- /dev/null
+++ b/windows/runner/utils.cpp
@@ -0,0 +1,65 @@
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+ if (::AllocConsole()) {
+ FILE *unused;
+ if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+ _dup2(_fileno(stdout), 1);
+ }
+ if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+ _dup2(_fileno(stdout), 2);
+ }
+ std::ios::sync_with_stdio();
+ FlutterDesktopResyncOutputStreams();
+ }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+ // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+ int argc;
+ wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+ if (argv == nullptr) {
+ return std::vector<std::string>();
+ }
+
+ std::vector<std::string> command_line_arguments;
+
+ // Skip the first argument as it's the binary name.
+ for (int i = 1; i < argc; i++) {
+ command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+ }
+
+ ::LocalFree(argv);
+
+ return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+ if (utf16_string == nullptr) {
+ return std::string();
+ }
+ int target_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, nullptr, 0, nullptr, nullptr)
+ -1; // remove the trailing null character
+ int input_length = (int)wcslen(utf16_string);
+ std::string utf8_string;
+ if (target_length <= 0 || target_length > utf8_string.max_size()) {
+ return utf8_string;
+ }
+ utf8_string.resize(target_length);
+ int converted_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ input_length, utf8_string.data(), target_length, nullptr, nullptr);
+ if (converted_length == 0) {
+ return std::string();
+ }
+ return utf8_string;
+}
diff --git a/windows/runner/utils.h b/windows/runner/utils.h
new file mode 100644
index 0000000..3879d54
--- /dev/null
+++ b/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif // RUNNER_UTILS_H_
diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..60608d0
--- /dev/null
+++ b/windows/runner/win32_window.cpp
@@ -0,0 +1,288 @@
+#include "win32_window.h"
+
+#include <dwmapi.h>
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+/// Window attribute that enables dark mode window decorations.
+///
+/// Redefined in case the developer's machine has a Windows SDK older than
+/// version 10.0.22000.0.
+/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
+#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
+#endif
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+/// Registry key for app theme preference.
+///
+/// A value of 0 indicates apps should use dark mode. A non-zero or missing
+/// value indicates apps should use light mode.
+constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
+
+// 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 registrar 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::Create(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,
+ 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);
+
+ if (!window) {
+ return false;
+ }
+
+ UpdateTheme(window);
+
+ return OnCreate();
+}
+
+bool Win32Window::Show() {
+ return ShowWindow(window_handle_, SW_SHOWNORMAL);
+}
+
+// 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 {
+ 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 = GetClientArea();
+ 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;
+
+ case WM_DWMCOLORIZATIONCOLORCHANGED:
+ UpdateTheme(hwnd);
+ 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 = GetClientArea();
+
+ MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+ frame.bottom - frame.top, true);
+
+ SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+ RECT frame;
+ GetClientRect(window_handle_, &frame);
+ return frame;
+}
+
+HWND Win32Window::GetHandle() {
+ return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+ quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+ // No-op; provided for subclasses.
+ return true;
+}
+
+void Win32Window::OnDestroy() {
+ // No-op; provided for subclasses.
+}
+
+void Win32Window::UpdateTheme(HWND const window) {
+ DWORD light_mode;
+ DWORD light_mode_size = sizeof(light_mode);
+ LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
+ kGetPreferredBrightnessRegValue,
+ RRF_RT_REG_DWORD, nullptr, &light_mode,
+ &light_mode_size);
+
+ if (result == ERROR_SUCCESS) {
+ BOOL enable_dark_mode = light_mode == 0;
+ DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
+ &enable_dark_mode, sizeof(enable_dark_mode));
+ }
+}
diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h
new file mode 100644
index 0000000..e901dde
--- /dev/null
+++ b/windows/runner/win32_window.h
@@ -0,0 +1,102 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+ struct Point {
+ unsigned int x;
+ unsigned int y;
+ Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+ };
+
+ struct Size {
+ unsigned int width;
+ unsigned int height;
+ Size(unsigned int width, unsigned int height)
+ : width(width), height(height) {}
+ };
+
+ Win32Window();
+ virtual ~Win32Window();
+
+ // Creates a win32 window with |title| that is positioned and sized using
+ // |origin| and |size|. New windows are created on the default monitor. Window
+ // sizes are specified to the OS in physical pixels, hence to ensure a
+ // consistent size this function will scale the inputted width and height as
+ // as appropriate for the default monitor. The window is invisible until
+ // |Show| is called. Returns true if the window was created successfully.
+ bool Create(const std::wstring& title, const Point& origin, const Size& size);
+
+ // Show the current window. Returns true if the window was successfully shown.
+ bool Show();
+
+ // Release OS resources associated with window.
+ void Destroy();
+
+ // Inserts |content| into the window tree.
+ void SetChildContent(HWND content);
+
+ // Returns the backing Window handle to enable clients to set icon and other
+ // window properties. Returns nullptr if the window has been destroyed.
+ HWND GetHandle();
+
+ // If true, closing this window will quit the application.
+ void SetQuitOnClose(bool quit_on_close);
+
+ // Return a RECT representing the bounds of the current client area.
+ RECT GetClientArea();
+
+ protected:
+ // Processes and route salient window messages for mouse handling,
+ // size change and DPI. Delegates handling of these to member overloads that
+ // inheriting classes can handle.
+ virtual LRESULT MessageHandler(HWND window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Called when CreateAndShow is called, allowing subclass window-related
+ // setup. Subclasses should return false if setup fails.
+ virtual bool OnCreate();
+
+ // Called when Destroy is called.
+ virtual void OnDestroy();
+
+ private:
+ friend class WindowClassRegistrar;
+
+ // OS callback called by message pump. Handles the WM_NCCREATE message which
+ // is passed when the non-client area is being created and enables automatic
+ // non-client DPI scaling so that the non-client area automatically
+ // responds to changes in DPI. All other messages are handled by
+ // MessageHandler.
+ static LRESULT CALLBACK WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Retrieves a class instance pointer for |window|
+ static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+ // Update the window frame's theme to match the system theme.
+ static void UpdateTheme(HWND const window);
+
+ bool quit_on_close_ = false;
+
+ // window handle for top level window.
+ HWND window_handle_ = nullptr;
+
+ // window handle for hosted content.
+ HWND child_content_ = nullptr;
+};
+
+#endif // RUNNER_WIN32_WINDOW_H_