diff --git a/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt b/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt +++ b/dev/integration_tests/flutter_gallery/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/dev/integration_tests/flutter_gallery/windows/runner/win32_window.cpp b/dev/integration_tests/flutter_gallery/windows/runner/win32_window.cpp index 635a5b29f53..baca1d553e4 100644 --- a/dev/integration_tests/flutter_gallery/windows/runner/win32_window.cpp +++ b/dev/integration_tests/flutter_gallery/windows/runner/win32_window.cpp @@ -4,14 +4,28 @@ #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"; +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; @@ -130,6 +144,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -196,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -251,3 +271,17 @@ 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); + LONG 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/dev/integration_tests/flutter_gallery/windows/runner/win32_window.h b/dev/integration_tests/flutter_gallery/windows/runner/win32_window.h index b161e9b84b8..84a27dc75df 100644 --- a/dev/integration_tests/flutter_gallery/windows/runner/win32_window.h +++ b/dev/integration_tests/flutter_gallery/windows/runner/win32_window.h @@ -91,6 +91,9 @@ class Win32Window { // 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. diff --git a/dev/integration_tests/windows_startup_test/lib/main.dart b/dev/integration_tests/windows_startup_test/lib/main.dart index e2d5d985aee..1ebd1e41809 100644 --- a/dev/integration_tests/windows_startup_test/lib/main.dart +++ b/dev/integration_tests/windows_startup_test/lib/main.dart @@ -5,9 +5,10 @@ import 'dart:async'; import 'dart:ui' as ui; -import 'package:flutter/services.dart'; import 'package:flutter_driver/driver_extension.dart'; +import 'windows.dart'; + void drawHelloWorld() { final ui.ParagraphStyle style = ui.ParagraphStyle(); final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(style) @@ -30,33 +31,38 @@ void drawHelloWorld() { } void main() async { - // Create a completer to send the result back to the integration test. - final Completer completer = Completer(); - enableFlutterDriverExtension(handler: (String? message) => completer.future); + // Create a completer to send the window visibility result back to the + // integration test. + final Completer visibilityCompleter = Completer(); + enableFlutterDriverExtension(handler: (String? message) async { + if (message == 'verifyWindowVisibility') { + return visibilityCompleter.future; + } else if (message == 'verifyTheme') { + final bool app = await isAppDarkModeEnabled(); + final bool system = await isSystemDarkModeEnabled(); + + return (app == system) + ? 'success' + : 'error: app dark mode ($app) does not match system dark mode ($system)'; + } + + throw 'Unrecognized message: $message'; + }); try { - const MethodChannel methodChannel = - MethodChannel('tests.flutter.dev/windows_startup_test'); - - final bool? visible = await methodChannel.invokeMethod('isWindowVisible'); - if (visible == null || visible == true) { + if (await isWindowVisible()) { throw 'Window should be hidden at startup'; } bool firstFrame = true; ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) async { - final bool? visible = await methodChannel.invokeMethod('isWindowVisible'); - if (visible == null) { - throw 'Method channel unavailable'; - } - - if (visible == true) { + if (await isWindowVisible()) { if (firstFrame) { throw 'Window should be hidden on first frame'; } - if (!completer.isCompleted) { - completer.complete('success'); + if (!visibilityCompleter.isCompleted) { + visibilityCompleter.complete('success'); } } @@ -68,7 +74,7 @@ void main() async { ui.PlatformDispatcher.instance.scheduleFrame(); } catch (e) { - completer.completeError(e); + visibilityCompleter.completeError(e); rethrow; } } diff --git a/dev/integration_tests/windows_startup_test/lib/windows.dart b/dev/integration_tests/windows_startup_test/lib/windows.dart new file mode 100644 index 00000000000..6773180b4f0 --- /dev/null +++ b/dev/integration_tests/windows_startup_test/lib/windows.dart @@ -0,0 +1,38 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +const MethodChannel _kMethodChannel = + MethodChannel('tests.flutter.dev/windows_startup_test'); + +/// Returns true if the application's window is visible. +Future isWindowVisible() async { + final bool? visible = await _kMethodChannel.invokeMethod('isWindowVisible'); + if (visible == null) { + throw 'Method channel unavailable'; + } + + return visible; +} + +/// Returns true if the app's dark mode is enabled. +Future isAppDarkModeEnabled() async { + final bool? enabled = await _kMethodChannel.invokeMethod('isAppDarkModeEnabled'); + if (enabled == null) { + throw 'Method channel unavailable'; + } + + return enabled; +} + +/// Returns true if the operating system dark mode setting is enabled. +Future isSystemDarkModeEnabled() async { + final bool? enabled = await _kMethodChannel.invokeMethod('isSystemDarkModeEnabled'); + if (enabled == null) { + throw 'Method channel unavailable'; + } + + return enabled; +} diff --git a/dev/integration_tests/windows_startup_test/test_driver/main_test.dart b/dev/integration_tests/windows_startup_test/test_driver/main_test.dart index f126dc92fb3..a002fb6763e 100644 --- a/dev/integration_tests/windows_startup_test/test_driver/main_test.dart +++ b/dev/integration_tests/windows_startup_test/test_driver/main_test.dart @@ -8,7 +8,16 @@ import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; void main() { test('Windows app starts and draws frame', () async { final FlutterDriver driver = await FlutterDriver.connect(printCommunication: true); - final String result = await driver.requestData(null); + final String result = await driver.requestData('verifyWindowVisibility'); + + expect(result, equals('success')); + + await driver.close(); + }, timeout: Timeout.none); + + test('Windows app theme matches system theme', () async { + final FlutterDriver driver = await FlutterDriver.connect(printCommunication: true); + final String result = await driver.requestData('verifyTheme'); expect(result, equals('success')); diff --git a/dev/integration_tests/windows_startup_test/windows/runner/CMakeLists.txt b/dev/integration_tests/windows_startup_test/windows/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/dev/integration_tests/windows_startup_test/windows/runner/CMakeLists.txt +++ b/dev/integration_tests/windows_startup_test/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/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp b/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp index cb4c3038568..7a26a0afcef 100644 --- a/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp +++ b/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp @@ -7,11 +7,25 @@ #include #include +#include #include #include #include "flutter/generated_plugin_registrant.h" +/// 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 kGetPreferredBrightnessRegKey[] = +L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} @@ -50,11 +64,38 @@ bool FlutterWindow::OnCreate() { &flutter::StandardMethodCodec::GetInstance()); channel.SetMethodCallHandler( - [](const flutter::MethodCall<>& call, + [&](const flutter::MethodCall<>& call, std::unique_ptr> result) { - std::scoped_lock lock(visible_mutex); - if (call.method_name() == "isWindowVisible") { + std::string method = call.method_name(); + + if (method == "isWindowVisible") { + std::scoped_lock lock(visible_mutex); result->Success(visible); + } else if (method == "isAppDarkModeEnabled") { + BOOL enabled; + HRESULT hr = DwmGetWindowAttribute(GetHandle(), + DWMWA_USE_IMMERSIVE_DARK_MODE, + &enabled, sizeof(enabled)); + if (SUCCEEDED(hr)) { + result->Success((bool)enabled); + } else { + result->Error("error", "Received result handle " + hr); + } + } else if (method == "isSystemDarkModeEnabled") { + DWORD data; + DWORD data_size = sizeof(data); + LONG status = RegGetValue(HKEY_CURRENT_USER, + kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &data, &data_size); + + if (status == ERROR_SUCCESS) { + // Preferred brightness is 0 if dark mode is enabled, + // otherwise non-zero. + result->Success(data == 0); + } else { + result->Error("error", "Received status " + status); + } } else { result->NotImplemented(); } diff --git a/dev/integration_tests/windows_startup_test/windows/runner/win32_window.cpp b/dev/integration_tests/windows_startup_test/windows/runner/win32_window.cpp index 635a5b29f53..baca1d553e4 100644 --- a/dev/integration_tests/windows_startup_test/windows/runner/win32_window.cpp +++ b/dev/integration_tests/windows_startup_test/windows/runner/win32_window.cpp @@ -4,14 +4,28 @@ #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"; +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; @@ -130,6 +144,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -196,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -251,3 +271,17 @@ 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); + LONG 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/dev/integration_tests/windows_startup_test/windows/runner/win32_window.h b/dev/integration_tests/windows_startup_test/windows/runner/win32_window.h index b161e9b84b8..84a27dc75df 100644 --- a/dev/integration_tests/windows_startup_test/windows/runner/win32_window.h +++ b/dev/integration_tests/windows_startup_test/windows/runner/win32_window.h @@ -91,6 +91,9 @@ class Win32Window { // 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. diff --git a/dev/manual_tests/windows/runner/CMakeLists.txt b/dev/manual_tests/windows/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/dev/manual_tests/windows/runner/CMakeLists.txt +++ b/dev/manual_tests/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/dev/manual_tests/windows/runner/win32_window.cpp b/dev/manual_tests/windows/runner/win32_window.cpp index 635a5b29f53..baca1d553e4 100644 --- a/dev/manual_tests/windows/runner/win32_window.cpp +++ b/dev/manual_tests/windows/runner/win32_window.cpp @@ -4,14 +4,28 @@ #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"; +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; @@ -130,6 +144,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -196,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -251,3 +271,17 @@ 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); + LONG 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/dev/manual_tests/windows/runner/win32_window.h b/dev/manual_tests/windows/runner/win32_window.h index b161e9b84b8..84a27dc75df 100644 --- a/dev/manual_tests/windows/runner/win32_window.h +++ b/dev/manual_tests/windows/runner/win32_window.h @@ -91,6 +91,9 @@ class Win32Window { // 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. diff --git a/examples/hello_world/windows/runner/CMakeLists.txt b/examples/hello_world/windows/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/examples/hello_world/windows/runner/CMakeLists.txt +++ b/examples/hello_world/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/examples/hello_world/windows/runner/win32_window.cpp b/examples/hello_world/windows/runner/win32_window.cpp index 9338467d48d..b4c1257e8d8 100644 --- a/examples/hello_world/windows/runner/win32_window.cpp +++ b/examples/hello_world/windows/runner/win32_window.cpp @@ -4,14 +4,28 @@ #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"; +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; @@ -130,6 +144,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -196,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -251,3 +271,17 @@ 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); + LONG 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/examples/hello_world/windows/runner/win32_window.h b/examples/hello_world/windows/runner/win32_window.h index b161e9b84b8..84a27dc75df 100644 --- a/examples/hello_world/windows/runner/win32_window.h +++ b/examples/hello_world/windows/runner/win32_window.h @@ -91,6 +91,9 @@ class Win32Window { // 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. diff --git a/examples/platform_channel/windows/runner/CMakeLists.txt b/examples/platform_channel/windows/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/examples/platform_channel/windows/runner/CMakeLists.txt +++ b/examples/platform_channel/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/examples/platform_channel/windows/runner/win32_window.cpp b/examples/platform_channel/windows/runner/win32_window.cpp index 87e7081db94..baca1d553e4 100644 --- a/examples/platform_channel/windows/runner/win32_window.cpp +++ b/examples/platform_channel/windows/runner/win32_window.cpp @@ -4,14 +4,28 @@ #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"; +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; @@ -81,6 +95,8 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() { 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; @@ -95,7 +111,9 @@ void WindowClassRegistrar::UnregisterWindowClass() { class_registered_ = false; } -Win32Window::Win32Window() { ++g_active_window_count; } +Win32Window::Win32Window() { + ++g_active_window_count; +} Win32Window::~Win32Window() { --g_active_window_count; @@ -126,6 +144,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -134,7 +154,8 @@ bool Win32Window::Show() { } // 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) { @@ -153,7 +174,9 @@ 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: @@ -189,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -228,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; @@ -242,3 +271,17 @@ 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); + LONG 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/examples/platform_channel/windows/runner/win32_window.h b/examples/platform_channel/windows/runner/win32_window.h index 23ed7296603..84a27dc75df 100644 --- a/examples/platform_channel/windows/runner/win32_window.h +++ b/examples/platform_channel/windows/runner/win32_window.h @@ -63,7 +63,8 @@ class Win32Window { // 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; @@ -82,13 +83,17 @@ class Win32Window { // non-client DPI scaling so that the non-client area automatically // responsponds 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; + // 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. diff --git a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt index 17411a8ab8e..394917c053a 100644 --- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt +++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/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/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.cpp b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.cpp index c7bcb4df477..09df9bce227 100644 --- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.cpp +++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.cpp @@ -1,13 +1,27 @@ #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"; +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; @@ -126,6 +140,8 @@ bool Win32Window::Create(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } @@ -192,6 +208,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -247,3 +267,17 @@ 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); + LONG 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/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.h b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.h index a4b8f1f0ef8..c86632d8a6b 100644 --- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.h +++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/win32_window.h @@ -87,6 +87,9 @@ class Win32Window { // 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.