1use std::fs::File;
2use std::os::fd::AsFd;
3use std::path::{Path, PathBuf};
4
5use image::codecs::png::{CompressionType, FilterType, PngEncoder};
6use image::{ExtendedColorType, ImageEncoder, Rgba, RgbaImage, imageops};
7use memmap2::MmapMut;
8use smithay_client_toolkit::{
9 delegate_output, delegate_registry,
10 output::{OutputHandler, OutputState},
11 registry::{ProvidesRegistryState, RegistryState},
12 registry_handlers,
13};
14use tempfile::tempfile;
15use wayland_client::{
16 Connection, Dispatch, Proxy, QueueHandle, WEnum,
17 globals::registry_queue_init,
18 protocol::{wl_buffer, wl_output, wl_shm, wl_shm_pool},
19};
20use wayland_protocols_wlr::screencopy::v1::client::{
21 zwlr_screencopy_frame_v1, zwlr_screencopy_manager_v1,
22};
23
24use crate::capture::{CaptureCrop, ensure_parent_dir, temp_output_path};
25
26#[derive(Clone, Debug)]
27pub struct CaptureOutputInfo {
28 pub name: Option<String>,
29 pub x: i32,
30 pub y: i32,
31 pub width: i32,
32 pub height: i32,
33 pub scale: i32,
34}
35
36pub fn capture_desktop_to_temp_file(final_out_path: &Path) -> Result<PathBuf, String> {
37 let (conn, mut queue, qh, mut app) = connect_capture_app()?;
38 let outputs = app.capture_outputs();
39 if outputs.is_empty() {
40 return Err("no outputs available for capture".into());
41 }
42 let mut captures = Vec::new();
43 for (wl_output, info) in outputs {
44 captures.push(capture_single_output(
45 &conn, &mut queue, &qh, &mut app, &wl_output, info,
46 )?);
47 }
48 let desktop = stitch_logical_desktop(&captures)?;
49 let tmp_out = temp_output_path(final_out_path);
50 save_rgba_png(&desktop, &tmp_out)?;
51 Ok(tmp_out)
52}
53
54pub fn capture_crop_to_png(final_out_path: &Path, crop: CaptureCrop) -> Result<(), String> {
55 let (conn, mut queue, qh, mut app) = connect_capture_app()?;
56 let outputs = app.capture_outputs();
57 if outputs.is_empty() {
58 return Err("no outputs available for capture".into());
59 }
60
61 let output_infos = outputs
62 .iter()
63 .map(|(_, info)| info.clone())
64 .collect::<Vec<_>>();
65 let crop = clamp_crop_to_output_bounds(crop, &output_infos)?;
66
67 let mut captures = Vec::new();
68 for (wl_output, info) in outputs {
69 if !output_intersects_crop(&info, crop) {
70 continue;
71 }
72 captures.push(capture_single_output(
73 &conn, &mut queue, &qh, &mut app, &wl_output, info,
74 )?);
75 }
76 if captures.is_empty() {
77 return Err("no outputs intersect the requested capture crop".into());
78 }
79
80 let image = render_logical_crop(&captures, crop)?;
81 save_rgba_png(&image, final_out_path)
82}
83
84struct CaptureApp {
85 registry_state: RegistryState,
86 output_state: OutputState,
87 shm: Option<wl_shm::WlShm>,
88 screencopy: Option<zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1>,
89 active: Option<ActiveCapture>,
90}
91
92struct ActiveCapture {
93 frame: zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
94 output: CaptureOutputInfo,
95 buffer_spec: Option<BufferSpec>,
96 buffer_done: bool,
97 copied: bool,
98 failed: bool,
99 ready: bool,
100 y_invert: bool,
101 shm_buffer: Option<CaptureShmBuffer>,
102}
103
104#[derive(Clone, Copy)]
105struct BufferSpec {
106 format: wl_shm::Format,
107 width: i32,
108 height: i32,
109 stride: i32,
110}
111
112struct CaptureShmBuffer {
113 _file: File,
114 mmap: MmapMut,
115 _pool: wl_shm_pool::WlShmPool,
116 buffer: wl_buffer::WlBuffer,
117}
118
119struct CapturedOutput {
120 info: CaptureOutputInfo,
121 width: i32,
122 height: i32,
123 stride: i32,
124 format: wl_shm::Format,
125 y_invert: bool,
126 bytes: Vec<u8>,
127}
128
129fn connect_capture_app() -> Result<
130 (
131 Connection,
132 wayland_client::EventQueue<CaptureApp>,
133 QueueHandle<CaptureApp>,
134 CaptureApp,
135 ),
136 String,
137> {
138 let conn = Connection::connect_to_env().map_err(|e| format!("wayland connect: {e}"))?;
139 let (globals, mut queue) =
140 registry_queue_init(&conn).map_err(|e| format!("registry init: {e}"))?;
141 let qh = queue.handle();
142 let registry_state = RegistryState::new(&globals);
143 let output_state = OutputState::new(&globals, &qh);
144 let mut app = CaptureApp {
145 registry_state,
146 output_state,
147 shm: globals.bind::<wl_shm::WlShm, _, _>(&qh, 1..=1, ()).ok(),
148 screencopy: globals
149 .bind::<zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1, _, _>(&qh, 1..=3, ())
150 .ok(),
151 active: None,
152 };
153 queue
154 .roundtrip(&mut app)
155 .map_err(|e| format!("roundtrip 1: {e}"))?;
156 queue
157 .roundtrip(&mut app)
158 .map_err(|e| format!("roundtrip 2: {e}"))?;
159 if app.shm.is_none() {
160 return Err("wl_shm not available".into());
161 }
162 if app.screencopy.is_none() {
163 return Err("zwlr_screencopy_manager_v1 not available".into());
164 }
165 Ok((conn, queue, qh, app))
166}
167
168impl CaptureApp {
169 fn capture_outputs(&self) -> Vec<(wl_output::WlOutput, CaptureOutputInfo)> {
170 let mut out = self
171 .output_state
172 .outputs()
173 .into_iter()
174 .filter_map(|output| {
175 let info = self.output_state.info(&output)?;
176 Some((
177 output,
178 CaptureOutputInfo {
179 name: info.name.clone(),
180 x: info.logical_position.map(|(x, _)| x).unwrap_or(0),
181 y: info.logical_position.map(|(_, y)| y).unwrap_or(0),
182 width: info.logical_size.map(|(w, _)| w as i32).unwrap_or(0),
183 height: info.logical_size.map(|(_, h)| h as i32).unwrap_or(0),
184 scale: info.scale_factor.max(1),
185 },
186 ))
187 })
188 .collect::<Vec<_>>();
189 out.sort_by_key(|(_, info)| (info.y, info.x));
190 out
191 }
192
193 fn maybe_issue_copy(&mut self, qh: &QueueHandle<Self>) -> Result<(), String> {
194 let Some(active) = self.active.as_mut() else {
195 return Ok(());
196 };
197 if active.copied || active.failed || active.ready {
198 return Ok(());
199 }
200 let Some(spec) = active.buffer_spec else {
201 return Ok(());
202 };
203 if active.frame.version() >= 3 && !active.buffer_done {
204 return Ok(());
205 }
206 let shm = self
207 .shm
208 .as_ref()
209 .ok_or("wl_shm unavailable during capture")?;
210 let buffer = CaptureShmBuffer::new(shm, qh, spec)?;
211 active.frame.copy(&buffer.buffer);
212 active.shm_buffer = Some(buffer);
213 active.copied = true;
214 Ok(())
215 }
216}
217
218impl ActiveCapture {
219 fn into_captured(self) -> Result<CapturedOutput, String> {
220 let spec = self.buffer_spec.ok_or_else(|| {
221 "screencopy frame never reported wl_shm buffer parameters".to_string()
222 })?;
223 let buffer = self
224 .shm_buffer
225 .ok_or_else(|| "screencopy frame never copied into a wl_shm buffer".to_string())?;
226 Ok(CapturedOutput {
227 info: self.output,
228 width: spec.width,
229 height: spec.height,
230 stride: spec.stride,
231 format: spec.format,
232 y_invert: self.y_invert,
233 bytes: buffer.mmap[..].to_vec(),
234 })
235 }
236}
237
238impl CaptureShmBuffer {
239 fn new(
240 shm: &wl_shm::WlShm,
241 qh: &QueueHandle<CaptureApp>,
242 spec: BufferSpec,
243 ) -> Result<Self, String> {
244 let width = spec.width.max(1);
245 let height = spec.height.max(1);
246 let stride = spec.stride.max(width.saturating_mul(4));
247 let size = stride.saturating_mul(height) as u64;
248 let file = tempfile().map_err(|e| format!("tempfile: {e}"))?;
249 file.set_len(size).map_err(|e| format!("set_len: {e}"))?;
250 let mmap = unsafe { MmapMut::map_mut(&file).map_err(|e| format!("mmap: {e}"))? };
251 let pool = shm.create_pool(file.as_fd(), size as i32, qh, ());
252 let buffer = pool.create_buffer(0, width, height, stride, spec.format, qh, ());
253 Ok(Self {
254 _file: file,
255 mmap,
256 _pool: pool,
257 buffer,
258 })
259 }
260}
261
262fn capture_single_output(
263 conn: &Connection,
264 queue: &mut wayland_client::EventQueue<CaptureApp>,
265 qh: &QueueHandle<CaptureApp>,
266 app: &mut CaptureApp,
267 wl_output: &wl_output::WlOutput,
268 info: CaptureOutputInfo,
269) -> Result<CapturedOutput, String> {
270 let manager = app
271 .screencopy
272 .as_ref()
273 .ok_or_else(|| "zwlr_screencopy_manager_v1 unavailable".to_string())?
274 .clone();
275 let frame = manager.capture_output(0, wl_output, qh, ());
276 app.active = Some(ActiveCapture {
277 frame,
278 output: info,
279 buffer_spec: None,
280 buffer_done: false,
281 copied: false,
282 failed: false,
283 ready: false,
284 y_invert: false,
285 shm_buffer: None,
286 });
287 let _ = conn.flush();
288 loop {
289 app.maybe_issue_copy(qh)?;
290 queue
291 .blocking_dispatch(app)
292 .map_err(|e| format!("dispatch capture frame: {e}"))?;
293 if app
294 .active
295 .as_ref()
296 .is_some_and(|active| active.failed || active.ready)
297 {
298 break;
299 }
300 }
301 let active = app
302 .active
303 .take()
304 .ok_or("capture state missing after dispatch")?;
305 if active.failed {
306 return Err(format!(
307 "screencopy failed for output {:?}",
308 active.output.name
309 ));
310 }
311 active.into_captured()
312}
313
314fn clamp_crop_to_output_bounds(
315 crop: CaptureCrop,
316 outputs: &[CaptureOutputInfo],
317) -> Result<CaptureCrop, String> {
318 let min_x = outputs.iter().map(|output| output.x).min().unwrap_or(0);
319 let min_y = outputs.iter().map(|output| output.y).min().unwrap_or(0);
320 let max_x = outputs
321 .iter()
322 .map(|output| output.x.saturating_add(output.width.max(0)))
323 .max()
324 .unwrap_or(0);
325 let max_y = outputs
326 .iter()
327 .map(|output| output.y.saturating_add(output.height.max(0)))
328 .max()
329 .unwrap_or(0);
330
331 let x0 = crop.x.max(min_x);
332 let y0 = crop.y.max(min_y);
333 let x1 = crop.x.saturating_add(crop.w.max(0)).min(max_x);
334 let y1 = crop.y.saturating_add(crop.h.max(0)).min(max_y);
335 let w = x1.saturating_sub(x0);
336 let h = y1.saturating_sub(y0);
337 if w <= 0 || h <= 0 {
338 return Err(format!(
339 "crop rect empty after clamping: ({},{}) {}x{} within ({},{})-({},{})",
340 crop.x, crop.y, crop.w, crop.h, min_x, min_y, max_x, max_y
341 ));
342 }
343
344 Ok(CaptureCrop { x: x0, y: y0, w, h })
345}
346
347fn output_intersects_crop(info: &CaptureOutputInfo, crop: CaptureCrop) -> bool {
348 let output_x1 = info.x.saturating_add(info.width.max(0));
349 let output_y1 = info.y.saturating_add(info.height.max(0));
350 let crop_x1 = crop.x.saturating_add(crop.w.max(0));
351 let crop_y1 = crop.y.saturating_add(crop.h.max(0));
352 info.width > 0
353 && info.height > 0
354 && info.x < crop_x1
355 && output_x1 > crop.x
356 && info.y < crop_y1
357 && output_y1 > crop.y
358}
359
360fn render_logical_crop(
361 captures: &[CapturedOutput],
362 crop: CaptureCrop,
363) -> Result<RgbaImage, String> {
364 let mut image = RgbaImage::from_pixel(
365 crop.w.max(1) as u32,
366 crop.h.max(1) as u32,
367 Rgba([0, 0, 0, 0]),
368 );
369 for capture in captures {
370 blit_capture_crop(&mut image, capture, crop)?;
371 }
372 Ok(image)
373}
374
375fn blit_capture_crop(
376 image: &mut RgbaImage,
377 capture: &CapturedOutput,
378 crop: CaptureCrop,
379) -> Result<(), String> {
380 if !output_intersects_crop(&capture.info, crop) {
381 return Ok(());
382 }
383
384 let has_alpha = match capture.format {
385 wl_shm::Format::Argb8888 => true,
386 wl_shm::Format::Xrgb8888 => false,
387 other => return Err(format!("unsupported screencopy wl_shm format {:?}", other)),
388 };
389
390 let logical_w = capture.info.width.max(1);
391 let logical_h = capture.info.height.max(1);
392 let physical_w = capture.width.max(1);
393 let physical_h = capture.height.max(1);
394 let x0 = crop.x.max(capture.info.x);
395 let y0 = crop.y.max(capture.info.y);
396 let x1 = crop
397 .x
398 .saturating_add(crop.w)
399 .min(capture.info.x.saturating_add(capture.info.width));
400 let y1 = crop
401 .y
402 .saturating_add(crop.h)
403 .min(capture.info.y.saturating_add(capture.info.height));
404
405 for y in y0..y1 {
406 let local_y = y - capture.info.y;
407 let mut src_y = map_logical_to_physical(local_y, logical_h, physical_h);
408 if capture.y_invert {
409 src_y = physical_h - 1 - src_y;
410 }
411 let dst_y = (y - crop.y) as u32;
412 for x in x0..x1 {
413 let local_x = x - capture.info.x;
414 let src_x = map_logical_to_physical(local_x, logical_w, physical_w);
415 let dst_x = (x - crop.x) as u32;
416 let offset = (src_y * capture.stride + src_x * 4) as usize;
417 let pixel = capture
418 .bytes
419 .get(offset..offset + 4)
420 .ok_or_else(|| format!("capture buffer too small at offset {offset}"))?;
421 let rgba = [
422 pixel[2],
423 pixel[1],
424 pixel[0],
425 if has_alpha { pixel[3] } else { 255 },
426 ];
427 image.put_pixel(dst_x, dst_y, Rgba(rgba));
428 }
429 }
430
431 Ok(())
432}
433
434fn map_logical_to_physical(logical_offset: i32, logical_len: i32, physical_len: i32) -> i32 {
435 if logical_len <= 1 || physical_len <= 1 {
436 return 0;
437 }
438
439 (((logical_offset as i64) * (physical_len as i64)) / (logical_len as i64))
440 .clamp(0, (physical_len - 1) as i64) as i32
441}
442
443fn save_rgba_png(image: &RgbaImage, out_path: &Path) -> Result<(), String> {
444 ensure_parent_dir(out_path)?;
445 let file =
446 File::create(out_path).map_err(|e| format!("create screenshot {out_path:?}: {e}"))?;
447 let encoder = PngEncoder::new_with_quality(file, CompressionType::Fast, FilterType::Adaptive);
448 encoder
449 .write_image(
450 image.as_raw(),
451 image.width(),
452 image.height(),
453 ExtendedColorType::Rgba8,
454 )
455 .map_err(|e| format!("save screenshot: {e}"))
456}
457
458fn stitch_logical_desktop(captures: &[CapturedOutput]) -> Result<RgbaImage, String> {
459 let min_x = captures.iter().map(|cap| cap.info.x).min().unwrap_or(0);
460 let min_y = captures.iter().map(|cap| cap.info.y).min().unwrap_or(0);
461 let max_x = captures
462 .iter()
463 .map(|cap| cap.info.x + cap.info.width)
464 .max()
465 .unwrap_or(0);
466 let max_y = captures
467 .iter()
468 .map(|cap| cap.info.y + cap.info.height)
469 .max()
470 .unwrap_or(0);
471 let desktop_w = (max_x - min_x).max(1) as u32;
472 let desktop_h = (max_y - min_y).max(1) as u32;
473 let mut desktop = RgbaImage::from_pixel(desktop_w, desktop_h, Rgba([0, 0, 0, 0]));
474 for cap in captures {
475 let image = captured_output_to_image(cap)?;
476 imageops::overlay(
477 &mut desktop,
478 &image,
479 (cap.info.x - min_x) as i64,
480 (cap.info.y - min_y) as i64,
481 );
482 }
483 Ok(desktop)
484}
485
486fn captured_output_to_image(cap: &CapturedOutput) -> Result<RgbaImage, String> {
487 let mut output = RgbaImage::new(cap.width.max(1) as u32, cap.height.max(1) as u32);
488 for y in 0..cap.height.max(0) {
489 let src_y = if cap.y_invert { cap.height - 1 - y } else { y };
490 for x in 0..cap.width.max(0) {
491 let off = (src_y * cap.stride + x * 4) as usize;
492 let b = cap.bytes.get(off).copied().unwrap_or(0);
493 let g = cap.bytes.get(off + 1).copied().unwrap_or(0);
494 let r = cap.bytes.get(off + 2).copied().unwrap_or(0);
495 let a = match cap.format {
496 wl_shm::Format::Argb8888 => cap.bytes.get(off + 3).copied().unwrap_or(255),
497 wl_shm::Format::Xrgb8888 => 255,
498 other => return Err(format!("unsupported screencopy wl_shm format {:?}", other)),
499 };
500 output.put_pixel(x as u32, y as u32, Rgba([r, g, b, a]));
501 }
502 }
503 Ok(output)
504}
505
506#[cfg(test)]
507mod tests {
508 use super::{
509 CaptureOutputInfo, CapturedOutput, blit_capture_crop, clamp_crop_to_output_bounds,
510 render_logical_crop,
511 };
512 use crate::capture::CaptureCrop;
513 use image::RgbaImage;
514 use wayland_client::protocol::wl_shm;
515
516 fn bgra(rgba: [u8; 4]) -> [u8; 4] {
517 [rgba[2], rgba[1], rgba[0], rgba[3]]
518 }
519
520 #[test]
521 fn clamp_crop_to_output_bounds_handles_negative_desktop_coordinates() {
522 let outputs = vec![
523 CaptureOutputInfo {
524 name: Some("left".into()),
525 x: -1920,
526 y: 0,
527 width: 1920,
528 height: 1080,
529 scale: 1,
530 },
531 CaptureOutputInfo {
532 name: Some("center".into()),
533 x: 0,
534 y: 0,
535 width: 1920,
536 height: 1080,
537 scale: 1,
538 },
539 ];
540
541 let crop = clamp_crop_to_output_bounds(
542 CaptureCrop {
543 x: -1940,
544 y: -10,
545 w: 50,
546 h: 30,
547 },
548 &outputs,
549 )
550 .expect("clamped crop");
551
552 assert_eq!(
553 crop,
554 CaptureCrop {
555 x: -1920,
556 y: 0,
557 w: 30,
558 h: 20
559 }
560 );
561 }
562
563 #[test]
564 fn render_logical_crop_blits_only_the_requested_overlap() {
565 let bytes = [
566 bgra([255, 0, 0, 255]),
567 bgra([0, 255, 0, 255]),
568 bgra([0, 0, 255, 255]),
569 bgra([255, 255, 255, 255]),
570 ]
571 .into_iter()
572 .flatten()
573 .collect::<Vec<_>>();
574 let capture = CapturedOutput {
575 info: CaptureOutputInfo {
576 name: Some("primary".into()),
577 x: 10,
578 y: 20,
579 width: 2,
580 height: 2,
581 scale: 1,
582 },
583 width: 2,
584 height: 2,
585 stride: 8,
586 format: wl_shm::Format::Argb8888,
587 y_invert: false,
588 bytes,
589 };
590
591 let image = render_logical_crop(
592 &[capture],
593 CaptureCrop {
594 x: 11,
595 y: 20,
596 w: 1,
597 h: 2,
598 },
599 )
600 .expect("rendered crop");
601
602 assert_eq!(image.dimensions(), (1, 2));
603 assert_eq!(image.get_pixel(0, 0).0, [0, 255, 0, 255]);
604 assert_eq!(image.get_pixel(0, 1).0, [255, 255, 255, 255]);
605 }
606
607 #[test]
608 fn blit_capture_crop_maps_physical_pixels_back_to_logical_resolution() {
609 let bytes = [
610 bgra([255, 0, 0, 255]),
611 bgra([255, 0, 0, 255]),
612 bgra([0, 255, 0, 255]),
613 bgra([0, 255, 0, 255]),
614 ]
615 .into_iter()
616 .flatten()
617 .collect::<Vec<_>>();
618 let capture = CapturedOutput {
619 info: CaptureOutputInfo {
620 name: Some("scaled".into()),
621 x: 0,
622 y: 0,
623 width: 2,
624 height: 1,
625 scale: 2,
626 },
627 width: 4,
628 height: 1,
629 stride: 16,
630 format: wl_shm::Format::Argb8888,
631 y_invert: false,
632 bytes,
633 };
634 let mut image = RgbaImage::from_pixel(2, 1, image::Rgba([0, 0, 0, 0]));
635
636 blit_capture_crop(
637 &mut image,
638 &capture,
639 CaptureCrop {
640 x: 0,
641 y: 0,
642 w: 2,
643 h: 1,
644 },
645 )
646 .expect("scaled blit");
647
648 assert_eq!(image.get_pixel(0, 0).0, [255, 0, 0, 255]);
649 assert_eq!(image.get_pixel(1, 0).0, [0, 255, 0, 255]);
650 }
651}
652
653impl ProvidesRegistryState for CaptureApp {
654 fn registry(&mut self) -> &mut RegistryState {
655 &mut self.registry_state
656 }
657 registry_handlers![OutputState];
658}
659impl OutputHandler for CaptureApp {
660 fn output_state(&mut self) -> &mut OutputState {
661 &mut self.output_state
662 }
663
664 fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
665
666 fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
667
668 fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
669}
670delegate_registry!(CaptureApp);
671delegate_output!(CaptureApp);
672
673impl Dispatch<wl_shm::WlShm, ()> for CaptureApp {
674 fn event(
675 _: &mut Self,
676 _: &wl_shm::WlShm,
677 _: wl_shm::Event,
678 _: &(),
679 _: &Connection,
680 _: &QueueHandle<Self>,
681 ) {
682 }
683}
684
685impl Dispatch<wl_shm_pool::WlShmPool, ()> for CaptureApp {
686 fn event(
687 _: &mut Self,
688 _: &wl_shm_pool::WlShmPool,
689 _: wl_shm_pool::Event,
690 _: &(),
691 _: &Connection,
692 _: &QueueHandle<Self>,
693 ) {
694 }
695}
696
697impl Dispatch<wl_buffer::WlBuffer, ()> for CaptureApp {
698 fn event(
699 _: &mut Self,
700 _: &wl_buffer::WlBuffer,
701 _: wl_buffer::Event,
702 _: &(),
703 _: &Connection,
704 _: &QueueHandle<Self>,
705 ) {
706 }
707}
708
709impl Dispatch<zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1, ()> for CaptureApp {
710 fn event(
711 _: &mut Self,
712 _: &zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
713 _: zwlr_screencopy_manager_v1::Event,
714 _: &(),
715 _: &Connection,
716 _: &QueueHandle<Self>,
717 ) {
718 }
719}
720
721impl Dispatch<zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, ()> for CaptureApp {
722 fn event(
723 state: &mut Self,
724 proxy: &zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1,
725 event: zwlr_screencopy_frame_v1::Event,
726 _: &(),
727 _: &Connection,
728 qh: &QueueHandle<Self>,
729 ) {
730 let Some(active) = state.active.as_mut() else {
731 return;
732 };
733 if active.frame.id() != proxy.id() {
734 return;
735 }
736 match event {
737 zwlr_screencopy_frame_v1::Event::Buffer {
738 format,
739 width,
740 height,
741 stride,
742 } => {
743 let WEnum::Value(format) = format else {
744 active.failed = true;
745 return;
746 };
747 active.buffer_spec = Some(BufferSpec {
748 format,
749 width: width as i32,
750 height: height as i32,
751 stride: stride as i32,
752 });
753 let _ = state.maybe_issue_copy(qh);
754 }
755 zwlr_screencopy_frame_v1::Event::LinuxDmabuf { .. } => {}
756 zwlr_screencopy_frame_v1::Event::BufferDone => {
757 active.buffer_done = true;
758 let _ = state.maybe_issue_copy(qh);
759 }
760 zwlr_screencopy_frame_v1::Event::Flags { flags } => {
761 active.y_invert = matches!(flags, WEnum::Value(value) if value.contains(zwlr_screencopy_frame_v1::Flags::YInvert));
762 }
763 zwlr_screencopy_frame_v1::Event::Ready { .. } => {
764 active.ready = true;
765 }
766 zwlr_screencopy_frame_v1::Event::Failed => {
767 active.failed = true;
768 }
769 _ => {}
770 }
771 }
772}