Provide monitor information. (#161359)

Fixes https://github.com/flutter/flutter/issues/144230
diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter
index 45adea5..9fa0987 100644
--- a/engine/src/flutter/ci/licenses_golden/licenses_flutter
+++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter
@@ -44348,6 +44348,9 @@
 ORIGIN: ../../../flutter/shell/platform/linux/fl_dart_project.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_dart_project_private.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_display_monitor.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_display_monitor.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_display_monitor_test.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_engine.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_engine_private.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_engine_test.cc + ../../../flutter/LICENSE
@@ -47317,6 +47320,9 @@
 FILE: ../../../flutter/shell/platform/linux/fl_dart_project.cc
 FILE: ../../../flutter/shell/platform/linux/fl_dart_project_private.h
 FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc
+FILE: ../../../flutter/shell/platform/linux/fl_display_monitor.cc
+FILE: ../../../flutter/shell/platform/linux/fl_display_monitor.h
+FILE: ../../../flutter/shell/platform/linux/fl_display_monitor_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_engine.cc
 FILE: ../../../flutter/shell/platform/linux/fl_engine_private.h
 FILE: ../../../flutter/shell/platform/linux/fl_engine_test.cc
diff --git a/engine/src/flutter/shell/platform/linux/BUILD.gn b/engine/src/flutter/shell/platform/linux/BUILD.gn
index 94c90ad..ba44674 100644
--- a/engine/src/flutter/shell/platform/linux/BUILD.gn
+++ b/engine/src/flutter/shell/platform/linux/BUILD.gn
@@ -107,6 +107,7 @@
     "fl_binary_codec.cc",
     "fl_binary_messenger.cc",
     "fl_dart_project.cc",
+    "fl_display_monitor.cc",
     "fl_engine.cc",
     "fl_event_channel.cc",
     "fl_framebuffer.cc",
@@ -216,6 +217,7 @@
     "fl_binary_codec_test.cc",
     "fl_binary_messenger_test.cc",
     "fl_dart_project_test.cc",
+    "fl_display_monitor_test.cc",
     "fl_engine_test.cc",
     "fl_event_channel_test.cc",
     "fl_framebuffer_test.cc",
diff --git a/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc b/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc
new file mode 100644
index 0000000..bea40b2
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_display_monitor.cc
@@ -0,0 +1,120 @@
+// Copyright 2013 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.
+
+#include "flutter/shell/platform/linux/fl_display_monitor.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+
+struct _FlDisplayMonitor {
+  GObject parent_instance;
+
+  // Engine being updated.
+  GWeakRef engine;
+
+  // Display being monitored.
+  GdkDisplay* display;
+
+  // Mapping of GdkMonitor to display IDs.
+  GHashTable* display_ids_by_monitor;
+
+  // Next ID to assign to a new monitor.
+  FlutterEngineDisplayId next_display_id;
+};
+
+G_DEFINE_TYPE(FlDisplayMonitor, fl_display_monitor, G_TYPE_OBJECT)
+
+// Send the current monitor state to the engine.
+static void notify_display_update(FlDisplayMonitor* self) {
+  g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine));
+  if (engine == nullptr) {
+    return;
+  }
+
+  int n_monitors = gdk_display_get_n_monitors(self->display);
+  g_autofree FlutterEngineDisplay* displays =
+      g_new0(FlutterEngineDisplay, n_monitors);
+  for (int i = 0; i < n_monitors; i++) {
+    FlutterEngineDisplay* display = &displays[i];
+
+    GdkMonitor* monitor = gdk_display_get_monitor(self->display, i);
+    FlutterEngineDisplayId display_id = GPOINTER_TO_INT(
+        g_hash_table_lookup(self->display_ids_by_monitor, monitor));
+    if (display_id == 0) {
+      display_id = self->next_display_id;
+      g_hash_table_insert(self->display_ids_by_monitor, g_object_ref(monitor),
+                          GINT_TO_POINTER(display_id));
+      self->next_display_id++;
+    }
+
+    GdkRectangle geometry;
+    gdk_monitor_get_geometry(monitor, &geometry);
+
+    display->struct_size = sizeof(FlutterEngineDisplay);
+    display->display_id = display_id;
+    display->single_display = false;
+    display->refresh_rate = gdk_monitor_get_refresh_rate(monitor) / 1000.0;
+    display->width = geometry.width;
+    display->height = geometry.height;
+    display->device_pixel_ratio = gdk_monitor_get_scale_factor(monitor);
+  }
+
+  fl_engine_notify_display_update(engine, displays, n_monitors);
+}
+
+static void monitor_added_cb(FlDisplayMonitor* self, GdkMonitor* monitor) {
+  notify_display_update(self);
+}
+
+static void monitor_removed_cb(FlDisplayMonitor* self, GdkMonitor* monitor) {
+  g_hash_table_remove(self->display_ids_by_monitor, monitor);
+  notify_display_update(self);
+}
+
+static void fl_display_monitor_dispose(GObject* object) {
+  FlDisplayMonitor* self = FL_DISPLAY_MONITOR(object);
+
+  g_weak_ref_clear(&self->engine);
+  g_clear_object(&self->display);
+  g_clear_pointer(&self->display_ids_by_monitor, g_hash_table_unref);
+
+  G_OBJECT_CLASS(fl_display_monitor_parent_class)->dispose(object);
+}
+
+static void fl_display_monitor_class_init(FlDisplayMonitorClass* klass) {
+  GObjectClass* object_class = G_OBJECT_CLASS(klass);
+  object_class->dispose = fl_display_monitor_dispose;
+}
+
+static void fl_display_monitor_init(FlDisplayMonitor* self) {
+  self->display_ids_by_monitor = g_hash_table_new_full(
+      g_direct_hash, g_direct_equal, g_object_unref, nullptr);
+  self->next_display_id = 1;
+}
+
+FlDisplayMonitor* fl_display_monitor_new(FlEngine* engine,
+                                         GdkDisplay* display) {
+  FlDisplayMonitor* self =
+      FL_DISPLAY_MONITOR(g_object_new(fl_display_monitor_get_type(), nullptr));
+  g_weak_ref_init(&self->engine, engine);
+  self->display = GDK_DISPLAY(g_object_ref(display));
+  return self;
+}
+
+void fl_display_monitor_start(FlDisplayMonitor* self) {
+  g_return_if_fail(FL_IS_DISPLAY_MONITOR(self));
+
+  g_signal_connect_object(self->display, "monitor-added",
+                          G_CALLBACK(monitor_added_cb), self,
+                          G_CONNECT_SWAPPED);
+  g_signal_connect_object(self->display, "monitor-removed",
+                          G_CALLBACK(monitor_removed_cb), self,
+                          G_CONNECT_SWAPPED);
+  notify_display_update(self);
+}
+
+FlutterEngineDisplayId fl_display_monitor_get_display_id(FlDisplayMonitor* self,
+                                                         GdkMonitor* monitor) {
+  g_return_val_if_fail(FL_IS_DISPLAY_MONITOR(self), 0);
+  return GPOINTER_TO_INT(
+      g_hash_table_lookup(self->display_ids_by_monitor, monitor));
+}
diff --git a/engine/src/flutter/shell/platform/linux/fl_display_monitor.h b/engine/src/flutter/shell/platform/linux/fl_display_monitor.h
new file mode 100644
index 0000000..5660a96
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_display_monitor.h
@@ -0,0 +1,56 @@
+// Copyright 2013 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.
+
+#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_DISPLAY_MONITOR_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_DISPLAY_MONITOR_H_
+
+#include <gdk/gdk.h>
+
+#include "flutter/shell/platform/embedder/embedder.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlDisplayMonitor,
+                     fl_display_monitor,
+                     FL,
+                     DISPLAY_MONITOR,
+                     GObject);
+
+/**
+ * fl_display_monitor_new:
+ * @engine: engine to update.
+ * @display: display to monitor.
+ *
+ * Creates a new object to keep the engine updated with the currently used
+ * displays. In GDK, a display is called a "monitor".
+ *
+ * Returns: a new #FlDisplayMontior.
+ */
+FlDisplayMonitor* fl_display_monitor_new(FlEngine* engine, GdkDisplay* display);
+
+/**
+ * fl_display_monitor_start:
+ * @monitor: an #FlDisplayMonitor.
+ *
+ * Start monitoring for display changes.
+ */
+void fl_display_monitor_start(FlDisplayMonitor* monitor);
+
+/**
+ * fl_display_monitor_get_display_id:
+ * @monitor: an #FlDisplayMonitor.
+ * @gdk_monitor: GDK monitor to get display ID for.
+ *
+ * Get the ID Flutter is using for a given monitor.
+ *
+ * Returns: an ID or 0 if unknown.
+ */
+FlutterEngineDisplayId fl_display_monitor_get_display_id(
+    FlDisplayMonitor* monitor,
+    GdkMonitor* gdk_monitor);
+
+G_END_DECLS
+
+#endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_DISPLAY_MONITOR_H_
diff --git a/engine/src/flutter/shell/platform/linux/fl_display_monitor_test.cc b/engine/src/flutter/shell/platform/linux/fl_display_monitor_test.cc
new file mode 100644
index 0000000..b21dc73
--- /dev/null
+++ b/engine/src/flutter/shell/platform/linux/fl_display_monitor_test.cc
@@ -0,0 +1,36 @@
+// Copyright 2013 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.
+
+#include "flutter/shell/platform/linux/fl_display_monitor.h"
+#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+
+#include "gtest/gtest.h"
+
+TEST(FlDisplayMonitorTest, Test) {
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  g_autoptr(FlEngine) engine = fl_engine_new(project);
+
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_engine_start(engine, &error));
+  EXPECT_EQ(error, nullptr);
+
+  bool called = false;
+  fl_engine_get_embedder_api(engine)->NotifyDisplayUpdate = MOCK_ENGINE_PROC(
+      NotifyDisplayUpdate,
+      ([&called](auto engine, FlutterEngineDisplaysUpdateType update_type,
+                 const FlutterEngineDisplay* displays, size_t displays_length) {
+        called = true;
+
+        EXPECT_EQ(displays_length, 1u);
+
+        return kSuccess;
+      }));
+
+  g_autoptr(FlDisplayMonitor) monitor =
+      fl_display_monitor_new(engine, gdk_display_get_default());
+  EXPECT_FALSE(called);
+  fl_display_monitor_start(monitor);
+  EXPECT_TRUE(called);
+}
diff --git a/engine/src/flutter/shell/platform/linux/fl_engine.cc b/engine/src/flutter/shell/platform/linux/fl_engine.cc
index 1523906..01329cf 100644
--- a/engine/src/flutter/shell/platform/linux/fl_engine.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_engine.cc
@@ -12,6 +12,7 @@
 #include "flutter/shell/platform/embedder/embedder.h"
 #include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
 #include "flutter/shell/platform/linux/fl_dart_project_private.h"
+#include "flutter/shell/platform/linux/fl_display_monitor.h"
 #include "flutter/shell/platform/linux/fl_engine_private.h"
 #include "flutter/shell/platform/linux/fl_pixel_buffer_texture_private.h"
 #include "flutter/shell/platform/linux/fl_platform_handler.h"
@@ -41,6 +42,9 @@
   // The project this engine is running.
   FlDartProject* project;
 
+  // Watches for monitors changes to update engine.
+  FlDisplayMonitor* display_monitor;
+
   // Renders the Flutter app.
   FlRenderer* renderer;
 
@@ -532,6 +536,11 @@
   return self->renderer;
 }
 
+FlDisplayMonitor* fl_engine_get_display_monitor(FlEngine* self) {
+  g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
+  return self->display_monitor;
+}
+
 gboolean fl_engine_start(FlEngine* self, GError** error) {
   g_return_val_if_fail(FL_IS_ENGINE(self), FALSE);
 
@@ -642,23 +651,9 @@
     g_warning("Failed to enable accessibility features on Flutter engine");
   }
 
-  gdouble refresh_rate = fl_renderer_get_refresh_rate(self->renderer);
-  // FlutterEngineDisplay::refresh_rate expects 0 if the refresh rate is
-  // unknown.
-  if (refresh_rate <= 0.0) {
-    refresh_rate = 0.0;
-  }
-  FlutterEngineDisplay display = {};
-  display.struct_size = sizeof(FlutterEngineDisplay);
-  display.display_id = 0;
-  display.single_display = true;
-  display.refresh_rate = refresh_rate;
-
-  result = self->embedder_api.NotifyDisplayUpdate(
-      self->engine, kFlutterEngineDisplaysUpdateTypeStartup, &display, 1);
-  if (result != kSuccess) {
-    g_warning("Failed to notify display update to Flutter engine: %d", result);
-  }
+  self->display_monitor =
+      fl_display_monitor_new(self, gdk_display_get_default());
+  fl_display_monitor_start(self->display_monitor);
 
   return TRUE;
 }
@@ -667,6 +662,19 @@
   return &(self->embedder_api);
 }
 
+void fl_engine_notify_display_update(FlEngine* self,
+                                     const FlutterEngineDisplay* displays,
+                                     size_t displays_length) {
+  g_return_if_fail(FL_IS_ENGINE(self));
+
+  FlutterEngineResult result = self->embedder_api.NotifyDisplayUpdate(
+      self->engine, kFlutterEngineDisplaysUpdateTypeStartup, displays,
+      displays_length);
+  if (result != kSuccess) {
+    g_warning("Failed to notify display update to Flutter engine: %d", result);
+  }
+}
+
 FlutterViewId fl_engine_add_view(FlEngine* self,
                                  size_t width,
                                  size_t height,
@@ -681,11 +689,16 @@
   FlutterViewId view_id = self->next_view_id;
   self->next_view_id++;
 
+  // We don't know which display this view will open on, so set to zero and this
+  // will be updated in a following FlutterWindowMetricsEvent
+  FlutterEngineDisplayId display_id = 0;
+
   FlutterWindowMetricsEvent metrics;
   metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
   metrics.width = width;
   metrics.height = height;
   metrics.pixel_ratio = pixel_ratio;
+  metrics.display_id = display_id;
   metrics.view_id = view_id;
   FlutterAddViewInfo info;
   info.struct_size = sizeof(FlutterAddViewInfo);
@@ -881,6 +894,7 @@
 }
 
 void fl_engine_send_window_metrics_event(FlEngine* self,
+                                         FlutterEngineDisplayId display_id,
                                          FlutterViewId view_id,
                                          size_t width,
                                          size_t height,
@@ -896,6 +910,7 @@
   event.width = width;
   event.height = height;
   event.pixel_ratio = pixel_ratio;
+  event.display_id = display_id;
   event.view_id = view_id;
   self->embedder_api.SendWindowMetricsEvent(self->engine, &event);
 }
diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_private.h b/engine/src/flutter/shell/platform/linux/fl_engine_private.h
index 96e9b6b..419073a 100644
--- a/engine/src/flutter/shell/platform/linux/fl_engine_private.h
+++ b/engine/src/flutter/shell/platform/linux/fl_engine_private.h
@@ -8,6 +8,7 @@
 #include <glib-object.h>
 
 #include "flutter/shell/platform/embedder/embedder.h"
+#include "flutter/shell/platform/linux/fl_display_monitor.h"
 #include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h"
 #include "flutter/shell/platform/linux/fl_renderer.h"
 #include "flutter/shell/platform/linux/fl_task_runner.h"
@@ -82,6 +83,16 @@
 FlRenderer* fl_engine_get_renderer(FlEngine* engine);
 
 /**
+ * fl_engine_get_display_monitor:
+ * @engine: an #FlEngine.
+ *
+ * Gets the display monitor used by this engine.
+ *
+ * Returns: an #FlDisplayMonitor.
+ */
+FlDisplayMonitor* fl_engine_get_display_monitor(FlEngine* engine);
+
+/**
  * fl_engine_start:
  * @engine: an #FlEngine.
  * @error: (allow-none): #GError location to store the error occurring, or %NULL
@@ -104,6 +115,18 @@
 FlutterEngineProcTable* fl_engine_get_embedder_api(FlEngine* engine);
 
 /**
+ * fl_engine_notify_display_update:
+ * @engine: an #FlEngine.
+ * @displays: displays present on the system.
+ * @displays_length: length of @displays.
+ *
+ * Notify the current displays that are in the system.
+ */
+void fl_engine_notify_display_update(FlEngine* engine,
+                                     const FlutterEngineDisplay* displays,
+                                     size_t displays_length);
+
+/**
  * fl_engine_add_view:
  * @engine: an #FlEngine.
  * @width: width of view in pixels.
@@ -213,6 +236,7 @@
 /**
  * fl_engine_send_window_metrics_event:
  * @engine: an #FlEngine.
+ * @display_id: the display this view is rendering on.
  * @view_id: the view that the event occured on.
  * @width: width of the window in pixels.
  * @height: height of the window in pixels.
@@ -221,6 +245,7 @@
  * Sends a window metrics event to the engine.
  */
 void fl_engine_send_window_metrics_event(FlEngine* engine,
+                                         FlutterEngineDisplayId display_id,
                                          FlutterViewId view_id,
                                          size_t width,
                                          size_t height,
diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
index 9378799..5f183b0 100644
--- a/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_engine_test.cc
@@ -14,6 +14,63 @@
 // MOCK_ENGINE_PROC is leaky by design
 // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
 
+// Checks notifying display updates works.
+TEST(FlEngineTest, NotifyDisplayUpdate) {
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  g_autoptr(FlEngine) engine = fl_engine_new(project);
+
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_engine_start(engine, &error));
+  EXPECT_EQ(error, nullptr);
+
+  bool called = false;
+  fl_engine_get_embedder_api(engine)->NotifyDisplayUpdate = MOCK_ENGINE_PROC(
+      NotifyDisplayUpdate,
+      ([&called](auto engine, FlutterEngineDisplaysUpdateType update_type,
+                 const FlutterEngineDisplay* displays, size_t displays_length) {
+        called = true;
+        EXPECT_EQ(update_type, kFlutterEngineDisplaysUpdateTypeStartup);
+        EXPECT_EQ(displays_length, 2u);
+
+        EXPECT_EQ(displays[0].display_id, 1u);
+        EXPECT_EQ(displays[0].refresh_rate, 60);
+        EXPECT_EQ(displays[0].width, 1024u);
+        EXPECT_EQ(displays[0].height, 768u);
+        EXPECT_EQ(displays[0].device_pixel_ratio, 1.0);
+
+        EXPECT_EQ(displays[1].display_id, 2u);
+        EXPECT_EQ(displays[1].refresh_rate, 120);
+        EXPECT_EQ(displays[1].width, 3840u);
+        EXPECT_EQ(displays[1].height, 2160u);
+        EXPECT_EQ(displays[1].device_pixel_ratio, 2.0);
+
+        return kSuccess;
+      }));
+
+  FlutterEngineDisplay displays[2] = {
+      {
+          .struct_size = sizeof(FlutterEngineDisplay),
+          .display_id = 1,
+          .single_display = false,
+          .refresh_rate = 60.0,
+          .width = 1024,
+          .height = 768,
+          .device_pixel_ratio = 1.0,
+      },
+      {
+          .struct_size = sizeof(FlutterEngineDisplay),
+          .display_id = 2,
+          .single_display = false,
+          .refresh_rate = 120.0,
+          .width = 3840,
+          .height = 2160,
+          .device_pixel_ratio = 2.0,
+      }};
+  fl_engine_notify_display_update(engine, displays, 2);
+
+  EXPECT_TRUE(called);
+}
+
 // Checks sending window metrics events works.
 TEST(FlEngineTest, WindowMetrics) {
   g_autoptr(FlDartProject) project = fl_dart_project_new();
@@ -28,6 +85,7 @@
       SendWindowMetricsEvent,
       ([&called](auto engine, const FlutterWindowMetricsEvent* event) {
         called = true;
+        EXPECT_EQ(event->display_id, 99u);
         EXPECT_EQ(event->view_id, 1);
         EXPECT_EQ(event->width, static_cast<size_t>(3840));
         EXPECT_EQ(event->height, static_cast<size_t>(2160));
@@ -36,7 +94,7 @@
         return kSuccess;
       }));
 
-  fl_engine_send_window_metrics_event(engine, 1, 3840, 2160, 2.0);
+  fl_engine_send_window_metrics_event(engine, 99, 1, 3840, 2160, 2.0);
 
   EXPECT_TRUE(called);
 }
diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.cc b/engine/src/flutter/shell/platform/linux/fl_renderer.cc
index 600dab0..2d5d918 100644
--- a/engine/src/flutter/shell/platform/linux/fl_renderer.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_renderer.cc
@@ -381,11 +381,6 @@
   FL_RENDERER_GET_CLASS(self)->clear_current(self);
 }
 
-gdouble fl_renderer_get_refresh_rate(FlRenderer* self) {
-  g_return_val_if_fail(FL_IS_RENDERER(self), -1.0);
-  return FL_RENDERER_GET_CLASS(self)->get_refresh_rate(self);
-}
-
 guint32 fl_renderer_get_fbo(FlRenderer* self) {
   g_return_val_if_fail(FL_IS_RENDERER(self), 0);
 
diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer.h b/engine/src/flutter/shell/platform/linux/fl_renderer.h
index b59f5e4..b4b5216 100644
--- a/engine/src/flutter/shell/platform/linux/fl_renderer.h
+++ b/engine/src/flutter/shell/platform/linux/fl_renderer.h
@@ -55,16 +55,6 @@
    * @renderer: an #FlRenderer.
    */
   void (*clear_current)(FlRenderer* renderer);
-
-  /**
-   * Virtual method called when Flutter wants to get the refresh rate of the
-   * renderer.
-   * @renderer: an #FlRenderer.
-   *
-   * Returns: The refresh rate of the display in Hz. If the refresh rate is
-   * not available, returns -1.0.
-   */
-  gdouble (*get_refresh_rate)(FlRenderer* renderer);
 };
 
 /**
@@ -235,15 +225,6 @@
  */
 void fl_renderer_cleanup(FlRenderer* renderer);
 
-/**
- * fl_renderer_get_refresh_rate:
- * @renderer: an #FlRenderer.
- *
- * Returns: The refresh rate of the display in Hz. If the refresh rate is
- * not available, returns -1.0.
- */
-gdouble fl_renderer_get_refresh_rate(FlRenderer* renderer);
-
 G_END_DECLS
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERER_H_
diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer_gdk.cc b/engine/src/flutter/shell/platform/linux/fl_renderer_gdk.cc
index 2cbb46e..a0bc4e7 100644
--- a/engine/src/flutter/shell/platform/linux/fl_renderer_gdk.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_renderer_gdk.cc
@@ -39,24 +39,6 @@
   gdk_gl_context_clear_current();
 }
 
-// Implements FlRenderer::get_refresh_rate.
-static gdouble fl_renderer_gdk_get_refresh_rate(FlRenderer* renderer) {
-  FlRendererGdk* self = FL_RENDERER_GDK(renderer);
-  GdkDisplay* display = gdk_window_get_display(self->window);
-  GdkMonitor* monitor =
-      gdk_display_get_monitor_at_window(display, self->window);
-  if (monitor == nullptr) {
-    return -1.0;
-  }
-
-  int refresh_rate = gdk_monitor_get_refresh_rate(monitor);
-  if (refresh_rate <= 0) {
-    return -1.0;
-  }
-  // the return value is in milli-hertz, convert to hertz
-  return static_cast<gdouble>(refresh_rate) / 1000.0;
-}
-
 static void fl_renderer_gdk_dispose(GObject* object) {
   FlRendererGdk* self = FL_RENDERER_GDK(object);
 
@@ -74,7 +56,6 @@
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_renderer_gdk_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_gdk_clear_current;
-  FL_RENDERER_CLASS(klass)->get_refresh_rate = fl_renderer_gdk_get_refresh_rate;
 }
 
 static void fl_renderer_gdk_init(FlRendererGdk* self) {}
diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer_headless.cc b/engine/src/flutter/shell/platform/linux/fl_renderer_headless.cc
index c94cd1f..1736e14 100644
--- a/engine/src/flutter/shell/platform/linux/fl_renderer_headless.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_renderer_headless.cc
@@ -19,18 +19,11 @@
 // Implements FlRenderer::clear_current.
 static void fl_renderer_headless_clear_current(FlRenderer* renderer) {}
 
-// Implements FlRenderer::get_refresh_rate.
-static gdouble fl_renderer_headless_get_refresh_rate(FlRenderer* renderer) {
-  return -1.0;
-}
-
 static void fl_renderer_headless_class_init(FlRendererHeadlessClass* klass) {
   FL_RENDERER_CLASS(klass)->make_current = fl_renderer_headless_make_current;
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_renderer_headless_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_renderer_headless_clear_current;
-  FL_RENDERER_CLASS(klass)->get_refresh_rate =
-      fl_renderer_headless_get_refresh_rate;
 }
 
 static void fl_renderer_headless_init(FlRendererHeadless* self) {}
diff --git a/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc b/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc
index c5d2346..0c808fb 100644
--- a/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_renderer_test.cc
@@ -93,20 +93,6 @@
   EXPECT_EQ(texture_2d_binding, kFakeTextureName);
 }
 
-static constexpr double kExpectedRefreshRate = 120.0;
-static gdouble renderer_get_refresh_rate(FlRenderer* renderer) {
-  return kExpectedRefreshRate;
-}
-
-TEST(FlRendererTest, RefreshRate) {
-  g_autoptr(FlMockRenderer) renderer =
-      fl_mock_renderer_new(&renderer_get_refresh_rate);
-
-  gdouble result_refresh_rate =
-      fl_renderer_get_refresh_rate(FL_RENDERER(renderer));
-  EXPECT_DOUBLE_EQ(result_refresh_rate, kExpectedRefreshRate);
-}
-
 TEST(FlRendererTest, BlitFramebuffer) {
   ::testing::NiceMock<flutter::testing::MockEpoxy> epoxy;
 
diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc
index 49a1ec6..7b5f87b 100644
--- a/engine/src/flutter/shell/platform/linux/fl_view.cc
+++ b/engine/src/flutter/shell/platform/linux/fl_view.cc
@@ -221,8 +221,21 @@
   GtkAllocation allocation;
   gtk_widget_get_allocation(GTK_WIDGET(self), &allocation);
   gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self));
+
+  // Note we can't detect if a window is moved between monitors - this
+  // information is provided by Wayland but GTK only notifies us if the scale
+  // has changed, so moving between two monitors of the same scale doesn't
+  // provide any information.
+
+  GdkWindow* window =
+      gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)));
+  GdkMonitor* monitor = gdk_display_get_monitor_at_window(
+      gtk_widget_get_display(GTK_WIDGET(self)), window);
   fl_engine_send_window_metrics_event(
-      self->engine, self->view_id, allocation.width * scale_factor,
+      self->engine,
+      fl_display_monitor_get_display_id(
+          fl_engine_get_display_monitor(self->engine), monitor),
+      self->view_id, allocation.width * scale_factor,
       allocation.height * scale_factor, scale_factor);
 
   // Make sure the view has been realized and its size has been allocated before
diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc
index 247537a..5ae2926 100644
--- a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc
+++ b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.cc
@@ -6,7 +6,6 @@
 
 struct _FlMockRenderer {
   FlRenderer parent_instance;
-  FlMockRendererGetRefreshRate get_refresh_rate;
 };
 
 struct _FlMockRenderable {
@@ -33,22 +32,11 @@
 // Implements FlRenderer::clear_current.
 static void fl_mock_renderer_clear_current(FlRenderer* renderer) {}
 
-// Implements FlRenderer::get_refresh_rate.
-static gdouble fl_mock_renderer_default_get_refresh_rate(FlRenderer* renderer) {
-  FlMockRenderer* self = FL_MOCK_RENDERER(renderer);
-  if (self->get_refresh_rate != nullptr) {
-    return self->get_refresh_rate(renderer);
-  }
-  return -1.0;
-}
-
 static void fl_mock_renderer_class_init(FlMockRendererClass* klass) {
   FL_RENDERER_CLASS(klass)->make_current = fl_mock_renderer_make_current;
   FL_RENDERER_CLASS(klass)->make_resource_current =
       fl_mock_renderer_make_resource_current;
   FL_RENDERER_CLASS(klass)->clear_current = fl_mock_renderer_clear_current;
-  FL_RENDERER_CLASS(klass)->get_refresh_rate =
-      fl_mock_renderer_default_get_refresh_rate;
 }
 
 static void fl_mock_renderer_init(FlMockRenderer* self) {}
@@ -70,11 +58,9 @@
 static void fl_mock_renderable_init(FlMockRenderable* self) {}
 
 // Creates a stub renderer
-FlMockRenderer* fl_mock_renderer_new(
-    FlMockRendererGetRefreshRate get_refresh_rate) {
+FlMockRenderer* fl_mock_renderer_new() {
   FlMockRenderer* self =
       FL_MOCK_RENDERER(g_object_new(fl_mock_renderer_get_type(), nullptr));
-  self->get_refresh_rate = get_refresh_rate;
   return self;
 }
 
diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.h b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.h
index 9a2207e..5c6ada3 100644
--- a/engine/src/flutter/shell/platform/linux/testing/mock_renderer.h
+++ b/engine/src/flutter/shell/platform/linux/testing/mock_renderer.h
@@ -24,8 +24,7 @@
 
 typedef gdouble (*FlMockRendererGetRefreshRate)(FlRenderer* renderer);
 
-FlMockRenderer* fl_mock_renderer_new(
-    FlMockRendererGetRefreshRate get_refresh_rate = nullptr);
+FlMockRenderer* fl_mock_renderer_new();
 
 FlMockRenderable* fl_mock_renderable_new();
 
diff --git a/engine/src/flutter/shell/platform/linux/testing/mock_window.cc b/engine/src/flutter/shell/platform/linux/testing/mock_window.cc
index 8990230..ab7e383 100644
--- a/engine/src/flutter/shell/platform/linux/testing/mock_window.cc
+++ b/engine/src/flutter/shell/platform/linux/testing/mock_window.cc
@@ -12,12 +12,26 @@
   mock = this;
 }
 
+GdkDisplay* gdk_display_get_default() {
+  return GDK_DISPLAY(g_object_new(gdk_display_get_type(), nullptr));
+}
+
+void gdk_display_beep(GdkDisplay* display) {}
+
 GdkWindowState gdk_window_get_state(GdkWindow* window) {
   return mock->gdk_window_get_state(window);
 }
 
 GdkDisplay* gdk_window_get_display(GdkWindow* window) {
-  return nullptr;
+  return GDK_DISPLAY(g_object_new(gdk_display_get_type(), nullptr));
+}
+
+int gdk_display_get_n_monitors(GdkDisplay* display) {
+  return 1;
+}
+
+GdkMonitor* gdk_display_get_monitor(GdkDisplay* display, int n) {
+  return GDK_MONITOR(g_object_new(gdk_monitor_get_type(), nullptr));
 }
 
 GdkMonitor* gdk_display_get_monitor_at_window(GdkDisplay* display,