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