diff --git a/packages/device_info_plus/device_info_plus/example/lib/main.dart b/packages/device_info_plus/device_info_plus/example/lib/main.dart index d65da9d85e..1c23698c93 100644 --- a/packages/device_info_plus/device_info_plus/example/lib/main.dart +++ b/packages/device_info_plus/device_info_plus/example/lib/main.dart @@ -237,44 +237,42 @@ class _MyAppState extends State { home: Scaffold( appBar: AppBar(title: Text(_getAppBarTitle()), elevation: 4), body: ListView( - children: - _deviceData.keys.map((String property) { - return Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - child: Text( - property, - style: const TextStyle(fontWeight: FontWeight.bold), - ), + children: _deviceData.keys.map((String property) { + return Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + child: Text( + property, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${_deviceData[property]}', + maxLines: 10, + overflow: TextOverflow.ellipsis, ), - Expanded( - child: Container( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${_deviceData[property]}', - maxLines: 10, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ); - }).toList(), + ), + ), + ], + ); + }).toList(), ), ), ); } - String _getAppBarTitle() => - kIsWeb - ? 'Web Browser info' - : switch (defaultTargetPlatform) { - TargetPlatform.android => 'Android Device Info', - TargetPlatform.iOS => 'iOS Device Info', - TargetPlatform.linux => 'Linux Device Info', - TargetPlatform.windows => 'Windows Device Info', - TargetPlatform.macOS => 'MacOS Device Info', - TargetPlatform.fuchsia => 'Fuchsia Device Info', - }; + String _getAppBarTitle() => kIsWeb + ? 'Web Browser info' + : switch (defaultTargetPlatform) { + TargetPlatform.android => 'Android Device Info', + TargetPlatform.iOS => 'iOS Device Info', + TargetPlatform.linux => 'Linux Device Info', + TargetPlatform.windows => 'Windows Device Info', + TargetPlatform.macOS => 'MacOS Device Info', + TargetPlatform.fuchsia => 'Fuchsia Device Info', + }; } diff --git a/packages/package_info_plus/package_info_plus/README.md b/packages/package_info_plus/package_info_plus/README.md index 6ec499a79d..d1ada60f5d 100644 --- a/packages/package_info_plus/package_info_plus/README.md +++ b/packages/package_info_plus/package_info_plus/README.md @@ -16,10 +16,10 @@ This Flutter plugin provides an API for querying information about an applicatio ## Requirements -- Flutter >=3.19.0 -- Dart >=3.3.0 <4.0.0 -- iOS >=12.0 -- macOS >=10.14 +- Flutter >=3.41.0 +- Dart >=3.11.0 <4.0.0 +- iOS >=13.0 +- macOS >=10.15 - Java 17 - Kotlin 2.2.0 - Android Gradle Plugin >=8.12.1 diff --git a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_test.dart b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_test.dart index 8ea41c3de7..e7c789db65 100644 --- a/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_test.dart +++ b/packages/package_info_plus/package_info_plus/example/integration_test/package_info_plus_test.dart @@ -130,10 +130,10 @@ void main() { ), ); } else if (Platform.isWindows) { - expect(info.appName, 'example'); + expect(info.appName, 'package_info_plus_example'); expect(info.buildNumber, '4'); expect(info.buildSignature, isEmpty); - expect(info.packageName, 'example'); + expect(info.packageName, 'package_info_plus_example'); expect(info.version, '1.2.3'); expect(info.installerStore, null); expect( @@ -221,7 +221,7 @@ void main() { expect(find.text('Not set'), findsOneWidget); expect(find.textContaining(installTimeRegex), findsNWidgets(2)); } else if (Platform.isWindows) { - expect(find.text('example'), findsNWidgets(2)); + expect(find.text('package_info_plus_example'), findsNWidgets(2)); expect(find.text('1.2.3'), findsOneWidget); expect(find.text('4'), findsOneWidget); expect(find.text('Not set'), findsOneWidget); diff --git a/packages/package_info_plus/package_info_plus/example/windows/CMakeLists.txt b/packages/package_info_plus/package_info_plus/example/windows/CMakeLists.txt index c0270746b1..720936fc7f 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/CMakeLists.txt +++ b/packages/package_info_plus/package_info_plus/example/windows/CMakeLists.txt @@ -1,14 +1,14 @@ # Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(example LANGUAGES CXX) +project(package_info_plus_example 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 "example") +set(BINARY_NAME "package_info_plus_example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. -cmake_policy(SET CMP0063 NEW) +cmake_policy(VERSION 3.14...3.25) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) @@ -52,6 +52,7 @@ 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) @@ -86,6 +87,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) 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") diff --git a/packages/package_info_plus/package_info_plus/example/windows/flutter/CMakeLists.txt b/packages/package_info_plus/package_info_plus/example/windows/flutter/CMakeLists.txt index 930d2071a3..903f4899d6 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/flutter/CMakeLists.txt +++ b/packages/package_info_plus/package_info_plus/example/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # 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") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/CMakeLists.txt b/packages/package_info_plus/package_info_plus/example/windows/runner/CMakeLists.txt index 17411a8ab8..394917c053 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/CMakeLists.txt +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/CMakeLists.txt @@ -33,6 +33,7 @@ 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. diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/Runner.rc b/packages/package_info_plus/package_info_plus/example/windows/runner/Runner.rc index 7f035cd3f7..8ea40e0192 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/Runner.rc +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/Runner.rc @@ -89,13 +89,13 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "io.flutter.plugins" "\0" - VALUE "FileDescription", "example" "\0" + VALUE "CompanyName", "io.flutter.plugins.packageinfoplusexample" "\0" + VALUE "FileDescription", "package_info_plus_example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 io.flutter.plugins. All rights reserved." "\0" - VALUE "OriginalFilename", "example.exe" "\0" - VALUE "ProductName", "example" "\0" + VALUE "InternalName", "package_info_plus_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 io.flutter.plugins.packageinfoplusexample. All rights reserved." "\0" + VALUE "OriginalFilename", "package_info_plus_example.exe" "\0" + VALUE "ProductName", "package_info_plus_example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.cpp b/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.cpp index edfe54e14e..955ee3038f 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.cpp +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.cpp @@ -4,7 +4,7 @@ #include "flutter/generated_plugin_registrant.h" -FlutterWindow::FlutterWindow(const flutter::DartProject &project) +FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} @@ -26,6 +26,16 @@ bool FlutterWindow::OnCreate() { } 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; } @@ -52,9 +62,9 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, } switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.h b/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.h index 7fa026bf24..6da0652f05 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.h +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/flutter_window.h @@ -10,19 +10,19 @@ // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { -public: + public: // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject &project); + explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); -protected: + protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; -private: + private: // The project to run. flutter::DartProject project_; @@ -30,4 +30,4 @@ class FlutterWindow : public Win32Window { std::unique_ptr flutter_controller_; }; -#endif // RUNNER_FLUTTER_WINDOW_H_ +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/main.cpp b/packages/package_info_plus/package_info_plus/example/windows/runner/main.cpp index 261f186935..eaee3bef58 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/main.cpp +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/main.cpp @@ -19,14 +19,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, flutter::DartProject project(L"data"); - std::vector command_line_arguments = GetCommandLineArguments(); + std::vector 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.CreateAndShow(L"example", origin, size)) { + if (!window.Create(L"package_info_plus_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/resource.h b/packages/package_info_plus/package_info_plus/example/windows/runner/resource.h index d5d958dc42..66a65d1e4a 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/resource.h +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/resource.h @@ -2,15 +2,15 @@ // Microsoft Visual C++ generated include file. // Used by Runner.rc // -#define IDI_APP_ICON 101 +#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 +#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/packages/package_info_plus/package_info_plus/example/windows/runner/runner.exe.manifest b/packages/package_info_plus/package_info_plus/example/windows/runner/runner.exe.manifest index a42ea7687c..153653e8d6 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/runner.exe.manifest +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/runner.exe.manifest @@ -9,12 +9,6 @@ - - - - - - diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/utils.cpp b/packages/package_info_plus/package_info_plus/example/windows/runner/utils.cpp index 739b61f203..3a0b46511a 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/utils.cpp +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/utils.cpp @@ -24,7 +24,7 @@ void CreateAndAttachConsole() { std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; - wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } @@ -41,21 +41,23 @@ std::vector GetCommandLineArguments() { return command_line_arguments; } -std::string Utf8FromUtf16(const wchar_t *utf16_string) { +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); + 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, -1, utf8_string.data(), - target_length, nullptr, nullptr); + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/utils.h b/packages/package_info_plus/package_info_plus/example/windows/runner/utils.h index ff43ce2ce5..3879d54755 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/utils.h +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/utils.h @@ -10,10 +10,10 @@ 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); +std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); -#endif // RUNNER_UTILS_H_ +#endif // RUNNER_UTILS_H_ diff --git a/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.cpp b/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.cpp index 90ff01e592..60608d0fe5 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.cpp +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.cpp @@ -1,13 +1,31 @@ #include "win32_window.h" +#include #include #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; @@ -27,23 +45,23 @@ void EnableFullDpiSupportIfAvailable(HWND hwnd) { return; } auto enable_non_client_dpi_scaling = - reinterpret_cast( + reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); } + FreeLibrary(user32_module); } -} // namespace +} // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { -public: + public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. - static WindowClassRegistrar *GetInstance() { + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } @@ -52,23 +70,23 @@ class WindowClassRegistrar { // Returns the name of the window class, registering the class if it hasn't // previously been registered. - const wchar_t *GetWindowClass(); + const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); -private: + private: WindowClassRegistrar() = default; - static WindowClassRegistrar *instance_; + static WindowClassRegistrar* instance_; bool class_registered_ = false; }; -WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr; +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; -const wchar_t *WindowClassRegistrar::GetWindowClass() { +const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); @@ -93,18 +111,21 @@ void WindowClassRegistrar::UnregisterWindowClass() { class_registered_ = false; } -Win32Window::Win32Window() { ++g_active_window_count; } +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) { +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { Destroy(); - const wchar_t *window_class = + const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), @@ -114,7 +135,7 @@ bool Win32Window::CreateAndShow(const std::wstring &title, const Point &origin, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 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); @@ -123,22 +144,29 @@ bool Win32Window::CreateAndShow(const std::wstring &title, const Point &origin, 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, +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(lparam); + auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); - auto that = static_cast(window_struct->lpCreateParams); + auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; - } else if (Win32Window *that = GetThisFromHandle(window)) { + } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } @@ -146,42 +174,48 @@ LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, } LRESULT -Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, +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); + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(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; } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(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); + 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; } - return 0; - } - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - 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); @@ -199,8 +233,8 @@ void Win32Window::Destroy() { } } -Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } @@ -221,7 +255,9 @@ RECT Win32Window::GetClientArea() { return frame; } -HWND Win32Window::GetHandle() { return window_handle_; } +HWND Win32Window::GetHandle() { + return window_handle_; +} void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; @@ -235,3 +271,18 @@ bool Win32Window::OnCreate() { 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/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.h b/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.h index 7b518125bb..e901dde684 100644 --- a/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.h +++ b/packages/package_info_plus/package_info_plus/example/windows/runner/win32_window.h @@ -11,7 +11,7 @@ // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { -public: + public: struct Point { unsigned int x; unsigned int y; @@ -28,14 +28,16 @@ class Win32Window { Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // 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 to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring &title, const Point &origin, - const Size &size); + // 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(); @@ -53,11 +55,12 @@ class Win32Window { // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); -protected: + 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, + virtual LRESULT MessageHandler(HWND window, + UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; @@ -68,20 +71,24 @@ class Win32Window { // Called when Destroy is called. virtual void OnDestroy(); -private: + 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 - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, UINT const message, + 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; + 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; @@ -92,4 +99,4 @@ class Win32Window { HWND child_content_ = nullptr; }; -#endif // RUNNER_WIN32_WINDOW_H_ +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/package_info_plus/package_info_plus/lib/src/file_attribute.dart b/packages/package_info_plus/package_info_plus/lib/src/file_attribute.dart index d4ce98c49d..87a9b7e367 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/file_attribute.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/file_attribute.dart @@ -28,38 +28,38 @@ class FileAttributes { late final DateTime? lastWriteTime; FileAttributes(this.filePath) { - final (:creationTime, :lastWriteTime) = - getFileCreationAndLastWriteTime(filePath); + final (:creationTime, :lastWriteTime) = getFileCreationAndLastWriteTime( + filePath, + ); this.creationTime = creationTime; this.lastWriteTime = lastWriteTime; } - static ({ - DateTime? creationTime, - DateTime? lastWriteTime, - }) getFileCreationAndLastWriteTime(String filePath) { + static ({DateTime? creationTime, DateTime? lastWriteTime}) + getFileCreationAndLastWriteTime(String filePath) { if (!File(filePath).existsSync()) { throw ArgumentError.value(filePath, 'filePath', 'File not present'); } - final lptstrFilename = TEXT(filePath); + final lptstrFilename = filePath.toPcwstr(); final lpFileInformation = calloc(); try { - if (GetFileAttributesEx(lptstrFilename, 0, lpFileInformation) == 0) { - throw WindowsException(HRESULT_FROM_WIN32(GetLastError())); + final result = GetFileAttributesEx( + lptstrFilename, + GetFileExInfoStandard, + lpFileInformation, + ); + if (!result.value) { + throw WindowsException(result.error.toHRESULT()); } final FILEATTRIBUTEDATA fileInformation = lpFileInformation.ref; return ( - creationTime: fileTimeToDartDateTime( - fileInformation.ftCreationTime, - ), - lastWriteTime: fileTimeToDartDateTime( - fileInformation.ftLastWriteTime, - ), + creationTime: fileTimeToDartDateTime(fileInformation.ftCreationTime), + lastWriteTime: fileTimeToDartDateTime(fileInformation.ftLastWriteTime), ); } finally { free(lptstrFilename); diff --git a/packages/package_info_plus/package_info_plus/lib/src/file_version_info.dart b/packages/package_info_plus/package_info_plus/lib/src/file_version_info.dart index 3e383a8da9..3ca879eaed 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/file_version_info.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/file_version_info.dart @@ -65,12 +65,16 @@ class FileVersionInfo { for (final langCodepage in langCodepages) { final lang = toHex4(langCodepage[0]); final codepage = toHex4(langCodepage[1]); - final lpSubBlock = TEXT('\\StringFileInfo\\$lang$codepage\\$name'); - final res = - VerQueryValue(_data.lpBlock, lpSubBlock, lplpBuffer.cast(), puLen); + final lpSubBlock = '\\StringFileInfo\\$lang$codepage\\$name'.toPcwstr(); + final res = VerQueryValue( + _data.lpBlock, + lpSubBlock, + lplpBuffer.cast(), + puLen, + ); free(lpSubBlock); - if (res != 0 && lplpBuffer.address != 0 && puLen.value > 0) { + if (res && lplpBuffer.address != 0 && puLen.value > 0) { return lplpBuffer.value.toDartString(); } } @@ -87,20 +91,22 @@ class FileVersionInfo { throw ArgumentError.value(filePath, 'filePath', 'File not present'); } - final lptstrFilename = TEXT(filePath); - final dwLen = GetFileVersionInfoSize(lptstrFilename, nullptr); + final lptstrFilename = filePath.toPcwstr(); + final sizeResult = GetFileVersionInfoSize(lptstrFilename, null); + final dwLen = sizeResult.value; final lpData = calloc(dwLen); // freed by the dispose() method - final lpSubBlock = TEXT(r'\VarFileInfo\Translation'); + final lpSubBlock = r'\VarFileInfo\Translation'.toPcwstr(); final lpTranslate = calloc>(); final puLen = calloc(); try { - if (GetFileVersionInfo(lptstrFilename, NULL, dwLen, lpData) == 0) { - throw WindowsException(HRESULT_FROM_WIN32(GetLastError())); + final infoResult = GetFileVersionInfo(lptstrFilename, dwLen, lpData); + if (!infoResult.value) { + throw WindowsException(infoResult.error.toHRESULT()); } - if (VerQueryValue(lpData, lpSubBlock, lpTranslate.cast(), puLen) == 0) { - throw WindowsException(HRESULT_FROM_WIN32(GetLastError())); + if (!VerQueryValue(lpData, lpSubBlock, lpTranslate.cast(), puLen)) { + throw WindowsException(GetLastError().toHRESULT()); } return FileVersionInfoData(lpBlock: lpData, lpLang: lpTranslate.value); } finally { diff --git a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_linux.dart b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_linux.dart index 0a0764201d..205bb027ce 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_linux.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_linux.dart @@ -45,7 +45,8 @@ class PackageInfoPlusLinuxPlugin extends PackageInfoPlatform { } Future<({DateTime? created, DateTime? modified})> _getExeAttributes( - String exePath) async { + String exePath, + ) async { try { final statResult = await Process.run( 'stat', @@ -58,8 +59,9 @@ class PackageInfoPlusLinuxPlugin extends PackageInfoPlatform { return await _fallbackAttributes(exePath); } - final String stdout = - statResult.stdout is String ? statResult.stdout : ''; + final String stdout = statResult.stdout is String + ? statResult.stdout + : ''; if (stdout.split(',').length != 2) { return await _fallbackAttributes(exePath); @@ -74,24 +76,24 @@ class PackageInfoPlusLinuxPlugin extends PackageInfoPlatform { ); final modificationTime = _parseSecondsString(modificationMillis); - return ( - created: creationTime, - modified: modificationTime, - ); + return (created: creationTime, modified: modificationTime); } catch (_) { return (created: null, modified: null); } } Future<({DateTime created, DateTime modified})> _fallbackAttributes( - String exePath) async { + String exePath, + ) async { final modifiedTime = await File(exePath).lastModified(); return (created: modifiedTime, modified: modifiedTime); } - DateTime? _parseSecondsString(String? secondsString, - {bool allowZero = true}) { + DateTime? _parseSecondsString( + String? secondsString, { + bool allowZero = true, + }) { if (secondsString == null) { return null; } diff --git a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart index abae97b8e4..3e884cae36 100644 --- a/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart +++ b/packages/package_info_plus/package_info_plus/lib/src/package_info_plus_web.dart @@ -17,7 +17,7 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { /// Create plugin with http client and asset manager for testing purposes. PackageInfoPlusWebPlugin([this._client, AssetManager? assetManagerMock]) - : _assetManager = assetManagerMock ?? assetManager; + : _assetManager = assetManagerMock ?? assetManager; /// Registers this class as the default instance of [PackageInfoPlatform]. static void registerWith(Registrar registrar) { @@ -48,8 +48,9 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { // Add file and cachebuster query return uri.replace( - query: 'cachebuster=$cacheBuster', - pathSegments: [...segments, 'version.json']); + query: 'cachebuster=$cacheBuster', + pathSegments: [...segments, 'version.json'], + ); } @override @@ -57,9 +58,9 @@ class PackageInfoPlusWebPlugin extends PackageInfoPlatform { final int cacheBuster = clock.now().millisecondsSinceEpoch; final Map versionMap = await _getVersionMap(baseUrl, cacheBuster) ?? - await _getVersionMap(_assetManager.baseUrl, cacheBuster) ?? - await _getVersionMap(web.window.document.baseURI, cacheBuster) ?? - {}; + await _getVersionMap(_assetManager.baseUrl, cacheBuster) ?? + await _getVersionMap(web.window.document.baseURI, cacheBuster) ?? + {}; return PackageInfoData( appName: versionMap['app_name'] ?? '', diff --git a/packages/package_info_plus/package_info_plus/pubspec.yaml b/packages/package_info_plus/package_info_plus/pubspec.yaml index dd7e3d3188..1e074c0dcb 100644 --- a/packages/package_info_plus/package_info_plus/pubspec.yaml +++ b/packages/package_info_plus/package_info_plus/pubspec.yaml @@ -28,25 +28,24 @@ flutter: dartPluginClass: PackageInfoPlusWindowsPlugin dependencies: - ffi: ^2.0.1 + clock: ^1.1.2 + ffi: ^2.2.0 flutter: sdk: flutter flutter_web_plugins: sdk: flutter - http: ">=0.13.5 <2.0.0" - meta: ^1.8.0 - path: ^1.8.2 + http: ^1.6.0 + meta: ^1.17.0 package_info_plus_platform_interface: ^3.2.1 - web: ">=0.5.0 <2.0.0" - win32: ^5.5.3 - clock: ^1.1.1 + path: ^1.9.1 + web: ^1.1.1 + win32: ^6.0.0 dev_dependencies: - flutter_lints: ">=4.0.0 <6.0.0" + flutter_lints: ^6.0.0 flutter_test: sdk: flutter - test: ^1.22.0 environment: - sdk: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + sdk: ">=3.11.0 <4.0.0" + flutter: ">=3.41.0" diff --git a/packages/package_info_plus/package_info_plus/test/package_info_plus_windows_test.dart b/packages/package_info_plus/package_info_plus/test/package_info_plus_windows_test.dart index 4b76909c7a..9aa78a4879 100644 --- a/packages/package_info_plus/package_info_plus/test/package_info_plus_windows_test.dart +++ b/packages/package_info_plus/package_info_plus/test/package_info_plus_windows_test.dart @@ -62,11 +62,14 @@ void main() { const missingFile = 'C:\\macos\\system128\\colonel.dll'; expect( - () => FileVersionInfo(missingFile), - throwsA(isArgumentError.having( + () => FileVersionInfo(missingFile), + throwsA( + isArgumentError.having( (e) => e.message, 'message', startsWith('File not present'), - ))); + ), + ), + ); }); } diff --git a/packages/package_info_plus/package_info_plus/test/package_info_test.dart b/packages/package_info_plus/package_info_plus/test/package_info_test.dart index 583cf4c508..8cd7976f73 100644 --- a/packages/package_info_plus/package_info_plus/test/package_info_test.dart +++ b/packages/package_info_plus/package_info_plus/test/package_info_test.dart @@ -20,51 +20,48 @@ void main() { final mockUpdateTime = now; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getAll': - return { - 'appName': 'package_info_example', - 'buildNumber': '1', - 'packageName': 'io.flutter.plugins.packageinfoexample', - 'version': '1.0', - 'installerStore': null, - 'installTime': mockInstallTime.millisecondsSinceEpoch.toString(), - 'updateTime': mockUpdateTime.millisecondsSinceEpoch.toString(), - }; - default: - assert(false); - return null; - } - }, - ); + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'getAll': + return { + 'appName': 'package_info_example', + 'buildNumber': '1', + 'packageName': 'io.flutter.plugins.packageinfoexample', + 'version': '1.0', + 'installerStore': null, + 'installTime': mockInstallTime.millisecondsSinceEpoch.toString(), + 'updateTime': mockUpdateTime.millisecondsSinceEpoch.toString(), + }; + default: + assert(false); + return null; + } + }); tearDown(() { log.clear(); }); - test('fromPlatform', () async { - final info = await PackageInfo.fromPlatform(); - expect(info.appName, 'package_info_example'); - expect(info.buildNumber, '1'); - expect(info.packageName, 'io.flutter.plugins.packageinfoexample'); - expect(info.version, '1.0'); - expect(info.installerStore, null); - expect(info.installTime, mockInstallTime); - expect(info.updateTime, mockUpdateTime); - expect( - log, - [ - isMethodCall('getAll', arguments: null), - ], - ); - }, onPlatform: { - 'linux': - const Skip('PackageInfoPlus on Linux does not use platform channels'), - }); + test( + 'fromPlatform', + () async { + final info = await PackageInfo.fromPlatform(); + expect(info.appName, 'package_info_example'); + expect(info.buildNumber, '1'); + expect(info.packageName, 'io.flutter.plugins.packageinfoexample'); + expect(info.version, '1.0'); + expect(info.installerStore, null); + expect(info.installTime, mockInstallTime); + expect(info.updateTime, mockUpdateTime); + expect(log, [isMethodCall('getAll', arguments: null)]); + }, + onPlatform: { + 'linux': const Skip( + 'PackageInfoPlus on Linux does not use platform channels', + ), + }, + ); test('Mock initial values', () async { PackageInfo.setMockInitialValues( diff --git a/packages/package_info_plus/package_info_plus_platform_interface/lib/method_channel_package_info.dart b/packages/package_info_plus/package_info_plus_platform_interface/lib/method_channel_package_info.dart index 3bf014328a..6a4f9ef156 100644 --- a/packages/package_info_plus/package_info_plus_platform_interface/lib/method_channel_package_info.dart +++ b/packages/package_info_plus/package_info_plus_platform_interface/lib/method_channel_package_info.dart @@ -3,8 +3,9 @@ import 'package:package_info_plus_platform_interface/package_info_data.dart'; import 'package_info_platform_interface.dart'; -const MethodChannel _channel = - MethodChannel('dev.fluttercommunity.plus/package_info'); +const MethodChannel _channel = MethodChannel( + 'dev.fluttercommunity.plus/package_info', +); /// An implementation of [PackageInfoPlatform] that uses method channels. class MethodChannelPackageInfo extends PackageInfoPlatform { diff --git a/packages/package_info_plus/package_info_plus_platform_interface/pubspec.yaml b/packages/package_info_plus/package_info_plus_platform_interface/pubspec.yaml index 73bdd040e5..ee6d0451b9 100644 --- a/packages/package_info_plus/package_info_plus_platform_interface/pubspec.yaml +++ b/packages/package_info_plus/package_info_plus_platform_interface/pubspec.yaml @@ -7,15 +7,15 @@ repository: https://github.com/fluttercommunity/plus_plugins/tree/main/packages/ dependencies: flutter: sdk: flutter - meta: ^1.8.0 - plugin_platform_interface: ^2.1.4 + meta: ^1.17.0 + plugin_platform_interface: ^2.1.8 dev_dependencies: flutter_test: sdk: flutter - mockito: ^5.4.0 - flutter_lints: ">=4.0.0 <6.0.0" + mockito: ^5.6.4 + flutter_lints: ^6.0.0 environment: - sdk: ">=2.18.0 <4.0.0" - flutter: ">=3.3.0" + sdk: ">=3.11.0 <4.0.0" + flutter: ">=3.41.0" diff --git a/packages/package_info_plus/package_info_plus_platform_interface/test/method_channel_package_info_test.dart b/packages/package_info_plus/package_info_plus_platform_interface/test/method_channel_package_info_test.dart index 4affe9f121..12ef697318 100644 --- a/packages/package_info_plus/package_info_plus_platform_interface/test/method_channel_package_info_test.dart +++ b/packages/package_info_plus/package_info_plus_platform_interface/test/method_channel_package_info_test.dart @@ -14,8 +14,10 @@ void main() { group('$PackageInfoPlatform', () { test('$PackageInfoPlatform() is the default instance', () { - expect(PackageInfoPlatform.instance, - isInstanceOf()); + expect( + PackageInfoPlatform.instance, + isInstanceOf(), + ); }); test('Cannot be implemented with `implements`', () { @@ -38,25 +40,22 @@ void main() { const channel = MethodChannel('dev.fluttercommunity.plus/package_info'); final log = []; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - channel, - (MethodCall methodCall) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getAll': - return { - 'appName': 'package_info_example', - 'buildNumber': '1', - 'packageName': 'io.flutter.plugins.packageinfoexample', - 'version': '1.0', - 'installerStore': 'testflight', - }; - default: - assert(false); - return null; - } - }, - ); + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + log.add(methodCall); + switch (methodCall.method) { + case 'getAll': + return { + 'appName': 'package_info_example', + 'buildNumber': '1', + 'packageName': 'io.flutter.plugins.packageinfoexample', + 'version': '1.0', + 'installerStore': 'testflight', + }; + default: + assert(false); + return null; + } + }); final packageInfo = MethodChannelPackageInfo(); @@ -66,10 +65,7 @@ void main() { test('getAll', () async { await packageInfo.getAll(); - expect( - log, - [isMethodCall('getAll', arguments: null)], - ); + expect(log, [isMethodCall('getAll', arguments: null)]); }); }); }