Skip to content

Commit

Permalink
Report displays for macOS (flutter#41998)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnfield authored May 17, 2023
1 parent d880a0c commit 0ae3719
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 23 deletions.
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 @@ -711,29 +711,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 @@ -782,14 +799,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 @@ -964,6 +982,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 @@ -987,6 +1013,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 @@ -800,6 +801,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 @@ -2063,6 +2063,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 @@ -2998,12 +2999,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;

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

0 comments on commit 0ae3719

Please sign in to comment.