diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 91aa17e903a33..2ee21664e2e82 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -705,29 +705,46 @@ - (BOOL)running { return _engine != nullptr; } +- (void)updateDisplayConfig:(NSNotification*)notification { + [self updateDisplayConfig]; +} + - (void)updateDisplayConfig { if (!_engine) { return; } - CVDisplayLinkRef displayLinkRef; - CGDirectDisplayID mainDisplayID = CGMainDisplayID(); - CVDisplayLinkCreateWithCGDisplay(mainDisplayID, &displayLinkRef); - CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef); - if (!(nominal.flags & kCVTimeIsIndefinite)) { - double refreshRate = static_cast(nominal.timeScale) / nominal.timeValue; + std::vector displays; + for (NSScreen* screen : [NSScreen screens]) { + CGDirectDisplayID displayID = + static_cast([screen.deviceDescription[@"NSScreenNumber"] integerValue]); FlutterEngineDisplay display; display.struct_size = sizeof(display); - display.display_id = mainDisplayID; - display.refresh_rate = round(refreshRate); + display.display_id = displayID; + display.single_display = false; + display.width = static_cast(screen.frame.size.width); + display.height = static_cast(screen.frame.size.height); + display.device_pixel_ratio = screen.backingScaleFactor; + + CVDisplayLinkRef displayLinkRef = nil; + CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLinkRef); + + if (error == 0) { + CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef); + if (!(nominal.flags & kCVTimeIsIndefinite)) { + double refreshRate = static_cast(nominal.timeScale) / nominal.timeValue; + display.refresh_rate = round(refreshRate); + } + CVDisplayLinkRelease(displayLinkRef); + } else { + display.refresh_rate = 0; + } - std::vector displays = {display}; - _embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup, - displays.data(), displays.size()); + displays.push_back(display); } - - CVDisplayLinkRelease(displayLinkRef); + _embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup, + displays.data(), displays.size()); } - (void)onSettingsChanged:(NSNotification*)notification { @@ -776,7 +793,7 @@ - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewControl CGRect scaledBounds = [view convertRectToBacking:view.bounds]; CGSize scaledSize = scaledBounds.size; double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width; - + auto displayId = [view.window.screen.deviceDescription[@"NSScreenNumber"] integerValue]; const FlutterWindowMetricsEvent windowMetricsEvent = { .struct_size = sizeof(windowMetricsEvent), .width = static_cast(scaledSize.width), @@ -784,6 +801,7 @@ - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewControl .pixel_ratio = pixelRatio, .left = static_cast(scaledBounds.origin.x), .top = static_cast(scaledBounds.origin.y), + .display_id = static_cast(displayId), }; _embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent); } @@ -958,6 +976,14 @@ - (void)setUpNotificationCenterListeners { selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; + [center addObserver:self + selector:@selector(windowDidChangeScreen:) + name:NSWindowDidChangeScreenNotification + object:nil]; + [center addObserver:self + selector:@selector(updateDisplayConfig:) + name:NSApplicationDidChangeScreenParametersNotification + object:nil]; } - (void)addInternalPlugins { @@ -981,6 +1007,16 @@ - (void)applicationWillTerminate:(NSNotification*)notification { [self shutDownEngine]; } +- (void)windowDidChangeScreen:(NSNotification*)notification { + // Update window metric for all view controllers since the display_id has + // changed. + NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator]; + FlutterViewController* nextViewController; + while ((nextViewController = [viewControllerEnumerator nextObject])) { + [self updateWindowMetricsForViewController:nextViewController]; + } +} + - (void)onAccessibilityStatusChanged:(NSNotification*)notification { BOOL enabled = [notification.userInfo[kEnhancedUserInterfaceKey] boolValue]; NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 92e87100dd0de..4b3182b3124fc 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #include "gtest/gtest.h" @@ -790,6 +791,57 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable EXPECT_TRUE(announced); } +TEST_F(FlutterEngineTest, RunWithEntrypointUpdatesDisplayConfig) { + BOOL updated = NO; + FlutterEngine* engine = GetFlutterEngine(); + auto original_update_displays = engine.embedderAPI.NotifyDisplayUpdate; + engine.embedderAPI.NotifyDisplayUpdate = MOCK_ENGINE_PROC( + NotifyDisplayUpdate, ([&updated, &original_update_displays]( + auto engine, auto update_type, auto* displays, auto display_count) { + updated = YES; + return original_update_displays(engine, update_type, displays, display_count); + })); + + EXPECT_TRUE([engine runWithEntrypoint:@"main"]); + EXPECT_TRUE(updated); + + updated = NO; + [[NSNotificationCenter defaultCenter] + postNotificationName:NSApplicationDidChangeScreenParametersNotification + object:nil]; + EXPECT_TRUE(updated); +} + +TEST_F(FlutterEngineTest, NotificationsUpdateDisplays) { + BOOL updated = NO; + FlutterEngine* engine = GetFlutterEngine(); + auto original_set_viewport_metrics = engine.embedderAPI.SendWindowMetricsEvent; + engine.embedderAPI.SendWindowMetricsEvent = MOCK_ENGINE_PROC( + SendWindowMetricsEvent, + ([&updated, &original_set_viewport_metrics](auto engine, auto* window_metrics) { + updated = YES; + return original_set_viewport_metrics(engine, window_metrics); + })); + + EXPECT_TRUE([engine runWithEntrypoint:@"main"]); + + updated = NO; + [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification + object:nil]; + // No VC. + EXPECT_FALSE(updated); + + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + [viewController loadView]; + viewController.flutterView.frame = CGRectMake(0, 0, 800, 600); + + [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification + object:nil]; + EXPECT_TRUE(updated); +} + } // namespace flutter::testing // NOLINTEND(clang-analyzer-core.StackAddressEscape) diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 876e8f3529351..5ca87ffc79e11 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2062,6 +2062,7 @@ FlutterEngineResult FlutterEngineSendWindowMetricsEvent( SAFE_ACCESS(flutter_metrics, physical_view_inset_bottom, 0.0); metrics.physical_view_inset_left = SAFE_ACCESS(flutter_metrics, physical_view_inset_left, 0.0); + metrics.display_id = SAFE_ACCESS(flutter_metrics, display_id, 0); if (metrics.device_pixel_ratio <= 0.0) { return LOG_EMBEDDER_ERROR( @@ -2997,12 +2998,16 @@ FlutterEngineResult FlutterEngineNotifyDisplayUpdate( switch (update_type) { case kFlutterEngineDisplaysUpdateTypeStartup: { std::vector> displays; + const auto* display = embedder_displays; for (size_t i = 0; i < display_count; i++) { displays.push_back(std::make_unique( - embedder_displays[i].display_id, embedder_displays[i].refresh_rate, - // TODO(dnfield): Supply real values - // https://github.com/flutter/flutter/issues/125939 - -1, -1, -1)); + SAFE_ACCESS(display, display_id, i), // + SAFE_ACCESS(display, refresh_rate, 0), // + SAFE_ACCESS(display, width, 0), // + SAFE_ACCESS(display, height, 0), // + SAFE_ACCESS(display, device_pixel_ratio, 1))); + display = reinterpret_cast( + reinterpret_cast(display) + display->struct_size); } engine->GetShell().OnDisplayUpdates(std::move(displays)); return kSuccess; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 1710299f6555d..bd92484433ccc 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -805,6 +805,11 @@ typedef struct { }; } FlutterRendererConfig; +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + typedef struct { /// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent). size_t struct_size; @@ -826,6 +831,8 @@ typedef struct { double physical_view_inset_bottom; /// Left inset of window. double physical_view_inset_left; + /// The identifier of the display the view is rendering on. + FlutterEngineDisplayId display_id; } FlutterWindowMetricsEvent; /// The phase of the pointer event. @@ -1653,11 +1660,6 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( const FlutterLocale** /* supported_locales*/, size_t /* Number of locales*/); -/// Display refers to a graphics hardware system consisting of a framebuffer, -/// typically a monitor or a screen. This ID is unique per display and is -/// stable until the Flutter application restarts. -typedef uint64_t FlutterEngineDisplayId; - typedef struct { /// This size of this struct. Must be sizeof(FlutterDisplay). size_t struct_size; @@ -1673,6 +1675,16 @@ typedef struct { /// This represents the refresh period in frames per second. This value may be /// zero if the device is not running or unavailable or unknown. double refresh_rate; + + /// The width of the display, in physical pixels. + size_t width; + + /// The height of the display, in physical pixels. + size_t height; + + /// The pixel ratio of the display, which is used to convert physical pixels + /// to logical pixels. + double device_pixel_ratio; } FlutterEngineDisplay; /// The update type parameter that is passed to