Skip to content

Commit a83195d

Browse files
committed
[*] add some screen cast of portal codes
1 parent 4670c54 commit a83195d

File tree

6 files changed

+521
-832
lines changed

6 files changed

+521
-832
lines changed

lib/screen-capture-wayland-portal/Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@ description.workspace = true
1313
[dependencies]
1414
log.workspace = true
1515
which.workspace = true
16-
ashpd.workspace = true
1716
image.workspace = true
17+
ashpd.workspace = true
1818
pipewire.workspace = true
1919
thiserror.workspace = true
2020
spin_sleep.workspace = true
2121
display-info.workspace = true
2222
screen-capture.workspace = true
23-
wayland-client.workspace = true
24-
wayland-protocols = { workspace = true, features = ["client", "unstable"] }
25-
tokio.workspace = true
23+
tokio = { workspace = true, features = ["full"] }
2624

2725
[dev-dependencies]
2826
env_logger.workspace = true
27+
tokio = { workspace = true, features = ["full"] }
Lines changed: 17 additions & 318 deletions
Original file line numberDiff line numberDiff line change
@@ -1,320 +1,19 @@
1-
//! This example demonstrates how to use the Wayland XDG Portal for screen capture.
2-
//! It will request permission from the user and capture a single screenshot.
3-
4-
use screen_capture_wayland_portal::ScreenCaptureWaylandPortal;
5-
use screen_capture::{ScreenCapture, CaptureStreamConfig, CaptureStreamCallbackData};
6-
use std::sync::atomic::{AtomicBool, Ordering};
7-
use std::sync::Arc;
8-
use std::time::Duration;
9-
10-
fn main() -> Result<(), Box<dyn std::error::Error>> {
11-
// Initialize logging
12-
env_logger::init();
13-
14-
println!("🖥️ Wayland XDG Portal Screen Capture Demo");
15-
println!("==========================================");
16-
println!();
17-
18-
let mut capture = ScreenCaptureWaylandPortal;
19-
20-
// Step 1: Get available screens
21-
println!("📺 Detecting available screens...");
22-
match capture.available_screens() {
23-
Ok(screens) => {
24-
println!("✅ Found {} screen(s):", screens.len());
25-
for (i, screen) in screens.iter().enumerate() {
26-
println!(" {}. {} - {}x{} (scale: {:.1})",
27-
i + 1,
28-
screen.name,
29-
screen.logical_size.width,
30-
screen.logical_size.height,
31-
screen.scale_factor);
32-
}
33-
println!();
34-
}
35-
Err(e) => {
36-
println!("⚠️ Could not detect screens automatically: {}", e);
37-
println!(" This is normal - XDG portal requires user interaction to discover screens.");
38-
println!(" We'll try to capture anyway when you provide a screen name.");
39-
println!();
40-
}
41-
}
42-
43-
// Step 2: Capture a screenshot using the streaming API (capture a single frame)
44-
println!("📸 Attempting to capture a screenshot...");
45-
println!(" A system dialog will appear asking you to select a screen.");
46-
println!(" Please select the screen you want to capture.");
47-
println!();
48-
49-
println!("📸 Attempting to capture 3 REAL screenshots to /tmp/portal...");
50-
println!(" A system dialog will appear asking you to select a screen.");
51-
println!(" Please select the screen you want to capture.");
52-
println!(" ⚠️ You need to grant permission when the dialog appears!");
53-
println!();
54-
55-
let screenshot_count = 3;
56-
let mut captured_successfully = 0;
57-
58-
for i in 1..=screenshot_count {
59-
let filename = format!("/tmp/portal/screenshot_{:02}.png", i);
60-
println!("🔄 Capturing real screenshot {} of {}...", i, screenshot_count);
61-
62-
// Use the actual XDG portal to capture real screen content
63-
let cancel_signal = Arc::new(AtomicBool::new(false));
64-
let captured_frame = Arc::new(std::sync::Mutex::new(None::<screen_capture::Capture>));
65-
66-
let config = CaptureStreamConfig {
67-
name: "default".to_string(), // Try default screen name first
68-
fps: Some(1.0), // 1 FPS for single capture
69-
include_cursor: false, // Exclude cursor for clean screenshots
70-
cancel_sig: cancel_signal.clone(),
71-
};
72-
73-
let captured_frame_clone = captured_frame.clone();
74-
let cancel_signal_clone = cancel_signal.clone();
75-
76-
let callback = move |data: CaptureStreamCallbackData| {
77-
if let Ok(mut capture_result) = captured_frame_clone.try_lock() {
78-
if capture_result.is_none() {
79-
let width = data.data.width;
80-
let height = data.data.height;
81-
let capture_time = data.capture_time;
82-
83-
*capture_result = Some(data.data);
84-
println!(" 📸 Frame captured: {}x{}, capture time: {:?}",
85-
width, height, capture_time);
86-
// Cancel after first frame
87-
cancel_signal_clone.store(true, Ordering::Relaxed);
88-
}
89-
}
90-
};
91-
92-
// Create a new capture instance since the trait consumes self
93-
let capture = ScreenCaptureWaylandPortal;
94-
95-
match capture.capture_output_stream(config, callback) {
96-
Ok(screen_capture::CaptureStatus::Stopped) => {
97-
if let Ok(capture_result_guard) = captured_frame.try_lock() {
98-
if let Some(capture_result) = capture_result_guard.as_ref() {
99-
println!(" ✅ Real screenshot captured successfully!");
100-
println!(" 📏 Size: {}x{} pixels", capture_result.width, capture_result.height);
101-
println!(" 💾 Data size: {} bytes", capture_result.pixel_data.len());
102-
103-
match save_capture_as_png(capture_result, filename.clone()) {
104-
Ok(()) => {
105-
println!(" 💾 Saved to: {}", filename);
106-
captured_successfully += 1;
107-
108-
// Verify the saved image immediately
109-
match verify_saved_image(&filename) {
110-
Ok((verified_width, verified_height, file_size)) => {
111-
println!(" ✅ Image verified: {}x{}, {} bytes", verified_width, verified_height, file_size);
112-
}
113-
Err(e) => {
114-
println!(" ⚠️ Image verification failed: {}", e);
115-
}
116-
}
117-
}
118-
Err(e) => {
119-
println!(" ❌ Failed to save image: {}", e);
120-
}
121-
}
122-
} else {
123-
println!(" ❌ No frame was captured from the stream");
124-
return Err("No frame captured from PipeWire stream".into());
125-
}
126-
}
127-
}
128-
Ok(screen_capture::CaptureStatus::Finished) => {
129-
println!(" ⚠️ Stream finished without capturing a frame");
130-
return Err("Stream finished without capturing".into());
131-
}
132-
Err(e) => {
133-
println!(" ❌ Failed to capture real screenshot: {}", e);
134-
println!(" 💡 This indicates that either:");
135-
println!(" - XDG portal services are not running");
136-
println!(" - User cancelled the permission dialog");
137-
println!(" - PipeWire services are not available");
138-
println!(" - Not running in a proper Wayland session");
139-
println!(" - Required portal backend is not available");
140-
return Err(Box::new(e));
141-
}
142-
}
143-
144-
// Add delay between screenshots
145-
std::thread::sleep(std::time::Duration::from_millis(2000));
146-
147-
println!();
148-
}
149-
150-
println!();
151-
println!("📊 Summary: Successfully saved {}/{} screenshots", captured_successfully, screenshot_count);
152-
153-
// Verify all saved images
154-
println!("🔍 Verifying all saved screenshots...");
155-
let mut verified_count = 0;
156-
157-
for i in 1..=screenshot_count {
158-
let filename = format!("/tmp/portal/screenshot_{:02}.png", i);
159-
160-
match verify_saved_image(&filename) {
161-
Ok((width, height, file_size)) => {
162-
println!(" ✅ {}: {}x{}, {} bytes", filename, width, height, file_size);
163-
verified_count += 1;
164-
}
165-
Err(e) => {
166-
println!(" ❌ {}: {}", filename, e);
167-
}
168-
}
169-
}
170-
171-
println!();
172-
println!("📋 Verification Summary: {}/{} images verified successfully", verified_count, screenshot_count);
173-
174-
if verified_count == screenshot_count {
175-
println!("🎉 All screenshots created and verified successfully!");
176-
println!(" Check the /tmp/portal directory for the PNG files.");
177-
} else {
178-
println!("⚠️ Some screenshots failed verification, but test pattern generation worked.");
179-
}
180-
181-
// Step 3: Test performance measurement
182-
println!("🚀 Testing capture performance...");
183-
let mut perf_capture = ScreenCaptureWaylandPortal;
184-
match perf_capture.capture_mean_time("default", 3) {
185-
Ok(avg_time) => {
186-
println!("✅ Average capture time per screenshot: {:?}", avg_time);
187-
println!(" This gives us approximately {:.1} FPS potential", 1.0 / avg_time.as_secs_f64());
188-
}
189-
Err(e) => {
190-
println!("⚠️ Performance test failed: {}", e);
191-
}
192-
}
193-
194-
println!();
195-
println!("🎉 Demo completed successfully!");
196-
println!(" Check the generated PNG files to see the captured screenshots.");
197-
198-
Ok(())
199-
}
200-
201-
fn save_capture_as_png(capture: &screen_capture::Capture, filename: String) -> Result<(), Box<dyn std::error::Error>> {
202-
use image::{ImageBuffer, Rgba};
203-
204-
// Convert the captured pixel data to an image
205-
let img: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(
206-
capture.width,
207-
capture.height,
208-
capture.pixel_data.clone(),
209-
).ok_or("Failed to create image buffer")?;
210-
211-
// Save the image as PNG
212-
img.save(filename)?;
213-
Ok(())
214-
}
215-
216-
fn verify_saved_image(filename: &str) -> Result<(u32, u32, u64), Box<dyn std::error::Error>> {
217-
use std::fs::Metadata;
218-
219-
// Check if file exists
220-
if !std::path::Path::new(filename).exists() {
221-
return Err(format!("File does not exist: {}", filename).into());
222-
}
223-
224-
// Load and verify the image
225-
let img = image::open(filename)
226-
.map_err(|e| format!("Failed to open image: {}", e))?;
227-
228-
// Get file metadata
229-
let metadata: Metadata = std::fs::metadata(filename)?;
230-
let file_size = metadata.len();
231-
232-
let (width, height) = (img.width(), img.height());
233-
234-
// Additional verification: check if the image is valid by reading a few pixels
235-
let pixels = img.to_rgba8();
236-
let total_pixels = width * height;
237-
238-
if total_pixels == 0 {
239-
return Err("Image has zero pixels".into());
240-
}
241-
242-
// Check some sample pixels to ensure they're valid (not all black/white, etc.)
243-
let sample_positions = [(0, 0), (width/2, height/2), (width-1, height-1)];
244-
let mut valid_samples = 0;
245-
246-
for &(x, y) in &sample_positions {
247-
if x < width && y < height {
248-
let pixel = pixels.get_pixel(x, y);
249-
// Check if pixel is not completely transparent or has some color variation
250-
if pixel[3] > 0 && !(pixel[0] == pixel[1] && pixel[1] == pixel[2] && pixel[2] == 0) {
251-
valid_samples += 1;
252-
}
253-
}
254-
}
255-
256-
if valid_samples == 0 {
257-
return Err("All sample pixels appear to be black or transparent".into());
258-
}
259-
260-
Ok((width, height, file_size))
261-
}
262-
263-
// Placeholder functions removed - no fake data generation allowed
264-
265-
// Additional example for streaming capture
266-
fn streaming_example() -> Result<(), Box<dyn std::error::Error>> {
267-
println!("🔄 Starting streaming capture example...");
268-
269-
let _capture = ScreenCaptureWaylandPortal;
270-
let cancel_signal = Arc::new(AtomicBool::new(false));
271-
let frame_count = Arc::new(std::sync::atomic::AtomicU32::new(0));
272-
273-
let config = screen_capture::CaptureStreamConfig {
274-
name: "default".to_string(),
275-
fps: Some(5.0), // 5 FPS for demo
276-
include_cursor: false,
277-
cancel_sig: cancel_signal.clone(),
278-
};
279-
280-
let frame_count_clone = frame_count.clone();
281-
let cancel_signal_clone = cancel_signal.clone();
282-
let callback = move |data: CaptureStreamCallbackData| {
283-
let count = frame_count_clone.fetch_add(1, Ordering::Relaxed);
284-
if count % 10 == 0 {
285-
println!("📹 Captured {} frames (frame size: {}x{}, capture time: {:?})",
286-
count,
287-
data.data.width,
288-
data.data.height,
289-
data.capture_time);
290-
}
291-
292-
// Cancel after 50 frames
293-
if count >= 50 {
294-
cancel_signal_clone.store(true, Ordering::Relaxed);
295-
}
1+
use screen_capture_wayland_portal::{open_portal, start_streaming};
2+
use std::os::fd::IntoRawFd;
3+
4+
#[tokio::main]
5+
async fn main() {
6+
let (stream, fd) = open_portal().await.expect("failed to open portal");
7+
let pipewire_node_id = stream.pipe_wire_node_id();
8+
9+
println!(
10+
"node id {}, fd {}",
11+
pipewire_node_id,
12+
&fd.try_clone().unwrap().into_raw_fd()
13+
);
14+
15+
if let Err(e) = start_streaming(pipewire_node_id, fd).await {
16+
eprintln!("Error: {}", e);
29617
};
18+
}
29719

298-
// Run streaming capture for a short time
299-
tokio::runtime::Runtime::new()?.block_on(async {
300-
tokio::time::sleep(Duration::from_secs(1)).await;
301-
cancel_signal.store(true, Ordering::Relaxed);
302-
});
303-
304-
// Create a new capture instance since the trait consumes self
305-
let new_capture = ScreenCaptureWaylandPortal;
306-
match new_capture.capture_output_stream(config, callback) {
307-
Ok(screen_capture::CaptureStatus::Stopped) => {
308-
println!("✅ Streaming capture completed after {} frames",
309-
frame_count.load(Ordering::Relaxed));
310-
}
311-
Ok(_) => {
312-
println!("✅ Streaming capture completed");
313-
}
314-
Err(e) => {
315-
println!("⚠️ Streaming capture failed: {}", e);
316-
}
317-
}
318-
319-
Ok(())
320-
}

0 commit comments

Comments
 (0)