Cargo.lock 🔗
@@ -7328,7 +7328,6 @@ dependencies = [
"parking_lot",
"pathfinder_geometry",
"pin-project",
- "png 0.17.16",
"postage",
"pretty_assertions",
"profiling",
Richard Feldman created
This reverts commit b5d0f5d4f89c02df151a2b017d8f46f0ada29ed3.
Cargo.lock | 1
crates/gpui/Cargo.toml | 9
crates/gpui/examples/capture_zed.rs | 447 -------------------------------
crates/gpui/examples/screenshot.rs | 428 -----------------------------
4 files changed, 885 deletions(-)
@@ -7328,7 +7328,6 @@ dependencies = [
"parking_lot",
"pathfinder_geometry",
"pin-project",
- "png 0.17.16",
"postage",
"pretty_assertions",
"profiling",
@@ -253,7 +253,6 @@ rand.workspace = true
reqwest_client = { workspace = true, features = ["test-support"] }
unicode-segmentation.workspace = true
util = { workspace = true, features = ["test-support"] }
-png = "0.17"
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-resource = "3.0"
@@ -332,14 +331,6 @@ path = "examples/window_shadow.rs"
name = "grid_layout"
path = "examples/grid_layout.rs"
-[[example]]
-name = "screenshot"
-path = "examples/screenshot.rs"
-
-[[example]]
-name = "capture_zed"
-path = "examples/capture_zed.rs"
-
[[example]]
name = "mouse_pressure"
path = "examples/mouse_pressure.rs"
@@ -1,447 +0,0 @@
-//! Utility: Capture Screenshots of Running Zed Windows
-//!
-//! This utility finds running Zed windows and captures screenshots of them.
-//! It can be used for debugging, documentation, or visual testing.
-//!
-//! Usage:
-//! cargo run -p gpui --example capture_zed
-//!
-//! Options (via environment variables):
-//! CAPTURE_OUTPUT_DIR - Directory to save screenshots (default: current directory)
-//! CAPTURE_WINDOW_INDEX - Which Zed window to capture, 0-indexed (default: all)
-//!
-//! Note: This requires macOS and Screen Recording permissions.
-//! The first time you run this, macOS will prompt you to grant permission.
-
-use std::path::PathBuf;
-
-fn main() {
- #[cfg(target_os = "macos")]
- {
- macos::run();
- }
-
- #[cfg(not(target_os = "macos"))]
- {
- eprintln!("This utility only works on macOS");
- std::process::exit(1);
- }
-}
-
-#[cfg(target_os = "macos")]
-mod macos {
- use std::path::PathBuf;
-
- // FFI declarations for CoreGraphics window list
- #[link(name = "CoreGraphics", kind = "framework")]
- unsafe extern "C" {
- fn CGWindowListCopyWindowInfo(option: u32, relativeToWindow: u32) -> CFArrayRef;
- fn CGWindowListCreateImage(
- rect: CGRect,
- list_option: u32,
- window_id: u32,
- image_option: u32,
- ) -> CGImageRef;
- fn CGImageGetWidth(image: CGImageRef) -> usize;
- fn CGImageGetHeight(image: CGImageRef) -> usize;
- fn CGImageGetBytesPerRow(image: CGImageRef) -> usize;
- fn CGImageGetDataProvider(image: CGImageRef) -> CGDataProviderRef;
- fn CGImageRelease(image: CGImageRef);
- fn CGDataProviderCopyData(provider: CGDataProviderRef) -> CFDataRef;
- }
-
- #[link(name = "CoreFoundation", kind = "framework")]
- unsafe extern "C" {
- fn CFArrayGetCount(array: CFArrayRef) -> isize;
- fn CFArrayGetValueAtIndex(array: CFArrayRef, idx: isize) -> *const std::ffi::c_void;
- fn CFDictionaryGetValue(
- dict: CFDictionaryRef,
- key: *const std::ffi::c_void,
- ) -> *const std::ffi::c_void;
- fn CFStringCreateWithCString(
- alloc: *const std::ffi::c_void,
- cstr: *const i8,
- encoding: u32,
- ) -> CFStringRef;
- fn CFStringGetCStringPtr(string: CFStringRef, encoding: u32) -> *const i8;
- fn CFNumberGetValue(
- number: CFNumberRef,
- theType: i32,
- valuePtr: *mut std::ffi::c_void,
- ) -> bool;
- fn CFDataGetLength(data: CFDataRef) -> isize;
- fn CFDataGetBytePtr(data: CFDataRef) -> *const u8;
- fn CFRelease(cf: *const std::ffi::c_void);
- }
-
- type CFArrayRef = *const std::ffi::c_void;
- type CFDictionaryRef = *const std::ffi::c_void;
- type CFStringRef = *const std::ffi::c_void;
- type CFNumberRef = *const std::ffi::c_void;
- type CGImageRef = *mut std::ffi::c_void;
- type CGDataProviderRef = *mut std::ffi::c_void;
- type CFDataRef = *mut std::ffi::c_void;
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGPoint {
- x: f64,
- y: f64,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGSize {
- width: f64,
- height: f64,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGRect {
- origin: CGPoint,
- size: CGSize,
- }
-
- impl CGRect {
- fn null() -> Self {
- CGRect {
- origin: CGPoint {
- x: f64::INFINITY,
- y: f64::INFINITY,
- },
- size: CGSize {
- width: 0.0,
- height: 0.0,
- },
- }
- }
- }
-
- // Constants
- #[allow(non_upper_case_globals)]
- const kCGWindowListOptionOnScreenOnly: u32 = 1 << 0;
- #[allow(non_upper_case_globals)]
- const kCGWindowListExcludeDesktopElements: u32 = 1 << 4;
- #[allow(non_upper_case_globals)]
- const kCGWindowListOptionIncludingWindow: u32 = 1 << 3;
- #[allow(non_upper_case_globals)]
- const kCGWindowImageBoundsIgnoreFraming: u32 = 1 << 0;
- #[allow(non_upper_case_globals)]
- const kCFStringEncodingUTF8: u32 = 0x08000100;
- #[allow(non_upper_case_globals)]
- const kCFNumberSInt32Type: i32 = 3;
-
- #[derive(Debug)]
- struct WindowInfo {
- window_id: u32,
- owner_name: String,
- window_name: String,
- bounds: (f64, f64, f64, f64), // x, y, width, height
- }
-
- fn get_cf_string(key: &str) -> CFStringRef {
- unsafe {
- let cstr = std::ffi::CString::new(key).unwrap();
- CFStringCreateWithCString(std::ptr::null(), cstr.as_ptr(), kCFStringEncodingUTF8)
- }
- }
-
- fn cf_string_to_rust(cf_string: CFStringRef) -> Option<String> {
- if cf_string.is_null() {
- return None;
- }
- unsafe {
- let ptr = CFStringGetCStringPtr(cf_string, kCFStringEncodingUTF8);
- if ptr.is_null() {
- return None;
- }
- Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
- }
- }
-
- fn cf_number_to_i32(cf_number: CFNumberRef) -> Option<i32> {
- if cf_number.is_null() {
- return None;
- }
- unsafe {
- let mut value: i32 = 0;
- if CFNumberGetValue(
- cf_number,
- kCFNumberSInt32Type,
- &mut value as *mut i32 as *mut std::ffi::c_void,
- ) {
- Some(value)
- } else {
- None
- }
- }
- }
-
- fn get_zed_windows() -> Vec<WindowInfo> {
- let mut windows = Vec::new();
-
- unsafe {
- let window_list = CGWindowListCopyWindowInfo(
- kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
- 0,
- );
-
- if window_list.is_null() {
- return windows;
- }
-
- let count = CFArrayGetCount(window_list);
-
- let key_owner_name = get_cf_string("kCGWindowOwnerName");
- let key_window_name = get_cf_string("kCGWindowName");
- let key_window_number = get_cf_string("kCGWindowNumber");
- let key_bounds = get_cf_string("kCGWindowBounds");
- let key_x = get_cf_string("X");
- let key_y = get_cf_string("Y");
- let key_width = get_cf_string("Width");
- let key_height = get_cf_string("Height");
-
- for i in 0..count {
- let dict = CFArrayGetValueAtIndex(window_list, i) as CFDictionaryRef;
- if dict.is_null() {
- continue;
- }
-
- // Get owner name
- let owner_name_cf = CFDictionaryGetValue(dict, key_owner_name) as CFStringRef;
- let owner_name = cf_string_to_rust(owner_name_cf).unwrap_or_default();
-
- // Check if this is a Zed window
- if !owner_name.contains("Zed") {
- continue;
- }
-
- // Get window name
- let window_name_cf = CFDictionaryGetValue(dict, key_window_name) as CFStringRef;
- let window_name = cf_string_to_rust(window_name_cf).unwrap_or_default();
-
- // Get window ID
- let window_number_cf = CFDictionaryGetValue(dict, key_window_number) as CFNumberRef;
- let window_id = cf_number_to_i32(window_number_cf).unwrap_or(0) as u32;
-
- // Get bounds
- let bounds_dict = CFDictionaryGetValue(dict, key_bounds) as CFDictionaryRef;
- let (x, y, width, height) = if !bounds_dict.is_null() {
- let x_cf = CFDictionaryGetValue(bounds_dict, key_x) as CFNumberRef;
- let y_cf = CFDictionaryGetValue(bounds_dict, key_y) as CFNumberRef;
- let w_cf = CFDictionaryGetValue(bounds_dict, key_width) as CFNumberRef;
- let h_cf = CFDictionaryGetValue(bounds_dict, key_height) as CFNumberRef;
-
- (
- cf_number_to_i32(x_cf).unwrap_or(0) as f64,
- cf_number_to_i32(y_cf).unwrap_or(0) as f64,
- cf_number_to_i32(w_cf).unwrap_or(0) as f64,
- cf_number_to_i32(h_cf).unwrap_or(0) as f64,
- )
- } else {
- (0.0, 0.0, 0.0, 0.0)
- };
-
- // Skip windows with zero size (like menu bar items)
- if width < 100.0 || height < 100.0 {
- continue;
- }
-
- windows.push(WindowInfo {
- window_id,
- owner_name,
- window_name,
- bounds: (x, y, width, height),
- });
- }
-
- // Clean up CF strings
- CFRelease(key_owner_name);
- CFRelease(key_window_name);
- CFRelease(key_window_number);
- CFRelease(key_bounds);
- CFRelease(key_x);
- CFRelease(key_y);
- CFRelease(key_width);
- CFRelease(key_height);
- CFRelease(window_list);
- }
-
- windows
- }
-
- fn capture_window_to_png(
- window_id: u32,
- output_path: &std::path::Path,
- ) -> Result<(usize, usize), Box<dyn std::error::Error>> {
- use std::fs::File;
- use std::io::BufWriter;
-
- // Capture the window
- let image = unsafe {
- CGWindowListCreateImage(
- CGRect::null(),
- kCGWindowListOptionIncludingWindow,
- window_id,
- kCGWindowImageBoundsIgnoreFraming,
- )
- };
-
- if image.is_null() {
- return Err("Failed to capture window - image is null. \
- Make sure Screen Recording permission is granted in \
- System Preferences > Privacy & Security > Screen Recording."
- .into());
- }
-
- // Get image dimensions
- let width = unsafe { CGImageGetWidth(image) };
- let height = unsafe { CGImageGetHeight(image) };
-
- if width == 0 || height == 0 {
- unsafe { CGImageRelease(image) };
- return Err("Captured image has zero dimensions".into());
- }
-
- // Get the image data
- let data_provider = unsafe { CGImageGetDataProvider(image) };
- if data_provider.is_null() {
- unsafe { CGImageRelease(image) };
- return Err("Failed to get image data provider".into());
- }
-
- let data = unsafe { CGDataProviderCopyData(data_provider) };
- if data.is_null() {
- unsafe { CGImageRelease(image) };
- return Err("Failed to copy image data".into());
- }
-
- let length = unsafe { CFDataGetLength(data) } as usize;
- let ptr = unsafe { CFDataGetBytePtr(data) };
- let bytes = unsafe { std::slice::from_raw_parts(ptr, length) };
- let bytes_per_row = unsafe { CGImageGetBytesPerRow(image) };
-
- // The image is in BGRA format with potential row padding, convert to RGBA for PNG
- let mut rgba_bytes = Vec::with_capacity(width * height * 4);
- for row in 0..height {
- let row_start = row * bytes_per_row;
- for col in 0..width {
- let pixel_start = row_start + col * 4;
- if pixel_start + 3 < length {
- rgba_bytes.push(bytes[pixel_start + 2]); // R (was B)
- rgba_bytes.push(bytes[pixel_start + 1]); // G
- rgba_bytes.push(bytes[pixel_start]); // B (was R)
- rgba_bytes.push(bytes[pixel_start + 3]); // A
- }
- }
- }
-
- // Write PNG file
- let file = File::create(output_path)?;
- let w = BufWriter::new(file);
- let mut encoder = png::Encoder::new(w, width as u32, height as u32);
- encoder.set_color(png::ColorType::Rgba);
- encoder.set_depth(png::BitDepth::Eight);
- let mut writer = encoder.write_header()?;
- writer.write_image_data(&rgba_bytes)?;
-
- // Cleanup
- unsafe {
- CFRelease(data as *const _);
- CGImageRelease(image);
- }
-
- Ok((width, height))
- }
-
- pub fn run() {
- println!("Looking for Zed windows...\n");
-
- let windows = get_zed_windows();
-
- if windows.is_empty() {
- eprintln!("No Zed windows found!");
- eprintln!("\nMake sure Zed is running and visible on screen.");
- eprintln!("Note: Minimized windows cannot be captured.");
- std::process::exit(1);
- }
-
- println!("Found {} Zed window(s):\n", windows.len());
- for (i, window) in windows.iter().enumerate() {
- println!(
- " [{}] Window ID: {}, Title: \"{}\", Size: {}x{}",
- i, window.window_id, window.window_name, window.bounds.2, window.bounds.3
- );
- }
- println!();
-
- // Get output directory
- let output_dir = std::env::var("CAPTURE_OUTPUT_DIR")
- .map(PathBuf::from)
- .unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
-
- // Get window index filter
- let window_index_filter: Option<usize> = std::env::var("CAPTURE_WINDOW_INDEX")
- .ok()
- .and_then(|s| s.parse().ok());
-
- // Capture windows
- let windows_to_capture: Vec<_> = match window_index_filter {
- Some(idx) => {
- if idx < windows.len() {
- vec![&windows[idx]]
- } else {
- eprintln!(
- "Window index {} is out of range (0-{})",
- idx,
- windows.len() - 1
- );
- std::process::exit(1);
- }
- }
- None => windows.iter().collect(),
- };
-
- println!("Capturing {} window(s)...\n", windows_to_capture.len());
-
- for (i, window) in windows_to_capture.iter().enumerate() {
- let filename = if window.window_name.is_empty() {
- format!("zed_window_{}.png", i)
- } else {
- // Sanitize window name for filename
- let safe_name: String = window
- .window_name
- .chars()
- .map(|c| {
- if c.is_alphanumeric() || c == '-' || c == '_' {
- c
- } else {
- '_'
- }
- })
- .collect();
- format!("zed_{}.png", safe_name)
- };
-
- let output_path = output_dir.join(&filename);
-
- match capture_window_to_png(window.window_id, &output_path) {
- Ok((width, height)) => {
- println!(
- "✓ Captured \"{}\" -> {} ({}x{})",
- window.window_name,
- output_path.display(),
- width,
- height
- );
- }
- Err(e) => {
- eprintln!("✗ Failed to capture \"{}\": {}", window.window_name, e);
- }
- }
- }
-
- println!("\nDone!");
- }
-}
@@ -1,428 +0,0 @@
-//! Example: Off-screen Window Rendering with Screenshots
-//!
-//! This example demonstrates how to:
-//! 1. Create a window positioned off-screen (so it's not visible to the user)
-//! 2. Render real GPUI content using Metal
-//! 3. Take screenshots of the window using CGWindowListCreateImage
-//! 4. Save the screenshots as PNG files
-//!
-//! This is useful for automated visual testing where you want real rendering
-//! but don't want windows appearing on screen.
-//!
-//! Usage:
-//! cargo run -p gpui --example screenshot
-//!
-//! Note: This requires macOS and Screen Recording permissions.
-//! The first time you run this, macOS will prompt you to grant permission.
-
-use gpui::{
- App, AppContext, Application, Bounds, Context, Entity, IntoElement, Render, SharedString,
- Window, WindowBounds, WindowHandle, WindowOptions, div, point, prelude::*, px, rgb, size,
-};
-use raw_window_handle::{HasWindowHandle, RawWindowHandle};
-use std::path::PathBuf;
-use std::time::Duration;
-
-// ============================================================================
-// GPUI View to Render
-// ============================================================================
-
-struct ScreenshotDemo {
- counter: u32,
- message: SharedString,
-}
-
-impl ScreenshotDemo {
- fn new() -> Self {
- Self {
- counter: 0,
- message: "Hello, Screenshot!".into(),
- }
- }
-
- fn increment(&mut self) {
- self.counter += 1;
- self.message = format!("Counter: {}", self.counter).into();
- }
-}
-
-impl Render for ScreenshotDemo {
- fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
- div()
- .flex()
- .flex_col()
- .gap_4()
- .bg(rgb(0x1e1e2e)) // Dark background
- .size_full()
- .justify_center()
- .items_center()
- .child(
- div()
- .text_3xl()
- .text_color(rgb(0xcdd6f4))
- .child(self.message.clone()),
- )
- .child(
- div()
- .flex()
- .gap_3()
- .child(colored_box(rgb(0xf38ba8))) // Red
- .child(colored_box(rgb(0xa6e3a1))) // Green
- .child(colored_box(rgb(0x89b4fa))) // Blue
- .child(colored_box(rgb(0xf9e2af))), // Yellow
- )
- .child(
- div()
- .mt_4()
- .px_4()
- .py_2()
- .bg(rgb(0x313244))
- .rounded_md()
- .text_color(rgb(0xbac2de))
- .child(format!("Frame: {}", self.counter)),
- )
- }
-}
-
-fn colored_box(color: gpui::Rgba) -> impl IntoElement {
- div()
- .size_16()
- .bg(color)
- .rounded_lg()
- .shadow_md()
- .border_2()
- .border_color(rgb(0x45475a))
-}
-
-// ============================================================================
-// Screenshot Capture (macOS-specific using CGWindowListCreateImage)
-// ============================================================================
-
-#[cfg(target_os = "macos")]
-mod screenshot {
- use std::path::Path;
-
- // FFI declarations for CoreGraphics
- #[link(name = "CoreGraphics", kind = "framework")]
- unsafe extern "C" {
- fn CGWindowListCreateImage(
- rect: CGRect,
- list_option: u32,
- window_id: u32,
- image_option: u32,
- ) -> CGImageRef;
-
- fn CGImageGetWidth(image: CGImageRef) -> usize;
- fn CGImageGetHeight(image: CGImageRef) -> usize;
- fn CGImageGetDataProvider(image: CGImageRef) -> CGDataProviderRef;
- fn CGImageRelease(image: CGImageRef);
- fn CGDataProviderCopyData(provider: CGDataProviderRef) -> CFDataRef;
- }
-
- #[link(name = "CoreFoundation", kind = "framework")]
- unsafe extern "C" {
- fn CFDataGetLength(data: CFDataRef) -> isize;
- fn CFDataGetBytePtr(data: CFDataRef) -> *const u8;
- fn CFRelease(cf: *const std::ffi::c_void);
- }
-
- type CGImageRef = *mut std::ffi::c_void;
- type CGDataProviderRef = *mut std::ffi::c_void;
- type CFDataRef = *mut std::ffi::c_void;
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGPoint {
- x: f64,
- y: f64,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGSize {
- width: f64,
- height: f64,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct CGRect {
- origin: CGPoint,
- size: CGSize,
- }
-
- impl CGRect {
- fn null() -> Self {
- CGRect {
- origin: CGPoint {
- x: f64::INFINITY,
- y: f64::INFINITY,
- },
- size: CGSize {
- width: 0.0,
- height: 0.0,
- },
- }
- }
- }
-
- #[allow(non_upper_case_globals)]
- const kCGWindowListOptionIncludingWindow: u32 = 1 << 3;
- #[allow(non_upper_case_globals)]
- const kCGWindowImageBoundsIgnoreFraming: u32 = 1 << 0;
-
- /// Captures a screenshot of the specified window and saves it as a PNG.
- pub fn capture_window_to_png(
- window_number: i64,
- output_path: &Path,
- ) -> Result<(), Box<dyn std::error::Error>> {
- use std::fs::File;
- use std::io::BufWriter;
-
- // Capture the window
- let image = unsafe {
- CGWindowListCreateImage(
- CGRect::null(),
- kCGWindowListOptionIncludingWindow,
- window_number as u32,
- kCGWindowImageBoundsIgnoreFraming,
- )
- };
-
- if image.is_null() {
- return Err("Failed to capture window - image is null. \
- Make sure Screen Recording permission is granted in \
- System Preferences > Privacy & Security > Screen Recording."
- .into());
- }
-
- // Get image dimensions
- let width = unsafe { CGImageGetWidth(image) };
- let height = unsafe { CGImageGetHeight(image) };
-
- if width == 0 || height == 0 {
- unsafe { CGImageRelease(image) };
- return Err("Captured image has zero dimensions".into());
- }
-
- // Get the image data
- let data_provider = unsafe { CGImageGetDataProvider(image) };
- if data_provider.is_null() {
- unsafe { CGImageRelease(image) };
- return Err("Failed to get image data provider".into());
- }
-
- let data = unsafe { CGDataProviderCopyData(data_provider) };
- if data.is_null() {
- unsafe { CGImageRelease(image) };
- return Err("Failed to copy image data".into());
- }
-
- let length = unsafe { CFDataGetLength(data) } as usize;
- let ptr = unsafe { CFDataGetBytePtr(data) };
- let bytes = unsafe { std::slice::from_raw_parts(ptr, length) };
-
- // The image is in BGRA format, convert to RGBA for PNG
- let mut rgba_bytes = Vec::with_capacity(length);
- for chunk in bytes.chunks(4) {
- if chunk.len() == 4 {
- rgba_bytes.push(chunk[2]); // R (was B)
- rgba_bytes.push(chunk[1]); // G
- rgba_bytes.push(chunk[0]); // B (was R)
- rgba_bytes.push(chunk[3]); // A
- }
- }
-
- // Write PNG file
- let file = File::create(output_path)?;
- let w = BufWriter::new(file);
- let mut encoder = png::Encoder::new(w, width as u32, height as u32);
- encoder.set_color(png::ColorType::Rgba);
- encoder.set_depth(png::BitDepth::Eight);
- let mut writer = encoder.write_header()?;
- writer.write_image_data(&rgba_bytes)?;
-
- // Cleanup
- unsafe {
- CFRelease(data as *const _);
- CGImageRelease(image);
- }
-
- println!(
- "Screenshot saved to {} ({}x{})",
- output_path.display(),
- width,
- height
- );
- Ok(())
- }
-}
-
-#[cfg(not(target_os = "macos"))]
-mod screenshot {
- use std::path::Path;
-
- pub fn capture_window_to_png(
- _window_number: i64,
- _output_path: &Path,
- ) -> Result<(), Box<dyn std::error::Error>> {
- Err("Screenshot capture is only supported on macOS".into())
- }
-}
-
-// ============================================================================
-// Main Application
-// ============================================================================
-
-fn main() {
- env_logger::init();
-
- Application::new().run(|cx: &mut App| {
- // Position the window FAR off-screen so it's not visible
- // but macOS still renders it (unlike minimized/hidden windows)
- let off_screen_origin = point(px(-10000.0), px(-10000.0));
- let window_size = size(px(800.0), px(600.0));
-
- let bounds = Bounds {
- origin: off_screen_origin,
- size: window_size,
- };
-
- println!("Creating off-screen window at {:?}", bounds);
- println!("(The window is positioned off-screen but is still being rendered by macOS)");
-
- // Open the window
- let window_handle: WindowHandle<ScreenshotDemo> = cx
- .open_window(
- WindowOptions {
- window_bounds: Some(WindowBounds::Windowed(bounds)),
- focus: false, // Don't steal focus
- show: true, // Must be true for rendering to occur
- ..Default::default()
- },
- |_, cx| cx.new(|_| ScreenshotDemo::new()),
- )
- .expect("Failed to open window");
-
- // Get the entity for later updates
- let view_entity: Entity<ScreenshotDemo> =
- window_handle.entity(cx).expect("Failed to get root entity");
-
- // Get output directory
- let output_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
-
- // Schedule screenshot captures after allowing time for rendering
- cx.spawn(async move |cx| {
- // Wait for the window to fully render
- smol::Timer::after(Duration::from_millis(500)).await;
-
- // Get the window number for screenshots
- let window_number = cx
- .update(|app: &mut App| get_window_number_from_handle(&window_handle, app))
- .ok()
- .flatten();
-
- let Some(window_number) = window_number else {
- eprintln!("Could not get window number. Are you running on macOS?");
- let _ = cx.update(|app: &mut App| app.quit());
- return;
- };
-
- println!("Window number: {}", window_number);
-
- // Take screenshot 1
- let output_path = output_dir.join("screenshot_1.png");
- match screenshot::capture_window_to_png(window_number, &output_path) {
- Ok(()) => println!("✓ Captured screenshot_1.png"),
- Err(e) => eprintln!("✗ Failed to capture screenshot_1.png: {}", e),
- }
-
- // Update the view (update the entity directly, not through window_handle.update)
- let _ = cx.update_entity(&view_entity, |view: &mut ScreenshotDemo, ecx| {
- view.increment();
- view.increment();
- view.increment();
- ecx.notify(); // Trigger a re-render
- });
-
- // Wait for re-render
- smol::Timer::after(Duration::from_millis(200)).await;
-
- // Take screenshot 2
- let output_path = output_dir.join("screenshot_2.png");
- match screenshot::capture_window_to_png(window_number, &output_path) {
- Ok(()) => println!("✓ Captured screenshot_2.png"),
- Err(e) => eprintln!("✗ Failed to capture screenshot_2.png: {}", e),
- }
-
- // Update again
- let _ = cx.update_entity(&view_entity, |view: &mut ScreenshotDemo, ecx| {
- for _ in 0..7 {
- view.increment();
- }
- ecx.notify(); // Trigger a re-render
- });
-
- // Wait for re-render
- smol::Timer::after(Duration::from_millis(200)).await;
-
- // Take screenshot 3
- let output_path = output_dir.join("screenshot_3.png");
- match screenshot::capture_window_to_png(window_number, &output_path) {
- Ok(()) => println!("✓ Captured screenshot_3.png"),
- Err(e) => eprintln!("✗ Failed to capture screenshot_3.png: {}", e),
- }
-
- println!("\nAll screenshots captured!");
- println!(
- "Check {} for screenshot_1.png, screenshot_2.png, screenshot_3.png",
- output_dir.display()
- );
-
- // Quit after screenshots are taken
- smol::Timer::after(Duration::from_millis(500)).await;
- let _ = cx.update(|app: &mut App| app.quit());
- })
- .detach();
- });
-}
-
-/// Extract the window number from a GPUI WindowHandle using raw_window_handle
-#[cfg(target_os = "macos")]
-fn get_window_number_from_handle<V: 'static + Render>(
- window_handle: &WindowHandle<V>,
- cx: &mut App,
-) -> Option<i64> {
- use objc::{msg_send, sel, sel_impl};
-
- window_handle
- .update(cx, |_root: &mut V, window: &mut Window, _cx| {
- let handle = window.window_handle().ok()?;
- match handle.as_raw() {
- RawWindowHandle::AppKit(appkit_handle) => {
- let ns_view = appkit_handle.ns_view.as_ptr();
- unsafe {
- let ns_window: *mut std::ffi::c_void =
- msg_send![ns_view as cocoa::base::id, window];
- if ns_window.is_null() {
- return None;
- }
- let window_number: i64 =
- msg_send![ns_window as cocoa::base::id, windowNumber];
- Some(window_number)
- }
- }
- _ => None,
- }
- })
- .ok()
- .flatten()
-}
-
-#[cfg(not(target_os = "macos"))]
-fn get_window_number_from_handle<V: 'static + Render>(
- _window_handle: &WindowHandle<V>,
- _cx: &mut App,
-) -> Option<i64> {
- None
-}