Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0ae3719

Browse files
authored
Report displays for macOS (#41998)
flutter/flutter#125939 and flutter/flutter#125938 for macOS fixes flutter/flutter#121352
1 parent d880a0c commit 0ae3719

File tree

4 files changed

+128
-23
lines changed

4 files changed

+128
-23
lines changed

shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -711,29 +711,46 @@ - (BOOL)running {
711711
return _engine != nullptr;
712712
}
713713

714+
- (void)updateDisplayConfig:(NSNotification*)notification {
715+
[self updateDisplayConfig];
716+
}
717+
714718
- (void)updateDisplayConfig {
715719
if (!_engine) {
716720
return;
717721
}
718722

719-
CVDisplayLinkRef displayLinkRef;
720-
CGDirectDisplayID mainDisplayID = CGMainDisplayID();
721-
CVDisplayLinkCreateWithCGDisplay(mainDisplayID, &displayLinkRef);
722-
CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef);
723-
if (!(nominal.flags & kCVTimeIsIndefinite)) {
724-
double refreshRate = static_cast<double>(nominal.timeScale) / nominal.timeValue;
723+
std::vector<FlutterEngineDisplay> displays;
724+
for (NSScreen* screen : [NSScreen screens]) {
725+
CGDirectDisplayID displayID =
726+
static_cast<CGDirectDisplayID>([screen.deviceDescription[@"NSScreenNumber"] integerValue]);
725727

726728
FlutterEngineDisplay display;
727729
display.struct_size = sizeof(display);
728-
display.display_id = mainDisplayID;
729-
display.refresh_rate = round(refreshRate);
730+
display.display_id = displayID;
731+
display.single_display = false;
732+
display.width = static_cast<size_t>(screen.frame.size.width);
733+
display.height = static_cast<size_t>(screen.frame.size.height);
734+
display.device_pixel_ratio = screen.backingScaleFactor;
735+
736+
CVDisplayLinkRef displayLinkRef = nil;
737+
CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLinkRef);
738+
739+
if (error == 0) {
740+
CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef);
741+
if (!(nominal.flags & kCVTimeIsIndefinite)) {
742+
double refreshRate = static_cast<double>(nominal.timeScale) / nominal.timeValue;
743+
display.refresh_rate = round(refreshRate);
744+
}
745+
CVDisplayLinkRelease(displayLinkRef);
746+
} else {
747+
display.refresh_rate = 0;
748+
}
730749

731-
std::vector<FlutterEngineDisplay> displays = {display};
732-
_embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
733-
displays.data(), displays.size());
750+
displays.push_back(display);
734751
}
735-
736-
CVDisplayLinkRelease(displayLinkRef);
752+
_embedderAPI.NotifyDisplayUpdate(_engine, kFlutterEngineDisplaysUpdateTypeStartup,
753+
displays.data(), displays.size());
737754
}
738755

739756
- (void)onSettingsChanged:(NSNotification*)notification {
@@ -782,14 +799,15 @@ - (void)updateWindowMetricsForViewController:(FlutterViewController*)viewControl
782799
CGRect scaledBounds = [view convertRectToBacking:view.bounds];
783800
CGSize scaledSize = scaledBounds.size;
784801
double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width;
785-
802+
auto displayId = [view.window.screen.deviceDescription[@"NSScreenNumber"] integerValue];
786803
const FlutterWindowMetricsEvent windowMetricsEvent = {
787804
.struct_size = sizeof(windowMetricsEvent),
788805
.width = static_cast<size_t>(scaledSize.width),
789806
.height = static_cast<size_t>(scaledSize.height),
790807
.pixel_ratio = pixelRatio,
791808
.left = static_cast<size_t>(scaledBounds.origin.x),
792809
.top = static_cast<size_t>(scaledBounds.origin.y),
810+
.display_id = static_cast<uint64_t>(displayId),
793811
};
794812
_embedderAPI.SendWindowMetricsEvent(_engine, &windowMetricsEvent);
795813
}
@@ -964,6 +982,14 @@ - (void)setUpNotificationCenterListeners {
964982
selector:@selector(applicationWillTerminate:)
965983
name:NSApplicationWillTerminateNotification
966984
object:nil];
985+
[center addObserver:self
986+
selector:@selector(windowDidChangeScreen:)
987+
name:NSWindowDidChangeScreenNotification
988+
object:nil];
989+
[center addObserver:self
990+
selector:@selector(updateDisplayConfig:)
991+
name:NSApplicationDidChangeScreenParametersNotification
992+
object:nil];
967993
}
968994

969995
- (void)addInternalPlugins {
@@ -987,6 +1013,16 @@ - (void)applicationWillTerminate:(NSNotification*)notification {
9871013
[self shutDownEngine];
9881014
}
9891015

1016+
- (void)windowDidChangeScreen:(NSNotification*)notification {
1017+
// Update window metric for all view controllers since the display_id has
1018+
// changed.
1019+
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
1020+
FlutterViewController* nextViewController;
1021+
while ((nextViewController = [viewControllerEnumerator nextObject])) {
1022+
[self updateWindowMetricsForViewController:nextViewController];
1023+
}
1024+
}
1025+
9901026
- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
9911027
BOOL enabled = [notification.userInfo[kEnhancedUserInterfaceKey] boolValue];
9921028
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];

shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include <objc/objc.h>
56
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
67
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
78
#include "gtest/gtest.h"
@@ -800,6 +801,57 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
800801
EXPECT_TRUE(announced);
801802
}
802803

804+
TEST_F(FlutterEngineTest, RunWithEntrypointUpdatesDisplayConfig) {
805+
BOOL updated = NO;
806+
FlutterEngine* engine = GetFlutterEngine();
807+
auto original_update_displays = engine.embedderAPI.NotifyDisplayUpdate;
808+
engine.embedderAPI.NotifyDisplayUpdate = MOCK_ENGINE_PROC(
809+
NotifyDisplayUpdate, ([&updated, &original_update_displays](
810+
auto engine, auto update_type, auto* displays, auto display_count) {
811+
updated = YES;
812+
return original_update_displays(engine, update_type, displays, display_count);
813+
}));
814+
815+
EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
816+
EXPECT_TRUE(updated);
817+
818+
updated = NO;
819+
[[NSNotificationCenter defaultCenter]
820+
postNotificationName:NSApplicationDidChangeScreenParametersNotification
821+
object:nil];
822+
EXPECT_TRUE(updated);
823+
}
824+
825+
TEST_F(FlutterEngineTest, NotificationsUpdateDisplays) {
826+
BOOL updated = NO;
827+
FlutterEngine* engine = GetFlutterEngine();
828+
auto original_set_viewport_metrics = engine.embedderAPI.SendWindowMetricsEvent;
829+
engine.embedderAPI.SendWindowMetricsEvent = MOCK_ENGINE_PROC(
830+
SendWindowMetricsEvent,
831+
([&updated, &original_set_viewport_metrics](auto engine, auto* window_metrics) {
832+
updated = YES;
833+
return original_set_viewport_metrics(engine, window_metrics);
834+
}));
835+
836+
EXPECT_TRUE([engine runWithEntrypoint:@"main"]);
837+
838+
updated = NO;
839+
[[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification
840+
object:nil];
841+
// No VC.
842+
EXPECT_FALSE(updated);
843+
844+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
845+
nibName:nil
846+
bundle:nil];
847+
[viewController loadView];
848+
viewController.flutterView.frame = CGRectMake(0, 0, 800, 600);
849+
850+
[[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification
851+
object:nil];
852+
EXPECT_TRUE(updated);
853+
}
854+
803855
} // namespace flutter::testing
804856

805857
// NOLINTEND(clang-analyzer-core.StackAddressEscape)

shell/platform/embedder/embedder.cc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,6 +2063,7 @@ FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
20632063
SAFE_ACCESS(flutter_metrics, physical_view_inset_bottom, 0.0);
20642064
metrics.physical_view_inset_left =
20652065
SAFE_ACCESS(flutter_metrics, physical_view_inset_left, 0.0);
2066+
metrics.display_id = SAFE_ACCESS(flutter_metrics, display_id, 0);
20662067

20672068
if (metrics.device_pixel_ratio <= 0.0) {
20682069
return LOG_EMBEDDER_ERROR(
@@ -2998,12 +2999,16 @@ FlutterEngineResult FlutterEngineNotifyDisplayUpdate(
29982999
switch (update_type) {
29993000
case kFlutterEngineDisplaysUpdateTypeStartup: {
30003001
std::vector<std::unique_ptr<flutter::Display>> displays;
3002+
const auto* display = embedder_displays;
30013003
for (size_t i = 0; i < display_count; i++) {
30023004
displays.push_back(std::make_unique<flutter::Display>(
3003-
embedder_displays[i].display_id, embedder_displays[i].refresh_rate,
3004-
// TODO(dnfield): Supply real values
3005-
// https://github.com/flutter/flutter/issues/125939
3006-
-1, -1, -1));
3005+
SAFE_ACCESS(display, display_id, i), //
3006+
SAFE_ACCESS(display, refresh_rate, 0), //
3007+
SAFE_ACCESS(display, width, 0), //
3008+
SAFE_ACCESS(display, height, 0), //
3009+
SAFE_ACCESS(display, device_pixel_ratio, 1)));
3010+
display = reinterpret_cast<const FlutterEngineDisplay*>(
3011+
reinterpret_cast<const uint8_t*>(display) + display->struct_size);
30073012
}
30083013
engine->GetShell().OnDisplayUpdates(std::move(displays));
30093014
return kSuccess;

shell/platform/embedder/embedder.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,11 @@ typedef struct {
805805
};
806806
} FlutterRendererConfig;
807807

808+
/// Display refers to a graphics hardware system consisting of a framebuffer,
809+
/// typically a monitor or a screen. This ID is unique per display and is
810+
/// stable until the Flutter application restarts.
811+
typedef uint64_t FlutterEngineDisplayId;
812+
808813
typedef struct {
809814
/// The size of this struct. Must be sizeof(FlutterWindowMetricsEvent).
810815
size_t struct_size;
@@ -826,6 +831,8 @@ typedef struct {
826831
double physical_view_inset_bottom;
827832
/// Left inset of window.
828833
double physical_view_inset_left;
834+
/// The identifier of the display the view is rendering on.
835+
FlutterEngineDisplayId display_id;
829836
} FlutterWindowMetricsEvent;
830837

831838
/// The phase of the pointer event.
@@ -1653,11 +1660,6 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)(
16531660
const FlutterLocale** /* supported_locales*/,
16541661
size_t /* Number of locales*/);
16551662

1656-
/// Display refers to a graphics hardware system consisting of a framebuffer,
1657-
/// typically a monitor or a screen. This ID is unique per display and is
1658-
/// stable until the Flutter application restarts.
1659-
typedef uint64_t FlutterEngineDisplayId;
1660-
16611663
typedef struct {
16621664
/// This size of this struct. Must be sizeof(FlutterDisplay).
16631665
size_t struct_size;
@@ -1673,6 +1675,16 @@ typedef struct {
16731675
/// This represents the refresh period in frames per second. This value may be
16741676
/// zero if the device is not running or unavailable or unknown.
16751677
double refresh_rate;
1678+
1679+
/// The width of the display, in physical pixels.
1680+
size_t width;
1681+
1682+
/// The height of the display, in physical pixels.
1683+
size_t height;
1684+
1685+
/// The pixel ratio of the display, which is used to convert physical pixels
1686+
/// to logical pixels.
1687+
double device_pixel_ratio;
16761688
} FlutterEngineDisplay;
16771689

16781690
/// The update type parameter that is passed to

0 commit comments

Comments
 (0)