1use std::fs::File;
30use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd};
31use std::os::unix::net::UnixStream;
32use std::sync::atomic::{AtomicBool, Ordering};
33use std::sync::mpsc::{Receiver, Sender, TryRecvError};
34use std::sync::{Arc, Mutex};
35use std::time::{Duration, Instant};
36
37use gbm::{BufferObjectFlags, Device as GbmDevice, Format as GbmFormat};
38use smithay::backend::allocator::dmabuf::Dmabuf;
39use smithay::backend::allocator::Buffer as _;
40use smithay::utils::{Physical, Rectangle};
41use wayland_client::protocol::{wl_output, wl_pointer, wl_registry, wl_seat, wl_shm, wl_shm_pool};
42use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, Proxy, QueueHandle, WEnum};
43use wayland_protocols::ext::image_capture_source::v1::client::{
44 ext_image_capture_source_v1::ExtImageCaptureSourceV1,
45 ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1,
46};
47use wayland_protocols::ext::image_copy_capture::v1::client::{
48 ext_image_copy_capture_frame_v1::{self, ExtImageCopyCaptureFrameV1},
49 ext_image_copy_capture_manager_v1::{self, ExtImageCopyCaptureManagerV1},
50 ext_image_copy_capture_session_v1::{self, ExtImageCopyCaptureSessionV1},
51};
52use wayland_protocols::wp::linux_dmabuf::zv1::client::{
53 zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1},
54 zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
55};
56use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
57 zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1,
58 zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
59};
60use wayland_protocols_wlr::screencopy::v1::client::{
61 zwlr_screencopy_frame_v1::{self, ZwlrScreencopyFrameV1},
62 zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
63};
64use wayland_protocols_wlr::output_management::v1::client::{
65 zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1,
66 zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1},
67 zwlr_output_head_v1::{self, ZwlrOutputHeadV1},
68 zwlr_output_manager_v1::{self, ZwlrOutputManagerV1},
69 zwlr_output_mode_v1::ZwlrOutputModeV1,
70};
71use wayland_protocols_wlr::virtual_pointer::v1::client::{
72 zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1,
73 zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1,
74};
75
76use crate::wayland::wlclient::{
77 bounded_roundtrip, drain_pipe, impl_sync_callback, memfd_with, socket_path, wait_readable2,
78 wake_pipe, wake_write, SyncState,
79};
80
81const KEYMAP_FORMAT_XKB_V1: u32 = 1;
82const SLOTS: usize = 2;
85
86pub struct HostFrame {
88 generation: u64,
89 slot: usize,
90 pub dmabuf: Option<Dmabuf>,
93 pub cpu: Option<HostCpuFrame>,
96 pub width: i32,
97 pub height: i32,
98 pub damage: Vec<Rectangle<i32, Physical>>,
99}
100
101pub struct HostCpuFrame {
103 map: Arc<memmap2::MmapMut>,
104 stride: usize,
105 format: u32,
106}
107
108fn shm_src_layout(format: u32) -> (usize, bool) {
121 match format {
122 f if f == wl_shm::Format::Xbgr8888 as u32 || f == wl_shm::Format::Abgr8888 as u32 => {
123 (4, true)
124 }
125 f if f == wl_shm::Format::Bgr888 as u32 => (3, true),
126 f if f == wl_shm::Format::Rgb888 as u32 => (3, false),
127 _ => (4, false),
128 }
129}
130
131fn convert_shm_row(src: &[u8], dst: &mut [u8], src_bpp: usize, swap_rb: bool) {
135 match (src_bpp, swap_rb) {
136 (4, false) => {
137 let n = dst.len().min(src.len());
138 dst[..n].copy_from_slice(&src[..n]);
139 }
140 (4, true) => {
141 for (d, s) in dst.chunks_exact_mut(4).zip(src.chunks_exact(4)) {
142 d[0] = s[2];
143 d[1] = s[1];
144 d[2] = s[0];
145 d[3] = s[3];
146 }
147 }
148 (_, true) => {
149 for (d, s) in dst.chunks_exact_mut(4).zip(src.chunks_exact(3)) {
150 d[0] = s[2];
151 d[1] = s[1];
152 d[2] = s[0];
153 d[3] = 0xff;
154 }
155 }
156 (_, false) => {
157 for (d, s) in dst.chunks_exact_mut(4).zip(src.chunks_exact(3)) {
158 d[0] = s[0];
159 d[1] = s[1];
160 d[2] = s[2];
161 d[3] = 0xff;
162 }
163 }
164 }
165}
166
167impl HostCpuFrame {
168 pub fn write_bgra(&self, w: i32, h: i32, dst: &mut [u8]) {
172 let row = (w * 4) as usize;
173 let (src_bpp, swap_rb) = shm_src_layout(self.format);
174 for y in 0..h as usize {
175 let src_start = y * self.stride;
176 let src_end = (src_start + src_bpp * w as usize).min(self.map.len());
177 if src_start >= src_end || (y + 1) * row > dst.len() {
178 break;
179 }
180 let src_row = &self.map[src_start..src_end];
181 let dst_row = &mut dst[y * row..(y + 1) * row];
182 convert_shm_row(src_row, dst_row, src_bpp, swap_rb);
183 }
184 }
185}
186
187#[derive(Clone, Copy, Default)]
191struct LayoutSlot {
192 want: (i32, i32),
193 pos: (i32, i32),
194 active: bool,
195 zero_copy: bool,
198 paint_cursor: bool,
201}
202
203#[derive(Clone, Copy, PartialEq, Eq)]
208struct Want {
209 size: (i32, i32),
210 zero_copy: bool,
211 paint_cursor: bool,
212}
213
214enum ToHost {
215 Start { width: i32, height: i32, zero_copy: bool, paint_cursor: bool },
216 Release { generation: u64, slot: usize },
219 Idle,
221}
222
223enum CtrlMsg {
224 Apply { epoch: u64, slots: Vec<LayoutSlot> },
225}
226
227#[derive(Default)]
234struct LayoutLedger {
235 epoch: u64,
236 decided: u64,
237 realized: bool,
238}
239
240impl LayoutLedger {
241 fn issue(&mut self) -> u64 {
243 self.epoch += 1;
244 self.epoch
245 }
246
247 fn decide(&mut self, epoch: u64, realized: bool) {
249 if epoch > self.decided {
250 self.decided = epoch;
251 self.realized = realized;
252 }
253 }
254
255 fn outcome(&self, epoch: u64) -> Option<bool> {
257 (self.decided >= epoch).then_some(self.realized)
258 }
259}
260
261pub const LAYOUT_DEADLINE: Duration = Duration::from_secs(5);
265
266#[derive(Default)]
271struct CtrlState {
272 seat: Option<wl_seat::WlSeat>,
273 vk_mgr: Option<ZwpVirtualKeyboardManagerV1>,
274 vptr_mgr: Option<ZwlrVirtualPointerManagerV1>,
275 has_screencopy: bool,
276 has_ext_capture: bool,
277 has_ext_source: bool,
278 outputs: Vec<(wl_output::WlOutput, Option<String>)>,
279 sizes: Arc<Mutex<Vec<(i32, i32)>>>,
283 order: Vec<usize>,
286 output_mgr: Option<ZwlrOutputManagerV1>,
287 heads: Vec<(ZwlrOutputHeadV1, Option<String>)>,
288 om_serial: Option<u32>,
289 cfg_result: Option<bool>,
290 cfg_cancelled: bool,
291 sync_done: bool,
292}
293
294
295fn output_order_key(name: Option<&String>, registry_idx: usize) -> (bool, String, u64, usize) {
299 match name {
300 Some(n) => {
301 let digits_at = n.rfind(|c: char| !c.is_ascii_digit()).map(|i| i + 1).unwrap_or(0);
302 let num = n[digits_at..].parse::<u64>().unwrap_or(0);
303 (false, n[..digits_at].to_string(), num, registry_idx)
304 }
305 None => (true, String::new(), 0, registry_idx),
306 }
307}
308
309impl SyncState for CtrlState {
310 fn sync_done_mut(&mut self) -> &mut bool {
311 &mut self.sync_done
312 }
313}
314impl_sync_callback!(CtrlState);
315
316impl Dispatch<wl_registry::WlRegistry, ()> for CtrlState {
317 fn event(
318 state: &mut Self,
319 registry: &wl_registry::WlRegistry,
320 event: wl_registry::Event,
321 _: &(),
322 _: &Connection,
323 qh: &QueueHandle<Self>,
324 ) {
325 if let wl_registry::Event::Global { name, interface, version } = event {
326 match interface.as_str() {
327 "wl_seat" if state.seat.is_none() => {
328 state.seat = Some(registry.bind(name, 1, qh, ()))
329 }
330 "zwp_virtual_keyboard_manager_v1" => {
331 state.vk_mgr = Some(registry.bind(name, 1, qh, ()))
332 }
333 "zwlr_virtual_pointer_manager_v1" => {
334 state.vptr_mgr = Some(registry.bind(name, version.min(2), qh, ()))
335 }
336 "zwlr_screencopy_manager_v1" if version >= 3 => state.has_screencopy = true,
337 "ext_image_copy_capture_manager_v1" => state.has_ext_capture = true,
338 "ext_output_image_capture_source_manager_v1" => state.has_ext_source = true,
339 "wl_output" => {
340 let idx = state.outputs.len();
341 let out = registry.bind(name, version.min(4), qh, idx);
342 state.outputs.push((out, None));
343 }
344 "zwlr_output_manager_v1" => {
345 state.output_mgr = Some(registry.bind(name, 1, qh, ()))
346 }
347 _ => {}
348 }
349 }
350 }
351}
352
353macro_rules! impl_output_name {
354 ($t:ty) => {
355 impl Dispatch<wl_output::WlOutput, usize> for $t {
356 fn event(
357 state: &mut Self,
358 _: &wl_output::WlOutput,
359 event: wl_output::Event,
360 idx: &usize,
361 _: &Connection,
362 _: &QueueHandle<Self>,
363 ) {
364 if let wl_output::Event::Name { name } = event {
365 if let Some(o) = state.outputs.get_mut(*idx) {
366 o.1 = Some(name);
367 }
368 }
369 }
370 }
371 };
372}
373impl Dispatch<wl_output::WlOutput, usize> for CtrlState {
374 fn event(
375 state: &mut Self,
376 _: &wl_output::WlOutput,
377 event: wl_output::Event,
378 idx: &usize,
379 _: &Connection,
380 _: &QueueHandle<Self>,
381 ) {
382 match event {
383 wl_output::Event::Name { name } => {
384 if let Some(o) = state.outputs.get_mut(*idx) {
385 o.1 = Some(name);
386 }
387 }
388 wl_output::Event::Mode { flags, width, height, .. } => {
389 if flags
390 .into_result()
391 .is_ok_and(|f| f.contains(wl_output::Mode::Current))
392 {
393 let mut sizes = state.sizes.lock().unwrap();
394 if sizes.len() <= *idx {
395 sizes.resize(*idx + 1, (0, 0));
396 }
397 sizes[*idx] = (width, height);
398 }
399 }
400 _ => {}
401 }
402 }
403}
404
405delegate_noop!(CtrlState: ignore wl_seat::WlSeat);
406delegate_noop!(CtrlState: ZwpVirtualKeyboardManagerV1);
407delegate_noop!(CtrlState: ZwpVirtualKeyboardV1);
408delegate_noop!(CtrlState: ZwlrVirtualPointerManagerV1);
409delegate_noop!(CtrlState: ZwlrVirtualPointerV1);
410delegate_noop!(CtrlState: ignore ZwlrOutputModeV1);
411delegate_noop!(CtrlState: ZwlrOutputConfigurationHeadV1);
412
413impl Dispatch<ZwlrOutputManagerV1, ()> for CtrlState {
414 fn event(
415 state: &mut Self,
416 _: &ZwlrOutputManagerV1,
417 event: zwlr_output_manager_v1::Event,
418 _: &(),
419 _: &Connection,
420 _: &QueueHandle<Self>,
421 ) {
422 match event {
423 zwlr_output_manager_v1::Event::Head { head } => state.heads.push((head, None)),
424 zwlr_output_manager_v1::Event::Done { serial } => state.om_serial = Some(serial),
425 _ => {}
426 }
427 }
428
429 wayland_client::event_created_child!(CtrlState, ZwlrOutputManagerV1, [
430 zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()),
431 ]);
432}
433
434impl Dispatch<ZwlrOutputHeadV1, ()> for CtrlState {
435 fn event(
436 state: &mut Self,
437 head: &ZwlrOutputHeadV1,
438 event: zwlr_output_head_v1::Event,
439 _: &(),
440 _: &Connection,
441 _: &QueueHandle<Self>,
442 ) {
443 match event {
444 zwlr_output_head_v1::Event::Name { name } => {
445 if let Some(h) = state.heads.iter_mut().find(|(h, _)| h.id() == head.id()) {
446 h.1 = Some(name);
447 }
448 }
449 zwlr_output_head_v1::Event::Finished => {
450 state.heads.retain(|(h, _)| h.id() != head.id());
451 }
452 _ => {}
453 }
454 }
455
456 wayland_client::event_created_child!(CtrlState, ZwlrOutputHeadV1, [
457 zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()),
458 ]);
459}
460
461impl Dispatch<ZwlrOutputConfigurationV1, ()> for CtrlState {
462 fn event(
463 state: &mut Self,
464 _: &ZwlrOutputConfigurationV1,
465 event: zwlr_output_configuration_v1::Event,
466 _: &(),
467 _: &Connection,
468 _: &QueueHandle<Self>,
469 ) {
470 match event {
471 zwlr_output_configuration_v1::Event::Succeeded => state.cfg_result = Some(true),
472 zwlr_output_configuration_v1::Event::Failed => state.cfg_result = Some(false),
473 zwlr_output_configuration_v1::Event::Cancelled => state.cfg_cancelled = true,
474 _ => {}
475 }
476 }
477}
478
479#[derive(Default)]
484struct CaptureState {
485 shm: Option<wl_shm::WlShm>,
486 dmabuf: Option<ZwpLinuxDmabufV1>,
487 screencopy: Option<ZwlrScreencopyManagerV1>,
488 ext_capture: Option<ExtImageCopyCaptureManagerV1>,
489 ext_source_mgr: Option<ExtOutputImageCaptureSourceManagerV1>,
490 outputs: Vec<(wl_output::WlOutput, Option<String>)>,
491 announce_dmabuf: Option<(u32, i32, i32)>,
494 announce_shm: Option<(u32, i32, i32, i32)>,
495 buffer_done: bool,
496 damage: Vec<Rectangle<i32, Physical>>,
497 ready: bool,
498 failed: bool,
499 sync_done: bool,
500 params_created: Option<wayland_client::protocol::wl_buffer::WlBuffer>,
503 params_failed: bool,
504 ext_pending_size: Option<(i32, i32)>,
507 ext_pending_shm: Vec<u32>,
508 ext_pending_dma: Vec<(u32, Vec<u64>)>,
509 ext_size: Option<(i32, i32)>,
510 ext_shm_formats: Vec<u32>,
511 ext_dma_formats: Vec<(u32, Vec<u64>)>,
512 ext_serial: u64,
513 ext_stopped: bool,
514 ext_fail_reason: Option<ext_image_copy_capture_frame_v1::FailureReason>,
515}
516
517
518impl CaptureState {
519 fn reset_frame(&mut self) {
520 self.announce_dmabuf = None;
521 self.announce_shm = None;
522 self.buffer_done = false;
523 self.damage.clear();
524 self.ready = false;
525 self.failed = false;
526 self.ext_fail_reason = None;
527 }
528}
529
530impl SyncState for CaptureState {
531 fn sync_done_mut(&mut self) -> &mut bool {
532 &mut self.sync_done
533 }
534}
535impl_sync_callback!(CaptureState);
536impl_output_name!(CaptureState);
537
538impl Dispatch<wl_registry::WlRegistry, ()> for CaptureState {
539 fn event(
540 state: &mut Self,
541 registry: &wl_registry::WlRegistry,
542 event: wl_registry::Event,
543 _: &(),
544 _: &Connection,
545 qh: &QueueHandle<Self>,
546 ) {
547 if let wl_registry::Event::Global { name, interface, version } = event {
548 match interface.as_str() {
549 "wl_shm" => state.shm = Some(registry.bind(name, 1, qh, ())),
550 "zwp_linux_dmabuf_v1" if version >= 3 => {
551 state.dmabuf = Some(registry.bind(name, 3, qh, ()))
552 }
553 "zwlr_screencopy_manager_v1" if version >= 3 => {
554 state.screencopy = Some(registry.bind(name, 3, qh, ()))
555 }
556 "ext_image_copy_capture_manager_v1" => {
557 state.ext_capture = Some(registry.bind(name, 1, qh, ()))
558 }
559 "ext_output_image_capture_source_manager_v1" => {
560 state.ext_source_mgr = Some(registry.bind(name, 1, qh, ()))
561 }
562 "wl_output" => {
563 let idx = state.outputs.len();
564 let out = registry.bind(name, version.min(4), qh, idx);
565 state.outputs.push((out, None));
566 }
567 _ => {}
568 }
569 }
570 }
571}
572
573delegate_noop!(CaptureState: ignore wl_shm::WlShm);
574delegate_noop!(CaptureState: ignore wl_shm_pool::WlShmPool);
575delegate_noop!(CaptureState: ignore wayland_client::protocol::wl_buffer::WlBuffer);
576delegate_noop!(CaptureState: ZwlrScreencopyManagerV1);
577delegate_noop!(CaptureState: ExtImageCopyCaptureManagerV1);
578delegate_noop!(CaptureState: ExtOutputImageCaptureSourceManagerV1);
579delegate_noop!(CaptureState: ExtImageCaptureSourceV1);
580
581impl Dispatch<ExtImageCopyCaptureSessionV1, ()> for CaptureState {
582 fn event(
583 state: &mut Self,
584 _: &ExtImageCopyCaptureSessionV1,
585 event: ext_image_copy_capture_session_v1::Event,
586 _: &(),
587 _: &Connection,
588 _: &QueueHandle<Self>,
589 ) {
590 use ext_image_copy_capture_session_v1::Event;
591 match event {
592 Event::BufferSize { width, height } => {
593 state.ext_pending_size = Some((width as i32, height as i32));
594 }
595 Event::ShmFormat { format } => {
596 let raw = match format {
597 WEnum::Value(f) => f as u32,
598 WEnum::Unknown(u) => u,
599 };
600 state.ext_pending_shm.push(raw);
601 }
602 Event::DmabufDevice { .. } => {
603 }
607 Event::DmabufFormat { format, modifiers } => {
608 let mods = modifiers
609 .chunks_exact(8)
610 .map(|c| u64::from_ne_bytes(c.try_into().unwrap()))
611 .collect();
612 state.ext_pending_dma.push((format, mods));
613 }
614 Event::Done => {
615 state.ext_size = state.ext_pending_size.take();
616 state.ext_shm_formats = std::mem::take(&mut state.ext_pending_shm);
617 state.ext_dma_formats = std::mem::take(&mut state.ext_pending_dma);
618 state.ext_serial += 1;
619 }
620 Event::Stopped => state.ext_stopped = true,
621 _ => {}
622 }
623 }
624}
625
626impl Dispatch<ExtImageCopyCaptureFrameV1, ()> for CaptureState {
627 fn event(
628 state: &mut Self,
629 _: &ExtImageCopyCaptureFrameV1,
630 event: ext_image_copy_capture_frame_v1::Event,
631 _: &(),
632 _: &Connection,
633 _: &QueueHandle<Self>,
634 ) {
635 use ext_image_copy_capture_frame_v1::Event;
636 match event {
637 Event::Damage { x, y, width, height } => {
638 state.damage.push(Rectangle::new(
639 (x, y).into(),
640 (width, height).into(),
641 ));
642 }
643 Event::Ready => state.ready = true,
644 Event::Failed { reason } => {
645 state.failed = true;
646 if let WEnum::Value(r) = reason {
647 state.ext_fail_reason = Some(r);
648 }
649 }
650 _ => {}
651 }
652 }
653}
654
655impl Dispatch<ZwpLinuxDmabufV1, ()> for CaptureState {
656 fn event(
657 _: &mut Self,
658 _: &ZwpLinuxDmabufV1,
659 _: <ZwpLinuxDmabufV1 as Proxy>::Event,
660 _: &(),
661 _: &Connection,
662 _: &QueueHandle<Self>,
663 ) {
664 }
667}
668
669impl Dispatch<ZwpLinuxBufferParamsV1, ()> for CaptureState {
670 fn event(
671 state: &mut Self,
672 _: &ZwpLinuxBufferParamsV1,
673 event: zwp_linux_buffer_params_v1::Event,
674 _: &(),
675 _: &Connection,
676 _: &QueueHandle<Self>,
677 ) {
678 match event {
679 zwp_linux_buffer_params_v1::Event::Created { buffer } => {
680 state.params_created = Some(buffer);
681 }
682 zwp_linux_buffer_params_v1::Event::Failed => state.params_failed = true,
683 _ => {}
684 }
685 }
686
687 fn event_created_child(
688 _opcode: u16,
689 qhandle: &QueueHandle<Self>,
690 ) -> std::sync::Arc<dyn wayland_client::backend::ObjectData> {
691 qhandle.make_data::<wayland_client::protocol::wl_buffer::WlBuffer, _>(())
692 }
693}
694
695impl Dispatch<ZwlrScreencopyFrameV1, ()> for CaptureState {
696 fn event(
697 state: &mut Self,
698 _: &ZwlrScreencopyFrameV1,
699 event: zwlr_screencopy_frame_v1::Event,
700 _: &(),
701 _: &Connection,
702 _: &QueueHandle<Self>,
703 ) {
704 match event {
705 zwlr_screencopy_frame_v1::Event::Buffer {
706 format: WEnum::Value(f), width, height, stride,
707 } => {
708 state.announce_shm = Some((f as u32, width as i32, height as i32, stride as i32));
709 }
710 zwlr_screencopy_frame_v1::Event::LinuxDmabuf { format, width, height } => {
711 state.announce_dmabuf = Some((format, width as i32, height as i32));
712 }
713 zwlr_screencopy_frame_v1::Event::BufferDone => state.buffer_done = true,
714 zwlr_screencopy_frame_v1::Event::Damage { x, y, width, height } => {
715 state.damage.push(Rectangle::new(
716 (x as i32, y as i32).into(),
717 (width as i32, height as i32).into(),
718 ));
719 }
720 zwlr_screencopy_frame_v1::Event::Ready { .. } => state.ready = true,
721 zwlr_screencopy_frame_v1::Event::Failed => state.failed = true,
722 _ => {}
723 }
724 }
725}
726
727enum SlotBuffer {
728 Gpu {
729 _bo: gbm::BufferObject<()>,
730 dmabuf: Dmabuf,
731 wl: wayland_client::protocol::wl_buffer::WlBuffer,
732 },
733 Cpu {
734 _pool: wl_shm_pool::WlShmPool,
735 map: Arc<memmap2::MmapMut>,
736 stride: i32,
737 format: u32,
738 wl: wayland_client::protocol::wl_buffer::WlBuffer,
739 },
740}
741
742impl Drop for SlotBuffer {
747 fn drop(&mut self) {
748 match self {
749 SlotBuffer::Gpu { wl, .. } => wl.destroy(),
750 SlotBuffer::Cpu { _pool, wl, .. } => {
751 wl.destroy();
752 _pool.destroy();
753 }
754 }
755 }
756}
757
758struct OutputHandle {
760 to_thread: Sender<ToHost>,
761 wake: OwnedFd,
762 frames: Receiver<HostFrame>,
763 retained: Mutex<Option<HostFrame>>,
766 name: Option<String>,
767}
768
769impl OutputHandle {
770 fn send(&self, msg: ToHost) {
771 let _ = self.to_thread.send(msg);
772 wake_write(self.wake.as_raw_fd());
773 }
774}
775
776pub struct HostSession {
778 conn: Connection,
779 vk: Option<ZwpVirtualKeyboardV1>,
780 vptr: Option<ZwlrVirtualPointerV1>,
781 ctrl_tx: Sender<CtrlMsg>,
782 ctrl_wake: OwnedFd,
783 outputs: Vec<OutputHandle>,
784 layout: Mutex<std::collections::BTreeMap<u32, LayoutSlot>>,
785 order: Vec<usize>,
787 sizes: Arc<Mutex<Vec<(i32, i32)>>>,
788 layouts: Arc<Mutex<LayoutLedger>>,
789 alive: Arc<AtomicBool>,
790}
791
792impl HostSession {
793 pub fn connect(display: &str, gbm_path: Option<std::path::PathBuf>) -> Result<Self, String> {
798 let path = socket_path(display).ok_or("XDG_RUNTIME_DIR is unset")?;
799 let stream = UnixStream::connect(&path).map_err(|e| format!("connect {path}: {e}"))?;
800 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
801 let mut queue = conn.new_event_queue();
802 let qh = queue.handle();
803 let _registry = conn.display().get_registry(&qh, ());
804 let mut state = CtrlState::default();
805 bounded_roundtrip(&conn, &mut queue, &mut state)?;
806
807 let seat = state.seat.clone().ok_or("host compositor advertises no wl_seat")?;
808 if !state.has_screencopy && !(state.has_ext_capture && state.has_ext_source) {
809 return Err(
810 "host compositor offers neither ext-image-copy-capture nor zwlr_screencopy_manager_v1 (v3)"
811 .into(),
812 );
813 }
814 if state.outputs.is_empty() {
815 return Err("host compositor has no wl_output".into());
816 }
817
818 let vk = match &state.vk_mgr {
819 Some(mgr) => {
820 let vk = mgr.create_virtual_keyboard(&seat, &qh, ());
821 if let Some(text) = crate::wayland::vkclient::us_base_text() {
824 let mut data = text.as_bytes().to_vec();
825 data.push(0);
826 let fd = memfd_with(&data)?;
827 vk.keymap(KEYMAP_FORMAT_XKB_V1, fd.as_fd(), data.len() as u32);
828 }
829 Some(vk)
830 }
831 None => {
832 eprintln!("[HostCapture] no zwp_virtual_keyboard_manager_v1: keyboard injection disabled.");
833 None
834 }
835 };
836 let vptr = match &state.vptr_mgr {
837 Some(mgr) => Some(mgr.create_virtual_pointer(Some(&seat), &qh, ())),
838 None => {
839 eprintln!("[HostCapture] no zwlr_virtual_pointer_manager_v1: pointer injection disabled.");
840 None
841 }
842 };
843 bounded_roundtrip(&conn, &mut queue, &mut state)?;
845
846 let alive = Arc::new(AtomicBool::new(true));
847 let mut order: Vec<usize> = (0..state.outputs.len()).collect();
848 order.sort_by_key(|&i| output_order_key(state.outputs[i].1.as_ref(), i));
849 let names: Vec<Option<String>> =
850 order.iter().map(|&i| state.outputs[i].1.clone()).collect();
851 state.order = order;
852 println!(
853 "[HostCapture] host outputs: {}.",
854 names
855 .iter()
856 .enumerate()
857 .map(|(i, n)| format!("{i}={}", n.as_deref().unwrap_or("?")))
858 .collect::<Vec<_>>()
859 .join(", ")
860 );
861
862 let mut outputs = Vec::new();
863 for (i, name) in names.iter().cloned().enumerate() {
864 let (wake_rd, wake_wr) = wake_pipe()?;
865 let (to_thread, from_main) = std::sync::mpsc::channel::<ToHost>();
866 let (frame_tx, frames) = std::sync::mpsc::channel::<HostFrame>();
867 let display = display.to_string();
868 let expect = name.clone();
869 let gbm_path = gbm_path.clone();
870 let alive = alive.clone();
871 std::thread::Builder::new()
872 .name(format!("pf-host-cap{i}"))
873 .spawn(move || {
874 if let Err(e) =
875 capture_loop(&display, i, expect, gbm_path, from_main, wake_rd, frame_tx)
876 {
877 eprintln!("[HostCapture] output {i} capture ended: {e}");
878 }
879 if i == 0 {
880 alive.store(false, Ordering::Relaxed);
881 }
882 })
883 .map_err(|e| format!("spawn: {e}"))?;
884 outputs.push(OutputHandle {
885 to_thread,
886 wake: wake_wr,
887 frames,
888 retained: Mutex::new(None),
889 name,
890 });
891 }
892
893 let (ctrl_tx, ctrl_rx) = std::sync::mpsc::channel::<CtrlMsg>();
894 let (ctrl_wake_rd, ctrl_wake) = wake_pipe()?;
895 let order_for_session = state.order.clone();
896 let sizes = state.sizes.clone();
897 let layouts = Arc::new(Mutex::new(LayoutLedger::default()));
898 {
899 let conn = conn.clone();
900 let ledger = layouts.clone();
901 let alive = alive.clone();
902 std::thread::Builder::new()
903 .name("pf-host-ctrl".into())
904 .spawn(move || {
905 control_loop(conn, queue, state, ctrl_rx, ctrl_wake_rd, ledger);
906 alive.store(false, Ordering::Relaxed);
909 })
910 .map_err(|e| format!("spawn: {e}"))?;
911 }
912
913 let layout = Mutex::new(std::collections::BTreeMap::new());
914 Ok(Self {
915 conn,
916 vk,
917 vptr,
918 ctrl_tx,
919 ctrl_wake,
920 outputs,
921 layout,
922 order: order_for_session,
923 sizes,
924 layouts,
925 alive,
926 })
927 }
928
929 pub fn output_count(&self) -> usize {
930 self.outputs.len()
931 }
932
933 fn output_index_for(&self, display_id: u32) -> Option<usize> {
936 let layout = self.layout.lock().unwrap();
937 let rank = layout
938 .iter()
939 .filter(|(_, s)| s.active)
940 .position(|(id, _)| *id == display_id)?;
941 (rank < self.outputs.len()).then_some(rank)
942 }
943
944 pub fn has_output_for(&self, display_id: u32) -> bool {
946 self.output_index_for(display_id).is_some()
947 }
948
949 pub fn set_layout(&self, display_id: u32, x: i32, y: i32) {
952 let mut layout = self.layout.lock().unwrap();
953 layout.entry(display_id).or_default().pos = (x, y);
954 }
955
956 pub fn current_output_size(&self, display_id: u32) -> Option<(i32, i32)> {
960 let rank = self.output_index_for(display_id)?;
961 let registry_idx = *self.order.get(rank)?;
962 let size = *self.sizes.lock().unwrap().get(registry_idx)?;
963 (size.0 > 0 && size.1 > 0).then_some(size)
964 }
965
966 fn send_apply(&self, slots: Vec<LayoutSlot>) -> u64 {
968 let epoch = self.layouts.lock().unwrap().issue();
969 let _ = self.ctrl_tx.send(CtrlMsg::Apply { epoch, slots });
970 wake_write(self.ctrl_wake.as_raw_fd());
971 epoch
972 }
973
974 pub fn layout_outcome(&self, epoch: u64) -> Option<bool> {
981 self.layouts.lock().unwrap().outcome(epoch)
982 }
983
984 pub fn start_capture(
992 &self,
993 display_id: u32,
994 width: i32,
995 height: i32,
996 zero_copy: bool,
997 paint_cursor: bool,
998 ) -> u64 {
999 let assignments = {
1000 let mut layout = self.layout.lock().unwrap();
1001 let slot = layout.entry(display_id).or_default();
1002 slot.want = (width, height);
1003 slot.zero_copy = zero_copy;
1004 slot.paint_cursor = paint_cursor;
1005 slot.active = true;
1006 let active: Vec<(u32, LayoutSlot)> = layout
1007 .iter()
1008 .filter(|(_, s)| s.active)
1009 .map(|(id, s)| (*id, *s))
1010 .collect();
1011 if active.iter().position(|(id, _)| *id == display_id).unwrap_or(usize::MAX)
1012 >= self.outputs.len()
1013 {
1014 eprintln!(
1015 "[HostCapture] display {display_id} has no host output (host has {}); not captured.",
1016 self.outputs.len()
1017 );
1018 }
1019 active
1020 };
1021 let mut by_output: Vec<LayoutSlot> = Vec::new();
1024 for (rank, (_, slot)) in assignments.iter().enumerate() {
1025 if rank >= self.outputs.len() {
1026 break;
1027 }
1028 let size_mismatch = self.outputs[rank]
1032 .retained
1033 .lock()
1034 .unwrap()
1035 .as_ref()
1036 .is_some_and(|f| f.width != slot.want.0 || f.height != slot.want.1);
1037 if size_mismatch {
1038 self.drop_buffered_frames(rank);
1039 }
1040 self.outputs[rank].send(ToHost::Start {
1041 width: slot.want.0,
1042 height: slot.want.1,
1043 zero_copy: slot.zero_copy,
1044 paint_cursor: slot.paint_cursor,
1045 });
1046 by_output.push(*slot);
1047 }
1048 self.send_apply(by_output)
1049 }
1050
1051 pub fn set_buffer_type(&self, display_id: u32, zero_copy: bool) {
1057 let target = {
1058 let mut layout = self.layout.lock().unwrap();
1059 let slot = layout.entry(display_id).or_default();
1060 if slot.zero_copy == zero_copy {
1061 return;
1062 }
1063 slot.zero_copy = zero_copy;
1064 let (want, active, paint_cursor) = (slot.want, slot.active, slot.paint_cursor);
1065 let rank = layout
1066 .iter()
1067 .filter(|(_, s)| s.active)
1068 .position(|(id, _)| *id == display_id);
1069 match rank {
1070 Some(r) if active && r < self.outputs.len() => (r, want, paint_cursor),
1071 _ => return,
1072 }
1073 };
1074 let (idx, want, paint_cursor) = target;
1075 self.drop_buffered_frames(idx);
1078 self.outputs[idx].send(ToHost::Start {
1079 width: want.0,
1080 height: want.1,
1081 zero_copy,
1082 paint_cursor,
1083 });
1084 }
1085
1086 pub fn set_cursor_painting(&self, paint_cursor: bool) {
1090 let targets: Vec<(usize, LayoutSlot)> = {
1091 let mut layout = self.layout.lock().unwrap();
1092 let mut out = Vec::new();
1093 for (rank, (_, slot)) in layout.iter_mut().filter(|(_, s)| s.active).enumerate() {
1094 if rank >= self.outputs.len() {
1095 break;
1096 }
1097 if slot.paint_cursor != paint_cursor {
1098 slot.paint_cursor = paint_cursor;
1099 out.push((rank, *slot));
1100 }
1101 }
1102 out
1103 };
1104 for (idx, slot) in targets {
1105 self.outputs[idx].send(ToHost::Start {
1106 width: slot.want.0,
1107 height: slot.want.1,
1108 zero_copy: slot.zero_copy,
1109 paint_cursor,
1110 });
1111 }
1112 }
1113
1114 fn drop_buffered_frames(&self, idx: usize) {
1120 let handle = &self.outputs[idx];
1121 if let Some(old) = handle.retained.lock().unwrap().take() {
1122 handle.send(ToHost::Release { generation: old.generation, slot: old.slot });
1123 }
1124 while let Ok(frame) = handle.frames.try_recv() {
1125 handle.send(ToHost::Release { generation: frame.generation, slot: frame.slot });
1126 }
1127 }
1128
1129 pub fn idle_output(&self, display_id: u32) {
1134 let Some(idx) = self.output_index_for(display_id) else {
1135 self.layout.lock().unwrap().entry(display_id).or_default().active = false;
1136 return;
1137 };
1138 self.layout.lock().unwrap().entry(display_id).or_default().active = false;
1139 self.outputs[idx].send(ToHost::Idle);
1140 self.drop_buffered_frames(idx);
1141 }
1142
1143 pub fn alive(&self) -> bool {
1147 self.alive.load(Ordering::Relaxed)
1148 }
1149
1150 pub fn release_frame(&self, display_id: u32, frame: HostFrame) {
1152 if let Some(idx) = self.output_index_for(display_id) {
1153 self.outputs[idx].send(ToHost::Release {
1154 generation: frame.generation,
1155 slot: frame.slot,
1156 });
1157 }
1158 }
1159
1160 pub fn try_take_frame(&self, display_id: u32) -> Option<HostFrame> {
1163 let handle = self.outputs.get(self.output_index_for(display_id)?)?;
1164 let mut newest: Option<HostFrame> = None;
1165 while let Ok(frame) = handle.frames.try_recv() {
1166 if let Some(stale) = newest.replace(frame) {
1167 handle.send(ToHost::Release { generation: stale.generation, slot: stale.slot });
1168 }
1169 }
1170 newest
1171 }
1172
1173 pub fn retain_frame(&self, display_id: u32, frame: HostFrame) {
1176 let Some(handle) = self.output_index_for(display_id).and_then(|i| self.outputs.get(i))
1177 else {
1178 return;
1179 };
1180 let old = handle.retained.lock().unwrap().replace(frame);
1181 if let Some(old) = old {
1182 handle.send(ToHost::Release { generation: old.generation, slot: old.slot });
1183 }
1184 }
1185
1186 pub fn with_retained<R>(
1188 &self,
1189 display_id: u32,
1190 f: impl FnOnce(Option<&HostFrame>) -> R,
1191 ) -> R {
1192 match self.output_index_for(display_id).and_then(|i| self.outputs.get(i)) {
1193 Some(handle) => f(handle.retained.lock().unwrap().as_ref()),
1194 None => f(None),
1195 }
1196 }
1197
1198 pub fn set_keymap(&self, text: &str) {
1200 let Some(vk) = &self.vk else { return };
1201 let mut data = text.as_bytes().to_vec();
1202 data.push(0);
1203 match memfd_with(&data) {
1204 Ok(fd) => {
1205 vk.keymap(KEYMAP_FORMAT_XKB_V1, fd.as_fd(), data.len() as u32);
1206 let _ = self.conn.flush();
1207 }
1208 Err(e) => eprintln!("[HostCapture] keymap upload failed: {e}"),
1209 }
1210 }
1211
1212 pub fn key(&self, xkb_keycode: u32, pressed: bool) {
1214 let Some(vk) = &self.vk else { return };
1215 if xkb_keycode < 8 {
1216 return;
1217 }
1218 vk.key(0, xkb_keycode - 8, if pressed { 1 } else { 0 });
1219 let _ = self.conn.flush();
1220 }
1221
1222 fn extent(&self) -> (i32, i32) {
1225 let layout = self.layout.lock().unwrap();
1226 let mut w = 0;
1227 let mut h = 0;
1228 for (rank, (_, slot)) in layout.iter().filter(|(_, s)| s.active).enumerate() {
1229 if rank >= self.outputs.len() {
1230 break;
1231 }
1232 w = w.max(slot.pos.0 + slot.want.0);
1233 h = h.max(slot.pos.1 + slot.want.1);
1234 }
1235 (w, h)
1236 }
1237
1238 pub fn pointer_motion_abs(&self, x: f64, y: f64) {
1239 let Some(vp) = &self.vptr else { return };
1240 let (w, h) = self.extent();
1241 if w <= 0 || h <= 0 {
1242 return;
1243 }
1244 let cx = x.clamp(0.0, (w - 1) as f64) as u32;
1245 let cy = y.clamp(0.0, (h - 1) as f64) as u32;
1246 vp.motion_absolute(0, cx, cy, w as u32, h as u32);
1247 vp.frame();
1248 let _ = self.conn.flush();
1249 }
1250
1251 pub fn pointer_motion_rel(&self, dx: f64, dy: f64) {
1257 let Some(vp) = &self.vptr else { return };
1258 if dx == 0.0 && dy == 0.0 {
1259 return;
1260 }
1261 vp.motion(0, dx, dy);
1262 vp.frame();
1263 let _ = self.conn.flush();
1264 }
1265
1266 pub fn pointer_button(&self, btn: u32, pressed: bool) {
1267 let Some(vp) = &self.vptr else { return };
1268 vp.button(
1269 0,
1270 btn,
1271 if pressed { wl_pointer::ButtonState::Pressed } else { wl_pointer::ButtonState::Released },
1272 );
1273 vp.frame();
1274 let _ = self.conn.flush();
1275 }
1276
1277 pub fn pointer_axis(&self, dx: f64, dy: f64) {
1282 let Some(vp) = &self.vptr else { return };
1283 if dx == 0.0 && dy == 0.0 {
1284 return;
1285 }
1286 for (axis, value) in [
1287 (wl_pointer::Axis::VerticalScroll, dy),
1288 (wl_pointer::Axis::HorizontalScroll, dx),
1289 ] {
1290 if value == 0.0 {
1291 continue;
1292 }
1293 let steps = (value * crate::SCROLL_V120_PER_UNIT / 120.0).round() as i32;
1294 if steps != 0 {
1295 vp.axis_discrete(0, axis, value, steps);
1296 } else {
1297 vp.axis(0, axis, value);
1298 }
1299 vp.axis_source(wl_pointer::AxisSource::Wheel);
1300 }
1301 vp.frame();
1302 let _ = self.conn.flush();
1303 }
1304}
1305
1306fn control_loop(
1315 conn: Connection,
1316 mut queue: EventQueue<CtrlState>,
1317 mut state: CtrlState,
1318 ctrl_rx: Receiver<CtrlMsg>,
1319 wake_rd: OwnedFd,
1320 ledger: Arc<Mutex<LayoutLedger>>,
1321) {
1322 let qh = queue.handle();
1323 loop {
1324 let mut pending: Option<(u64, Vec<LayoutSlot>)> = None;
1325 loop {
1326 match ctrl_rx.try_recv() {
1327 Ok(CtrlMsg::Apply { epoch, slots }) => pending = Some((epoch, slots)),
1328 Err(TryRecvError::Empty) => break,
1329 Err(TryRecvError::Disconnected) => return,
1330 }
1331 }
1332 if let Some((epoch, slots)) = pending {
1333 let realized = apply_layout(&conn, &mut queue, &mut state, &qh, &slots);
1334 ledger.lock().unwrap().decide(epoch, realized);
1335 }
1336 if queue.dispatch_pending(&mut state).is_err() {
1337 return;
1338 }
1339 let _ = queue.flush();
1340 if let Some(guard) = conn.prepare_read() {
1341 match wait_readable2(
1342 guard.connection_fd().as_raw_fd(),
1343 wake_rd.as_raw_fd(),
1344 Some(Duration::from_secs(1)),
1345 ) {
1346 Ok((wl, wake)) => {
1347 if wake {
1348 drain_pipe(wake_rd.as_raw_fd());
1349 }
1350 if wl {
1351 let _ = guard.read();
1352 } else {
1353 drop(guard);
1354 }
1355 }
1356 Err(_) => return,
1357 }
1358 }
1359 }
1360}
1361
1362fn apply_layout(
1370 conn: &Connection,
1371 queue: &mut EventQueue<CtrlState>,
1372 state: &mut CtrlState,
1373 qh: &QueueHandle<CtrlState>,
1374 slots: &[LayoutSlot],
1375) -> bool {
1376 let Some(mgr) = state.output_mgr.clone() else {
1377 eprintln!("[HostCapture] host lacks zwlr_output_manager_v1; capture follows the host's own size.");
1378 return false;
1379 };
1380 let deadline = Instant::now() + LAYOUT_DEADLINE;
1381 for _ in 0..3 {
1382 while state.om_serial.is_none() && Instant::now() < deadline {
1383 if !pump_ctrl(conn, queue, state) {
1384 return false;
1385 }
1386 }
1387 let Some(serial) = state.om_serial else {
1388 eprintln!("[HostCapture] output-management serial never arrived; resize skipped.");
1389 return false;
1390 };
1391 state.cfg_result = None;
1392 state.cfg_cancelled = false;
1393 let cfg = mgr.create_configuration(serial, qh, ());
1394 let mut any = false;
1395 for (i, slot) in slots.iter().enumerate() {
1396 if !slot.active {
1397 continue;
1398 }
1399 let want_name = state
1400 .order
1401 .get(i)
1402 .and_then(|&oi| state.outputs.get(oi))
1403 .and_then(|(_, n)| n.clone());
1404 let head = match want_name
1405 .as_ref()
1406 .and_then(|n| state.heads.iter().find(|(_, hn)| hn.as_ref() == Some(n)))
1407 .or_else(|| state.heads.get(i))
1408 {
1409 Some((h, _)) => h.clone(),
1410 None => {
1411 eprintln!("[HostCapture] no output-management head for output {i}; not resized.");
1412 continue;
1413 }
1414 };
1415 let cfg_head = cfg.enable_head(&head, qh, ());
1416 cfg_head.set_custom_mode(slot.want.0, slot.want.1, 0);
1417 cfg_head.set_position(slot.pos.0, slot.pos.1);
1418 any = true;
1419 }
1420 if !any {
1421 cfg.destroy();
1422 return false;
1423 }
1424 cfg.apply();
1425 let _ = queue.flush();
1426 while state.cfg_result.is_none() && !state.cfg_cancelled && Instant::now() < deadline {
1427 if !pump_ctrl(conn, queue, state) {
1428 cfg.destroy();
1429 return false;
1430 }
1431 }
1432 cfg.destroy();
1433 if state.cfg_cancelled {
1434 state.om_serial = None;
1436 continue;
1437 }
1438 if state.cfg_result != Some(true) {
1439 eprintln!("[HostCapture] host refused the layout; capture follows the host's own size.");
1440 return false;
1441 }
1442 return true;
1443 }
1444 false
1445}
1446
1447fn pump_ctrl(conn: &Connection, queue: &mut EventQueue<CtrlState>, state: &mut CtrlState) -> bool {
1449 if queue.dispatch_pending(state).is_err() {
1450 return false;
1451 }
1452 let _ = queue.flush();
1453 let Some(guard) = conn.prepare_read() else { return true };
1454 match crate::wayland::wlclient::wait_readable(
1455 guard.connection_fd().as_raw_fd(),
1456 Duration::from_millis(200),
1457 ) {
1458 Ok(readable) => {
1459 if readable {
1460 let _ = guard.read();
1461 } else {
1462 drop(guard);
1463 }
1464 true
1465 }
1466 Err(_) => false,
1467 }
1468}
1469
1470fn fourcc_to_gbm(fourcc: u32) -> GbmFormat {
1475 match fourcc {
1478 0x34325258 => GbmFormat::Xrgb8888,
1479 _ => GbmFormat::Argb8888,
1480 }
1481}
1482
1483#[allow(clippy::too_many_arguments)]
1490fn alloc_gpu_slot(
1491 conn: &Connection,
1492 queue: &mut EventQueue<CaptureState>,
1493 state: &mut CaptureState,
1494 wake: RawFd,
1495 dev: &GbmDevice<File>,
1496 dmabuf_global: &ZwpLinuxDmabufV1,
1497 fourcc: u32,
1498 w: i32,
1499 h: i32,
1500 modifiers: &[u64],
1501) -> Result<Option<SlotBuffer>, String> {
1502 let qh = queue.handle();
1503 let format = fourcc_to_gbm(fourcc);
1504 let bo = if modifiers.is_empty() {
1505 dev.create_buffer_object::<()>(w as u32, h as u32, format, BufferObjectFlags::RENDERING)
1506 } else {
1507 dev.create_buffer_object_with_modifiers2::<()>(
1508 w as u32,
1509 h as u32,
1510 format,
1511 modifiers.iter().map(|&m| gbm::Modifier::from(m)),
1512 BufferObjectFlags::RENDERING,
1513 )
1514 .or_else(|_| {
1515 dev.create_buffer_object::<()>(w as u32, h as u32, format, BufferObjectFlags::RENDERING)
1516 })
1517 }
1518 .map_err(|e| format!("GBM allocation {w}x{h}: {e:?}"))?;
1519 let dmabuf = crate::create_dmabuf_from_bo(&bo);
1520 let params = dmabuf_global.create_params(&qh, ());
1521 let modifier: u64 = dmabuf.format().modifier.into();
1522 for (i, handle) in dmabuf.handles().enumerate() {
1523 params.add(
1524 handle,
1525 i as u32,
1526 dmabuf.offsets().nth(i).unwrap_or(0),
1527 dmabuf.strides().nth(i).unwrap_or(0),
1528 (modifier >> 32) as u32,
1529 (modifier & 0xffff_ffff) as u32,
1530 );
1531 }
1532 state.params_created = None;
1533 state.params_failed = false;
1534 params.create(w, h, fourcc, zwp_linux_buffer_params_v1::Flags::empty());
1535 loop {
1536 match pump_until(conn, queue, state, wake, Some(Duration::from_secs(2)), |s| {
1537 s.params_created.is_some() || s.params_failed
1538 })? {
1539 Pump::Done => break,
1540 Pump::Control => continue,
1541 Pump::Timeout => {
1542 params.destroy();
1543 return Err("host answered neither created nor failed for the dmabuf".into());
1544 }
1545 }
1546 }
1547 params.destroy();
1548 match state.params_created.take() {
1549 Some(wl) => Ok(Some(SlotBuffer::Gpu { _bo: bo, dmabuf, wl })),
1550 None => Ok(None),
1551 }
1552}
1553
1554fn alloc_cpu_slot(
1556 shm: &wl_shm::WlShm,
1557 qh: &QueueHandle<CaptureState>,
1558 format: u32,
1559 w: i32,
1560 h: i32,
1561 stride: i32,
1562) -> Result<SlotBuffer, String> {
1563 let size = (stride * h) as usize;
1564 let fd = memfd_with(&vec![0u8; size])?;
1565 let pool = shm.create_pool(fd.as_fd(), size as i32, qh, ());
1566 let wl = pool.create_buffer(
1567 0,
1568 w,
1569 h,
1570 stride,
1571 WEnum::<wl_shm::Format>::from(format).into_result().unwrap_or(wl_shm::Format::Xrgb8888),
1572 qh,
1573 (),
1574 );
1575 let file = File::from(fd);
1576 let map = unsafe { memmap2::MmapMut::map_mut(&file) }.map_err(|e| format!("shm map: {e}"))?;
1577 Ok(SlotBuffer::Cpu { _pool: pool, map: Arc::new(map), stride, format, wl })
1578}
1579
1580enum Ctl {
1582 None,
1583 Renegotiate,
1584 Idle,
1585 Dead,
1586}
1587
1588enum Pump {
1589 Done,
1590 Control,
1591 Timeout,
1592}
1593
1594fn pump_until(
1599 conn: &Connection,
1600 queue: &mut EventQueue<CaptureState>,
1601 state: &mut CaptureState,
1602 wake_rd: RawFd,
1603 timeout: Option<Duration>,
1604 done: impl Fn(&CaptureState) -> bool,
1605) -> Result<Pump, String> {
1606 let deadline = timeout.map(|t| Instant::now() + t);
1607 loop {
1608 queue.dispatch_pending(state).map_err(|e| format!("dispatch: {e}"))?;
1609 if done(state) {
1610 return Ok(Pump::Done);
1611 }
1612 queue.flush().map_err(|e| format!("flush: {e}"))?;
1613 let remaining = match deadline {
1614 Some(d) => match d.checked_duration_since(Instant::now()) {
1615 Some(r) => Some(r),
1616 None => return Ok(Pump::Timeout),
1617 },
1618 None => None,
1619 };
1620 let Some(guard) = conn.prepare_read() else { continue };
1621 let (wl, wake) = wait_readable2(guard.connection_fd().as_raw_fd(), wake_rd, remaining)?;
1622 if wake {
1623 drop(guard);
1624 drain_pipe(wake_rd);
1625 return Ok(Pump::Control);
1626 }
1627 if wl {
1628 let _ = guard.read();
1629 } else {
1630 drop(guard);
1631 if deadline.is_some_and(|d| Instant::now() >= d) {
1632 return Ok(Pump::Timeout);
1633 }
1634 }
1635 }
1636}
1637
1638fn capture_loop(
1639 display: &str,
1640 index: usize,
1641 expect_name: Option<String>,
1642 gbm_path: Option<std::path::PathBuf>,
1643 from_main: Receiver<ToHost>,
1644 wake_rd: OwnedFd,
1645 frame_tx: Sender<HostFrame>,
1646) -> Result<(), String> {
1647 let path = socket_path(display).ok_or("XDG_RUNTIME_DIR is unset")?;
1648 let stream = UnixStream::connect(&path).map_err(|e| format!("connect {path}: {e}"))?;
1649 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
1650 let mut queue = conn.new_event_queue();
1651 let qh = queue.handle();
1652 let _registry = conn.display().get_registry(&qh, ());
1653 let mut state = CaptureState::default();
1654 bounded_roundtrip(&conn, &mut queue, &mut state)?;
1655 bounded_roundtrip(&conn, &mut queue, &mut state)?;
1657
1658 let by_name = expect_name.as_ref().and_then(|expect| {
1661 state.outputs.iter().find(|(_, n)| n.as_ref() == Some(expect)).cloned()
1662 });
1663 let (output, _name) = match by_name {
1664 Some(o) => o,
1665 None => {
1666 let mut order: Vec<usize> = (0..state.outputs.len()).collect();
1667 order.sort_by_key(|&i| output_order_key(state.outputs[i].1.as_ref(), i));
1668 let oi = *order
1669 .get(index)
1670 .ok_or_else(|| format!("host has no wl_output {index}"))?;
1671 state.outputs[oi].clone()
1672 }
1673 };
1674 let gbm = gbm_path
1675 .as_ref()
1676 .and_then(|p| File::options().read(true).write(true).open(p).ok())
1677 .and_then(|f| GbmDevice::new(f).ok());
1678 let wake = wake_rd.as_raw_fd();
1679
1680 let mut want: Option<Want> = None;
1687 let force = std::env::var("PIXELFLUX_HOST_CAPTURE").unwrap_or_default();
1688 if force != "zwlr" && state.ext_capture.is_some() && state.ext_source_mgr.is_some() {
1689 match capture_loop_ext(
1690 &conn, &mut queue, &mut state, &output, gbm.as_ref(), wake, index, &from_main,
1691 &frame_tx, &mut want,
1692 ) {
1693 ExtOutcome::Finished => return Ok(()),
1694 ExtOutcome::Unavailable(e) => {
1695 eprintln!("[HostCapture] output {index}: ext capture unavailable ({e}); using wlr-screencopy.");
1696 }
1697 }
1698 }
1699
1700 let screencopy = state.screencopy.clone().ok_or("no zwlr_screencopy_manager_v1")?;
1701
1702 let mut slots: Vec<Option<SlotBuffer>> = (0..SLOTS).map(|_| None).collect();
1703 let mut free: Vec<usize> = (0..SLOTS).collect();
1704 let mut generation: u64 = 0;
1705 let mut announced: Option<(i32, i32)> = None;
1706 let mut warned_mismatch = false;
1707 let mut consecutive_failures = 0u32;
1708 let mut slot_zero_copy: Option<bool> = None;
1710 let mut gpu_refused = false;
1711
1712 'main: loop {
1713 loop {
1715 let blocking = want.is_none() || free.is_empty();
1716 let msg = if blocking {
1717 match from_main.recv() {
1718 Ok(m) => m,
1719 Err(_) => return Ok(()),
1720 }
1721 } else {
1722 match from_main.try_recv() {
1723 Ok(m) => m,
1724 Err(TryRecvError::Empty) => break,
1725 Err(TryRecvError::Disconnected) => return Ok(()),
1726 }
1727 };
1728 match msg {
1729 ToHost::Release { generation: g, slot } => {
1730 if g == generation {
1731 free.push(slot);
1732 }
1733 }
1734 ToHost::Idle => want = None,
1735 ToHost::Start { width, height, zero_copy, paint_cursor } => {
1736 let next = Want { size: (width, height), zero_copy, paint_cursor };
1737 if want != Some(next) {
1738 warned_mismatch = false;
1739 }
1740 want = Some(next);
1741 }
1742 }
1743 }
1744 let (want_w, want_h, want_zero_copy, want_paint) = match want {
1745 Some(w) => (w.size.0, w.size.1, w.zero_copy, w.paint_cursor),
1746 None => continue,
1747 };
1748
1749 state.reset_frame();
1752 let frame = screencopy.capture_output(i32::from(want_paint), &output, &qh, ());
1753 loop {
1754 match pump_until(&conn, &mut queue, &mut state, wake, None, |s| {
1755 s.buffer_done || s.failed
1756 })? {
1757 Pump::Done => break,
1758 Pump::Control => match drain_ctl(&from_main, generation, &mut free, &mut want) {
1759 Ctl::None => {}
1760 Ctl::Renegotiate | Ctl::Idle => {
1761 frame.destroy();
1762 continue 'main;
1763 }
1764 Ctl::Dead => {
1765 frame.destroy();
1766 return Ok(());
1767 }
1768 },
1769 Pump::Timeout => {}
1770 }
1771 }
1772 if state.failed {
1773 frame.destroy();
1774 consecutive_failures += 1;
1775 if consecutive_failures == 3 {
1776 eprintln!("[HostCapture] output {index}: repeated screencopy failures; is the output alive?");
1777 }
1778 let _ = pump_until(
1779 &conn,
1780 &mut queue,
1781 &mut state,
1782 wake,
1783 Some(Duration::from_millis(100)),
1784 |_| false,
1785 );
1786 continue;
1787 }
1788
1789 let (fw, fh) = state
1790 .announce_dmabuf
1791 .map(|(_, w, h)| (w, h))
1792 .or(state.announce_shm.map(|(_, w, h, _)| (w, h)))
1793 .ok_or("screencopy announced no buffer type")?;
1794 let mut renegotiate = false;
1795 if announced != Some((fw, fh)) {
1796 announced = Some((fw, fh));
1797 eprintln!(
1798 "[HostCapture] output {index} negotiated: {fw}x{fh} gbm={} dmabuf_global={} dmabuf_announce={:?} shm={:?}",
1799 gbm.is_some(),
1800 state.dmabuf.is_some(),
1801 state.announce_dmabuf,
1802 state.announce_shm,
1803 );
1804 renegotiate = true;
1805 }
1806 if slot_zero_copy.is_some_and(|z| z != want_zero_copy) {
1807 renegotiate = true;
1811 }
1812 if renegotiate {
1813 for s in slots.iter_mut() {
1816 *s = None;
1817 }
1818 free = (0..SLOTS).collect();
1819 slot_zero_copy = None;
1820 gpu_refused = false;
1821 generation += 1;
1822 }
1823 if (fw, fh) != (want_w, want_h) {
1824 frame.destroy();
1827 if !warned_mismatch {
1828 warned_mismatch = true;
1829 eprintln!(
1830 "[HostCapture] output {index}: waiting for host {want_w}x{want_h} (currently {fw}x{fh})."
1831 );
1832 }
1833 let _ = pump_until(
1834 &conn,
1835 &mut queue,
1836 &mut state,
1837 wake,
1838 Some(Duration::from_millis(150)),
1839 |_| false,
1840 );
1841 continue;
1842 }
1843 warned_mismatch = false;
1844
1845 let slot_idx = *free.last().unwrap();
1846 if slots[slot_idx].is_none() {
1847 slot_zero_copy = Some(want_zero_copy);
1850 let mut built: Option<SlotBuffer> = None;
1851 if want_zero_copy && !gpu_refused {
1852 let dmabuf_global = state.dmabuf.clone();
1853 if let (Some(dev), Some(dmabuf_global), Some((fourcc, w, h))) =
1854 (gbm.as_ref(), dmabuf_global.as_ref(), state.announce_dmabuf)
1855 {
1856 match alloc_gpu_slot(
1857 &conn, &mut queue, &mut state, wake, dev, dmabuf_global, fourcc, w, h, &[],
1858 )? {
1859 Some(slot) => built = Some(slot),
1860 None => {
1861 gpu_refused = true;
1862 eprintln!(
1863 "[HostCapture] output {index}: host refused the dmabuf import; capturing via shm."
1864 );
1865 }
1866 }
1867 }
1868 }
1869 slots[slot_idx] = Some(match built {
1870 Some(slot) => slot,
1871 None => {
1872 let (format, w, h, stride) =
1873 state.announce_shm.ok_or("no shm fallback announced")?;
1874 let shm = state.shm.clone().ok_or("host compositor lacks wl_shm")?;
1875 alloc_cpu_slot(&shm, &qh, format, w, h, stride)?
1876 }
1877 });
1878 }
1879 free.pop();
1880
1881 {
1882 let slot = slots[slot_idx].as_ref().unwrap();
1883 let wl = match slot {
1884 SlotBuffer::Gpu { wl, .. } => wl,
1885 SlotBuffer::Cpu { wl, .. } => wl,
1886 };
1887 frame.copy_with_damage(wl);
1888 }
1889 queue.flush().map_err(|e| format!("flush: {e}"))?;
1890 let mut aborted = false;
1891 loop {
1892 match pump_until(&conn, &mut queue, &mut state, wake, None, |s| s.ready || s.failed)? {
1893 Pump::Done => break,
1894 Pump::Control => match drain_ctl(&from_main, generation, &mut free, &mut want) {
1895 Ctl::None => {}
1896 Ctl::Renegotiate | Ctl::Idle => {
1897 aborted = true;
1898 break;
1899 }
1900 Ctl::Dead => {
1901 frame.destroy();
1902 return Ok(());
1903 }
1904 },
1905 Pump::Timeout => {}
1906 }
1907 }
1908 frame.destroy();
1909 if aborted {
1910 free.push(slot_idx);
1911 continue 'main;
1912 }
1913 if state.failed {
1914 free.push(slot_idx);
1915 consecutive_failures += 1;
1916 if consecutive_failures == 3 {
1917 eprintln!("[HostCapture] output {index}: repeated screencopy failures; is the output alive?");
1918 }
1919 let _ = pump_until(
1920 &conn,
1921 &mut queue,
1922 &mut state,
1923 wake,
1924 Some(Duration::from_millis(50)),
1925 |_| false,
1926 );
1927 continue;
1928 }
1929 consecutive_failures = 0;
1930
1931 let damage = std::mem::take(&mut state.damage);
1932 let out = match slots[slot_idx].as_ref().unwrap() {
1933 SlotBuffer::Gpu { dmabuf, .. } => HostFrame {
1934 generation,
1935 slot: slot_idx,
1936 dmabuf: Some(dmabuf.clone()),
1937 cpu: None,
1938 width: fw,
1939 height: fh,
1940 damage,
1941 },
1942 SlotBuffer::Cpu { map, stride, format, .. } => HostFrame {
1943 generation,
1944 slot: slot_idx,
1945 dmabuf: None,
1946 cpu: Some(HostCpuFrame {
1947 map: map.clone(),
1948 stride: *stride as usize,
1949 format: *format,
1950 }),
1951 width: fw,
1952 height: fh,
1953 damage,
1954 },
1955 };
1956 if frame_tx.send(out).is_err() {
1957 return Ok(());
1958 }
1959 }
1960}
1961
1962enum ExtOutcome {
1965 Finished,
1966 Unavailable(String),
1967}
1968
1969#[allow(clippy::too_many_arguments)]
1974fn capture_loop_ext(
1975 conn: &Connection,
1976 queue: &mut EventQueue<CaptureState>,
1977 state: &mut CaptureState,
1978 output: &wl_output::WlOutput,
1979 gbm: Option<&GbmDevice<File>>,
1980 wake: RawFd,
1981 index: usize,
1982 from_main: &Receiver<ToHost>,
1983 frame_tx: &Sender<HostFrame>,
1984 want: &mut Option<Want>,
1985) -> ExtOutcome {
1986 let qh = queue.handle();
1987 let mut slots: Vec<Option<SlotBuffer>> = (0..SLOTS).map(|_| None).collect();
1988 let mut free: Vec<usize> = (0..SLOTS).collect();
1989 let mut generation: u64 = 0;
1990 let mut slot_zero_copy: Option<bool> = None;
1991 let mut gpu_refused = false;
1992 let mut seen_serial: u64 = 0;
1993 let mut warned_mismatch = false;
1994 let mut consecutive_failures = 0u32;
1995
1996 let teardown = |session: &ExtImageCopyCaptureSessionV1, source: &ExtImageCaptureSourceV1| {
1997 session.destroy();
1998 source.destroy();
1999 };
2000
2001 let mut session_paints = false;
2004 let (mut source, mut session) = match open_ext_session(
2005 conn, queue, state, output, wake, index, from_main, session_paints, generation,
2006 &mut free, want,
2007 ) {
2008 Ok(opened) => opened,
2009 Err(outcome) => return outcome,
2010 };
2011
2012 'main: loop {
2013 loop {
2015 let blocking = want.is_none() || free.is_empty();
2016 let msg = if blocking {
2017 match from_main.recv() {
2018 Ok(m) => m,
2019 Err(_) => {
2020 teardown(&session, &source);
2021 return ExtOutcome::Finished;
2022 }
2023 }
2024 } else {
2025 match from_main.try_recv() {
2026 Ok(m) => m,
2027 Err(TryRecvError::Empty) => break,
2028 Err(TryRecvError::Disconnected) => {
2029 teardown(&session, &source);
2030 return ExtOutcome::Finished;
2031 }
2032 }
2033 };
2034 match msg {
2035 ToHost::Release { generation: g, slot } => {
2036 if g == generation {
2037 free.push(slot);
2038 }
2039 }
2040 ToHost::Idle => *want = None,
2041 ToHost::Start { width, height, zero_copy, paint_cursor } => {
2042 let next = Want { size: (width, height), zero_copy, paint_cursor };
2043 if *want != Some(next) {
2044 warned_mismatch = false;
2045 }
2046 *want = Some(next);
2047 }
2048 }
2049 }
2050 let (want_w, want_h, want_zero_copy, want_paint) = match *want {
2051 Some(w) => (w.size.0, w.size.1, w.zero_copy, w.paint_cursor),
2052 None => continue,
2053 };
2054 if state.ext_stopped {
2055 teardown(&session, &source);
2057 return ExtOutcome::Finished;
2058 }
2059 if want_paint != session_paints {
2060 teardown(&session, &source);
2063 (source, session) = match open_ext_session(
2064 conn, queue, state, output, wake, index, from_main, want_paint, generation,
2065 &mut free, want,
2066 ) {
2067 Ok(opened) => opened,
2068 Err(outcome) => return outcome,
2069 };
2070 session_paints = want_paint;
2071 continue;
2072 }
2073
2074 if seen_serial != state.ext_serial || slot_zero_copy.is_some_and(|z| z != want_zero_copy) {
2076 seen_serial = state.ext_serial;
2077 for s in slots.iter_mut() {
2078 *s = None;
2079 }
2080 free = (0..SLOTS).collect();
2081 slot_zero_copy = None;
2082 gpu_refused = false;
2083 generation += 1;
2084 }
2085
2086 let Some((cw, ch)) = state.ext_size else {
2087 teardown(&session, &source);
2088 return ExtOutcome::Unavailable("constraints carried no size".into());
2089 };
2090 if (cw, ch) != (want_w, want_h) {
2091 if !warned_mismatch {
2094 warned_mismatch = true;
2095 eprintln!(
2096 "[HostCapture] output {index}: waiting for host {want_w}x{want_h} (currently {cw}x{ch})."
2097 );
2098 }
2099 let _ = pump_until(conn, queue, state, wake, Some(Duration::from_millis(150)), |s| {
2100 s.ext_serial != seen_serial
2101 });
2102 continue;
2103 }
2104 warned_mismatch = false;
2105
2106 let slot_idx = *free.last().unwrap();
2107 if slots[slot_idx].is_none() {
2108 let dma_choice = [0x3432_5258u32, 0x3432_5241]
2113 .iter()
2114 .find_map(|f| state.ext_dma_formats.iter().find(|(code, _)| code == f))
2115 .cloned();
2116 slot_zero_copy = Some(want_zero_copy);
2117 let mut slot: Option<SlotBuffer> = None;
2118 if want_zero_copy && !gpu_refused {
2119 let dmabuf_global = state.dmabuf.clone();
2120 if let (Some(dev), Some(dmabuf_global), Some((fourcc, modifiers))) =
2121 (gbm, dmabuf_global.as_ref(), dma_choice)
2122 {
2123 match alloc_gpu_slot(
2124 conn, queue, state, wake, dev, dmabuf_global, fourcc, cw, ch, &modifiers,
2125 ) {
2126 Ok(Some(built)) => slot = Some(built),
2127 Ok(None) => {
2128 gpu_refused = true;
2129 eprintln!(
2130 "[HostCapture] output {index}: host refused the dmabuf import; capturing via shm."
2131 );
2132 }
2133 Err(e) => {
2134 teardown(&session, &source);
2135 return ExtOutcome::Unavailable(e);
2136 }
2137 }
2138 }
2139 }
2140 let built = match slot {
2141 Some(slot) => Ok(slot),
2142 None => {
2143 let format = [1u32, 0]
2144 .iter()
2145 .find(|f| state.ext_shm_formats.contains(f))
2146 .copied()
2147 .or_else(|| state.ext_shm_formats.first().copied());
2148 match (state.shm.clone(), format) {
2149 (Some(shm), Some(format)) => {
2150 alloc_cpu_slot(&shm, &qh, format, cw, ch, cw * 4)
2151 }
2152 _ => Err("host offers no usable shm format".into()),
2153 }
2154 }
2155 };
2156 match built {
2157 Ok(slot) => slots[slot_idx] = Some(slot),
2158 Err(e) => {
2159 teardown(&session, &source);
2160 return ExtOutcome::Unavailable(e);
2161 }
2162 }
2163 }
2164 free.pop();
2165
2166 state.reset_frame();
2167 let frame = {
2168 let slot = slots[slot_idx].as_ref().unwrap();
2169 let wl = match slot {
2170 SlotBuffer::Gpu { wl, .. } => wl,
2171 SlotBuffer::Cpu { wl, .. } => wl,
2172 };
2173 let frame = session.create_frame(&qh, ());
2174 frame.attach_buffer(wl);
2175 frame.damage_buffer(0, 0, cw, ch);
2178 frame.capture();
2179 frame
2180 };
2181 if let Err(e) = queue.flush() {
2182 frame.destroy();
2183 teardown(&session, &source);
2184 return ExtOutcome::Unavailable(format!("flush: {e}"));
2185 }
2186
2187 let mut aborted = false;
2188 loop {
2189 match pump_until(conn, queue, state, wake, None, |s| {
2190 s.ready || s.failed || s.ext_stopped || s.ext_serial != seen_serial
2191 }) {
2192 Ok(Pump::Done) => break,
2193 Ok(Pump::Control) => match drain_ctl(from_main, generation, &mut free, want) {
2194 Ctl::None => {}
2195 Ctl::Renegotiate | Ctl::Idle => {
2196 aborted = true;
2197 break;
2198 }
2199 Ctl::Dead => {
2200 frame.destroy();
2201 teardown(&session, &source);
2202 return ExtOutcome::Finished;
2203 }
2204 },
2205 Ok(Pump::Timeout) => {}
2206 Err(e) => {
2207 frame.destroy();
2208 teardown(&session, &source);
2209 return ExtOutcome::Unavailable(e);
2210 }
2211 }
2212 }
2213 frame.destroy();
2214 if aborted || state.ext_serial != seen_serial {
2215 free.push(slot_idx);
2216 continue 'main;
2217 }
2218 if state.ext_stopped {
2219 teardown(&session, &source);
2220 return ExtOutcome::Finished;
2221 }
2222 if state.failed {
2223 free.push(slot_idx);
2224 if state.ext_fail_reason
2225 == Some(ext_image_copy_capture_frame_v1::FailureReason::BufferConstraints)
2226 {
2227 continue;
2230 }
2231 consecutive_failures += 1;
2232 if consecutive_failures == 3 {
2233 eprintln!("[HostCapture] output {index}: repeated capture failures; is the output alive?");
2234 }
2235 let _ = pump_until(conn, queue, state, wake, Some(Duration::from_millis(50)), |_| false);
2236 continue;
2237 }
2238 consecutive_failures = 0;
2239
2240 let damage = std::mem::take(&mut state.damage);
2241 let out = match slots[slot_idx].as_ref().unwrap() {
2242 SlotBuffer::Gpu { dmabuf, .. } => HostFrame {
2243 generation,
2244 slot: slot_idx,
2245 dmabuf: Some(dmabuf.clone()),
2246 cpu: None,
2247 width: cw,
2248 height: ch,
2249 damage,
2250 },
2251 SlotBuffer::Cpu { map, stride, format, .. } => HostFrame {
2252 generation,
2253 slot: slot_idx,
2254 dmabuf: None,
2255 cpu: Some(HostCpuFrame {
2256 map: map.clone(),
2257 stride: *stride as usize,
2258 format: *format,
2259 }),
2260 width: cw,
2261 height: ch,
2262 damage,
2263 },
2264 };
2265 if frame_tx.send(out).is_err() {
2266 teardown(&session, &source);
2267 return ExtOutcome::Finished;
2268 }
2269 }
2270}
2271
2272#[allow(clippy::too_many_arguments)]
2277fn open_ext_session(
2278 conn: &Connection,
2279 queue: &mut EventQueue<CaptureState>,
2280 state: &mut CaptureState,
2281 output: &wl_output::WlOutput,
2282 wake: RawFd,
2283 index: usize,
2284 from_main: &Receiver<ToHost>,
2285 paint_cursor: bool,
2286 generation: u64,
2287 free: &mut Vec<usize>,
2288 want: &mut Option<Want>,
2289) -> Result<(ExtImageCaptureSourceV1, ExtImageCopyCaptureSessionV1), ExtOutcome> {
2290 let qh = queue.handle();
2291 let src_mgr = state.ext_source_mgr.clone().expect("checked by caller");
2292 let mgr = state.ext_capture.clone().expect("checked by caller");
2293 let source = src_mgr.create_source(output, &qh, ());
2294 let options = if paint_cursor {
2295 ext_image_copy_capture_manager_v1::Options::PaintCursors
2296 } else {
2297 ext_image_copy_capture_manager_v1::Options::empty()
2298 };
2299 let session = mgr.create_session(&source, options, &qh, ());
2300 let teardown = |session: &ExtImageCopyCaptureSessionV1, source: &ExtImageCaptureSourceV1| {
2301 session.destroy();
2302 source.destroy();
2303 };
2304 state.ext_stopped = false;
2306 state.ext_pending_size = None;
2307 state.ext_pending_shm.clear();
2308 state.ext_pending_dma.clear();
2309 let before = state.ext_serial;
2310 loop {
2311 match pump_until(conn, queue, state, wake, Some(Duration::from_secs(5)), |s| {
2312 s.ext_serial > before || s.ext_stopped
2313 }) {
2314 Ok(Pump::Done) => break,
2315 Ok(Pump::Control) => match drain_ctl(from_main, generation, free, want) {
2316 Ctl::Dead => {
2317 teardown(&session, &source);
2318 return Err(ExtOutcome::Finished);
2319 }
2320 _ => continue,
2321 },
2322 Ok(Pump::Timeout) => {
2323 teardown(&session, &source);
2324 return Err(ExtOutcome::Unavailable("no buffer constraints within 5s".into()));
2325 }
2326 Err(e) => {
2327 teardown(&session, &source);
2328 return Err(ExtOutcome::Unavailable(e));
2329 }
2330 }
2331 }
2332 if state.ext_stopped {
2333 teardown(&session, &source);
2334 return Err(ExtOutcome::Unavailable("session stopped before constraints".into()));
2335 }
2336 eprintln!(
2337 "[HostCapture] output {index} ext session: {:?} dma_formats={} shm_formats={} cursor={}",
2338 state.ext_size,
2339 state.ext_dma_formats.len(),
2340 state.ext_shm_formats.len(),
2341 if paint_cursor { "painted" } else { "consumer" },
2342 );
2343 Ok((source, session))
2344}
2345
2346fn drain_ctl(
2349 rx: &Receiver<ToHost>,
2350 generation: u64,
2351 free: &mut Vec<usize>,
2352 want: &mut Option<Want>,
2353) -> Ctl {
2354 let mut out = Ctl::None;
2355 loop {
2356 match rx.try_recv() {
2357 Ok(ToHost::Release { generation: g, slot }) => {
2358 if g == generation {
2359 free.push(slot);
2360 }
2361 }
2362 Ok(ToHost::Start { width, height, zero_copy, paint_cursor }) => {
2363 let next = Want { size: (width, height), zero_copy, paint_cursor };
2364 if *want != Some(next) {
2365 *want = Some(next);
2366 out = Ctl::Renegotiate;
2367 }
2368 }
2369 Ok(ToHost::Idle) => {
2370 *want = None;
2371 out = Ctl::Idle;
2372 }
2373 Err(TryRecvError::Empty) => return out,
2374 Err(TryRecvError::Disconnected) => return Ctl::Dead,
2375 }
2376 }
2377}
2378
2379#[cfg(test)]
2380mod tests {
2381 use super::*;
2382
2383 #[test]
2387 fn shm_layout_matches_wl_shm_definitions() {
2388 assert_eq!(shm_src_layout(wl_shm::Format::Bgr888 as u32), (3, true));
2389 assert_eq!(shm_src_layout(wl_shm::Format::Rgb888 as u32), (3, false));
2390 assert_eq!(shm_src_layout(wl_shm::Format::Xbgr8888 as u32), (4, true));
2391 assert_eq!(shm_src_layout(wl_shm::Format::Abgr8888 as u32), (4, true));
2392 assert_eq!(shm_src_layout(wl_shm::Format::Xrgb8888 as u32), (4, false));
2393 assert_eq!(shm_src_layout(wl_shm::Format::Argb8888 as u32), (4, false));
2394 }
2395
2396 #[test]
2401 fn bgr888_known_pixel_swaps_red_and_blue() {
2402 let (bpp, swap) = shm_src_layout(wl_shm::Format::Bgr888 as u32);
2403 let src = [0x11u8, 0x22, 0x33];
2404 let mut dst = [0u8; 4];
2405 convert_shm_row(&src, &mut dst, bpp, swap);
2406 assert_eq!(dst, [0x33, 0x22, 0x11, 0xff]);
2407 }
2408
2409 #[test]
2412 fn rgb888_known_pixel_copies_straight() {
2413 let (bpp, swap) = shm_src_layout(wl_shm::Format::Rgb888 as u32);
2414 let src = [0x33u8, 0x22, 0x11];
2415 let mut dst = [0u8; 4];
2416 convert_shm_row(&src, &mut dst, bpp, swap);
2417 assert_eq!(dst, [0x33, 0x22, 0x11, 0xff]);
2418 }
2419
2420 fn convert(format: wl_shm::Format, src: &[u8]) -> [u8; 4] {
2423 let (bpp, swap) = shm_src_layout(format as u32);
2424 let mut dst = [0u8; 4];
2425 convert_shm_row(src, &mut dst, bpp, swap);
2426 dst
2427 }
2428
2429 #[test]
2430 fn four_byte_formats_convert_to_bgra() {
2431 assert_eq!(convert(wl_shm::Format::Xbgr8888, &[0x11, 0x22, 0x33, 0x44]), [0x33, 0x22, 0x11, 0x44]);
2432 assert_eq!(convert(wl_shm::Format::Xrgb8888, &[0x33, 0x22, 0x11, 0x44]), [0x33, 0x22, 0x11, 0x44]);
2433 }
2434
2435 #[test]
2440 fn layout_ledger_answers_by_epoch() {
2441 let mut ledger = LayoutLedger::default();
2442 let first = ledger.issue();
2443 let second = ledger.issue();
2444 assert_eq!((first, second), (1, 2));
2445 assert_eq!(ledger.outcome(first), None);
2446 assert_eq!(ledger.outcome(second), None);
2447
2448 ledger.decide(first, true);
2449 assert_eq!(ledger.outcome(first), Some(true));
2450 assert_eq!(ledger.outcome(second), None);
2451
2452 let third = ledger.issue();
2455 ledger.decide(third, false);
2456 assert_eq!(ledger.outcome(second), Some(false));
2457 assert_eq!(ledger.outcome(third), Some(false));
2458
2459 ledger.decide(second, true);
2461 assert_eq!(ledger.outcome(third), Some(false));
2462 let fourth = ledger.issue();
2463 assert_eq!(ledger.outcome(fourth), None);
2464 }
2465}