Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 50 additions & 14 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>(nominal.timeScale) / nominal.timeValue;
std::vector<FlutterEngineDisplay> displays;
for (NSScreen* screen : [NSScreen screens]) {
CGDirectDisplayID displayID =
static_cast<CGDirectDisplayID>([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<size_t>(screen.frame.size.width);
display.height = static_cast<size_t>(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<double>(nominal.timeScale) / nominal.timeValue;
display.refresh_rate = round(refreshRate);
}
CVDisplayLinkRelease(displayLinkRef);
} else {
display.refresh_rate = 0;
}

std::vector<FlutterEngineDisplay> 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 {
Expand Down Expand Up @@ -776,14 +793,15 @@ - (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<size_t>(scaledSize.width),
.height = static_cast<size_t>(scaledSize.height),
.pixel_ratio = pixelRatio,
.left = static_cast<size_t>(scaledBounds.origin.x),
.top = static_cast<size_t>(scaledBounds.origin.y),
.display_id = static_cast<uint64_t>(displayId),
};
_embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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];
Expand Down
52 changes: 52 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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 <objc/objc.h>
#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"
Expand Down Expand Up @@ -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)
13 changes: 9 additions & 4 deletions shell/platform/embedder/embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -2997,12 +2998,16 @@ FlutterEngineResult FlutterEngineNotifyDisplayUpdate(
switch (update_type) {
case kFlutterEngineDisplaysUpdateTypeStartup: {
std::vector<std::unique_ptr<flutter::Display>> displays;
const auto* display = embedder_displays;
for (size_t i = 0; i < display_count; i++) {
displays.push_back(std::make_unique<flutter::Display>(
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<const FlutterEngineDisplay*>(
reinterpret_cast<const uint8_t*>(display) + display->struct_size);
}
engine->GetShell().OnDisplayUpdates(std::move(displays));
return kSuccess;
Expand Down
22 changes: 17 additions & 5 deletions shell/platform/embedder/embedder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


typedef struct {
/// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent).
size_t struct_size;
Expand All @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down