diff options
Diffstat (limited to 'windows')
| -rw-r--r-- | windows/.gitignore | 17 | ||||
| -rw-r--r-- | windows/CMakeLists.txt | 108 | ||||
| -rw-r--r-- | windows/flutter/CMakeLists.txt | 109 | ||||
| -rw-r--r-- | windows/flutter/generated_plugin_registrant.cc | 14 | ||||
| -rw-r--r-- | windows/flutter/generated_plugin_registrant.h | 15 | ||||
| -rw-r--r-- | windows/flutter/generated_plugins.cmake | 24 | ||||
| -rw-r--r-- | windows/runner/CMakeLists.txt | 40 | ||||
| -rw-r--r-- | windows/runner/Runner.rc | 121 | ||||
| -rw-r--r-- | windows/runner/flutter_window.cpp | 71 | ||||
| -rw-r--r-- | windows/runner/flutter_window.h | 33 | ||||
| -rw-r--r-- | windows/runner/main.cpp | 43 | ||||
| -rw-r--r-- | windows/runner/resource.h | 16 | ||||
| -rw-r--r-- | windows/runner/resources/app_icon.ico | bin | 0 -> 33772 bytes | |||
| -rw-r--r-- | windows/runner/runner.exe.manifest | 14 | ||||
| -rw-r--r-- | windows/runner/utils.cpp | 65 | ||||
| -rw-r--r-- | windows/runner/utils.h | 19 | ||||
| -rw-r--r-- | windows/runner/win32_window.cpp | 288 | ||||
| -rw-r--r-- | windows/runner/win32_window.h | 102 | 
18 files changed, 1099 insertions, 0 deletions
| diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..f3a33d1 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(badge LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "badge") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) +  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" +    CACHE STRING "" FORCE) +else() +  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) +    set(CMAKE_BUILD_TYPE "Debug" CACHE +      STRING "Flutter build mode" FORCE) +    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS +      "Debug" "Profile" "Release") +  endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) +  target_compile_features(${TARGET} PUBLIC cxx_std_17) +  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") +  target_compile_options(${TARGET} PRIVATE /EHsc) +  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") +  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) +  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" +  COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" +  COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +  COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) +  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" +    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +    COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" +   DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" +   COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " +  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") +  " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" +  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" +  CONFIGURATIONS Profile;Release +  COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) +  set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS +  "flutter_export.h" +  "flutter_windows.h" +  "flutter_messenger.h" +  "flutter_plugin_registrar.h" +  "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE +  "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE +  "core_implementations.cc" +  "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN +  "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP +  "flutter_engine.cc" +  "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC +  ${CPP_WRAPPER_SOURCES_CORE} +  ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES +  POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES +  CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC +  "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC +  ${CPP_WRAPPER_SOURCES_CORE} +  ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC +  "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( +  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} +    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} +    ${CPP_WRAPPER_SOURCES_APP} +    ${PHONY_OUTPUT} +  COMMAND ${CMAKE_COMMAND} -E env +    ${FLUTTER_TOOL_ENVIRONMENT} +    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" +      ${FLUTTER_TARGET_PLATFORM} $<CONFIG> +  VERBATIM +) +add_custom_target(flutter_assemble DEPENDS +  "${FLUTTER_LIBRARY}" +  ${FLUTTER_LIBRARY_HEADERS} +  ${CPP_WRAPPER_SOURCES_CORE} +  ${CPP_WRAPPER_SOURCES_PLUGIN} +  ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..48de52b --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +//  Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include <permission_handler_windows/permission_handler_windows_plugin.h> + +void RegisterPlugins(flutter::PluginRegistry* registry) { +  PermissionHandlerWindowsPluginRegisterWithRegistrar( +      registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +//  Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include <flutter/plugin_registry.h> + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif  // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..0e69e40 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +  permission_handler_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) +  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) +  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) +  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) +  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) +  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) +  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) 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..fab392b --- /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", "de.uvok" "\0" +            VALUE "FileDescription", "badge" "\0" +            VALUE "FileVersion", VERSION_AS_STRING "\0" +            VALUE "InternalName", "badge" "\0" +            VALUE "LegalCopyright", "Copyright (C) 2025 de.uvok. All rights reserved." "\0" +            VALUE "OriginalFilename", "badge.exe" "\0" +            VALUE "ProductName", "badge" "\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..1212c6f --- /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"badge", 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.icoBinary files differ new file mode 100644 index 0000000..c04e20c --- /dev/null +++ b/windows/runner/resources/app_icon.ico diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ +<?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}"/> +    </application> +  </compatibility> +</assembly> diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /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(); +  } +  unsigned 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_ | 
