This commit is contained in:
Richard Feldman
2025-12-17 10:03:01 -05:00
parent b29e8244d5
commit b5d0f5d4f8
4 changed files with 885 additions and 0 deletions

1
Cargo.lock generated
View File

@@ -7328,6 +7328,7 @@ dependencies = [
"parking_lot",
"pathfinder_geometry",
"pin-project",
"png 0.17.16",
"postage",
"pretty_assertions",
"profiling",

View File

@@ -253,6 +253,7 @@ 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"
@@ -331,6 +332,14 @@ 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"

View File

@@ -0,0 +1,447 @@
//! 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!");
}
}

View File

@@ -0,0 +1,428 @@
//! 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
}