diff --git a/eframe/src/epi.rs b/eframe/src/epi.rs
index a7c3ece4b726..5e8c1d11c8e7 100644
--- a/eframe/src/epi.rs
+++ b/eframe/src/epi.rs
@@ -341,6 +341,17 @@ pub struct WebInfo {
     pub location: Location,
 }
 
+/// Information about the application's main window, if available.
+#[derive(Clone, Debug)]
+pub struct WindowInfo {
+    /// Coordinates of the window's outer top left corner, relative to the top left corner of the first display.
+    /// Unit: egui points (logical pixels).
+    pub position: egui::Pos2,
+
+    /// Window inner size in egui points (logical pixels).
+    pub size: egui::Vec2,
+}
+
 /// Information about the URL.
 ///
 /// Everything has been percent decoded (`%20` -> ` ` etc).
@@ -411,6 +422,9 @@ pub struct IntegrationInfo {
 
     /// The OS native pixels-per-point
     pub native_pixels_per_point: Option<f32>,
+
+    /// Window-specific geometry information, if provided by the platform.
+    pub window_info: Option<WindowInfo>,
 }
 
 // ----------------------------------------------------------------------------
diff --git a/eframe/src/native/epi_integration.rs b/eframe/src/native/epi_integration.rs
index 3f9cd45481fb..3d082fbdedb4 100644
--- a/eframe/src/native/epi_integration.rs
+++ b/eframe/src/native/epi_integration.rs
@@ -1,4 +1,4 @@
-use crate::epi;
+use crate::{epi, WindowInfo};
 use egui_winit::{native_pixels_per_point, WindowSettings};
 
 pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
@@ -8,6 +8,28 @@ pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
     }
 }
 
+pub fn read_window_info(
+    window: &winit::window::Window,
+    pixels_per_point: f32,
+) -> Option<WindowInfo> {
+    match window.outer_position() {
+        Ok(pos) => {
+            let pos = pos.to_logical::<f32>(pixels_per_point.into());
+            let size = window
+                .inner_size()
+                .to_logical::<f32>(pixels_per_point.into());
+            Some(WindowInfo {
+                position: egui::Pos2 { x: pos.x, y: pos.y },
+                size: egui::Vec2 {
+                    x: size.width,
+                    y: size.height,
+                },
+            })
+        }
+        Err(_) => None,
+    }
+}
+
 pub fn window_builder(
     native_options: &epi::NativeOptions,
     window_settings: &Option<WindowSettings>,
@@ -176,6 +198,7 @@ impl EpiIntegration {
                 prefer_dark_mode,
                 cpu_usage: None,
                 native_pixels_per_point: Some(native_pixels_per_point(window)),
+                window_info: read_window_info(window, egui_ctx.pixels_per_point()),
             },
             output: Default::default(),
             storage,
@@ -238,6 +261,7 @@ impl EpiIntegration {
     ) -> egui::FullOutput {
         let frame_start = std::time::Instant::now();
 
+        self.frame.info.window_info = read_window_info(window, self.egui_ctx.pixels_per_point());
         let raw_input = self.egui_winit.take_egui_input(window);
         let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
             crate::profile_scope!("App::update");
diff --git a/eframe/src/web/backend.rs b/eframe/src/web/backend.rs
index 41608e8a7211..c871cd893c41 100644
--- a/eframe/src/web/backend.rs
+++ b/eframe/src/web/backend.rs
@@ -152,6 +152,7 @@ impl AppRunner {
             prefer_dark_mode,
             cpu_usage: None,
             native_pixels_per_point: Some(native_pixels_per_point()),
+            window_info: None,
         };
         let storage = LocalStorage::default();