1#![allow(dead_code)]
58
59use std::fs::File;
60use std::sync::Arc;
61use std::thread;
62use std::time::{Duration, Instant};
63
64use gbm::{BufferObject, BufferObjectFlags, Device as RawGbmDevice, Format as GbmFormat};
65use smithay::backend::egl::fence::EGLFence;
66use smithay::desktop::utils::{OutputPresentationFeedback, send_frames_surface_tree};
67use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
68use smithay::reexports::wayland_server::protocol::wl_shm;
69use smithay::wayland::cursor_shape::CursorShapeManagerState;
70use smithay::wayland::image_capture_source::{ImageCaptureSourceState, OutputCaptureSourceState};
71use smithay::wayland::image_copy_capture::{CaptureFailureReason, ImageCopyCaptureState};
72use smithay::wayland::presentation::{PresentationState, Refresh};
73use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;
74use smithay::wayland::selection::wlr_data_control::DataControlState;
75use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
76use smithay::wayland::viewporter::ViewporterState;
77use smithay::{
78 backend::{
79 allocator::{
80 Buffer as _, Fourcc, Modifier,
81 dmabuf::{Dmabuf, DmabufFlags},
82 gbm::GbmDevice,
83 },
84 drm::DrmNode,
85 egl::{EGLContext, EGLDisplay},
86 input::{Axis, AxisSource, KeyState, Keycode},
87 renderer::{
88 Bind, ExportMem, Frame as _, ImportAll, ImportDma, ImportEgl, ImportMem, Renderer as _,
89 damage::OutputDamageTracker,
90 element::{
91 AsRenderElements, Element, Kind, RenderElement, Wrap,
92 memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement,
93 },
94 gles::GlesRenderer,
95 pixman::PixmanRenderer,
96 sync::SyncPoint,
97 utils::Buffer as SurfaceBuffer,
98 },
99 },
100 desktop::{LayerMap, PopupManager, layer_map_for_output},
101 desktop::{Space, space::SpaceRenderElements},
102 input::{
103 SeatState,
104 keyboard::XkbConfig,
105 pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent, RelativeMotionEvent},
106 },
107 output::{Mode as OutputMode, Output, PhysicalProperties, Scale as OutputScale, Subpixel},
108 reexports::{
109 calloop::{
110 EventLoop, Interest, Mode, PostAction,
111 generic::Generic,
112 timer::{TimeoutAction, Timer},
113 },
114 pixman,
115 wayland_server::{Display, DisplayHandle},
116 },
117 utils::{Clock, Logical, Physical, Point, Rectangle, Scale, Size, Transform},
118 wayland::selection::primary_selection::PrimarySelectionState,
119 wayland::shell::wlr_layer::WlrLayerShellState,
120 wayland::xdg_activation::XdgActivationState,
121 wayland::{
122 compositor::{CompositorState, with_states},
123 dmabuf::{DmabufFeedbackBuilder, DmabufState, get_dmabuf},
124 foreign_toplevel_list::ForeignToplevelListState,
125 fractional_scale::FractionalScaleManagerState,
126 output::OutputManagerState,
127 pointer_constraints::PointerConstraintsState,
128 pointer_warp::PointerWarpManager,
129 relative_pointer::RelativePointerManagerState,
130 seat::WaylandFocus,
131 selection::data_device::DataDeviceState,
132 shell::xdg::XdgShellState,
133 shell::xdg::decoration::XdgDecorationState,
134 shm::{ShmState, with_buffer_contents},
135 socket::ListeningSocketSource,
136 },
137};
138
139pub mod encoders;
141pub mod error;
143pub use error::Error;
144pub const VERSION: &str = env!("CARGO_PKG_VERSION");
146pub mod log;
148#[cfg(feature = "python")]
150mod python;
151
152pub mod computer_use;
154pub mod ledger;
156pub mod pace;
158pub mod recorder;
160pub mod recording_sink;
162pub mod report;
164pub mod sink;
166pub mod uinput;
168pub mod wayland;
170pub mod wm;
172
173#[cfg(test)]
174mod cleanup_bench;
175pub mod nvgpufilter;
177pub mod pipeline;
179pub mod pipewire;
181pub mod x11;
183
184pub mod webcam;
185
186pub use encoders::nvenc;
187pub use encoders::software::StripeState;
188
189fn get_process_rss_bytes() -> usize {
190 if let Ok(contents) = std::fs::read_to_string("/proc/self/statm")
191 && let Some(rss_pages) = contents.split_whitespace().nth(1)
192 && let Ok(pages) = rss_pages.parse::<usize>()
193 {
194 return pages * 4096;
195 }
196 0
197}
198
199fn shm_usage_in(dir: &str) -> u64 {
202 use std::os::unix::fs::MetadataExt;
203 let mut total_size = 0;
204 if let Ok(entries) = std::fs::read_dir(dir) {
205 for entry in entries.flatten() {
206 if let Ok(metadata) = entry.metadata() {
207 total_size += metadata.blocks() * 512;
208 }
209 }
210 }
211 total_size
212}
213
214fn get_shm_usage_bytes() -> u64 {
215 shm_usage_in("/dev/shm")
216}
217
218use encoders::overlay::OverlayState;
219use encoders::software::MAX_STRIPE_CAPACITY;
220use encoders::{Codec, FrameEncoder, FrameSource};
221
222use smithay::reexports::wayland_protocols_misc::zwp_virtual_keyboard_v1::server::zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1;
223
224use pace::{FramePace, TickTrigger};
225use wayland::cursor::{Cursor, CursorJob};
226use wayland::frontend::{AppState, ClientState, FocusTarget, next_serial, wayland_time};
227
228smithay::backend::renderer::element::render_elements! {
229 pub CompositionElements<R, E> where R: ImportAll + ImportMem;
230 Space=SpaceRenderElements<R, E>,
231 Window=Wrap<E>,
232 Cursor=MemoryRenderBufferRenderElement<R>,
233 Surface=WaylandSurfaceRenderElement<R>,
234 Scaled=smithay::backend::renderer::element::utils::RescaleRenderElement<WaylandSurfaceRenderElement<R>>,
235 Ghost=smithay::backend::renderer::element::texture::TextureRenderElement<<R as smithay::backend::renderer::RendererSuper>::TextureId>,
236}
237
238fn push_window_elements<R>(
243 space: &smithay::desktop::Space<smithay::desktop::Window>,
244 wm: Option<&wayland::wm::WmState>,
245 renderer: &mut R,
246 elements: &mut Vec<CompositionElements<R, WaylandSurfaceRenderElement<R>>>,
247 window: &smithay::desktop::Window,
248 origin: Point<i32, smithay::utils::Logical>,
249 scale: f64,
250) where
251 R: smithay::backend::renderer::Renderer + ImportAll + ImportMem,
252 R::TextureId: Clone + Send + 'static,
253{
254 use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;
255 use smithay::backend::renderer::element::utils::RescaleRenderElement;
256 let window_loc = space.element_location(window).unwrap_or_default() - origin;
257 let id = wayland::frontend::window_meta(window).map(|m| m.id);
258 let effect = id.and_then(|id| wm.and_then(|wm| wm.effects.get(&id).copied()));
259 if let Some(effect) = effect {
260 if effect.alpha <= 0.0 || effect.rect.w <= 0 || effect.rect.h <= 0 {
261 return;
262 }
263 let geometry = window.geometry();
264 if let Some(id) = id {
265 let client_loc = space.element_location(window).unwrap_or_default();
269 let client = wm
270 .and_then(|wm| wm.rects.get(&id).copied())
271 .unwrap_or(wm::Rect::new(
272 client_loc.x,
273 client_loc.y,
274 geometry.size.w,
275 geometry.size.h,
276 ));
277 elements.extend(
278 wayland::wm::frame_elements_through(
279 wm, renderer, id, client, &effect, origin, scale,
280 )
281 .into_iter()
282 .map(CompositionElements::Cursor),
283 );
284 }
285 let sx = f64::from(effect.rect.w) / f64::from(geometry.size.w.max(1));
286 let sy = f64::from(effect.rect.h) / f64::from(geometry.size.h.max(1));
287 let target: Point<i32, smithay::utils::Logical> =
288 (effect.rect.x - origin.x, effect.rect.y - origin.y).into();
289 let origin_phys = target.to_physical_precise_round(scale);
290 let surface_loc = (target - geometry.loc).to_physical_precise_round(scale);
291 if let Some(surface) = window.wl_surface() {
292 let surface_elements: Vec<WaylandSurfaceRenderElement<R>> =
293 render_elements_from_surface_tree(
294 renderer,
295 &surface,
296 surface_loc,
297 Scale::from(scale),
298 effect.alpha,
299 smithay::backend::renderer::element::Kind::Unspecified,
300 );
301 elements.extend(surface_elements.into_iter().map(|element| {
302 CompositionElements::Scaled(RescaleRenderElement::from_element(
303 element,
304 origin_phys,
305 Scale::from((sx, sy)),
306 ))
307 }));
308 }
309 return;
310 }
311 let surface_loc = window_loc - window.geometry().loc;
315 elements.extend(
316 window
317 .render_elements(
318 renderer,
319 surface_loc.to_physical_precise_round(scale),
320 Scale::from(scale),
321 1.0,
322 )
323 .into_iter()
324 .map(CompositionElements::Space),
325 );
326 if let Some(id) = id {
327 let client_loc = space.element_location(window).unwrap_or_default();
328 elements.extend(
329 wayland::wm::frame_elements(wm, renderer, id, client_loc, origin, scale, 1.0)
330 .into_iter()
331 .map(CompositionElements::Cursor),
332 );
333 }
334}
335
336fn push_ghost_elements<R>(
339 renderer: &mut R,
340 elements: &mut Vec<CompositionElements<R, WaylandSurfaceRenderElement<R>>>,
341 wm: Option<&wayland::wm::WmState>,
342 origin: Point<i32, smithay::utils::Logical>,
343 scale: f64,
344) where
345 R: smithay::backend::renderer::Renderer + ImportAll + ImportMem,
346 R::TextureId: Clone + Send + 'static,
347{
348 let Some(state) = wm else {
349 return;
350 };
351 for id in state.ghosts.keys().copied() {
352 let ghost = wayland::wm::ghost_elements(wm, renderer, id, origin, scale);
353 elements.extend(ghost.frame.into_iter().map(CompositionElements::Cursor));
354 if let Some(element) = ghost.texture {
355 elements.push(CompositionElements::Ghost(element));
356 }
357 if let Some(element) = ghost.memory {
358 elements.push(CompositionElements::Cursor(element));
359 }
360 }
361}
362
363fn push_layer_elements<R>(
368 renderer: &mut R,
369 elements: &mut Vec<CompositionElements<R, WaylandSurfaceRenderElement<R>>>,
370 layer_map: &LayerMap,
371 target_layer: smithay::wayland::shell::wlr_layer::Layer,
372 scale: f64,
373) where
374 R: smithay::backend::renderer::Renderer + ImportAll + ImportMem,
375 R::TextureId: Clone + 'static,
376{
377 for surface in layer_map.layers().rev() {
378 if surface.layer() == target_layer
379 && let Some(geo) = layer_map.layer_geometry(surface)
380 {
381 elements.extend(
382 AsRenderElements::<R>::render_elements::<WaylandSurfaceRenderElement<R>>(
383 surface,
384 renderer,
385 geo.loc.to_physical_precise_round(scale),
386 Scale::from(scale),
387 1.0,
388 )
389 .into_iter()
390 .map(CompositionElements::Surface),
391 );
392 }
393 }
394}
395
396pub(crate) fn alloc_render_target<T: std::os::fd::AsFd>(
404 gbm: &RawGbmDevice<T>,
405 width: u32,
406 height: u32,
407 format: GbmFormat,
408) -> std::io::Result<BufferObject<()>> {
409 let aligned = |bo: &BufferObject<()>| {
410 Into::<u64>::into(bo.modifier()) != 0 || bo.stride().is_multiple_of(64)
411 };
412 let bo = gbm.create_buffer_object::<()>(width, height, format, BufferObjectFlags::RENDERING)?;
413 if aligned(&bo) {
414 return Ok(bo);
415 }
416 match gbm.create_buffer_object::<()>(
417 width,
418 height,
419 format,
420 BufferObjectFlags::RENDERING | BufferObjectFlags::SCANOUT,
421 ) {
422 Ok(scanout) if aligned(&scanout) => Ok(scanout),
423 _ => Ok(bo),
424 }
425}
426
427pub(crate) fn create_dmabuf_from_bo(bo: &BufferObject<()>) -> Dmabuf {
435 let fd = bo.fd().expect("Failed to get FD from GBM BO");
436 let modifier = bo.modifier();
437 let stride = bo.stride();
438 let width = bo.width();
439 let height = bo.height();
440
441 let drm_modifier = Modifier::from(Into::<u64>::into(modifier));
442
443 let mut builder = Dmabuf::builder(
444 (width as i32, height as i32),
445 bo.format(),
446 drm_modifier,
447 DmabufFlags::empty(),
448 );
449
450 builder.add_plane(fd, 0, stride);
451 builder.build().expect("Failed to build Dmabuf from GBM BO")
452}
453
454#[derive(Clone, Debug, PartialEq)]
463pub struct CaptureSettings {
464 pub display_id: u32,
466 pub use_wayland: Option<bool>,
469 pub render_node_path: String,
472 pub encode_node_path: String,
475 pub auto_gpu: String,
478 pub cursor_size: i32,
480 pub width: i32,
481 pub height: i32,
482 pub scale: f64,
483 pub capture_x: i32,
484 pub capture_y: i32,
485 pub target_fps: f64,
486 pub jpeg_quality: i32,
487 pub paint_over_jpeg_quality: i32,
488 pub use_paint_over_quality: bool,
489 pub paint_over_trigger_frames: u32,
491 pub damage_block_threshold: u32,
492 pub damage_block_duration: u32,
493 pub codec: Codec,
494 pub video_crf: i32,
495 pub video_paintover_crf: i32,
496 pub video_paintover_burst_frames: i32,
497 pub video_fullcolor: bool,
498 pub video_bit_depth: i32,
501 pub video_streaming_mode: bool,
502 pub capture_cursor: bool,
503 pub cursor_size_cap: i32,
506 pub watermark_path: String,
507 pub watermark_location_enum: i32,
508 pub encode_node_index: i32,
509 pub use_cpu: bool,
510 pub cpu_encode_threading: bool,
514 pub debug_logging: bool,
515 pub auto_adjust_screen_capture_size: bool,
516 pub recording_socket: String,
517 pub wayland_host_display: String,
520 pub omit_stripe_headers: bool,
523 pub acknowledge_references: bool,
528 pub video_reference_frames: u32,
533 pub video_cbr_mode: bool,
534 pub video_bitrate_kbps: i32,
535 pub video_vbv_multiplier: f64,
542 pub video_link_kbps: i32,
546 pub keyframe_interval_s: f64,
549 pub video_min_qp: i32,
556 pub video_max_qp: i32,
557}
558
559#[derive(Clone, Copy, Debug)]
567pub struct LiveTunables {
568 pub jpeg_quality: i32,
569 pub paint_over_jpeg_quality: i32,
570 pub use_paint_over_quality: bool,
571 pub paint_over_trigger_frames: u32,
572 pub video_crf: i32,
573 pub video_paintover_crf: i32,
574 pub video_paintover_burst_frames: i32,
575 pub video_streaming_mode: bool,
576 pub keyframe_interval_s: f64,
577 pub capture_cursor: bool,
578 pub cursor_size_cap: i32,
579 pub video_reference_frames: u32,
580}
581
582impl LiveTunables {
583 pub fn from_settings(s: &CaptureSettings) -> Self {
585 Self {
586 jpeg_quality: s.jpeg_quality,
587 paint_over_jpeg_quality: s.paint_over_jpeg_quality,
588 use_paint_over_quality: s.use_paint_over_quality,
589 paint_over_trigger_frames: s.paint_over_trigger_frames,
590 video_crf: s.video_crf,
591 video_paintover_crf: s.video_paintover_crf,
592 video_paintover_burst_frames: s.video_paintover_burst_frames,
593 video_streaming_mode: s.video_streaming_mode,
594 keyframe_interval_s: s.keyframe_interval_s,
595 capture_cursor: s.capture_cursor,
596 cursor_size_cap: s.cursor_size_cap,
597 video_reference_frames: s.video_reference_frames,
598 }
599 }
600
601 pub fn apply_to(&self, s: &mut CaptureSettings) {
603 s.jpeg_quality = self.jpeg_quality;
604 s.paint_over_jpeg_quality = self.paint_over_jpeg_quality;
605 s.use_paint_over_quality = self.use_paint_over_quality;
606 s.paint_over_trigger_frames = self.paint_over_trigger_frames;
607 s.video_crf = self.video_crf;
608 s.video_paintover_crf = self.video_paintover_crf;
609 s.video_paintover_burst_frames = self.video_paintover_burst_frames;
610 s.video_streaming_mode = self.video_streaming_mode;
611 s.keyframe_interval_s = self.keyframe_interval_s;
612 s.cursor_size_cap = self.cursor_size_cap;
613 s.capture_cursor = self.capture_cursor;
614 s.video_reference_frames = self.video_reference_frames;
615 }
616}
617
618impl Default for CaptureSettings {
619 fn default() -> Self {
620 Self {
621 display_id: 0,
622 use_wayland: None,
623 render_node_path: String::new(),
624 encode_node_path: String::new(),
625 auto_gpu: String::new(),
626 cursor_size: -1,
627 width: 1024,
628 height: 768,
629 scale: 1.0,
630 capture_x: 0,
631 capture_y: 0,
632 target_fps: 60.0,
633 jpeg_quality: 75,
634 paint_over_jpeg_quality: 95,
635 use_paint_over_quality: true,
636 paint_over_trigger_frames: 15,
637 damage_block_threshold: 10,
638 damage_block_duration: 30,
639 codec: Codec::Jpeg,
640 video_crf: 25,
641 video_paintover_crf: 18,
642 video_paintover_burst_frames: 5,
643 video_fullcolor: false,
644 video_bit_depth: 8,
645 video_streaming_mode: false,
646 capture_cursor: false,
647 cursor_size_cap: 128,
648 watermark_path: String::new(),
649 watermark_location_enum: 0,
650 encode_node_index: -2,
651 use_cpu: false,
652 cpu_encode_threading: true,
653 debug_logging: false,
654 auto_adjust_screen_capture_size: false,
655 recording_socket: String::new(),
656 wayland_host_display: String::new(),
657 omit_stripe_headers: false,
658 acknowledge_references: false,
659 video_reference_frames: 0,
660 video_cbr_mode: false,
661 video_bitrate_kbps: 4000,
662 video_vbv_multiplier: 0.0,
663 video_link_kbps: 0,
664 keyframe_interval_s: 0.0,
665 video_min_qp: 0,
666 video_max_qp: 0,
667 }
668 }
669}
670
671const TRIGGER_FPS: f64 = 60.0;
673
674impl CaptureSettings {
675 pub fn wants_wayland(&self) -> bool {
679 if let Some(explicit) = self.use_wayland {
680 return explicit;
681 }
682 std::env::var("WAYLAND_DISPLAY")
683 .map(|v| !v.is_empty())
684 .unwrap_or(false)
685 }
686
687 pub fn cleans_up(&self) -> bool {
693 self.use_paint_over_quality || (self.video_cbr_mode && self.codec.is_video())
694 }
695
696 pub fn paint_over_trigger(&self) -> u32 {
699 let frames = self.paint_over_trigger_frames.max(1);
700 let scaled = (f64::from(frames) * self.target_fps / TRIGGER_FPS).ceil();
701 (scaled as u32).clamp(1, frames)
702 }
703}
704
705pub type OutputDesc = (u32, i32, i32, i32, i32, f64, bool);
708
709pub type WindowDesc = (u32, String, String, u32, bool);
712
713pub enum ThreadCommand {
720 StartCapture {
724 display_id: u32,
725 callback: Option<Arc<dyn crate::sink::FrameSink>>,
726 settings: CaptureSettings,
727 },
728 StopCapture {
730 display_id: u32,
731 },
732 CreateOutput {
736 id: u32,
737 width: i32,
738 height: i32,
739 x: i32,
740 y: i32,
741 scale: f64,
742 reply: std::sync::mpsc::Sender<bool>,
743 },
744 CreateView {
748 id: u32,
749 owner: u32,
750 x: i32,
751 y: i32,
752 width: i32,
753 height: i32,
754 reply: std::sync::mpsc::Sender<bool>,
755 },
756 ResizeOutput {
759 id: u32,
760 width: i32,
761 height: i32,
762 scale: f64,
763 reply: std::sync::mpsc::Sender<bool>,
764 },
765 DestroyOutput {
768 id: u32,
769 reply: std::sync::mpsc::Sender<bool>,
770 },
771 RepositionOutput {
776 id: u32,
777 x: i32,
778 y: i32,
779 reply: std::sync::mpsc::Sender<bool>,
780 },
781 ListOutputs {
783 reply: std::sync::mpsc::Sender<Vec<OutputDesc>>,
784 },
785 OutputCapacity {
789 reply: std::sync::mpsc::Sender<i64>,
790 },
791 MoveWindowToOutput {
793 window_id: u32,
794 output_id: u32,
795 reply: std::sync::mpsc::Sender<bool>,
796 },
797 ListWindows {
799 reply: std::sync::mpsc::Sender<Vec<WindowDesc>>,
800 },
801 DesktopView {
803 reply: std::sync::mpsc::Sender<Option<wm::DesktopView>>,
804 },
805 WindowRequest {
808 id: wm::WindowId,
809 request: wm::Request,
810 reply: std::sync::mpsc::Sender<bool>,
811 },
812 SetCursorCallback(Option<Arc<dyn crate::sink::CursorSink>>),
813 SetClipboardCallback(Arc<dyn crate::sink::ClipboardSink>),
814 InstallWindowPolicy(Box<dyn crate::wm::WindowPolicy>),
816 StartXwayland {
818 notify: Option<std::sync::mpsc::Sender<u32>>,
819 },
820 SetClipboard {
823 entries: Vec<(String, Vec<u8>)>,
824 },
825 KeyboardKey {
826 scancode: u32,
827 state: u32,
828 },
829 KeyboardKeys {
833 events: Vec<(u32, u32)>,
834 },
835 SetKeymapString(String),
839 SetXkbLayout {
843 rules: String,
844 model: String,
845 layout: String,
846 variant: String,
847 options: String,
848 reply: std::sync::mpsc::Sender<bool>,
849 },
850 BindKeysyms {
855 keysyms: Vec<u32>,
856 reply: std::sync::mpsc::Sender<Vec<(u32, u32)>>,
857 },
858 SetKeymapOverlay {
864 binds: Vec<(u32, u32)>,
865 },
866 GetKeyboardState {
869 reply: std::sync::mpsc::Sender<(Vec<u32>, u32)>,
870 },
871 GetXkbKeymap {
874 reply: std::sync::mpsc::Sender<String>,
875 },
876 Barrier {
881 reply: std::sync::mpsc::Sender<()>,
882 },
883 PointerMotion {
886 x: f64,
887 y: f64,
888 motion: u64,
889 },
890 PointerRelativeMotion {
891 dx: f64,
892 dy: f64,
893 motion: u64,
894 },
895 PointerButton {
899 btn: u32,
900 state: u32,
901 },
902 PointerAxis {
903 x: f64,
904 y: f64,
905 },
906 PointerScroll {
909 dx: f64,
910 dy: f64,
911 },
912 PointerScrollEnd,
915 UpdateCursorConfig {
916 render_on_framebuffer: bool,
917 },
918 SetCursorSize {
922 size: i32,
923 reply: std::sync::mpsc::Sender<bool>,
924 },
925 RequestIdr {
928 display_id: u32,
929 },
930 InvalidateReference {
934 display_id: u32,
935 frame_id: u16,
936 withheld: bool,
937 },
938 AcknowledgeReference {
941 display_id: u32,
942 frame_id: u16,
943 held: bool,
944 },
945 UpdateRate {
948 display_id: u32,
949 bitrate_kbps: Option<i32>,
950 vbv_multiplier: Option<f64>,
951 fps: Option<f64>,
952 },
953 UpdateLinkRate {
956 display_id: u32,
957 link_kbps: i32,
958 },
959 UpdateTunables {
962 display_id: u32,
963 tunables: LiveTunables,
964 },
965 CuScreenshot {
967 display_id: u32,
968 resp: std::sync::mpsc::Sender<Result<computer_use::ScreenshotFrame, String>>,
969 canceled: Arc<AtomicBool>,
970 deadline: Instant,
971 },
972 CuCursorPosition {
973 resp: std::sync::mpsc::Sender<(f64, f64)>,
974 },
975 CuGetInfo {
976 display_id: u32,
977 resp: std::sync::mpsc::Sender<(i32, i32, f64)>,
978 },
979}
980
981pub(crate) fn memfd_create(name: &std::ffi::CStr, flags: libc::c_uint) -> libc::c_int {
984 unsafe { libc::syscall(libc::SYS_memfd_create, name.as_ptr(), flags) as libc::c_int }
985}
986
987pub(crate) fn get_gpu_driver(card_index: i32) -> String {
1006 if !std::path::Path::new(&format!("/dev/dri/renderD{}", 128 + card_index)).exists() {
1007 return String::new();
1008 }
1009 let path = format!("/sys/class/drm/renderD{}/device/driver", 128 + card_index);
1010 match std::fs::read_link(&path) {
1011 Ok(link_path) => link_path.to_string_lossy().to_lowercase(),
1012 Err(_) => String::new(),
1013 }
1014}
1015
1016pub(crate) fn driver_selects_nvenc(encode_driver: &str) -> bool {
1021 encode_driver.is_empty() || encode_driver.contains("nvidia")
1022}
1023
1024struct CardIdentity {
1029 driver: String,
1030 pci_vendor: Option<u32>,
1031 compatibles: Vec<String>,
1032}
1033
1034fn read_card_identity(device: &std::path::Path) -> CardIdentity {
1041 let mut id = CardIdentity {
1042 driver: String::new(),
1043 pci_vendor: None,
1044 compatibles: Vec::new(),
1045 };
1046 if let Ok(uevent) = std::fs::read_to_string(device.join("uevent")) {
1047 for line in uevent.lines() {
1048 if let Some(v) = line.strip_prefix("DRIVER=") {
1049 id.driver = v.trim().to_lowercase();
1050 } else if let Some(v) = line.strip_prefix("PCI_ID=") {
1051 id.pci_vendor = v
1052 .split(':')
1053 .next()
1054 .and_then(|h| u32::from_str_radix(h, 16).ok());
1055 } else if line.starts_with("OF_COMPATIBLE_")
1056 && !line.starts_with("OF_COMPATIBLE_N")
1057 && let Some(v) = line.split_once('=').map(|x| x.1)
1058 {
1059 id.compatibles.push(v.trim().to_lowercase());
1060 }
1061 }
1062 }
1063 if id.pci_vendor.is_none() {
1064 id.pci_vendor = std::fs::read_to_string(device.join("vendor"))
1065 .ok()
1066 .and_then(|v| u32::from_str_radix(v.trim().trim_start_matches("0x"), 16).ok());
1067 }
1068 if id.compatibles.is_empty()
1069 && let Ok(modalias) = std::fs::read_to_string(device.join("modalias"))
1070 {
1071 let modalias = modalias.trim();
1072 if let Some(rest) = modalias.strip_prefix("of:") {
1073 id.compatibles
1074 .extend(rest.split('C').skip(1).map(|c| c.to_lowercase()));
1075 }
1076 }
1077 if id.driver.is_empty() {
1078 id.driver = std::fs::read_link(device.join("driver"))
1079 .map(|p| {
1080 p.file_name()
1081 .map(|n| n.to_string_lossy().to_lowercase())
1082 .unwrap_or_default()
1083 })
1084 .unwrap_or_default();
1085 }
1086 id
1087}
1088
1089const VENDOR_PCI_IDS: &[(&str, &[u32])] = &[
1093 ("nvidia", &[0x10de, 0x12d2]),
1094 ("amd", &[0x1002, 0x1022]),
1095 ("ati", &[0x1002]),
1096 ("intel", &[0x8086, 0x8087]),
1097 ("arm", &[0x13b5]),
1098 ("qualcomm", &[0x5143, 0x17cb]),
1099 ("broadcom", &[0x14e4]),
1100 ("apple", &[0x106b]),
1101 ("mediatek", &[0x14c3]),
1102 ("samsung", &[0x144d]),
1103 ("vmware", &[0x15ad]),
1104 ("microsoft", &[0x1414]),
1105 ("virtio", &[0x1af4]),
1106];
1107
1108const OF_PREFIX_ALIASES: &[(&str, &str)] = &[
1111 ("mali", "arm"),
1112 ("qualcomm", "qcom"),
1113 ("adreno", "qcom"),
1114 ("broadcom", "brcm"),
1115 ("videocore", "brcm"),
1116 ("imagination", "img"),
1117 ("powervr", "img"),
1118];
1119
1120fn card_matches_token(token: &str, id: &CardIdentity) -> bool {
1126 if !id.driver.is_empty() && token == id.driver {
1127 return true;
1128 }
1129 if let Some(vid) = id.pci_vendor {
1130 if u32::from_str_radix(token.trim_start_matches("0x"), 16) == Ok(vid) {
1131 return true;
1132 }
1133 if let Some((_, ids)) = VENDOR_PCI_IDS.iter().find(|(n, _)| *n == token)
1134 && ids.contains(&vid)
1135 {
1136 return true;
1137 }
1138 }
1139 if !id.compatibles.is_empty() {
1140 let prefix = OF_PREFIX_ALIASES
1141 .iter()
1142 .find(|(n, _)| *n == token)
1143 .map(|(_, p)| *p)
1144 .unwrap_or(token);
1145 let want = format!("{prefix},");
1146 if id.compatibles.iter().any(|c| c.starts_with(&want)) {
1147 return true;
1148 }
1149 }
1150 false
1151}
1152
1153fn parse_auto_gpu(value: &str) -> Option<Option<String>> {
1159 let value = value.to_lowercase();
1160 match value.as_str() {
1161 "" | "false" | "0" | "off" | "no" => None,
1162 "true" | "1" | "on" | "yes" => Some(None),
1163 token => Some(Some(token.to_string())),
1164 }
1165}
1166
1167fn auto_render_node(auto_gpu: &str) -> Option<String> {
1170 auto_select_render_node(parse_auto_gpu(auto_gpu)?.as_deref())
1171}
1172
1173fn render_node_index(path: &str) -> Option<i32> {
1175 Some(path.strip_prefix("/dev/dri/renderD")?.parse::<i32>().ok()? - 128)
1176}
1177
1178fn first_render_node_index() -> Option<i32> {
1181 render_node_index(&auto_select_render_node(None)?)
1182}
1183
1184fn auto_select_render_node(token: Option<&str>) -> Option<String> {
1201 let mut cards: Vec<(u32, std::path::PathBuf)> = std::fs::read_dir("/sys/class/drm")
1202 .into_iter()
1203 .flatten()
1204 .flatten()
1205 .filter_map(|e| {
1206 let num = e
1207 .file_name()
1208 .into_string()
1209 .ok()?
1210 .strip_prefix("card")?
1211 .parse::<u32>()
1212 .ok()?;
1213 Some((num, e.path()))
1214 })
1215 .collect();
1216 cards.sort_by_key(|(n, _)| *n);
1217 for (_, path) in &cards {
1218 if let Some(t) = token
1219 && !card_matches_token(t, &read_card_identity(&path.join("device")))
1220 {
1221 continue;
1222 }
1223 if let Ok(drm_entries) = std::fs::read_dir(path.join("device/drm")) {
1224 for de in drm_entries.flatten() {
1225 let name = de.file_name().into_string().unwrap_or_default();
1226 if name.starts_with("renderD") {
1227 let dev = format!("/dev/dri/{}", name);
1228 if std::path::Path::new(&dev).exists() {
1229 return Some(dev);
1230 }
1231 }
1232 }
1233 }
1234 }
1235 if token.is_some() {
1236 return None;
1237 }
1238 let mut nodes: Vec<String> = std::fs::read_dir("/dev/dri")
1239 .ok()?
1240 .flatten()
1241 .filter_map(|e| e.file_name().into_string().ok())
1242 .filter(|n| n.starts_with("renderD"))
1243 .collect();
1244 nodes.sort();
1245 nodes.first().map(|n| format!("/dev/dri/{}", n))
1246}
1247
1248pub struct WlFrame<B = Vec<u8>> {
1253 id: usize,
1255 buf: B,
1256 source: Option<Dmabuf>,
1260 scanout: bool,
1264 shm: Option<SurfaceBuffer>,
1268 frame_id: u16,
1269 damage: Vec<Rectangle<i32, Physical>>,
1270 is_animated: bool,
1271 captured_ns: i64,
1273}
1274
1275struct WlPoolInner<B> {
1277 free: Vec<(usize, B)>,
1278 slot: Option<WlFrame<B>>,
1279}
1280
1281pub struct WlFramePool<B = Vec<u8>> {
1293 inner: Mutex<WlPoolInner<B>>,
1294 cv: Condvar,
1295 stop: AtomicBool,
1296}
1297
1298impl WlFramePool<Vec<u8>> {
1299 fn new(n: usize, buf_len: usize) -> Self {
1303 Self::of((0..n).map(|_| vec![0u8; buf_len]).collect())
1304 }
1305}
1306
1307impl<B> WlFramePool<B> {
1308 fn of(buffers: Vec<B>) -> Self {
1310 Self {
1311 inner: Mutex::new(WlPoolInner {
1312 free: buffers.into_iter().enumerate().collect(),
1313 slot: None,
1314 }),
1315 cv: Condvar::new(),
1316 stop: AtomicBool::new(false),
1317 }
1318 }
1319
1320 fn try_begin(&self) -> Option<(usize, B)> {
1325 let mut g = self.inner.lock().unwrap();
1326 if g.slot.is_some() {
1327 return None;
1328 }
1329 g.free.pop()
1330 }
1331
1332 fn publish(&self, frame: WlFrame<B>) {
1334 let mut g = self.inner.lock().unwrap();
1335 debug_assert!(g.slot.is_none());
1336 g.slot = Some(frame);
1337 drop(g);
1338 self.cv.notify_all();
1339 }
1340
1341 fn cancel(&self, id: usize, buf: B) {
1343 self.inner.lock().unwrap().free.push((id, buf));
1344 }
1345
1346 fn take(&self) -> Option<WlFrame<B>> {
1349 let mut g = self.inner.lock().unwrap();
1350 loop {
1351 if let Some(f) = g.slot.take() {
1352 return Some(f);
1353 }
1354 if self.stop.load(Ordering::Acquire) {
1355 return None;
1356 }
1357 let (gg, _) = self.cv.wait_timeout(g, WL_POOL_WAKE_QUANTUM).unwrap();
1358 g = gg;
1359 }
1360 }
1361
1362 fn recycle(&self, id: usize, buf: B) {
1365 self.inner.lock().unwrap().free.push((id, buf));
1366 }
1367
1368 fn shutdown(&self) {
1371 let g = self.inner.lock().unwrap();
1372 self.stop.store(true, Ordering::Release);
1373 drop(g);
1374 self.cv.notify_all();
1375 }
1376}
1377
1378pub struct WlEncodeControls {
1386 rate_dirty: AtomicBool,
1387 bitrate_kbps: AtomicI32,
1388 vbv_mult_milli: AtomicI32,
1389 fps_bits: AtomicU64,
1390 link_kbps: AtomicI32,
1392 force_idr: AtomicBool,
1393 scanout_refused: AtomicBool,
1396 scanout_planes: AtomicU32,
1400 reference_reports: Mutex<Vec<encoders::reference::ReferenceReport>>,
1401 tunables_dirty: AtomicBool,
1404 tunables: Mutex<Option<LiveTunables>>,
1405}
1406
1407impl WlEncodeControls {
1408 fn new() -> Self {
1409 Self {
1410 rate_dirty: AtomicBool::new(false),
1411 bitrate_kbps: AtomicI32::new(0),
1412 vbv_mult_milli: AtomicI32::new(0),
1413 fps_bits: AtomicU64::new(0),
1414 link_kbps: AtomicI32::new(0),
1415 force_idr: AtomicBool::new(false),
1416 scanout_refused: AtomicBool::new(false),
1417 scanout_planes: AtomicU32::new(1),
1418 reference_reports: Mutex::new(Vec::new()),
1419 tunables_dirty: AtomicBool::new(false),
1420 tunables: Mutex::new(None),
1421 }
1422 }
1423}
1424
1425const WL_POOL_SURFACES: usize = 2;
1428const WL_POOL_WAKE_QUANTUM: Duration = Duration::from_millis(20);
1430const WL_CONTENT_HOLD: Duration = Duration::from_millis(500);
1437
1438const MAX_CAPTURE_DIM: i32 = 16384;
1442const MAX_FPS: f64 = 1000.0;
1443const IDLE_FRAME_INTERVAL: Duration = Duration::from_millis(250);
1447const DEFAULT_FPS: f64 = 60.0;
1448
1449fn frame_period(fps: f64) -> Duration {
1453 let fps = if fps.is_finite() && fps > 0.0 {
1454 fps.min(MAX_FPS)
1455 } else {
1456 DEFAULT_FPS
1457 };
1458 Duration::from_secs_f64(1.0 / fps)
1459}
1460
1461fn capture_period(cap: &wayland::frontend::WlCapture) -> Duration {
1463 frame_period(cap.settings.target_fps)
1464}
1465const MAX_SCALE: f64 = 8.0;
1466pub(crate) const SCROLL_V120_PER_UNIT: f64 = 12.0;
1470
1471pub struct WlEncodeStats {
1475 frames: AtomicU32,
1476 stripes: AtomicU32,
1477 n_stripes: AtomicU32,
1478 desc: Mutex<String>,
1479 recycled: AtomicU64,
1482}
1483
1484impl WlEncodeStats {
1485 fn new() -> Self {
1486 Self {
1487 frames: AtomicU32::new(0),
1488 stripes: AtomicU32::new(0),
1489 n_stripes: AtomicU32::new(1),
1490 desc: Mutex::new(String::new()),
1491 recycled: AtomicU64::new(0),
1492 }
1493 }
1494}
1495
1496struct WlEncodeConfig {
1500 settings: CaptureSettings,
1501 display_id: u32,
1503 use_gpu: bool,
1505 try_gpu: bool,
1507 prior: Option<FrameEncoder>,
1510 predecessor: Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
1515 zero_copy: Option<ZeroCopySession>,
1517 host: bool,
1519 recording_sink: Option<std::sync::Weak<crate::recording_sink::RecordingSink>>,
1522 deliver_tx: std::sync::mpsc::SyncSender<Vec<EncodedStripe>>,
1523 controls: Arc<WlEncodeControls>,
1524 stats: Arc<WlEncodeStats>,
1525 report: Arc<report::StreamReport>,
1526}
1527
1528struct ZeroCopySession {
1532 encoder: Arc<Mutex<Option<FrameEncoder>>>,
1533 egl_display: usize,
1536}
1537
1538trait WlPayload: Send + 'static {
1541 #[allow(clippy::too_many_arguments)]
1544 fn encode(
1545 &self,
1546 source: Option<&Dmabuf>,
1547 encoder: &mut FrameEncoder,
1548 width: i32,
1549 rgba: bool,
1550 frame_number: u64,
1551 qp: u32,
1552 force_idr: bool,
1553 ) -> Result<Vec<u8>, String>;
1554
1555 fn rows(&self) -> Option<&[u8]>;
1558}
1559
1560impl WlPayload for Vec<u8> {
1561 fn encode(
1562 &self,
1563 _source: Option<&Dmabuf>,
1564 encoder: &mut FrameEncoder,
1565 width: i32,
1566 rgba: bool,
1567 frame_number: u64,
1568 qp: u32,
1569 force_idr: bool,
1570 ) -> Result<Vec<u8>, String> {
1571 encoder.encode_host(
1575 self,
1576 (width.max(0) as usize) * 4,
1577 rgba,
1578 frame_number,
1579 qp,
1580 force_idr,
1581 )
1582 }
1583
1584 fn rows(&self) -> Option<&[u8]> {
1585 Some(self)
1586 }
1587}
1588
1589impl WlPayload for Dmabuf {
1590 fn encode(
1591 &self,
1592 source: Option<&Dmabuf>,
1593 encoder: &mut FrameEncoder,
1594 _width: i32,
1595 _rgba: bool,
1596 frame_number: u64,
1597 qp: u32,
1598 force_idr: bool,
1599 ) -> Result<Vec<u8>, String> {
1600 encoder.encode_dmabuf(source.unwrap_or(self), frame_number, qp, force_idr)
1601 }
1602
1603 fn rows(&self) -> Option<&[u8]> {
1604 None
1605 }
1606}
1607
1608fn build_readback_encoders(
1615 settings: &mut CaptureSettings,
1616 try_gpu: bool,
1617 rgba: bool,
1618 prior: Option<FrameEncoder>,
1619) -> Option<FrameEncoder> {
1620 let mut attempt = settings.clone();
1621 if !try_gpu {
1622 attempt.use_cpu = true;
1623 }
1624 let encoder =
1625 encoders::select_frame_encoder(&mut attempt, FrameSource::Host { rgba }, prior, "Wayland");
1626 settings.codec = attempt.codec;
1627 encoder
1628}
1629
1630fn wayland_encode_loop<B: WlPayload>(
1664 pool: &WlFramePool<B>,
1665 cfg: WlEncodeConfig,
1666) -> Option<FrameEncoder> {
1667 crate::boost_thread_priority(-10);
1668 let _report = report::enter(&cfg.report);
1669 let mut settings = cfg.settings;
1670 let zero_copy = cfg.zero_copy;
1671 let session = match zero_copy.as_ref() {
1672 Some(z) => z.encoder.clone(),
1673 None => {
1674 let inherited = cfg.predecessor.and_then(|h| h.join().ok().flatten());
1675 let video_encoder = build_readback_encoders(
1676 &mut settings,
1677 cfg.try_gpu,
1678 cfg.use_gpu,
1679 cfg.prior.or(inherited),
1680 );
1681 if cfg.try_gpu && video_encoder.is_none() {
1682 println!(
1683 "[Wayland] Readback encode: no hardware encoder opened; encoding in software ({}).",
1684 encoders::software_library(settings.codec)
1685 );
1686 }
1687 let n_stripes = encoders::software::stripe_count(settings.height, settings.codec);
1688 cfg.stats
1689 .n_stripes
1690 .store(n_stripes as u32, Ordering::Relaxed);
1691 *cfg.stats.desc.lock().unwrap() =
1692 encoder_desc(&settings, video_encoder.as_ref(), false);
1693 set_wayland_active_codec(cfg.display_id, Some(settings.codec));
1694 log_stream_settings("Wayland", &settings, n_stripes, video_encoder.as_ref());
1695 Arc::new(Mutex::new(video_encoder))
1696 }
1697 };
1698
1699 let width = settings.width;
1700 let height = settings.height;
1701 let mut stripes: Vec<StripeState> = Vec::with_capacity(MAX_STRIPE_CAPACITY);
1702 let mut hw_state = StripeState::default();
1703 let mut hw_error_streak: u32 = 0;
1705 let mut hw_rebuilt = false;
1706
1707 while let Some(mut f) = pool.take() {
1708 let mut guard = session.lock().unwrap();
1709 let video_encoder = &mut *guard;
1710 if zero_copy.is_some() && video_encoder.is_none() {
1713 drop(guard);
1714 pool.recycle(f.id, f.buf);
1715 break;
1716 }
1717 let planes = video_encoder.as_ref().map_or(1, |e| e.dmabuf_planes());
1718 cfg.controls
1719 .scanout_planes
1720 .store(planes as u32, Ordering::Relaxed);
1721 if cfg.controls.tunables_dirty.swap(false, Ordering::Acquire)
1722 && let Some(t) = cfg.controls.tunables.lock().unwrap().take()
1723 {
1724 t.apply_to(&mut settings);
1725 }
1726 settings.video_link_kbps = cfg.controls.link_kbps.load(Ordering::Relaxed);
1727 if cfg.controls.rate_dirty.swap(false, Ordering::Acquire) {
1728 settings.video_bitrate_kbps = cfg.controls.bitrate_kbps.load(Ordering::Relaxed);
1729 settings.video_vbv_multiplier =
1730 f64::from(cfg.controls.vbv_mult_milli.load(Ordering::Relaxed)) / 1000.0;
1731 let fps = f64::from_bits(cfg.controls.fps_bits.load(Ordering::Relaxed));
1732 if fps > 0.0 {
1733 settings.target_fps = fps;
1734 }
1735 if let Some(enc) = video_encoder.as_mut()
1736 && let Err(e) = enc.reconfigure_rate(&settings)
1737 {
1738 eprintln!("[Wayland] rate reconfigure failed: {e}");
1742 hw_error_streak = HW_ERROR_RECOVERY_THRESHOLD - 1;
1743 }
1744 }
1745
1746 let recording_sink = cfg.recording_sink.as_ref().and_then(|w| w.upgrade());
1750 for report in std::mem::take(&mut *cfg.controls.reference_reports.lock().unwrap()) {
1751 hw_state.take_report(report);
1752 stripes.iter_mut().for_each(|st| st.take_report(report));
1753 let taken = match (video_encoder.as_mut(), report) {
1754 (Some(encoder), report) => encoder.take_report(report),
1755 (
1756 None,
1757 encoders::reference::ReferenceReport::Lost(frame_id)
1758 | encoders::reference::ReferenceReport::Withheld(frame_id),
1759 ) => encoders::software::invalidate_reference(&mut stripes, frame_id),
1760 (None, encoders::reference::ReferenceReport::Held(frame_id)) => {
1761 encoders::software::acknowledge_reference(&mut stripes, frame_id, true);
1762 true
1763 }
1764 (None, encoders::reference::ReferenceReport::Sent(frame_id)) => {
1765 encoders::software::acknowledge_reference(&mut stripes, frame_id, false);
1766 true
1767 }
1768 };
1769 if !taken {
1770 cfg.controls.force_idr.store(true, Ordering::Relaxed);
1771 }
1772 }
1773 let requested_idr = cfg.controls.force_idr.swap(false, Ordering::Relaxed)
1774 || recording_sink
1775 .as_ref()
1776 .is_some_and(|s| s.should_force_idr());
1777
1778 let mut out: Vec<EncodedStripe> = Vec::new();
1779 let mut given_up = false;
1780 if let Some(encoder) = video_encoder.as_mut() {
1781 if encoder.take_lost_cleanup() {
1782 hw_state.lost_frame();
1783 }
1784 let damage = crate::pipeline::Damage::of_rects(&f.damage, width, height);
1785 let mut decision = crate::pipeline::decide_hw_fullframe(
1786 &mut hw_state,
1787 &settings,
1788 f.frame_id,
1789 damage,
1790 f.is_animated,
1791 requested_idr,
1792 crate::pipeline::EncoderQuality::of(encoder),
1793 );
1794 let bound = encoder.rate_bound(settings.video_cbr_mode);
1797 crate::pipeline::plan_refinement(
1798 &mut hw_state,
1799 &settings,
1800 &mut decision,
1801 damage,
1802 Some(&f.damage),
1803 encoder.refines(),
1804 encoder.last_quality(),
1805 bound,
1806 );
1807 if !decision.send && !encoder.holds_frame() {
1808 encoder.idle();
1809 }
1810 if decision.send || encoder.holds_frame() {
1811 let force_idr = decision.force_idr;
1812 decision.prepare(encoder);
1813 let encode_start_ns = wayland::host::now_ns();
1814 let outcome = if decision.send {
1815 if f.scanout
1818 && let Some(client) = f.source.as_ref()
1819 {
1820 wayland::host::wait_gpu_done(client);
1821 }
1822 match f.shm.as_ref() {
1823 Some(shm) => encode_shm_rows(shm, width, height, |rows| {
1824 encoder.encode_client_host(
1825 rows,
1826 (width.max(0) as usize) * 4,
1827 cfg.use_gpu,
1828 u64::from(f.frame_id),
1829 decision.target_qp,
1830 force_idr,
1831 )
1832 }),
1833 None => f.buf.encode(
1834 f.source.as_ref(),
1835 encoder,
1836 width,
1837 cfg.use_gpu,
1838 u64::from(f.frame_id),
1839 decision.target_qp,
1840 force_idr,
1841 ),
1842 }
1843 } else {
1844 encoder.push_held(u64::from(f.frame_id))
1845 };
1846 crate::pipeline::refinement_coded(
1847 &mut hw_state,
1848 &settings,
1849 &decision,
1850 crate::pipeline::EncoderQuality::of(encoder),
1851 outcome.as_ref().map_or(0, Vec::len),
1852 bound,
1853 encoder.coded_capped(),
1854 );
1855 match outcome {
1856 Ok(data) => {
1857 hw_error_streak = 0;
1858 hw_rebuilt = false;
1859 if !data.is_empty() {
1860 let encode_end_ns = wayland::host::now_ns();
1861 if cfg.host && wayland::host::trace() {
1862 eprintln!(
1863 "[HostTrace] output {} frame {} encoded +{:.2}ms {}B",
1864 cfg.display_id,
1865 f.frame_id,
1866 (encode_end_ns - f.captured_ns) as f64 / 1e6,
1867 data.len()
1868 );
1869 }
1870 for (data, id, reference) in encoder.delivered_units(data, f.frame_id) {
1871 out.push(EncodedStripe {
1872 data: Arc::new(data),
1873 codec: settings.codec,
1874 stripe_y_start: 0,
1875 stripe_height: height,
1876 frame_id: i32::from(id),
1877 timing: FrameTiming {
1878 capture_ns: f.captured_ns,
1879 encode_start_ns,
1880 encode_end_ns,
1881 },
1882 reference,
1883 });
1884 }
1885 }
1886 }
1887 Err(e) if e == SHM_GONE => {
1888 crate::log::debug!("[Wayland] Output {}: {e}.", cfg.display_id);
1891 cfg.controls.force_idr.store(true, Ordering::Relaxed);
1892 }
1893 Err(e) if f.scanout => {
1894 println!(
1898 "[Wayland] Output {}: the encoder does not take the client's buffers in place ({e}); compositing.",
1899 cfg.display_id
1900 );
1901 cfg.controls.scanout_refused.store(true, Ordering::Relaxed);
1902 cfg.controls.force_idr.store(true, Ordering::Relaxed);
1903 }
1904 Err(e) => {
1905 if hw_error_streak.is_multiple_of(HW_ERROR_RECOVERY_THRESHOLD) {
1908 eprintln!("[Wayland] HW encode error: {e}");
1909 }
1910 hw_error_streak = hw_error_streak.saturating_add(1);
1911 if hw_error_streak >= HW_ERROR_RECOVERY_THRESHOLD {
1912 hw_error_streak = 0;
1921 let try_gpu = !hw_rebuilt;
1922 let zero_copy_label = if zero_copy.is_some() {
1923 "zero-copy"
1924 } else {
1925 "readback"
1926 };
1927 if try_gpu {
1928 eprintln!(
1929 "[Wayland] rebuilding {zero_copy_label} HW encoder after repeated encode errors."
1930 );
1931 } else if zero_copy.is_some() {
1932 eprintln!(
1933 "[Wayland] zero-copy HW encoder unrecoverable; demoting to readback encode."
1934 );
1935 } else {
1936 eprintln!(
1937 "[Wayland] readback HW encoder unrecoverable; demoting to software encoding ({}).",
1938 encoders::software_library(settings.codec)
1939 );
1940 }
1941 drop(video_encoder.take());
1945 *video_encoder = match zero_copy.as_ref() {
1946 Some(z) if try_gpu => {
1947 let mut attempt = settings.clone();
1948 encoders::select_frame_encoder(
1949 &mut attempt,
1950 FrameSource::Dmabuf {
1951 egl_display: z.egl_display as *const std::ffi::c_void,
1952 },
1953 None,
1954 "Wayland",
1955 )
1956 }
1957 Some(_) => None,
1958 None => build_readback_encoders(
1959 &mut settings,
1960 try_gpu,
1961 cfg.use_gpu,
1962 None,
1963 ),
1964 };
1965 if zero_copy.is_some() && video_encoder.is_none() {
1966 given_up = true;
1967 } else {
1968 if !try_gpu {
1969 report::encoder_reason(
1970 "the hardware encoder failed repeatedly and was given up",
1971 );
1972 }
1973 hw_rebuilt = try_gpu && video_encoder.is_some();
1974 cfg.controls.force_idr.store(true, Ordering::Relaxed);
1975 let n = encoders::software::stripe_count(
1976 settings.height,
1977 settings.codec,
1978 );
1979 cfg.stats.n_stripes.store(n as u32, Ordering::Relaxed);
1980 *cfg.stats.desc.lock().unwrap() = encoder_desc(
1981 &settings,
1982 video_encoder.as_ref(),
1983 zero_copy.is_some(),
1984 );
1985 set_wayland_active_codec(cfg.display_id, Some(settings.codec));
1986 log_stream_settings(
1987 "Wayland",
1988 &settings,
1989 n,
1990 video_encoder.as_ref(),
1991 );
1992 }
1993 }
1994 }
1995 }
1996 }
1997 } else if let Some(rows) = f.buf.rows() {
1998 let mut damage = std::mem::take(&mut f.damage);
1999 if f.is_animated {
2000 damage.push(Rectangle::new((0, 0).into(), (width, height).into()));
2001 }
2002 let encode_start_ns = wayland::host::now_ns();
2003 let frame_id = f.frame_id;
2004 let mut run = |rows: &[u8]| {
2005 encoders::software::encode_cpu(
2006 &mut stripes,
2007 rows,
2008 width,
2009 height,
2010 &damage,
2011 &settings,
2012 frame_id,
2013 cfg.use_gpu,
2014 false,
2015 requested_idr,
2016 )
2017 };
2018 out = match f.shm.as_ref() {
2019 Some(shm) => match encode_shm_rows(shm, width, height, |rows| Ok(run(rows))) {
2020 Ok(out) => out,
2021 Err(e) => {
2022 crate::log::debug!("[Wayland] Output {}: {e}.", cfg.display_id);
2023 cfg.controls.force_idr.store(true, Ordering::Relaxed);
2024 Vec::new()
2025 }
2026 },
2027 None => run(rows),
2028 };
2029 FrameTiming::stamp(&mut out, f.captured_ns, encode_start_ns);
2030 }
2031 drop(guard);
2032
2033 let WlFrame { id, buf, .. } = f;
2034 pool.recycle(id, buf);
2035 cfg.stats.recycled.fetch_add(1, Ordering::Release);
2036 if requested_idr && out.is_empty() {
2039 cfg.controls.force_idr.store(true, Ordering::Relaxed);
2040 }
2041 if !out.is_empty() {
2042 cfg.stats.frames.fetch_add(1, Ordering::Relaxed);
2043 cfg.stats
2044 .stripes
2045 .fetch_add(out.len() as u32, Ordering::Relaxed);
2046 if let Some(ref socket) = recording_sink {
2047 socket.write_frame(&out, settings.width, settings.height);
2048 }
2049 crate::recorder::wayland_tap(cfg.display_id, &out);
2050 let _ = cfg.deliver_tx.send(out);
2051 }
2052 if given_up {
2053 break;
2054 }
2055 }
2056 let mut video_encoder = if zero_copy.is_some() {
2057 None
2058 } else {
2059 session.lock().unwrap().take()
2060 };
2061 if let Some(FrameEncoder::Nvenc(enc)) = video_encoder.as_mut() {
2065 enc.release_pinned_hosts();
2066 }
2067 crate::log::debug!(
2068 "[Wayland] Encode thread exiting (hw={}, stripes={}).",
2069 video_encoder.is_some(),
2070 stripes.len()
2071 );
2072 video_encoder
2073}
2074
2075fn encoder_desc(
2078 settings: &CaptureSettings,
2079 video_encoder: Option<&FrameEncoder>,
2080 zero_copy: bool,
2081) -> String {
2082 if !settings.codec.is_video() {
2083 return format!("JPEG Q:{}", settings.jpeg_quality);
2084 }
2085 let copy_mode = if zero_copy { "ZeroCopy" } else { "Readback" };
2086 let backend = match video_encoder {
2087 Some(enc) if enc.is_hardware() => format!("{} ({})", enc.backend_name(), copy_mode),
2088 Some(enc) => format!("CPU {}", enc.backend_name()),
2089 None => format!("CPU {}", encoders::software_library(Codec::H264)),
2090 };
2091 let is_444 = encoders::session_fullcolor(video_encoder, settings);
2092 let cs_str = if is_444 { "CS_IN:I444" } else { "CS_IN:I420" };
2093 let range_str = if encoders::session_full_range(video_encoder, settings) {
2094 "FR"
2095 } else {
2096 "LR"
2097 };
2098 format!(
2099 "{} ({}) {} {} {}",
2100 settings.codec.display(),
2101 backend,
2102 cs_str,
2103 range_str,
2104 encoders::rate_desc(
2105 settings,
2106 video_encoder.and_then(FrameEncoder::fixed_rate_control),
2107 encoders::session_capped(video_encoder, settings)
2108 )
2109 )
2110}
2111
2112pub(crate) fn log_stream_settings(
2116 tag: &str,
2117 settings: &CaptureSettings,
2118 n_stripes: usize,
2119 video_encoder: Option<&FrameEncoder>,
2120) {
2121 let backend = video_encoder.map(|enc| (enc.backend_name(), enc.is_hardware()));
2122 let fixed_rate = video_encoder.and_then(FrameEncoder::fixed_rate_control);
2123 let holds = video_encoder.map_or_else(
2124 || encoders::software::stripes_hold_quantizer(settings),
2125 FrameEncoder::holds_quantizer,
2126 );
2127 let fullcolor = encoders::session_fullcolor(video_encoder, settings);
2128 let full_range = encoders::session_full_range(video_encoder, settings);
2129 let capped = encoders::session_capped(video_encoder, settings);
2130 log_stream_settings_of(
2131 tag, settings, n_stripes, backend, fixed_rate, holds, capped, fullcolor, full_range,
2132 );
2133 report::bit_depth(encoders::session_bit_depth(video_encoder, settings));
2134 report::capped(capped);
2135}
2136
2137#[allow(clippy::too_many_arguments)]
2143pub(crate) fn log_stream_settings_of(
2144 tag: &str,
2145 settings: &CaptureSettings,
2146 n_stripes: usize,
2147 backend: Option<(&str, bool)>,
2148 fixed_rate: Option<&str>,
2149 holds: bool,
2150 capped: bool,
2151 fullcolor: bool,
2152 full_range: bool,
2153) {
2154 report::stream(settings, n_stripes, backend, fullcolor, full_range);
2155 println!(
2156 "{}",
2157 stream_settings_line(
2158 tag, settings, n_stripes, backend, fixed_rate, holds, capped, fullcolor, full_range
2159 )
2160 );
2161}
2162
2163#[allow(clippy::too_many_arguments)]
2166fn stream_settings_line(
2167 tag: &str,
2168 settings: &CaptureSettings,
2169 n_stripes: usize,
2170 backend: Option<(&str, bool)>,
2171 fixed_rate: Option<&str>,
2172 holds: bool,
2173 capped: bool,
2174 fullcolor: bool,
2175 full_range: bool,
2176) -> String {
2177 let mut log_msg = format!(
2178 "[{tag}] Stream settings active -> Res: {}x{} | FPS: {:.1} | Stripes: {}",
2179 settings.width, settings.height, settings.target_fps, n_stripes
2180 );
2181
2182 if !settings.codec.is_video() {
2183 log_msg.push_str(&format!(
2184 " | Mode: JPEG | Quality: {}",
2185 settings.jpeg_quality
2186 ));
2187 if let Some(paint_over) = encoders::paint_over_desc(settings, fixed_rate, holds) {
2188 log_msg.push_str(&format!(" | {paint_over}"));
2189 }
2190 } else {
2191 let encoder_type = match backend {
2192 Some((name, _)) => name,
2193 None => encoders::software_library(Codec::H264),
2194 };
2195 log_msg.push_str(&format!(
2196 " | Mode: {} ({})",
2197 settings.codec.display(),
2198 encoder_type
2199 ));
2200
2201 if settings.video_streaming_mode {
2202 log_msg.push_str(" Streaming");
2203 }
2204
2205 log_msg.push_str(&format!(
2206 " | {}",
2207 encoders::rate_desc(settings, fixed_rate, capped)
2208 ));
2209 if let Some(paint_over) = encoders::paint_over_desc(settings, fixed_rate, holds) {
2210 log_msg.push_str(&format!(" | {paint_over}"));
2211 }
2212
2213 log_msg.push_str(&format!(
2214 " | Colorspace: {}",
2215 encoders::colorspace_desc(fullcolor, full_range)
2216 ));
2217 }
2218
2219 log_msg.push_str(&format!(
2220 " | Damage Thresh: {}f | Damage Dur: {}f",
2221 settings.damage_block_threshold, settings.damage_block_duration
2222 ));
2223 log_msg
2224}
2225
2226#[cfg(test)]
2227mod stream_settings_line_tests {
2228 use super::*;
2229
2230 #[test]
2234 fn the_line_states_what_the_session_applies() {
2235 let crf = CaptureSettings {
2236 codec: Codec::H264,
2237 video_crf: 25,
2238 video_bitrate_kbps: 8000,
2239 use_paint_over_quality: false,
2240 ..Default::default()
2241 };
2242 let line = |s: &CaptureSettings, fixed: Option<&str>| {
2243 stream_settings_line(
2244 "X11",
2245 s,
2246 1,
2247 Some(("NVENC", true)),
2248 fixed,
2249 fixed.is_none(),
2250 false,
2251 false,
2252 false,
2253 )
2254 };
2255 let nvenc = line(&crf, None);
2256 assert!(
2257 nvenc.contains("| CRF: 25 |") && !nvenc.contains("VBV") && !nvenc.contains("8000"),
2258 "{nvenc}"
2259 );
2260 let v4l2 = line(&crf, Some("VBR"));
2261 assert!(
2262 v4l2.contains("| VBR 8000 |") && !v4l2.contains("CRF"),
2263 "{v4l2}"
2264 );
2265 assert!(line(&crf, Some("CBR")).contains("| CBR 8000 |"));
2266 assert!(
2267 line(
2268 &CaptureSettings {
2269 video_cbr_mode: true,
2270 ..crf
2271 },
2272 None
2273 )
2274 .contains("| CBR 8000 |")
2275 );
2276 let jpeg = CaptureSettings {
2277 codec: Codec::Jpeg,
2278 jpeg_quality: 75,
2279 paint_over_jpeg_quality: 95,
2280 ..Default::default()
2281 };
2282 assert!(line(&jpeg, None).contains("| PaintOver Q: 95 "));
2283 assert!(
2284 !line(
2285 &CaptureSettings {
2286 paint_over_jpeg_quality: 60,
2287 ..jpeg
2288 },
2289 None
2290 )
2291 .contains("PaintOver")
2292 );
2293 }
2294}
2295
2296fn teardown_capture(
2306 cap: &mut wayland::frontend::WlCapture,
2307) -> (
2308 Option<std::thread::JoinHandle<()>>,
2309 Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
2310) {
2311 if let Some(p) = cap.encode_pool.take() {
2312 p.shutdown();
2313 }
2314 if let Some(p) = cap.gpu_pool.take() {
2315 p.shutdown();
2316 }
2317 if let Some(flag) = cap.deliver_discard.take() {
2318 flag.store(true, Ordering::Relaxed);
2319 }
2320 let encode_join = cap.encode_join.take();
2321 if let Some(tx) = cap.deliver_tx.take() {
2322 drop(tx);
2323 }
2324 let join = cap.deliver_join.take();
2325 release_scanout_inflight(cap, true);
2326 cap.recording_sink = None;
2330 (join, encode_join)
2331}
2332
2333fn end_zero_copy(cap: &mut wayland::frontend::WlCapture) -> bool {
2336 let Some(pool) = cap.gpu_pool.take() else {
2337 return false;
2338 };
2339 pool.shutdown();
2340 cap.zero_copy = None;
2341 cap.gpu_ring.clear();
2342 cap.host_still = None;
2343 release_scanout_inflight(cap, true);
2344 true
2345}
2346
2347fn retain_host_frame(
2350 host: &wayland::host::HostSession,
2351 cap: Option<&mut wayland::frontend::WlCapture>,
2352 display_id: u32,
2353 frame: wayland::host::HostFrame,
2354) {
2355 match cap {
2356 Some(cap) if cap.gpu_pool.is_some() => {
2357 if let Some(old) = host.swap_retained(display_id, frame) {
2358 cap.host_inflight.push((cap.host_retained_use, old));
2359 }
2360 cap.host_retained_use = 0;
2361 release_host_inflight(Some(host), cap, display_id, false);
2362 }
2363 _ => host.retain_frame(display_id, frame),
2364 }
2365}
2366
2367fn detach_retained_gpu(
2371 renderer: Option<&mut GlesRenderer>,
2372 gbm: Option<&RawGbmDevice<std::fs::File>>,
2373 host: &wayland::host::HostSession,
2374 cap: &mut wayland::frontend::WlCapture,
2375 display_id: u32,
2376 size: (i32, i32),
2377) {
2378 if !host.is_portal() || cap.gpu_pool.is_none() {
2379 return;
2380 }
2381 if cap.host_inflight.iter().any(|(_, f)| f.is_detached()) {
2383 return;
2384 }
2385 let Some(src) = host.with_retained(display_id, |r| {
2386 r.filter(|f| !f.is_detached() && (f.width, f.height) == size)
2387 .and_then(|f| f.dmabuf.clone())
2388 }) else {
2389 return;
2390 };
2391 let (Some(renderer), Some(gbm)) = (renderer, gbm) else {
2392 return;
2393 };
2394 let fits = |bo: &BufferObject<()>| (bo.width() as i32, bo.height() as i32) == size;
2395 if !cap.host_still.as_ref().is_some_and(|(bo, _)| fits(bo)) {
2396 cap.host_still = alloc_render_target(
2397 gbm,
2398 size.0.max(0) as u32,
2399 size.1.max(0) as u32,
2400 GbmFormat::Argb8888,
2401 )
2402 .ok()
2403 .map(|bo| {
2404 let dmabuf = create_dmabuf_from_bo(&bo);
2405 (bo, dmabuf)
2406 });
2407 }
2408 let Some((_, still)) = cap.host_still.as_mut() else {
2409 return;
2410 };
2411 match compose_host_frame(renderer, None, &src, still, size) {
2412 Ok(sync) if wait_render_fence(&sync, display_id) => {}
2413 Ok(_) => return,
2414 Err(e) => {
2415 static WARNED: std::sync::atomic::AtomicBool =
2416 std::sync::atomic::AtomicBool::new(false);
2417 if !WARNED.swap(true, Ordering::Relaxed) {
2418 eprintln!("[HostCapture] a still frame could not be copied out of the stream: {e}");
2419 }
2420 return;
2421 }
2422 }
2423 let still = still.clone();
2424 if let Some(copy) = host.with_retained(display_id, |r| r.map(|f| f.detached_into(still))) {
2425 retain_host_frame(host, Some(cap), display_id, copy);
2426 }
2427}
2428
2429fn cancel_slots(
2431 node: &wayland::frontend::OutputNode,
2432 pool_slot: &mut Option<(usize, Vec<u8>)>,
2433 gpu_slot: &mut Option<(usize, Dmabuf)>,
2434) {
2435 let Some(cap) = node.capture.as_ref() else {
2436 return;
2437 };
2438 if let Some((id, buf)) = pool_slot.take()
2439 && let Some(pool) = cap.encode_pool.as_ref()
2440 {
2441 pool.cancel(id, buf);
2442 }
2443 if let Some((id, dmabuf)) = gpu_slot.take()
2444 && let Some(pool) = cap.gpu_pool.as_ref()
2445 {
2446 pool.cancel(id, dmabuf);
2447 }
2448}
2449
2450fn release_host_inflight(
2454 host: Option<&wayland::host::HostSession>,
2455 cap: &mut wayland::frontend::WlCapture,
2456 display_id: u32,
2457 all: bool,
2458) {
2459 if cap.host_inflight.is_empty() {
2460 return;
2461 }
2462 let done = cap.encode_stats.recycled.load(Ordering::Acquire);
2463 let (free, held): (Vec<_>, Vec<_>) = std::mem::take(&mut cap.host_inflight)
2464 .into_iter()
2465 .partition(|(last_use, _)| all || *last_use <= done);
2466 cap.host_inflight = held;
2467 if let Some(host) = host {
2468 for (_, frame) in free {
2469 host.release_frame(display_id, frame);
2470 }
2471 }
2472}
2473
2474fn release_scanout_inflight(cap: &mut wayland::frontend::WlCapture, all: bool) {
2478 if cap.scanout_inflight.is_empty() {
2479 return;
2480 }
2481 let done = cap.encode_stats.recycled.load(Ordering::Acquire);
2482 cap.scanout_inflight
2483 .retain(|(last_use, _)| !all && *last_use > done);
2484}
2485
2486fn note_direct(
2490 cap: &mut wayland::frontend::WlCapture,
2491 display_id: u32,
2492 direct: &Result<String, &'static str>,
2493) {
2494 let now = direct.as_ref().map(|_| ()).map_err(|why| *why);
2495 if cap.direct == Some(now) {
2496 return;
2497 }
2498 cap.direct = Some(now);
2499 match direct {
2500 Ok(what) => crate::log::debug!(
2501 "[Wayland] Output {display_id}: encoding the covering client's {what} as it stands."
2502 ),
2503 Err(why) => crate::log::debug!("[Wayland] Output {display_id}: compositing: {why}."),
2504 }
2505}
2506
2507struct SoftwareCursor<'a> {
2511 theme: &'a Cursor,
2512 icon: &'a Option<CursorImageStatus>,
2513 pos: Point<f64, Logical>,
2514 time: Duration,
2515}
2516
2517struct FrameScene<'a> {
2519 space: &'a Space<smithay::desktop::Window>,
2520 wm: Option<&'a wayland::wm::WmState>,
2521 cursor: Option<SoftwareCursor<'a>>,
2523 drag_icon: Option<(
2526 &'a smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
2527 Point<f64, Logical>,
2528 )>,
2529 overlay: &'a OverlayState,
2530 output: &'a Output,
2531 origin: Point<i32, Logical>,
2533 size: (i32, i32),
2535 scale: f64,
2536}
2537
2538fn composition_elements<R>(
2543 renderer: &mut R,
2544 scene: &FrameScene<'_>,
2545) -> Vec<CompositionElements<R, WaylandSurfaceRenderElement<R>>>
2546where
2547 R: smithay::backend::renderer::Renderer + ImportAll + ImportMem,
2548 R::TextureId: Clone + Send + 'static,
2549{
2550 use smithay::wayland::shell::wlr_layer::Layer;
2551 let mut elements = Vec::new();
2552 let scale = scene.scale;
2553 if let Some(cursor) = scene.cursor.as_ref() {
2554 let theme_scale = scale.round() as u32;
2555 match cursor.icon {
2556 Some(CursorImageStatus::Named(icon)) => {
2557 let name = wayland::frontend::cursor_icon_to_str(icon);
2558 if let Some(image) = cursor
2559 .theme
2560 .get_image_by_name(name, theme_scale, cursor.time)
2561 && let Some(elem) = scene
2562 .overlay
2563 .get_cursor_element(renderer, image, cursor.pos, scale)
2564 {
2565 elements.push(CompositionElements::Cursor(elem));
2566 }
2567 }
2568 Some(CursorImageStatus::Surface(surface)) => {
2569 let hot = cursor_surface_hotspot(surface).to_f64();
2570 let phys_pos = (cursor.pos - hot).to_physical(Scale::from(scale));
2571 let elem_result = with_states(surface, |states| {
2572 WaylandSurfaceRenderElement::from_surface(
2573 renderer,
2574 surface,
2575 states,
2576 phys_pos,
2577 1.0,
2578 Kind::Cursor,
2579 )
2580 });
2581 if let Ok(Some(cursor_elem)) = elem_result {
2582 elements.push(CompositionElements::Surface(cursor_elem));
2583 }
2584 }
2585 Some(CursorImageStatus::Hidden) => {}
2586 None => {
2587 let image = cursor.theme.get_image(theme_scale, cursor.time);
2588 if let Some(elem) = scene
2589 .overlay
2590 .get_cursor_element(renderer, image, cursor.pos, scale)
2591 {
2592 elements.push(CompositionElements::Cursor(elem));
2593 }
2594 }
2595 }
2596 }
2597 if let Some((surface, at)) = scene.drag_icon {
2598 let at = at.to_physical(Scale::from(scale)).to_i32_round();
2599 elements.extend(
2600 smithay::backend::renderer::element::surface::render_elements_from_surface_tree(
2601 renderer,
2602 surface,
2603 at,
2604 scale,
2605 1.0,
2606 Kind::Unspecified,
2607 )
2608 .into_iter()
2609 .map(CompositionElements::Surface),
2610 );
2611 }
2612 if let Some(elem) = scene.overlay.get_watermark_element(renderer) {
2613 elements.push(CompositionElements::Cursor(elem));
2614 }
2615 let logical: Size<i32, Logical> = (
2616 (f64::from(scene.size.0) / scale).round() as i32,
2617 (f64::from(scene.size.1) / scale).round() as i32,
2618 )
2619 .into();
2620 elements.extend(
2621 wayland::wm::overlay_elements(scene.wm, renderer, scene.origin, logical, scale)
2622 .into_iter()
2623 .map(CompositionElements::Cursor),
2624 );
2625 let fullscreen_on_top = scene
2633 .space
2634 .elements_for_output(scene.output)
2635 .rfind(|window| !wayland::frontend::is_override_redirect(window))
2636 .and_then(|window| wayland::frontend::window_meta(window).map(|m| m.id))
2637 .and_then(|id| scene.wm.and_then(|wm| wm.states.get(&id)))
2638 .is_some_and(|states| states.fullscreen);
2639 {
2640 let layer_map = layer_map_for_output(scene.output);
2641 push_layer_elements(renderer, &mut elements, &layer_map, Layer::Overlay, scale);
2642 }
2643 let lifted = |window: &&smithay::desktop::Window| {
2644 !fullscreen_on_top && wayland::frontend::is_override_redirect(window)
2645 };
2646 for window in scene
2647 .space
2648 .elements_for_output(scene.output)
2649 .rev()
2650 .filter(lifted)
2651 {
2652 push_window_elements(
2653 scene.space,
2654 scene.wm,
2655 renderer,
2656 &mut elements,
2657 window,
2658 scene.origin,
2659 scale,
2660 );
2661 }
2662 if !fullscreen_on_top {
2663 let layer_map = layer_map_for_output(scene.output);
2664 push_layer_elements(renderer, &mut elements, &layer_map, Layer::Top, scale);
2665 }
2666 push_ghost_elements(renderer, &mut elements, scene.wm, scene.origin, scale);
2667 for window in scene
2668 .space
2669 .elements_for_output(scene.output)
2670 .rev()
2671 .filter(|window| !lifted(window))
2672 {
2673 push_window_elements(
2674 scene.space,
2675 scene.wm,
2676 renderer,
2677 &mut elements,
2678 window,
2679 scene.origin,
2680 scale,
2681 );
2682 }
2683 {
2684 let layer_map = layer_map_for_output(scene.output);
2685 if fullscreen_on_top {
2686 push_layer_elements(renderer, &mut elements, &layer_map, Layer::Top, scale);
2687 }
2688 push_layer_elements(renderer, &mut elements, &layer_map, Layer::Bottom, scale);
2689 push_layer_elements(
2690 renderer,
2691 &mut elements,
2692 &layer_map,
2693 Layer::Background,
2694 scale,
2695 );
2696 }
2697 elements
2698}
2699
2700fn scanout_candidate<R>(
2711 elements: &[CompositionElements<R, WaylandSurfaceRenderElement<R>>],
2712 size: (i32, i32),
2713 scale: f64,
2714) -> Result<&WaylandSurfaceRenderElement<R>, &'static str>
2715where
2716 R: smithay::backend::renderer::Renderer + ImportAll + ImportMem,
2717 R::TextureId: Clone + 'static,
2718{
2719 let full = Rectangle::<i32, Physical>::from_size(size.into());
2720 let scale = Scale::from(scale);
2721 let Some(top) = elements.iter().find(|e| e.geometry(scale).overlaps(full)) else {
2722 return Err("nothing is on the output");
2723 };
2724 let surface = match top {
2725 CompositionElements::Space(SpaceRenderElements::Surface(s))
2726 | CompositionElements::Surface(s)
2727 if s.kind() != Kind::Cursor =>
2728 {
2729 s
2730 }
2731 _ => return Err("something is drawn over the window"),
2732 };
2733 if surface.alpha() < 1.0 {
2734 return Err("the window is translucent");
2735 }
2736 if surface.geometry(scale) != full {
2737 return Err("the window does not cover the output");
2738 }
2739 if surface.transform() != Transform::Normal {
2740 return Err("the buffer is transformed");
2741 }
2742 let buffer_full = Rectangle::<f64, smithay::utils::Buffer>::from_size(
2743 (f64::from(size.0), f64::from(size.1)).into(),
2744 );
2745 if surface.src() != buffer_full {
2746 return Err("the buffer is scaled or cropped");
2747 }
2748 if !full
2749 .subtract_rects(surface.opaque_regions(scale).iter().copied())
2750 .is_empty()
2751 {
2752 return Err("the window is not opaque throughout");
2753 }
2754 Ok(surface)
2755}
2756
2757fn scanout_dmabuf<R: smithay::backend::renderer::Renderer + ImportAll>(
2765 surface: &WaylandSurfaceRenderElement<R>,
2766 size: (i32, i32),
2767 planes: usize,
2768) -> Result<(Dmabuf, SurfaceBuffer, String), &'static str> {
2769 let buffer = surface.buffer();
2770 let dmabuf = get_dmabuf(buffer).map_err(|_| "the window's buffer is in system memory")?;
2771 if (dmabuf.width() as i32, dmabuf.height() as i32) != size {
2772 return Err("the buffer is not the size of the capture");
2773 }
2774 if dmabuf.num_planes() > planes {
2775 return Err("the buffer has more planes than the encoder imports");
2776 }
2777 let format = dmabuf.format();
2778 if !matches!(
2779 format.code,
2780 Fourcc::Argb8888 | Fourcc::Xrgb8888 | Fourcc::Abgr8888 | Fourcc::Xbgr8888
2781 ) {
2782 return Err("the buffer's format is not one the encoder imports");
2783 }
2784 let what = format!(
2785 "{:?} dmabuf (modifier {:#x})",
2786 format.code,
2787 u64::from(format.modifier)
2788 );
2789 Ok((dmabuf.clone(), buffer.clone(), what))
2790}
2791
2792fn scanout_shm<R: smithay::backend::renderer::Renderer + ImportAll>(
2796 surface: &WaylandSurfaceRenderElement<R>,
2797 size: (i32, i32),
2798) -> Result<(SurfaceBuffer, String), &'static str> {
2799 use smithay::reexports::wayland_server::protocol::wl_shm;
2800 let buffer = surface.buffer();
2801 let layout = with_buffer_contents(buffer, |_, len, data| {
2802 let fits = matches!(
2803 data.format,
2804 wl_shm::Format::Argb8888 | wl_shm::Format::Xrgb8888
2805 ) && (data.width, data.height) == size
2806 && data.stride == size.0.saturating_mul(4)
2807 && data.offset >= 0
2808 && (data.offset as usize).saturating_add(
2809 (data.height.max(0) as usize).saturating_mul(data.stride.max(0) as usize),
2810 ) <= len;
2811 fits.then(|| format!("{:?} shm buffer", data.format))
2812 })
2813 .map_err(|_| "the window's buffer is on the GPU")?;
2814 layout
2815 .map(|what| (buffer.clone(), what))
2816 .ok_or("the buffer's format or layout is not the pool's")
2817}
2818
2819const SHM_GONE: &str = "the client's shm buffer is gone";
2821
2822fn encode_shm_rows<T>(
2827 buffer: &SurfaceBuffer,
2828 width: i32,
2829 height: i32,
2830 encode: impl FnOnce(&[u8]) -> Result<T, String>,
2831) -> Result<T, String> {
2832 with_buffer_contents(buffer, |ptr, len, data| {
2833 let stride = (width.max(0) as usize) * 4;
2834 let offset = data.offset.max(0) as usize;
2835 let needed = (height.max(0) as usize) * stride;
2836 if data.stride.max(0) as usize != stride || offset.saturating_add(needed) > len {
2837 return Err(SHM_GONE.to_string());
2838 }
2839 let rows = unsafe { std::slice::from_raw_parts(ptr.add(offset), needed) };
2843 encode(rows)
2844 })
2845 .map_err(|_| SHM_GONE.to_string())?
2846}
2847
2848fn reap_dead_host(state: &mut AppState) {
2853 if !state.host.as_ref().is_some_and(|h| !h.alive()) {
2854 return;
2855 }
2856 eprintln!("[HostCapture] host compositor connection lost; captures stop until restarted.");
2857 let ids: Vec<u32> = state
2858 .output_nodes
2859 .iter()
2860 .filter(|n| n.capture.is_some())
2861 .map(|n| n.id)
2862 .collect();
2863 for id in ids {
2864 stop_capture_on_display(state, id);
2865 set_wayland_capture_err(
2869 id,
2870 Some("host compositor connection lost; capture stopped".to_string()),
2871 );
2872 }
2873 state
2874 .pending_screenshots
2875 .fail_all("Host compositor connection lost");
2876 state.host = None;
2877 *crate::wayland::pwcapture::HOST_POINTER.lock().unwrap() = None;
2878 state.host_mode_refusals.clear();
2879 let pending: Vec<_> = state.host_layout_pending.drain().collect();
2882 for (id, p) in pending {
2883 answer_geometry_waiters(state, id, p.geometry_waiters);
2884 }
2885}
2886
2887fn stop_capture_on_display(state: &mut AppState, display_id: u32) {
2890 let Some(idx) = state.node_idx_for_id(display_id) else {
2891 return;
2892 };
2893 if let Some(mut cap) = state.output_nodes[idx].capture.take() {
2894 crate::log::debug!("[Wayland] Capture loop stopped (display {display_id}).");
2895 let (join, encode_join) = teardown_capture(&mut cap);
2896 release_host_inflight(state.host.as_ref(), &mut cap, display_id, true);
2897 state.deliver_reaper.extend(join);
2898 state.encode_reaper.extend(encode_join);
2899 }
2900 wayland_alive().lock().unwrap().remove(&display_id);
2901 set_wayland_capture_err(display_id, None);
2902 set_wayland_active_codec(display_id, None);
2903 report::wayland_reports()
2904 .lock()
2905 .unwrap()
2906 .remove(&display_id);
2907 if let Some(p) = state.host_layout_pending.remove(&display_id) {
2908 answer_geometry_waiters(state, display_id, p.geometry_waiters);
2909 }
2910}
2911
2912fn realized_geometry(state: &AppState, display_id: u32) -> (i32, i32, f64) {
2915 state
2916 .node_idx_for_id(display_id)
2917 .map(|idx| {
2918 let node = &state.output_nodes[idx];
2919 match node.capture.as_ref() {
2920 Some(c) => (c.settings.width, c.settings.height, c.settings.scale),
2921 None => node
2922 .output
2923 .current_mode()
2924 .map(|m| {
2925 (
2926 m.size.w,
2927 m.size.h,
2928 node.output.current_scale().fractional_scale(),
2929 )
2930 })
2931 .unwrap_or((0, 0, 0.0)),
2932 }
2933 })
2934 .unwrap_or((0, 0, 0.0))
2935}
2936
2937fn answer_geometry_waiters(
2940 state: &AppState,
2941 display_id: u32,
2942 waiters: Vec<std::sync::mpsc::Sender<(i32, i32, f64)>>,
2943) {
2944 if waiters.is_empty() {
2945 return;
2946 }
2947 let info = realized_geometry(state, display_id);
2948 for w in waiters {
2949 let _ = w.send(info);
2950 }
2951}
2952
2953fn host_layout_resolution(want: (i32, i32), current: Option<(i32, i32)>) -> Option<(i32, i32)> {
2961 current.filter(|&c| c != want)
2962}
2963
2964const HOST_QUIET_PERIODS: f64 = 1.5;
2969
2970const HOST_MODE_REFUSALS_BEFORE_HINT: u32 = 3;
2974
2975fn reconcile_host_layouts(state: &mut AppState) {
2984 if state.host_layout_pending.is_empty() {
2985 return;
2986 }
2987 let ids: Vec<u32> = state.host_layout_pending.keys().copied().collect();
2988 for id in ids {
2989 let Some(pending) = state.host_layout_pending.get(&id) else {
2991 continue;
2992 };
2993 let host_mode = match state.host.as_ref() {
2994 Some(host) => match host.layout_outcome(pending.epoch) {
2995 None => continue,
2998 Some(_) => host_layout_resolution(pending.want, host.current_output_size(id)),
2999 },
3000 None => None,
3001 };
3002 let Some(pending) = state.host_layout_pending.remove(&id) else {
3003 continue;
3004 };
3005 if host_mode.is_none() {
3006 state.host_mode_refusals.remove(&id);
3007 }
3008 if let Some((rw, rh)) = host_mode {
3009 let (w, h) = pending.want;
3010 let restart = state
3011 .node_idx_for_id(id)
3012 .and_then(|idx| state.output_nodes[idx].capture.as_ref())
3013 .map(|cap| (cap.callback.clone(), cap.settings.clone()));
3014 if let Some((cb, mut settings)) = restart {
3015 settings.width = rw;
3016 settings.height = rh;
3017 let followed = if settings.codec.is_video() {
3020 (rw & !1, rh & !1)
3021 } else {
3022 (rw, rh)
3023 };
3024 if followed != (w, h) {
3025 eprintln!(
3026 "[HostCapture] host runs {rw}x{rh} for display {id} ({w}x{h} not applied); capturing at that size."
3027 );
3028 let refusals = {
3029 let n = state.host_mode_refusals.entry(id).or_insert(0);
3030 *n += 1;
3031 *n
3032 };
3033 if refusals == HOST_MODE_REFUSALS_BEFORE_HINT {
3034 eprintln!(
3035 "[HostCapture] display {id}: the host keeps its own mode; a manual resolution of {rw}x{rh} stops the capture restarting on every resize."
3036 );
3037 }
3038 start_capture_on_display(state, id, cb, settings);
3039 let mismatch = if refusals >= HOST_MODE_REFUSALS_BEFORE_HINT {
3040 format!(
3041 "host runs {rw}x{rh} ({w}x{h} not applied; set a manual resolution of {rw}x{rh})"
3042 )
3043 } else {
3044 format!("host runs {rw}x{rh} ({w}x{h} not applied)")
3045 };
3046 let own = wayland_capture_err().lock().unwrap().get(&id).cloned();
3047 set_wayland_capture_err(
3048 id,
3049 Some(match own {
3050 Some(e) => format!("{mismatch}; {e}"),
3051 None => mismatch,
3052 }),
3053 );
3054 }
3055 }
3056 }
3057 answer_geometry_waiters(state, id, pending.geometry_waiters);
3060 }
3061}
3062
3063fn bootstrap_readback_pool(
3073 cap: &mut wayland::frontend::WlCapture,
3074 display_id: u32,
3075 use_gpu: bool,
3076 try_gpu: bool,
3077 prior: Option<FrameEncoder>,
3078 predecessor: Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
3079) {
3080 let Some(deliver_tx) = cap.deliver_tx.clone() else {
3081 return;
3082 };
3083 let settings = cap.settings.clone();
3084 let pool = Arc::new(WlFramePool::new(
3085 WL_POOL_SURFACES,
3086 (settings.width.max(0) as usize) * (settings.height.max(0) as usize) * 4,
3087 ));
3088 cap.pool_last_render = vec![0; WL_POOL_SURFACES];
3089 cap.render_seq = 0;
3090 cap.pool_content_gen = vec![u64::MAX; WL_POOL_SURFACES];
3091 cap.content_gen = 0;
3092 let c = &cap.encode_controls;
3093 c.bitrate_kbps
3094 .store(settings.video_bitrate_kbps, Ordering::Relaxed);
3095 c.vbv_mult_milli.store(
3096 (settings.video_vbv_multiplier * 1000.0).round() as i32,
3097 Ordering::Relaxed,
3098 );
3099 c.fps_bits
3100 .store(settings.target_fps.max(1.0).to_bits(), Ordering::Relaxed);
3101 c.link_kbps
3102 .store(settings.video_link_kbps, Ordering::Relaxed);
3103 let cfg = WlEncodeConfig {
3104 settings,
3105 display_id,
3106 use_gpu,
3107 try_gpu,
3108 prior,
3109 predecessor,
3110 zero_copy: None,
3111 host: false,
3112 recording_sink: cap.recording_sink.as_ref().map(Arc::downgrade),
3113 deliver_tx,
3114 controls: cap.encode_controls.clone(),
3115 stats: cap.encode_stats.clone(),
3116 report: cap.report.clone(),
3117 };
3118 let pool2 = pool.clone();
3119 cap.encode_join = Some(
3120 thread::Builder::new()
3121 .name(format!("wl-encode-{display_id}"))
3122 .spawn(move || wayland_encode_loop(&pool2, cfg))
3123 .expect("failed to spawn wl-encode thread"),
3124 );
3125 cap.encode_pool = Some(pool);
3126}
3127
3128fn alloc_zero_copy_ring(
3131 gbm: &RawGbmDevice<std::fs::File>,
3132 settings: &CaptureSettings,
3133 display_id: u32,
3134) -> Option<Vec<(BufferObject<()>, Dmabuf)>> {
3135 let mut ring = Vec::with_capacity(WL_POOL_SURFACES);
3136 for _ in 0..WL_POOL_SURFACES {
3137 match alloc_render_target(
3138 gbm,
3139 settings.width.max(0) as u32,
3140 settings.height.max(0) as u32,
3141 GbmFormat::Argb8888,
3142 ) {
3143 Ok(bo) => {
3144 let dmabuf = create_dmabuf_from_bo(&bo);
3145 ring.push((bo, dmabuf));
3146 }
3147 Err(e) => {
3148 eprintln!(
3149 "[Wayland] Display {display_id}: zero-copy render target {}x{} not allocated ({e:?}).",
3150 settings.width, settings.height
3151 );
3152 return None;
3153 }
3154 }
3155 }
3156 Some(ring)
3157}
3158
3159fn bootstrap_zero_copy_pool(
3162 cap: &mut wayland::frontend::WlCapture,
3163 ring: Vec<(BufferObject<()>, Dmabuf)>,
3164 display_id: u32,
3165 encoder: FrameEncoder,
3166 egl_display: *const std::ffi::c_void,
3167 host: bool,
3168) {
3169 let Some(deliver_tx) = cap.deliver_tx.clone() else {
3170 return;
3171 };
3172 let settings = cap.settings.clone();
3173 let pool = Arc::new(WlFramePool::of(
3174 ring.iter().map(|(_, dmabuf)| dmabuf.clone()).collect(),
3175 ));
3176 cap.gpu_ring = ring;
3177 cap.pool_last_render = vec![0; WL_POOL_SURFACES];
3178 cap.render_seq = 0;
3179 let session = Arc::new(Mutex::new(Some(encoder)));
3180 cap.zero_copy = Some(session.clone());
3181 let c = &cap.encode_controls;
3182 c.bitrate_kbps
3183 .store(settings.video_bitrate_kbps, Ordering::Relaxed);
3184 c.vbv_mult_milli.store(
3185 (settings.video_vbv_multiplier * 1000.0).round() as i32,
3186 Ordering::Relaxed,
3187 );
3188 c.fps_bits
3189 .store(settings.target_fps.max(1.0).to_bits(), Ordering::Relaxed);
3190 c.link_kbps
3191 .store(settings.video_link_kbps, Ordering::Relaxed);
3192 let cfg = WlEncodeConfig {
3193 settings,
3194 display_id,
3195 use_gpu: true,
3196 try_gpu: true,
3197 prior: None,
3198 predecessor: None,
3199 zero_copy: Some(ZeroCopySession {
3200 encoder: session,
3201 egl_display: egl_display as usize,
3202 }),
3203 host,
3204 recording_sink: cap.recording_sink.as_ref().map(Arc::downgrade),
3205 deliver_tx,
3206 controls: cap.encode_controls.clone(),
3207 stats: cap.encode_stats.clone(),
3208 report: cap.report.clone(),
3209 };
3210 let pool2 = pool.clone();
3211 cap.encode_join = Some(
3212 thread::Builder::new()
3213 .name(format!("wl-encode-{display_id}"))
3214 .spawn(move || wayland_encode_loop(&pool2, cfg))
3215 .expect("failed to spawn wl-encode thread"),
3216 );
3217 cap.gpu_pool = Some(pool);
3218}
3219
3220pub(crate) const HW_ERROR_RECOVERY_THRESHOLD: u32 = 30;
3224
3225fn start_capture_on_display(
3226 state: &mut AppState,
3227 display_id: u32,
3228 cb: Option<Arc<dyn crate::sink::FrameSink>>,
3229 mut settings: CaptureSettings,
3230) {
3231 let _ = state
3234 .cursor_tx
3235 .send(CursorJob::SetSizeCap(settings.cursor_size_cap));
3236
3237 set_wayland_capture_err(display_id, None);
3239
3240 let Some(node_idx) = state.node_idx_for_id(display_id) else {
3241 eprintln!("[Wayland] StartCapture: no output with display id {display_id}.");
3242 set_wayland_capture_err(
3243 display_id,
3244 Some(format!("no output with display id {display_id}")),
3245 );
3246 return;
3247 };
3248 let stream_report = report::StreamReport::new("wayland");
3249 report::wayland_reports()
3250 .lock()
3251 .unwrap()
3252 .insert(display_id, stream_report.clone());
3253 let _report = report::enter(&stream_report);
3254 let mut node = state.output_nodes.remove(node_idx);
3255 let mut geometry_waiters = state
3258 .host_layout_pending
3259 .remove(&display_id)
3260 .map(|p| p.geometry_waiters)
3261 .unwrap_or_default();
3262
3263 if settings.encode_node_index < -1 {
3264 let picked = if state.auto_gpu_selected {
3265 render_node_index(&state.render_node_path)
3266 } else {
3267 first_render_node_index()
3268 };
3269 if let Some(idx) = picked {
3270 settings.encode_node_index = idx;
3271 }
3272 }
3273
3274 if settings.codec.is_video() {
3275 settings.width &= !1;
3276 settings.height &= !1;
3277 }
3278
3279 let mut prior_zero_copy: Option<FrameEncoder> = None;
3282 let mut prior_encode_join: Option<std::thread::JoinHandle<Option<FrameEncoder>>> = None;
3283 let mut prior_deliver_join: Option<std::thread::JoinHandle<()>> = None;
3284 if let Some(mut old) = node.capture.take() {
3285 (prior_deliver_join, prior_encode_join) = teardown_capture(&mut old);
3286 prior_zero_copy = old.zero_copy.take().and_then(|z| z.lock().unwrap().take());
3288 release_host_inflight(state.host.as_ref(), &mut old, display_id, true);
3289 }
3290
3291 let recording_sink = crate::recording_sink::RecordingSink::try_bind(
3295 &settings.recording_socket,
3296 settings.target_fps,
3297 );
3298
3299 let host_capture = !settings.wayland_host_display.is_empty();
3305 reap_dead_host(state);
3306 if host_capture && state.host.is_none() {
3307 let gbm_path = if state.use_gpu {
3311 state.gbm_device.as_ref().and_then(|dev| {
3312 use std::os::fd::{AsFd as _, AsRawFd as _};
3313 let fd = dev.as_fd().as_raw_fd();
3314 std::fs::read_link(format!("/proc/self/fd/{fd}")).ok()
3315 })
3316 } else {
3317 None
3318 };
3319 let dma_formats: Vec<(u32, Vec<u64>)> = match (&state.gles_renderer, state.use_gpu) {
3322 (Some(renderer), true) => {
3323 let formats = renderer.egl_context().display().dmabuf_texture_formats();
3324 [Fourcc::Xrgb8888, Fourcc::Argb8888]
3325 .into_iter()
3326 .map(|code| {
3327 (
3328 code as u32,
3329 formats
3330 .iter()
3331 .filter(|f| f.code == code)
3332 .map(|f| u64::from(f.modifier))
3333 .collect(),
3334 )
3335 })
3336 .collect()
3337 }
3338 _ => Vec::new(),
3339 };
3340 match crate::wayland::host::HostSession::connect(
3341 &settings.wayland_host_display,
3342 gbm_path,
3343 state.host_frame_tx.clone(),
3344 dma_formats,
3345 state.cursor_tx.clone(),
3346 ) {
3347 Ok(h) => {
3348 println!(
3349 "[HostCapture] capturing host compositor '{}' ({} outputs).",
3350 settings.wayland_host_display,
3351 h.output_count()
3352 );
3353 state.host = Some(h);
3354 *crate::wayland::pwcapture::HOST_POINTER.lock().unwrap() = Some(None);
3357 }
3358 Err(e) => {
3359 eprintln!(
3360 "[HostCapture] connect '{}' failed: {e}",
3361 settings.wayland_host_display
3362 );
3363 set_wayland_capture_err(
3364 display_id,
3365 Some(format!(
3366 "host compositor '{}' connect failed ({e}); capturing locally",
3367 settings.wayland_host_display
3368 )),
3369 );
3370 }
3371 }
3372 }
3373 let host_capture = host_capture && state.host.is_some();
3376 if host_capture && let Some(host) = &state.host {
3377 host.set_layout(display_id, node.pos.0, node.pos.1);
3380 }
3381
3382 if node.owner.is_none() {
3387 let target_refresh = (settings.target_fps * 1000.0).round() as i32;
3391 let (current_w, current_h, current_refresh) = match node.output.current_mode() {
3392 Some(m) => (m.size.w, m.size.h, m.refresh),
3393 None => (settings.width, settings.height, target_refresh),
3394 };
3395 let current_scale = node.output.current_scale().fractional_scale();
3396
3397 if current_w != settings.width
3398 || current_h != settings.height
3399 || (current_scale - settings.scale).abs() > 0.001
3400 || current_refresh != target_refresh
3401 {
3402 let mut new_offscreen = None;
3408 let mut gbm_resize_failed = false;
3409 if state.use_gpu
3410 && let Some(gbm) = state.gbm_device.as_mut()
3411 {
3412 match alloc_render_target(
3413 gbm,
3414 settings.width as u32,
3415 settings.height as u32,
3416 GbmFormat::Argb8888,
3417 ) {
3418 Ok(bo) => {
3419 let dmabuf = create_dmabuf_from_bo(&bo);
3420 new_offscreen = Some((bo, dmabuf));
3421 }
3422 Err(e) => {
3423 eprintln!(
3424 "[Wayland] GBM buffer resize to {}x{} failed ({:?}); keeping previous output mode.",
3425 settings.width, settings.height, e
3426 );
3427 gbm_resize_failed = true;
3428 }
3429 }
3430 }
3431 if gbm_resize_failed {
3432 set_wayland_capture_err(
3436 display_id,
3437 Some(format!(
3438 "GPU buffer resize to {}x{} refused; kept {current_w}x{current_h}",
3439 settings.width, settings.height
3440 )),
3441 );
3442 settings.width = current_w;
3443 settings.height = current_h;
3444 settings.scale = current_scale;
3445 settings.target_fps = f64::from(current_refresh) / 1000.0;
3446 } else {
3447 println!(
3448 "[Wayland] Configuring Output {} ({}): {}x{} @ {:.2} FPS (Scale {:.2})",
3449 display_id,
3450 node.output.name(),
3451 settings.width,
3452 settings.height,
3453 settings.target_fps,
3454 settings.scale
3455 );
3456 let new_mode = OutputMode {
3457 size: (settings.width, settings.height).into(),
3458 refresh: target_refresh,
3459 };
3460 node.output.change_current_state(
3461 Some(new_mode),
3462 Some(Transform::Normal),
3463 Some(OutputScale::Fractional(settings.scale)),
3464 Some(Point::from(node.pos)),
3465 );
3466 node.output.set_preferred(new_mode);
3467 for cs in state
3470 .copy_sessions
3471 .iter()
3472 .filter(|cs| cs.output.upgrade().as_ref() == Some(&node.output))
3473 {
3474 if let Some(c) = wayland::frontend::output_capture_constraints(
3475 &node.output,
3476 state.gles_renderer.as_ref(),
3477 &state.render_node_path,
3478 ) {
3479 cs.session.update_constraints(c);
3480 }
3481 }
3482
3483 let pixel_count =
3484 (settings.width.max(0) as usize) * (settings.height.max(0) as usize);
3485 node.frame_buffer = vec![0u8; pixel_count * 4];
3486 node.target_seeded = false;
3487
3488 if let Some(off) = new_offscreen.take() {
3489 node.offscreen_buffer = Some(off);
3490 }
3491 }
3492 }
3493
3494 let out = node.output.clone();
3495 configure_windows_for_mode(
3496 state,
3497 display_id,
3498 &out,
3499 settings.width,
3500 settings.height,
3501 settings.scale,
3502 );
3503 } else if state.use_gpu {
3504 let have = node
3511 .offscreen_buffer
3512 .as_ref()
3513 .map(|(bo, _)| (bo.width() as i32, bo.height() as i32));
3514 if have != Some((settings.width, settings.height))
3515 && let Some(gbm) = state.gbm_device.as_mut()
3516 {
3517 match alloc_render_target(
3518 gbm,
3519 settings.width as u32,
3520 settings.height as u32,
3521 GbmFormat::Argb8888,
3522 ) {
3523 Ok(bo) => {
3524 let dmabuf = create_dmabuf_from_bo(&bo);
3525 node.offscreen_buffer = Some((bo, dmabuf));
3526 node.damage_tracker = OutputDamageTracker::new(
3527 (settings.width, settings.height),
3528 settings.scale,
3529 Transform::Normal,
3530 );
3531 node.content_tracker = None;
3532 node.view_size = (settings.width, settings.height);
3533 node.view_scale = settings.scale;
3534 node.frame_buffer = vec![
3535 0u8;
3536 (settings.width.max(0) as usize)
3537 * (settings.height.max(0) as usize)
3538 * 4
3539 ];
3540 node.target_seeded = false;
3541 crate::log::debug!(
3542 "[Wayland] View {display_id} render target resized to {}x{}.",
3543 settings.width,
3544 settings.height
3545 );
3546 }
3547 Err(e) => {
3548 eprintln!(
3549 "[Wayland] GBM view buffer resize to {}x{} failed ({:?}); keeping previous target.",
3550 settings.width, settings.height, e
3551 );
3552 set_wayland_capture_err(
3553 display_id,
3554 Some(format!(
3555 "GPU view buffer resize to {}x{} refused",
3556 settings.width, settings.height
3557 )),
3558 );
3559 }
3560 }
3561 }
3562 }
3563
3564 let use_cpu_explicit = settings.use_cpu || settings.encode_node_index == -1;
3565 let gpu_intent = settings.codec.is_video() && !use_cpu_explicit;
3566
3567 let mut different_gpu = false;
3568 if gpu_intent {
3569 let encode_node_idx = settings.encode_node_index.max(0);
3570 if !state.render_node_path.is_empty()
3571 && !state
3572 .render_node_path
3573 .contains(&format!("renderD{}", 128 + encode_node_idx))
3574 {
3575 different_gpu = true;
3576 }
3577 }
3578
3579 let mut video_encoder: Option<FrameEncoder> = None;
3580 if gpu_intent && state.use_gpu && !different_gpu {
3581 let egl_display = state
3582 .gles_renderer
3583 .as_ref()
3584 .map(|r| r.egl_context().display().get_display_handle().handle)
3585 .unwrap_or(std::ptr::null());
3586 video_encoder = encoders::select_frame_encoder(
3587 &mut settings,
3588 FrameSource::Dmabuf { egl_display },
3589 prior_zero_copy.take(),
3590 "Wayland",
3591 );
3592 if video_encoder.is_none() {
3593 set_wayland_capture_err(
3594 display_id,
3595 Some(format!(
3596 "no zero-copy {} encoder on the render node; using readback encode",
3597 settings.codec.display()
3598 )),
3599 );
3600 }
3601 }
3602 drop(prior_zero_copy);
3603
3604 let rendered = if state.use_gpu {
3605 format!("rendered on {}", state.render_node_path)
3606 } else {
3607 "rendered in software (Pixman)".to_string()
3608 };
3609 match video_encoder.as_ref() {
3610 Some(enc) => {
3611 report::capture("dmabuf", true);
3612 println!(
3613 "[Wayland] Zero-copy capture: output {display_id} {}x{} {rendered}, encoded in place on {}.",
3614 settings.width,
3615 settings.height,
3616 enc.backend_name()
3617 );
3618 }
3619 None => {
3620 let why = if different_gpu {
3621 "the encode node is another GPU"
3622 } else if !settings.codec.is_video() {
3623 "JPEG encodes on the CPU"
3624 } else if use_cpu_explicit {
3625 "software encoding selected"
3626 } else if !state.use_gpu {
3627 "no GPU renderer"
3628 } else {
3629 "no zero-copy encoder on the render node"
3630 };
3631 report::capture("readback", false);
3632 report::zero_copy_available(gpu_intent && state.use_gpu && !different_gpu);
3633 report::capture_reason(why);
3634 println!(
3635 "[Wayland] Readback capture: output {display_id} {}x{} {rendered}, read back for the encode thread ({why}).",
3636 settings.width, settings.height
3637 );
3638 }
3639 }
3640
3641 if host_capture && let Some(host) = &state.host {
3650 let epoch = host.start_capture(
3651 display_id,
3652 settings.width,
3653 settings.height,
3654 video_encoder.is_some(),
3655 settings.capture_cursor,
3656 settings.target_fps,
3657 );
3658 state.host_layout_pending.insert(
3659 display_id,
3660 wayland::frontend::PendingHostLayout {
3661 epoch,
3662 want: (settings.width, settings.height),
3663 geometry_waiters: std::mem::take(&mut geometry_waiters),
3664 },
3665 );
3666 }
3667
3668 if recording_sink.is_some() && !settings.codec.is_video() {
3669 eprintln!(
3670 "[recording_sink] WARNING: recording_socket is set but the codec is JPEG. The \
3671 recording socket requires a video stream; set a video codec on the Python \
3672 CaptureSettings to produce a recordable output."
3673 );
3674 }
3675
3676 let watermark_output_scale = node.output.current_scale().fractional_scale();
3679 node.overlay_state
3680 .load_watermark(&settings.watermark_path, watermark_output_scale);
3681 if display_id == 0 {
3682 state.settings = settings.clone();
3683 if state.cursor_callback_set
3684 && let Some(icon) = state.current_cursor_icon.clone()
3685 {
3686 state.send_cursor_image(&icon);
3687 }
3688 }
3689 state.render_cursor_on_framebuffer = settings.capture_cursor;
3690
3691 let mut cap = wayland::frontend::WlCapture {
3692 settings: settings.clone(),
3693 callback: cb.clone(),
3694 zero_copy: None,
3695 gpu_pool: None,
3696 gpu_ring: Vec::new(),
3697 host_still: None,
3698 host_inflight: Vec::new(),
3699 scanout_inflight: Vec::new(),
3700 direct: None,
3701 published: 0,
3702 host_retained_use: 0,
3703 recording_sink,
3704 deliver_tx: None,
3705 deliver_join: None,
3706 deliver_discard: None,
3707 encode_pool: None,
3708 encode_join: None,
3709 encode_controls: Arc::new(WlEncodeControls::new()),
3710 encode_stats: Arc::new(WlEncodeStats::new()),
3711 report: stream_report.clone(),
3712 pool_last_render: Vec::new(),
3713 render_seq: 0,
3714 pool_content_gen: Vec::new(),
3715 content_gen: 0,
3716 frame_counter: 0,
3717 pending_force_idr: false,
3718 needs_full_render: true,
3719 pace: FramePace::windowed(),
3720 change_pending: false,
3721 callbacks_at: None,
3722 callbacks_owed: false,
3723 };
3724
3725 {
3726 let (tx, rx) = std::sync::mpsc::sync_channel::<Vec<EncodedStripe>>(1);
3727 let discard = Arc::new(AtomicBool::new(false));
3728 let thread_discard = discard.clone();
3729 let predecessor = prior_deliver_join;
3730 let join = thread::spawn(move || {
3734 if let Some(handle) = predecessor {
3739 let _ = handle.join();
3740 }
3741 match cb {
3742 Some(cb) => {
3743 crate::boost_thread_priority(-10);
3744 while let Ok(stripes) = rx.recv() {
3745 if thread_discard.load(Ordering::Relaxed)
3746 || SHUTDOWN.load(Ordering::Relaxed)
3747 {
3748 continue;
3749 }
3750 stream_report.tally(&stripes);
3751 cb.frames(stripes);
3752 }
3753 }
3754 None => while rx.recv().is_ok() {},
3755 }
3756 });
3757 cap.deliver_tx = Some(tx);
3758 cap.deliver_discard = Some(discard);
3759 cap.deliver_join = Some(join);
3760 }
3761
3762 let mut zero_copy_refused = false;
3765 if let Some(encoder) = video_encoder {
3766 cap.encode_stats.n_stripes.store(1, Ordering::Relaxed);
3767 *cap.encode_stats.desc.lock().unwrap() = encoder_desc(&settings, Some(&encoder), true);
3768 set_wayland_active_codec(display_id, Some(settings.codec));
3769 log_stream_settings("Wayland", &settings, 1, Some(&encoder));
3770 let egl_display = state
3771 .gles_renderer
3772 .as_ref()
3773 .map(|r| r.egl_context().display().get_display_handle().handle)
3774 .unwrap_or(std::ptr::null());
3775 match state
3776 .gbm_device
3777 .as_ref()
3778 .and_then(|gbm| alloc_zero_copy_ring(gbm, &settings, display_id))
3779 {
3780 Some(ring) => bootstrap_zero_copy_pool(
3781 &mut cap,
3782 ring,
3783 display_id,
3784 encoder,
3785 egl_display,
3786 host_capture,
3787 ),
3788 None => {
3789 drop(encoder);
3790 eprintln!(
3791 "[Wayland] Display {display_id}: zero-copy capture not started; using readback encode."
3792 );
3793 zero_copy_refused = true;
3794 report::capture("readback", false);
3795 report::capture_reason("the zero-copy render targets could not be allocated");
3796 if host_capture && let Some(h) = state.host.as_ref() {
3797 h.set_buffer_type(display_id, false);
3798 }
3799 }
3800 }
3801 }
3802 if cap.gpu_pool.is_none() {
3803 bootstrap_readback_pool(
3804 &mut cap,
3805 display_id,
3806 state.use_gpu && !host_capture,
3809 gpu_intent && (zero_copy_refused || !state.use_gpu || different_gpu),
3810 None,
3811 prior_encode_join.take(),
3812 );
3813 }
3814 state.encode_reaper.extend(prior_encode_join);
3817 cap.request_idr();
3821
3822 node.capture = Some(cap);
3823 node.content_hold_until = Some(Instant::now() + WL_CONTENT_HOLD);
3829 state.output_nodes.insert(node_idx, node);
3830 wayland_alive().lock().unwrap().insert(display_id);
3831 answer_geometry_waiters(state, display_id, geometry_waiters);
3832}
3833
3834fn draw_host_watermark(
3843 renderer: &mut GlesRenderer,
3844 overlay: &crate::encoders::overlay::OverlayState,
3845 target: &mut Dmabuf,
3846 size: (i32, i32),
3847) -> Result<SyncPoint, String> {
3848 let elem = overlay
3849 .get_watermark_element(renderer)
3850 .ok_or("watermark element unavailable")?;
3851 let mut fb = renderer.bind(target).map_err(|e| format!("bind: {e:?}"))?;
3852 let mut frame = renderer
3853 .render(&mut fb, (size.0, size.1).into(), Transform::Normal)
3854 .map_err(|e| format!("render: {e:?}"))?;
3855 let dst = elem.geometry(1.0.into());
3856 let local = Rectangle::from_size(dst.size);
3857 elem.draw(&mut frame, elem.src(), dst, &[local], &[], None)
3858 .map_err(|e| format!("draw: {e:?}"))?;
3859 frame.finish().map_err(|e| format!("finish: {e:?}"))
3860}
3861
3862fn compose_host_frame(
3866 renderer: &mut GlesRenderer,
3867 overlay: Option<&crate::encoders::overlay::OverlayState>,
3868 src: &Dmabuf,
3869 target: &mut Dmabuf,
3870 size: (i32, i32),
3871) -> Result<SyncPoint, String> {
3872 let elem = match overlay {
3873 Some(overlay) => Some(
3874 overlay
3875 .get_watermark_element(renderer)
3876 .ok_or("watermark element unavailable")?,
3877 ),
3878 None => None,
3879 };
3880 let tex = renderer
3881 .import_dmabuf(src, None)
3882 .map_err(|e| format!("import: {e:?}"))?;
3883 let mut fb = renderer.bind(target).map_err(|e| format!("bind: {e:?}"))?;
3884 let full: Rectangle<i32, Physical> = Rectangle::from_size((size.0, size.1).into());
3885 let mut frame = renderer
3886 .render(&mut fb, (size.0, size.1).into(), Transform::Normal)
3887 .map_err(|e| format!("render: {e:?}"))?;
3888 frame
3889 .render_texture_from_to(
3890 &tex,
3891 Rectangle::from_size((f64::from(size.0), f64::from(size.1)).into()),
3892 full,
3893 &[full],
3894 &[full],
3898 Transform::Normal,
3899 1.0,
3900 None,
3901 &[],
3902 )
3903 .map_err(|e| format!("texture: {e:?}"))?;
3904 if let Some(elem) = elem {
3905 let dst = elem.geometry(1.0.into());
3906 let local = Rectangle::from_size(dst.size);
3907 elem.draw(&mut frame, elem.src(), dst, &[local], &[], None)
3908 .map_err(|e| format!("draw: {e:?}"))?;
3909 }
3910 frame.finish().map_err(|e| format!("finish: {e:?}"))
3911}
3912
3913fn warn_once_host_watermark(e: &str) {
3914 static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
3915 if !WARNED.swap(true, Ordering::Relaxed) {
3916 eprintln!("[HostCapture] watermark compositing failed: {e}");
3917 }
3918}
3919
3920fn copy_rows_into_shm(
3923 buffer: &smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer,
3924 src: &[u8],
3925 width: i32,
3926 height: i32,
3927) -> Result<(), String> {
3928 use smithay::wayland::shm::with_buffer_contents_mut;
3929 let src_stride = (width.max(0) as usize) * 4;
3930 if src.len() < src_stride * (height.max(0) as usize) {
3931 return Err("source smaller than advertised".into());
3932 }
3933 with_buffer_contents_mut(buffer, |ptr, len, spec| {
3934 if spec.width < width || spec.height < height {
3935 return Err("shm buffer smaller than the output".to_string());
3936 }
3937 let dst_stride = spec.stride as usize;
3938 let offset = spec.offset as usize;
3939 if dst_stride < src_stride || len < offset + dst_stride * (height as usize) {
3940 return Err("shm stride or length mismatch".to_string());
3941 }
3942 for y in 0..height as usize {
3943 unsafe {
3944 std::ptr::copy_nonoverlapping(
3945 src.as_ptr().add(y * src_stride),
3946 ptr.add(offset + y * dst_stride),
3947 src_stride,
3948 );
3949 }
3950 }
3951 Ok(())
3952 })
3953 .map_err(|e| format!("{e:?}"))?
3954}
3955
3956fn composited_target(
3959 node: &wayland::frontend::OutputNode,
3960 source: Option<&Dmabuf>,
3961) -> Result<Dmabuf, String> {
3962 source
3963 .or(node.offscreen_buffer.as_ref().map(|(_, dmabuf)| dmabuf))
3964 .cloned()
3965 .ok_or_else(|| "no composited buffer".to_string())
3966}
3967
3968fn service_copy_frames(
3974 state: &mut AppState,
3975 node: &mut wayland::frontend::OutputNode,
3976 source: Option<&Dmabuf>,
3977 width: i32,
3978 height: i32,
3979 damage_rects: &[Rectangle<i32, Physical>],
3980) {
3981 use smithay::backend::renderer::{Blit, BufferType, ExportMem, TextureFilter, buffer_type};
3982 use smithay::utils::Buffer as BufferCoords;
3983
3984 if state.copy_sessions.is_empty() {
3985 return;
3986 }
3987 let time = state.clock.now();
3988 for i in 0..state.copy_sessions.len() {
3989 {
3990 let cs = &state.copy_sessions[i];
3991 if cs.output.upgrade().as_ref() != Some(&node.output)
3992 || cs.pending.is_none()
3993 || (damage_rects.is_empty() && cs.delivered_once)
3994 {
3995 continue;
3996 }
3997 }
3998 let first = !state.copy_sessions[i].delivered_once;
3999 let frame = state.copy_sessions[i].pending.take().unwrap();
4000 let buffer = frame.buffer();
4001 let full = Rectangle::<i32, Physical>::new((0, 0).into(), (width, height).into());
4002 let result: Result<(), String> = match buffer_type(&buffer) {
4003 Some(BufferType::Dma) => (|| {
4004 let renderer = state.gles_renderer.as_mut().ok_or("no GLES renderer")?;
4005 let mut client = smithay::wayland::dmabuf::get_dmabuf(&buffer)
4006 .map_err(|e| e.to_string())?
4007 .clone();
4008 let mut composited = composited_target(node, source)?;
4009 let src = renderer
4010 .bind(&mut composited)
4011 .map_err(|e| format!("{e:?}"))?;
4012 let mut dst = renderer.bind(&mut client).map_err(|e| format!("{e:?}"))?;
4013 let sync = renderer
4017 .blit(&src, &mut dst, full, full, TextureFilter::Linear)
4018 .map_err(|e| format!("{e:?}"))?;
4019 if !wait_render_fence(&sync, node.id) {
4020 return Err("blit fence not signaled".to_string());
4021 }
4022 Ok(())
4023 })(),
4024 Some(BufferType::Shm) => (|| {
4025 if let Some(renderer) = state.gles_renderer.as_mut() {
4026 let mut composited = composited_target(node, source)?;
4027 let fb = renderer
4028 .bind(&mut composited)
4029 .map_err(|e| format!("{e:?}"))?;
4030 let mapping = renderer
4031 .copy_framebuffer(
4032 &fb,
4033 Rectangle::new((0, 0).into(), (width, height).into()),
4034 Fourcc::Argb8888,
4035 )
4036 .map_err(|e| format!("{e:?}"))?;
4037 let data = renderer
4038 .map_texture(&mapping)
4039 .map_err(|e| format!("{e:?}"))?;
4040 copy_rows_into_shm(&buffer, data, width, height)
4041 } else {
4042 copy_rows_into_shm(&buffer, &node.frame_buffer, width, height)
4043 }
4044 })(),
4045 _ => Err("unsupported buffer type".into()),
4046 };
4047 match result {
4048 Ok(()) => {
4049 let damage: Option<Vec<Rectangle<i32, BufferCoords>>> = if first {
4050 None
4051 } else {
4052 Some(
4053 damage_rects
4054 .iter()
4055 .map(|r| {
4056 Rectangle::new(
4057 (r.loc.x, r.loc.y).into(),
4058 (r.size.w, r.size.h).into(),
4059 )
4060 })
4061 .collect(),
4062 )
4063 };
4064 frame.success(Transform::Normal, damage, time);
4065 state.copy_sessions[i].delivered_once = true;
4066 }
4067 Err(e) => {
4068 eprintln!("[Wayland] copy-capture frame failed: {e}");
4069 frame.fail(CaptureFailureReason::Unknown);
4070 }
4071 }
4072 }
4073}
4074
4075fn send_cursor_frame(state: &AppState, output: &Output, time: impl Into<Duration>) {
4079 if let Some(CursorImageStatus::Surface(surface)) = &state.current_cursor_icon {
4080 send_frames_surface_tree(surface, output, time, Some(Duration::ZERO), |_, _| {
4081 Some(output.clone())
4082 });
4083 }
4084}
4085
4086fn cursor_surface_hotspot(
4089 surface: &smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
4090) -> Point<i32, smithay::utils::Logical> {
4091 with_states(surface, |states| {
4092 states
4093 .data_map
4094 .get::<std::sync::Mutex<smithay::input::pointer::CursorImageAttributes>>()
4095 .and_then(|attrs| attrs.lock().ok().map(|guard| guard.hotspot))
4096 .unwrap_or_default()
4097 })
4098}
4099
4100const RENDER_FENCE_TIMEOUT: Duration = Duration::from_millis(500);
4102
4103const UNDRAWN_TICKS_PER_FRAME: u32 = 60;
4108
4109fn read_back_rgba(
4113 renderer: &mut GlesRenderer,
4114 target: &mut smithay::backend::renderer::gles::GlesTarget<'_>,
4115 width: i32,
4116 height: i32,
4117 dst: &mut [u8],
4118) {
4119 if let Err(e) = read_back_rgba_result(renderer, target, width, height, dst, false) {
4120 eprintln!("[Wayland] Readback failed: {e}");
4121 }
4122}
4123
4124type BufferStorageFn = unsafe extern "system" fn(u32, isize, *const std::ffi::c_void, u32);
4126
4127const GL_MAP_READ_BIT: u32 = 0x0001;
4128const GL_MAP_PERSISTENT_BIT_EXT: u32 = 0x0040;
4129const GL_MAP_COHERENT_BIT_EXT: u32 = 0x0080;
4130const GL_CLIENT_STORAGE_BIT_EXT: u32 = 0x0200;
4131const READBACK_SLOTS_MAX: usize = computer_use::MAX_SCREENSHOT_REQUESTS;
4133const READBACK_SLOT_IDLE: Duration = Duration::from_secs(10);
4135
4136#[derive(Default)]
4143pub(crate) struct ScreenshotReadbacks {
4144 slots: Vec<(Arc<computer_use::ReadbackSlot>, Instant)>,
4145 storage: Option<Option<BufferStorageFn>>,
4147}
4148
4149impl ScreenshotReadbacks {
4150 fn lookup(gl: &smithay::backend::renderer::gles::ffi::Gles2) -> Option<BufferStorageFn> {
4151 if std::env::var_os("PIXELFLUX_SCREENSHOT_SYNC").is_some_and(|v| !v.is_empty() && v != "0")
4152 {
4153 return None;
4154 }
4155 let extensions = unsafe {
4157 let names = gl.GetString(smithay::backend::renderer::gles::ffi::EXTENSIONS);
4158 if names.is_null() {
4159 return None;
4160 }
4161 std::ffi::CStr::from_ptr(names as *const std::ffi::c_char)
4162 .to_string_lossy()
4163 .into_owned()
4164 };
4165 if !extensions
4166 .split(' ')
4167 .any(|name| name == "GL_EXT_buffer_storage")
4168 {
4169 return None;
4170 }
4171 unsafe {
4173 #[allow(clippy::disallowed_methods)]
4176 let address = smithay::backend::egl::get_proc_address("glBufferStorageEXT");
4177 (!address.is_null())
4178 .then(|| std::mem::transmute::<*const std::ffi::c_void, BufferStorageFn>(address))
4179 }
4180 }
4181
4182 fn acquire(
4185 &mut self,
4186 gl: &smithay::backend::renderer::gles::ffi::Gles2,
4187 len: usize,
4188 now: Instant,
4189 ) -> Option<Arc<computer_use::ReadbackSlot>> {
4190 let storage = (*self.storage.get_or_insert_with(|| Self::lookup(gl)))?;
4191 if let Some((slot, used)) = self
4192 .slots
4193 .iter_mut()
4194 .find(|(slot, _)| !slot.in_use() && slot.size == len)
4195 {
4196 *used = now;
4197 return Some(Arc::clone(slot));
4198 }
4199 if self.slots.len() >= READBACK_SLOTS_MAX {
4200 let free = self.slots.iter().position(|(slot, _)| !slot.in_use())?;
4201 Self::delete(gl, &self.slots.swap_remove(free).0);
4202 }
4203 let slot = Arc::new(Self::create(gl, storage, len)?);
4204 self.slots.push((Arc::clone(&slot), now));
4205 Some(slot)
4206 }
4207
4208 fn create(
4209 gl: &smithay::backend::renderer::gles::ffi::Gles2,
4210 storage: BufferStorageFn,
4211 len: usize,
4212 ) -> Option<computer_use::ReadbackSlot> {
4213 use smithay::backend::renderer::gles::ffi;
4214 let size = isize::try_from(len).ok()?;
4215 let access = GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT;
4216 unsafe {
4218 let mut id = 0;
4219 gl.GenBuffers(1, &mut id);
4220 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, id);
4221 storage(
4222 ffi::PIXEL_PACK_BUFFER,
4223 size,
4224 std::ptr::null(),
4225 access | GL_CLIENT_STORAGE_BIT_EXT,
4226 );
4227 let ptr = gl.MapBufferRange(ffi::PIXEL_PACK_BUFFER, 0, size, access);
4228 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
4229 if ptr.is_null() || gl.GetError() != ffi::NO_ERROR {
4230 gl.DeleteBuffers(1, &id);
4231 return None;
4232 }
4233 Some(computer_use::ReadbackSlot::new(id, ptr as *const u8, len))
4234 }
4235 }
4236
4237 fn delete(
4238 gl: &smithay::backend::renderer::gles::ffi::Gles2,
4239 slot: &computer_use::ReadbackSlot,
4240 ) {
4241 use smithay::backend::renderer::gles::ffi;
4242 slot.unmap();
4243 unsafe {
4245 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, slot.id);
4246 gl.UnmapBuffer(ffi::PIXEL_PACK_BUFFER);
4247 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
4248 gl.DeleteBuffers(1, &slot.id);
4249 }
4250 }
4251
4252 fn has_idle(&self, now: Instant) -> bool {
4254 self.slots
4255 .iter()
4256 .any(|(slot, used)| !slot.in_use() && now.duration_since(*used) >= READBACK_SLOT_IDLE)
4257 }
4258
4259 fn reap_idle(&mut self, gl: &smithay::backend::renderer::gles::ffi::Gles2, now: Instant) {
4260 self.slots.retain(|(slot, used)| {
4261 let idle = !slot.in_use() && now.duration_since(*used) >= READBACK_SLOT_IDLE;
4262 if idle {
4263 Self::delete(gl, slot);
4264 }
4265 !idle
4266 });
4267 }
4268}
4269
4270impl Drop for ScreenshotReadbacks {
4271 fn drop(&mut self) {
4272 for (slot, _) in &self.slots {
4273 slot.unmap();
4274 }
4275 }
4276}
4277
4278fn read_back_rgba_async(
4281 renderer: &mut GlesRenderer,
4282 readbacks: &mut ScreenshotReadbacks,
4283 target: &mut smithay::backend::renderer::gles::GlesTarget<'_>,
4284 width: i32,
4285 height: i32,
4286) -> Option<Result<computer_use::GpuReadback, String>> {
4287 use smithay::backend::renderer::gles::ffi;
4288 let len = screenshot_len(width, height)?;
4289 let now = Instant::now();
4290 let slot = renderer
4291 .with_context(|gl| readbacks.acquire(gl, len, now))
4292 .ok()??;
4293 Some((|| {
4294 let mut frame = renderer
4295 .render(target, (width, height).into(), Transform::Normal)
4296 .map_err(|e| format!("Screenshot readback failed: {e:?}"))?;
4297 let error = frame
4298 .with_context(|gl| unsafe {
4299 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, slot.id);
4300 gl.ReadPixels(
4301 0,
4302 0,
4303 width,
4304 height,
4305 ffi::RGBA,
4306 ffi::UNSIGNED_BYTE,
4307 std::ptr::null_mut(),
4308 );
4309 gl.BindBuffer(ffi::PIXEL_PACK_BUFFER, 0);
4310 gl.GetError()
4311 })
4312 .map_err(|e| format!("Screenshot readback failed: {e:?}"))?;
4313 if error != ffi::NO_ERROR {
4314 return Err(format!("Screenshot readback GL error: 0x{error:x}"));
4315 }
4316 let sync = frame
4318 .finish()
4319 .map_err(|e| format!("Screenshot readback failed: {e:?}"))?;
4320 Ok(computer_use::GpuReadback { slot, sync, len })
4321 })())
4322}
4323
4324fn read_back_rgba_result(
4326 renderer: &mut GlesRenderer,
4327 target: &mut smithay::backend::renderer::gles::GlesTarget<'_>,
4328 width: i32,
4329 height: i32,
4330 dst: &mut [u8],
4331 check_error: bool,
4332) -> Result<(), String> {
4333 if check_error
4334 && (width <= 0 || height <= 0 || dst.len() < width as usize * height as usize * 4)
4335 {
4336 return Err("Screenshot readback buffer is too small".to_string());
4337 }
4338 let error = renderer
4339 .render(target, (width, height).into(), Transform::Normal)
4340 .and_then(|mut frame| {
4341 frame.with_context(|gl| unsafe {
4342 gl.ReadPixels(
4343 0,
4344 0,
4345 width,
4346 height,
4347 smithay::backend::renderer::gles::ffi::RGBA,
4348 smithay::backend::renderer::gles::ffi::UNSIGNED_BYTE,
4349 dst.as_mut_ptr() as *mut std::ffi::c_void,
4350 );
4351 if check_error { gl.GetError() } else { 0 }
4352 })
4353 })
4354 .map_err(|e| format!("Screenshot readback failed: {e:?}"))?;
4355 if error != 0 {
4356 return Err(format!("Screenshot readback GL error: 0x{error:x}"));
4357 }
4358 Ok(())
4359}
4360
4361enum ShotPixels {
4363 Rendered,
4365 Owned(computer_use::ScreenshotBuffer),
4367 Gpu(computer_use::GpuReadback),
4369}
4370
4371fn screenshot_len(width: i32, height: i32) -> Option<usize> {
4373 usize::try_from(width)
4374 .ok()
4375 .zip(usize::try_from(height).ok())
4376 .filter(|(w, h)| *w > 0 && *h > 0)
4377 .and_then(|(w, h)| w.checked_mul(h))
4378 .and_then(|n| n.checked_mul(4))
4379}
4380
4381fn screenshot_pixels(
4384 pixels: &[u8],
4385 width: i32,
4386 height: i32,
4387) -> Result<computer_use::ScreenshotBuffer, String> {
4388 let expected = screenshot_len(width, height)
4389 .ok_or_else(|| "Screenshot dimensions are invalid".to_string())?;
4390 let pixels = pixels
4391 .get(..expected)
4392 .ok_or_else(|| "Screenshot readback produced incomplete pixels".to_string())?;
4393 Ok(computer_use::ScreenshotBuffer::copy_of(pixels))
4394}
4395
4396fn validate_host_screenshot(
4398 frame: &wayland::host::HostCpuFrame,
4399 width: i32,
4400 height: i32,
4401) -> Result<computer_use::ScreenshotPixelFormat, String> {
4402 if width <= 0 || height <= 0 {
4403 return Err("Screenshot host dimensions are invalid".to_string());
4404 }
4405 let src_bpp = match frame.format {
4406 f if f == wl_shm::Format::Bgr888 as u32 || f == wl_shm::Format::Rgb888 as u32 => 3,
4407 f if f == wl_shm::Format::Xrgb8888 as u32
4408 || f == wl_shm::Format::Argb8888 as u32
4409 || f == wl_shm::Format::Xbgr8888 as u32
4410 || f == wl_shm::Format::Abgr8888 as u32 =>
4411 {
4412 4
4413 }
4414 _ => return Err("Screenshot host pixel format is unsupported".to_string()),
4415 };
4416 let row = (width as usize)
4417 .checked_mul(src_bpp)
4418 .ok_or_else(|| "Screenshot host dimensions overflow".to_string())?;
4419 let required = (height as usize - 1)
4420 .checked_mul(frame.stride)
4421 .and_then(|n| n.checked_add(row))
4422 .and_then(|n| n.checked_add(frame.offset));
4423 if frame.stride < row || required.is_none_or(|n| n > (*frame.map).as_ref().len()) {
4424 return Err("Screenshot host frame is incomplete".to_string());
4425 }
4426 Ok(
4427 if frame.format == wl_shm::Format::Xrgb8888 as u32
4428 || frame.format == wl_shm::Format::Xbgr8888 as u32
4429 {
4430 computer_use::ScreenshotPixelFormat::Bgrx
4431 } else {
4432 computer_use::ScreenshotPixelFormat::Bgra
4433 },
4434 )
4435}
4436
4437#[cfg(test)]
4438mod screenshot_frame_tests {
4439 use super::{screenshot_pixels, validate_host_screenshot};
4440 use crate::computer_use::ScreenshotPixelFormat;
4441 use crate::wayland::host::HostCpuFrame;
4442 use smithay::reexports::wayland_server::protocol::wl_shm;
4443 use std::sync::Arc;
4444
4445 #[test]
4446 fn screenshot_readbacks_forget_their_mappings_before_the_context_goes() {
4447 let mapping = [0u8; 8];
4448 let slot = Arc::new(unsafe {
4450 crate::computer_use::ReadbackSlot::new(3, mapping.as_ptr(), mapping.len())
4451 });
4452 let held = Arc::clone(&slot);
4453 let then = std::time::Instant::now();
4454 let mut readbacks = super::ScreenshotReadbacks::default();
4455 readbacks.slots.push((slot, then));
4456 assert!(!readbacks.has_idle(then + super::READBACK_SLOT_IDLE));
4458 drop(readbacks);
4459 assert_eq!(
4460 held.copy_out(8).err().as_deref(),
4461 Some("Screenshot compositor stopped"),
4462 "a screenshot still holding a slot cannot read a mapping its context let go"
4463 );
4464 let mut idle = super::ScreenshotReadbacks::default();
4465 let free = Arc::new(unsafe {
4467 crate::computer_use::ReadbackSlot::new(4, mapping.as_ptr(), mapping.len())
4468 });
4469 idle.slots.push((free, then));
4470 assert!(!idle.has_idle(then));
4471 assert!(idle.has_idle(then + super::READBACK_SLOT_IDLE));
4472 }
4473
4474 #[test]
4475 fn screenshot_owns_only_the_current_frame_pixels() {
4476 let mut source = vec![1, 2, 3, 4, 5, 6, 7, 8, 99, 99, 99, 99];
4477 let pixels = screenshot_pixels(&source, 2, 1).unwrap();
4478 source.fill(0);
4479 assert_eq!(&*pixels, [1, 2, 3, 4, 5, 6, 7, 8]);
4480 }
4481
4482 #[test]
4483 fn screenshot_rejects_invalid_dimensions_and_short_pixels() {
4484 for (width, height) in [(0, 1), (1, 0), (-1, 2), (2, -1), (2, 2)] {
4485 assert!(screenshot_pixels(&[0; 12], width, height).is_err());
4486 }
4487 }
4488
4489 fn host_frame(
4490 bytes: usize,
4491 stride: usize,
4492 offset: usize,
4493 format: wl_shm::Format,
4494 ) -> HostCpuFrame {
4495 HostCpuFrame {
4496 map: Arc::new(vec![0u8; bytes]),
4497 offset,
4498 stride,
4499 format: format as u32,
4500 }
4501 }
4502
4503 #[test]
4504 fn screenshot_validates_host_rows_offset_and_truncation() {
4505 for (format, row) in [(wl_shm::Format::Argb8888, 8), (wl_shm::Format::Bgr888, 6)] {
4506 let stride = row + 4;
4507 let required = 3 + stride + row;
4508 assert!(
4509 validate_host_screenshot(&host_frame(required, stride, 3, format), 2, 2).is_ok()
4510 );
4511 assert!(
4512 validate_host_screenshot(&host_frame(required - 1, stride, 3, format), 2, 2)
4513 .is_err()
4514 );
4515 assert!(
4516 validate_host_screenshot(&host_frame(required, row - 1, 3, format), 2, 2).is_err()
4517 );
4518 }
4519 }
4520
4521 #[test]
4522 fn screenshot_retains_host_alpha_semantics_after_bgra_conversion() {
4523 for (source, expected) in [
4524 (wl_shm::Format::Xrgb8888, ScreenshotPixelFormat::Bgrx),
4525 (wl_shm::Format::Xbgr8888, ScreenshotPixelFormat::Bgrx),
4526 (wl_shm::Format::Argb8888, ScreenshotPixelFormat::Bgra),
4527 (wl_shm::Format::Abgr8888, ScreenshotPixelFormat::Bgra),
4528 (wl_shm::Format::Rgb888, ScreenshotPixelFormat::Bgra),
4529 (wl_shm::Format::Bgr888, ScreenshotPixelFormat::Bgra),
4530 ] {
4531 assert_eq!(
4532 validate_host_screenshot(&host_frame(16, 8, 0, source), 2, 2).unwrap(),
4533 expected
4534 );
4535 }
4536 }
4537
4538 #[test]
4539 fn screenshot_rejects_unsupported_host_formats_and_dimensions() {
4540 let mut frame = host_frame(16, 8, 0, wl_shm::Format::Argb8888);
4541 assert!(validate_host_screenshot(&frame, 0, 2).is_err());
4542 assert!(validate_host_screenshot(&frame, 2, 0).is_err());
4543 frame.offset = usize::MAX;
4544 assert!(validate_host_screenshot(&frame, 2, 2).is_err());
4545 frame.offset = 0;
4546 frame.format = u32::MAX;
4547 assert!(validate_host_screenshot(&frame, 2, 2).is_err());
4548 }
4549}
4550
4551fn wait_render_fence(sync: &SyncPoint, display_id: u32) -> bool {
4562 let reached = match sync.get::<EGLFence>() {
4563 Some(fence) => fence
4564 .client_wait(Some(RENDER_FENCE_TIMEOUT), true)
4565 .unwrap_or(false),
4566 None => sync.wait().is_ok(),
4567 };
4568 if !reached {
4569 eprintln!(
4570 "[Wayland] Display {display_id}: render fence not signaled within {:?}; frame dropped.",
4571 RENDER_FENCE_TIMEOUT
4572 );
4573 }
4574 reached
4575}
4576
4577fn render_pass(state: &mut AppState, trigger: TickTrigger) -> bool {
4581 let drivers: Vec<bool> = state
4582 .output_nodes
4583 .iter()
4584 .map(|n| drives_screen(&state.output_nodes, n))
4585 .collect();
4586 let mut nodes = std::mem::take(&mut state.output_nodes);
4587 let mut any_pool_busy = false;
4588 for (node, drives) in nodes.iter_mut().zip(drivers) {
4589 if render_node_tick(state, node, trigger, drives) {
4590 any_pool_busy = true;
4591 }
4592 }
4593 state.output_nodes = nodes;
4594 any_pool_busy
4595}
4596
4597fn serve_changes(state: &mut AppState) {
4602 let now = Instant::now();
4603 let open: Vec<u32> = state
4604 .output_nodes
4605 .iter()
4606 .filter_map(|n| {
4607 let cap = n.capture.as_ref()?;
4608 (cap.change_pending && cap.pace.due(TickTrigger::Damage, capture_period(cap), now))
4609 .then_some(n.id)
4610 })
4611 .collect();
4612 if open.is_empty() {
4613 return;
4614 }
4615 state.space.refresh();
4616 for id in open {
4617 render_display(state, id, TickTrigger::Damage);
4618 }
4619}
4620
4621fn answer_frame_callbacks(state: &mut AppState) {
4626 let now = Instant::now();
4627 let time = state.clock.now();
4628 for i in 0..state.output_nodes.len() {
4629 let node = &state.output_nodes[i];
4630 let Some(cap) = node.capture.as_ref() else {
4631 continue;
4632 };
4633 if state
4634 .host
4635 .as_ref()
4636 .is_some_and(|h| h.has_output_for(node.id))
4637 {
4638 continue;
4639 }
4640 let Some(opened) = cap.pace.window_opened(capture_period(cap), now) else {
4641 continue;
4642 };
4643 if cap.callbacks_at.is_some_and(|at| at >= opened) {
4644 continue;
4645 }
4646 let owed = cap.change_pending;
4649 let output = node.output.clone();
4650 if !owed && drives_screen(&state.output_nodes, node) {
4651 for window in state.space.elements_for_output(&output) {
4652 window.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
4653 Some(output.clone())
4654 });
4655 }
4656 for layer in layer_map_for_output(&output).layers() {
4657 layer.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
4658 Some(output.clone())
4659 });
4660 }
4661 }
4662 if let Some(cap) = state.output_nodes[i].capture.as_mut() {
4663 cap.callbacks_at = Some(opened);
4664 cap.callbacks_owed = owed;
4665 }
4666 }
4667}
4668
4669fn render_display(state: &mut AppState, display_id: u32, trigger: TickTrigger) {
4671 let drives = state
4672 .output_nodes
4673 .iter()
4674 .find(|n| n.id == display_id)
4675 .is_some_and(|n| drives_screen(&state.output_nodes, n));
4676 let mut nodes = std::mem::take(&mut state.output_nodes);
4677 if let Some(node) = nodes.iter_mut().find(|n| n.id == display_id) {
4678 render_node_tick(state, node, trigger, drives);
4679 }
4680 state.output_nodes = nodes;
4681}
4682
4683fn drives_screen(
4689 nodes: &[wayland::frontend::OutputNode],
4690 node: &wayland::frontend::OutputNode,
4691) -> bool {
4692 let screen = node.owner.unwrap_or(node.id);
4693 let fps = |n: &wayland::frontend::OutputNode| {
4694 n.capture
4695 .as_ref()
4696 .map(|c| c.settings.target_fps)
4697 .unwrap_or(0.0)
4698 };
4699 let own_fps = fps(node);
4700 !nodes.iter().any(|n| {
4701 n.owner.unwrap_or(n.id) == screen
4702 && n.capture.is_some()
4703 && (fps(n) > own_fps || (fps(n) == own_fps && n.id < node.id))
4704 })
4705}
4706
4707fn render_node_tick(
4723 state: &mut AppState,
4724 node: &mut wayland::frontend::OutputNode,
4725 trigger: TickTrigger,
4726 drives_screen: bool,
4727) -> bool {
4728 state.pending_screenshots.prune(Instant::now());
4729 let take_screenshot = state.pending_screenshots.contains(node.id);
4730 let copy_frame_wanted = node.owner.is_none() && state.copy_frame_pending_for(&node.output);
4732 if node.capture.is_none() && !take_screenshot && !copy_frame_wanted {
4733 return false;
4734 }
4735
4736 if let Some(cap) = node.capture.as_ref()
4738 && !take_screenshot
4739 && !cap.pace.due(trigger, capture_period(cap), Instant::now())
4740 {
4741 return false;
4742 }
4743
4744 let output = node.output.clone();
4745 let origin: Point<i32, smithay::utils::Logical> = node.pos.into();
4746 let output_scale_val = output.current_scale().fractional_scale();
4747 let (width, height) = match node.capture.as_ref() {
4748 Some(c) => (c.settings.width, c.settings.height),
4749 None if node.owner.is_some() => node.view_size,
4750 None => output
4751 .current_mode()
4752 .map(|m| (m.size.w, m.size.h))
4753 .unwrap_or((0, 0)),
4754 };
4755 if width <= 0 || height <= 0 {
4756 return false;
4757 }
4758 if node.frame_buffer.len() < (width as usize) * (height as usize) * 4 {
4759 node.frame_buffer = vec![0u8; (width as usize) * (height as usize) * 4];
4760 }
4761 if node.owner.is_some()
4764 && (node.view_size != (width, height) || (node.view_scale - output_scale_val).abs() > 1e-6)
4765 {
4766 node.damage_tracker =
4767 OutputDamageTracker::new((width, height), output_scale_val, Transform::Normal);
4768 node.content_tracker = None;
4769 node.view_size = (width, height);
4770 node.view_scale = output_scale_val;
4771 }
4772 let logical_w = (f64::from(width) / output_scale_val).round();
4773 let logical_h = (f64::from(height) / output_scale_val).round();
4774
4775 let hold_frame = match node.content_hold_until {
4781 Some(deadline)
4782 if state.host.is_none()
4783 && Instant::now() < deadline
4784 && !wayland::frontend::output_content_covers(
4785 &state.space,
4786 node.owner.unwrap_or(node.id),
4787 logical_w,
4788 logical_h,
4789 ) =>
4790 {
4791 true
4792 }
4793 Some(_) => {
4794 node.content_hold_until = None;
4795 false
4796 }
4797 None => false,
4798 };
4799
4800 if let Some(cap) = node.capture.as_mut()
4802 && cap
4803 .recording_sink
4804 .as_ref()
4805 .map(|s| s.should_force_idr())
4806 .unwrap_or(false)
4807 {
4808 cap.request_idr();
4809 }
4810 let requested_idr = node
4811 .capture
4812 .as_ref()
4813 .map(|c| c.pending_force_idr)
4814 .unwrap_or(false);
4815 let hw_idr_pending = node
4819 .capture
4820 .as_ref()
4821 .map(|c| c.encode_controls.force_idr.load(Ordering::Relaxed))
4822 .unwrap_or(false);
4823 let want_idr_for_host = requested_idr || hw_idr_pending;
4824
4825 if let Some(cap) = node.capture.as_mut()
4830 && cap.encode_join.as_ref().is_some_and(|j| j.is_finished())
4831 {
4832 let host_owned = state
4833 .host
4834 .as_ref()
4835 .is_some_and(|h| h.has_output_for(node.id));
4836 let prior = cap.encode_join.take().and_then(|j| j.join().ok().flatten());
4837 if let Some(pool) = cap.encode_pool.take() {
4838 pool.shutdown();
4839 }
4840 if end_zero_copy(cap) {
4841 let _report = report::enter(&cap.report);
4842 report::capture("readback", false);
4843 report::capture_reason("the zero-copy encoder failed repeatedly and was given up");
4844 release_host_inflight(state.host.as_ref(), cap, node.id, true);
4845 if host_owned && let Some(h) = state.host.as_ref() {
4847 h.set_buffer_type(node.id, false);
4848 }
4849 } else {
4850 eprintln!("[Wayland] encode thread died; rebuilding the readback path.");
4851 }
4852 let s = &cap.settings;
4853 let try_gpu = s.codec.is_video() && !(s.use_cpu || s.encode_node_index == -1);
4854 bootstrap_readback_pool(
4856 cap,
4857 node.id,
4858 state.use_gpu && !host_owned,
4859 try_gpu,
4860 prior,
4861 None,
4862 );
4863 cap.request_idr();
4864 }
4865
4866 let mut pool_slot: Option<(usize, Vec<u8>)> = None;
4867 if !hold_frame
4868 && let Some(cap) = node.capture.as_ref()
4869 && let Some(ref pool) = cap.encode_pool
4870 {
4871 pool_slot = pool.try_begin();
4872 if pool_slot.is_none() {
4873 return true;
4874 }
4875 }
4876 let mut gpu_slot: Option<(usize, Dmabuf)> = None;
4879 if let Some(cap) = node.capture.as_ref()
4880 && let Some(ref pool) = cap.gpu_pool
4881 {
4882 gpu_slot = pool.try_begin();
4883 if gpu_slot.is_none() {
4884 return true;
4885 }
4886 }
4887
4888 let loc_enum = node
4889 .capture
4890 .as_ref()
4891 .map(|c| c.settings.watermark_location_enum)
4892 .unwrap_or(state.settings.watermark_location_enum);
4893 node.overlay_state.update_position(width, height, loc_enum);
4894
4895 let host_mode = state
4899 .host
4900 .as_ref()
4901 .map(|h| h.has_output_for(node.id))
4902 .unwrap_or(false);
4903 if !host_mode && let Some(cap) = node.capture.as_mut() {
4904 let period = capture_period(cap);
4905 let paced = FramePace::input_paced(state.input_interval, period);
4906 cap.pace.ticked(trigger, period, Instant::now(), paced);
4907 cap.change_pending = false;
4908 }
4909
4910 let pointer_local: Option<Point<f64, smithay::utils::Logical>> =
4913 state.pointer_shown_at().and_then(|pos| {
4914 let rect = Rectangle::<f64, smithay::utils::Logical>::new(
4915 origin.to_f64(),
4916 (logical_w, logical_h).into(),
4917 );
4918 if rect.contains(pos) {
4919 Some(pos - origin.to_f64())
4920 } else {
4921 None
4922 }
4923 });
4924
4925 let composite_ns = wayland::host::now_ns();
4927 let mut render_success = false;
4928 let mut screenshot_result = None;
4929 let mut render_sync = None;
4930 let mut damage_rects: Vec<Rectangle<i32, Physical>> = Vec::new();
4931 let needs_full = node
4932 .capture
4933 .as_ref()
4934 .map(|c| c.needs_full_render)
4935 .unwrap_or(!node.target_seeded);
4936
4937 if state.host.is_some() && !host_mode {
4938 cancel_slots(node, &mut pool_slot, &mut gpu_slot);
4941 return false;
4942 }
4943 let mut source_dmabuf: Option<Dmabuf> = None;
4947 let mut host_enc_retained = false;
4948 let mut new_stamp: Option<i64> = None;
4949 let mut screenshot_format = if state.use_gpu {
4952 computer_use::ScreenshotPixelFormat::Rgba
4953 } else {
4954 computer_use::ScreenshotPixelFormat::Bgra
4955 };
4956 if host_mode {
4957 const RETAINED_OK: u8 = 0;
4958 const RETAINED_NONE: u8 = 1;
4959 const RETAINED_CPU_FRAME: u8 = 2;
4962 const RETAINED_GPU_FRAME: u8 = 3;
4963 let host_idx = node.id;
4964 let gpu_encoder = node
4965 .capture
4966 .as_ref()
4967 .map(|c| c.zero_copy.is_some())
4968 .unwrap_or(false);
4969 let host = state.host.take().unwrap();
4972 if let Some(cap) = node.capture.as_mut() {
4973 release_host_inflight(Some(&host), cap, host_idx, false);
4974 }
4975 let expect = node
4981 .capture
4982 .as_ref()
4983 .map(|c| (c.settings.width, c.settings.height));
4984 let new_frame = match (host.try_take_frame(host_idx), expect) {
4985 (Some(f), Some((w, h))) if f.width != w || f.height != h => {
4986 retain_host_frame(&host, node.capture.as_mut(), host_idx, f);
4987 None
4988 }
4989 (f, _) => f,
4990 };
4991 let have_new = new_frame.is_some();
4992 new_stamp = new_frame.as_ref().map(|f| f.stamp_ns);
4993 let streaming = node
4999 .capture
5000 .as_ref()
5001 .map(|c| c.settings.video_streaming_mode)
5002 .unwrap_or(false);
5003 let wm_active = node.overlay_state.is_active();
5004 let wm_animated = wm_active && node.overlay_state.is_animated();
5005 let now = Instant::now();
5011 let period = node.capture.as_ref().map(capture_period);
5012 let host_quiet = node.capture.as_ref().is_none_or(|cap| {
5013 cap.pace
5014 .since_last_tick(now)
5015 .is_none_or(|since| since >= capture_period(cap).mul_f64(HOST_QUIET_PERIODS))
5016 });
5017 if !have_new && host_quiet {
5018 host.detach_retained(host_idx);
5019 if let Some(cap) = node.capture.as_mut() {
5020 detach_retained_gpu(
5021 state.gles_renderer.as_mut(),
5022 state.gbm_device.as_ref(),
5023 &host,
5024 cap,
5025 host_idx,
5026 (width, height),
5027 );
5028 }
5029 }
5030 if !have_new
5031 && !want_idr_for_host
5032 && !take_screenshot
5033 && !wm_animated
5034 && !(streaming && host_quiet)
5035 {
5036 cancel_slots(node, &mut pool_slot, &mut gpu_slot);
5037 if let Some(cap) = node.capture.as_mut()
5038 && let Some(period) = period
5039 {
5040 let held = if streaming {
5041 cap.pace
5042 .last_tick
5043 .map_or(now, |last| last + period.mul_f64(HOST_QUIET_PERIODS))
5044 } else {
5045 now + period
5046 };
5047 cap.pace.defer(held.max(now + Duration::from_millis(1)));
5048 }
5049 state.host = Some(host);
5050 return false;
5051 }
5052 if let Some(cap) = node.capture.as_mut()
5053 && let Some(period) = period
5054 {
5055 cap.pace.ticked(trigger, period, now, false);
5056 cap.change_pending = false;
5057 }
5058 if let Some(f) = new_frame {
5059 retain_host_frame(&host, node.capture.as_mut(), host_idx, f);
5060 }
5061 let mut wm_drawn = false;
5066 let outcome = host.with_retained(host_idx, |r| {
5067 let Some(f) = r else { return RETAINED_NONE };
5068 if (f.width, f.height) != (width, height) {
5071 return RETAINED_NONE;
5072 }
5073 damage_rects = if have_new {
5074 f.damage.clone()
5075 } else {
5076 Vec::new()
5077 };
5078 if let Some(cpu) = f.cpu.as_ref() {
5079 if gpu_encoder {
5080 return RETAINED_CPU_FRAME;
5081 }
5082 if take_screenshot {
5083 screenshot_result =
5084 Some(validate_host_screenshot(cpu, width, height).map(|format| {
5085 screenshot_format = format;
5086 ShotPixels::Rendered
5087 }));
5088 }
5089 if let Some((_, ref mut buf)) = pool_slot {
5090 cpu.write_bgra(f.width, f.height, buf);
5091 if wm_active {
5092 node.overlay_state.blend_bgra(
5093 buf,
5094 (f.width as usize) * 4,
5095 f.width,
5096 f.height,
5097 );
5098 wm_drawn = true;
5099 }
5100 }
5101 cpu.write_bgra(f.width, f.height, &mut node.frame_buffer);
5102 if wm_active {
5103 node.overlay_state.blend_bgra(
5104 &mut node.frame_buffer,
5105 (f.width as usize) * 4,
5106 f.width,
5107 f.height,
5108 );
5109 }
5110 } else if let Some(dmabuf) = f.dmabuf.as_ref() {
5111 if !gpu_encoder {
5112 return RETAINED_GPU_FRAME;
5113 }
5114 source_dmabuf = Some(dmabuf.clone());
5115 host_enc_retained = true;
5116 }
5117 RETAINED_OK
5118 });
5119 if have_new && let Some(src) = source_dmabuf.as_ref() {
5121 wayland::host::wait_gpu_done(src);
5122 }
5123 if let Some(src) = source_dmabuf.clone() {
5129 if take_screenshot && matches!(src.format().code, Fourcc::Xrgb8888 | Fourcc::Xbgr8888) {
5130 screenshot_format = computer_use::ScreenshotPixelFormat::Rgbx;
5131 }
5132 if wm_active && let Some(renderer) = state.gles_renderer.as_mut() {
5133 if wm_animated {
5134 let target = match gpu_slot.as_mut() {
5135 Some((_, target)) => Some(target),
5136 None => node.offscreen_buffer.as_mut().map(|(_, target)| target),
5137 };
5138 if let Some(target) = target {
5139 match compose_host_frame(
5140 renderer,
5141 Some(&node.overlay_state),
5142 &src,
5143 target,
5144 (width, height),
5145 ) {
5146 Ok(sync) => {
5147 render_sync = Some(sync);
5148 source_dmabuf = Some(target.clone());
5149 host_enc_retained = false;
5150 wm_drawn = true;
5151 }
5152 Err(e) => warn_once_host_watermark(&e),
5153 }
5154 }
5155 } else if have_new {
5156 let mut target = src.clone();
5157 match draw_host_watermark(
5158 renderer,
5159 &node.overlay_state,
5160 &mut target,
5161 (width, height),
5162 ) {
5163 Ok(sync) => {
5164 render_sync = Some(sync);
5165 wm_drawn = true;
5166 }
5167 Err(e) => warn_once_host_watermark(&e),
5168 }
5169 }
5170 }
5171 if take_screenshot && let Some(renderer) = state.gles_renderer.as_mut() {
5172 let mut shot = source_dmabuf.clone().unwrap_or(src);
5173 screenshot_result = Some((|| {
5174 let fb = renderer
5175 .bind(&mut shot)
5176 .map_err(|e| format!("Screenshot bind failed: {e:?}"))?;
5177 let rect = Rectangle::new((0, 0).into(), (width, height).into());
5178 let mapping = renderer
5179 .copy_framebuffer(&fb, rect, Fourcc::Abgr8888)
5180 .map_err(|e| format!("Screenshot copy failed: {e:?}"))?;
5181 let data = renderer
5182 .map_texture(&mapping)
5183 .map_err(|e| format!("Screenshot map failed: {e:?}"))?;
5184 if Some(data.len()) != screenshot_len(width, height) {
5185 return Err("Screenshot readback produced incomplete pixels".to_string());
5186 }
5187 Ok(ShotPixels::Owned(computer_use::ScreenshotBuffer::copy_of(
5188 data,
5189 )))
5190 })());
5191 }
5192 }
5193 if wm_drawn && let Some(rect) = node.overlay_state.damage_rect(width, height) {
5194 damage_rects.push(rect);
5195 }
5196 state.host = Some(host);
5197 if outcome != RETAINED_OK {
5198 cancel_slots(node, &mut pool_slot, &mut gpu_slot);
5199 if outcome == RETAINED_GPU_FRAME {
5203 if let Some(h) = state.host.as_ref() {
5207 h.set_buffer_type(node.id, false);
5208 }
5209 } else if outcome == RETAINED_CPU_FRAME
5210 && let Some(cap) = node.capture.as_mut()
5211 {
5212 eprintln!(
5216 "[HostCapture] host delivers software frames; demoting the zero-copy encoder to readback encode."
5217 );
5218 let _report = report::enter(&cap.report);
5219 report::capture("readback", false);
5220 report::zero_copy_available(false);
5221 report::capture_reason("the host compositor delivers software frames only");
5222 end_zero_copy(cap);
5223 let predecessor = cap.encode_join.take();
5224 release_host_inflight(state.host.as_ref(), cap, node.id, true);
5225 let s = &cap.settings;
5226 let try_gpu = s.codec.is_video() && !(s.use_cpu || s.encode_node_index == -1);
5227 bootstrap_readback_pool(cap, node.id, false, try_gpu, None, predecessor);
5228 cap.request_idr();
5229 if let Some(h) = state.host.as_ref() {
5231 h.set_buffer_type(node.id, false);
5232 }
5233 }
5234 return false;
5235 }
5236 render_success = true;
5237 }
5238
5239 if !host_mode && let Some(cap) = node.capture.as_mut() {
5240 release_scanout_inflight(cap, false);
5241 }
5242 let direct_wanted: Result<(), &'static str> = if take_screenshot {
5249 Err("a screenshot reads the composite")
5250 } else if copy_frame_wanted {
5251 Err("a copy-capture client reads the composite")
5252 } else if hold_frame {
5253 Err("the frames are held")
5254 } else if node
5255 .capture
5256 .as_ref()
5257 .is_some_and(|c| c.encode_controls.scanout_refused.load(Ordering::Relaxed))
5258 {
5259 Err("the encoder refused a client's buffer")
5260 } else {
5261 Ok(())
5262 };
5263 let scanout_planes = node.capture.as_ref().map_or(1, |c| {
5264 c.encode_controls.scanout_planes.load(Ordering::Relaxed) as usize
5265 });
5266 let mut scanout_hold: Option<SurfaceBuffer> = None;
5270 let mut shm_hold: Option<SurfaceBuffer> = None;
5271 let cursor_time = Duration::from_millis(u64::from(state.clock.now().as_millis()));
5272 let drag_icon = state
5273 .dnd_icon
5274 .as_ref()
5275 .zip(pointer_local)
5276 .map(|((surface, offset), pointer)| (surface, pointer + offset.to_f64()));
5277 let software_cursor = |pos| SoftwareCursor {
5278 theme: &state.cursor_helper,
5279 icon: &state.current_cursor_icon,
5280 pos,
5281 time: cursor_time,
5282 };
5283
5284 if !host_mode && state.use_gpu {
5285 if let Some(renderer) = state.gles_renderer.as_mut() {
5286 let mut cap = node.capture.as_mut();
5287 let slot_id = gpu_slot.as_ref().map(|(id, _)| *id);
5289 let slot_age = slot_id.map(|id| {
5290 cap.as_ref().map_or(0, |c| {
5291 if c.pool_last_render[id] == 0 {
5292 0
5293 } else {
5294 (c.render_seq + 1 - c.pool_last_render[id]) as usize
5295 }
5296 })
5297 });
5298 let scene = FrameScene {
5299 space: &state.space,
5300 wm: state.wm.as_ref(),
5301 cursor: pointer_local
5302 .filter(|_| state.render_cursor_on_framebuffer)
5303 .map(software_cursor),
5304 drag_icon,
5305 overlay: &node.overlay_state,
5306 output: &output,
5307 origin,
5308 size: (width, height),
5309 scale: output_scale_val,
5310 };
5311 let elements = composition_elements(renderer, &scene);
5312 let direct = match (direct_wanted, slot_id) {
5314 (Ok(()), Some(_)) => {
5315 scanout_candidate(&elements, (width, height), output_scale_val)
5316 .and_then(|s| scanout_dmabuf(s, (width, height), scanout_planes))
5317 }
5318 (Err(why), _) => Err(why),
5319 (Ok(()), None) => Err("the readback path composites"),
5320 };
5321 if let Some(c) = cap.as_deref_mut() {
5322 let said = direct
5323 .as_ref()
5324 .map(|(_, _, what)| what.clone())
5325 .map_err(|why| *why);
5326 note_direct(c, node.id, &said);
5327 }
5328 match direct {
5329 Ok((client, hold, _)) => {
5330 let full = Rectangle::<i32, Physical>::from_size((width, height).into());
5331 damage_rects = node
5336 .content_tracker
5337 .get_or_insert_with(|| {
5338 OutputDamageTracker::from_mode_source(
5339 node.damage_tracker.mode().clone(),
5340 )
5341 })
5342 .damage_output(1, &elements)
5343 .map_or_else(
5344 |_| vec![full],
5345 |(damage, _)| damage.cloned().unwrap_or_default(),
5346 );
5347 let _ = node.damage_tracker.damage_output(1, &elements);
5348 render_success = true;
5349 source_dmabuf = Some(client);
5350 scanout_hold = Some(hold);
5351 if let (Some(id), Some(c)) = (slot_id, cap.as_deref_mut()) {
5352 c.render_seq += 1;
5353 c.pool_last_render[id] = 0;
5355 }
5356 }
5357 Err(_) => {
5358 let target = match gpu_slot.as_mut() {
5359 Some((_, dmabuf)) => Some(dmabuf),
5360 None => node
5363 .offscreen_buffer
5364 .as_mut()
5365 .filter(|(bo, _)| {
5366 (bo.width() as i32, bo.height() as i32) == (width, height)
5367 })
5368 .map(|(_, dmabuf)| dmabuf),
5369 };
5370 if let Some(dmabuf) = target {
5371 let render_age = if node.overlay_state.is_animated() || needs_full {
5372 0
5373 } else {
5374 slot_age.unwrap_or(1)
5375 };
5376 match renderer.bind(dmabuf) {
5377 Ok(mut frame) => {
5378 let changed = slot_id
5381 .and_then(|_| {
5382 node.content_tracker
5383 .get_or_insert_with(|| {
5384 OutputDamageTracker::from_mode_source(
5385 node.damage_tracker.mode().clone(),
5386 )
5387 })
5388 .damage_output(1, &elements)
5389 .ok()
5390 })
5391 .map(|(damage, _)| damage.cloned().unwrap_or_default());
5392 match node.damage_tracker.render_output(
5393 renderer,
5394 &mut frame,
5395 render_age,
5396 &elements,
5397 [0.1, 0.1, 0.1, 1.0],
5398 ) {
5399 Ok(result) => {
5400 render_success = true;
5401 if let Some(damage) = result.damage {
5402 damage_rects = damage.clone();
5403 node.undrawn_ticks = 0;
5404 } else {
5405 node.undrawn_ticks += 1;
5406 if node.undrawn_ticks >= UNDRAWN_TICKS_PER_FRAME {
5407 node.undrawn_ticks = 0;
5408 let _ = renderer
5409 .render(
5410 &mut frame,
5411 (width, height).into(),
5412 Transform::Normal,
5413 )
5414 .and_then(|f| f.finish());
5415 }
5416 }
5417 if wait_render_fence(&result.sync, node.id) {
5418 if let Some(c) = cap.as_deref_mut() {
5419 c.needs_full_render = false;
5420 }
5421 } else {
5422 render_success = false;
5426 damage_rects.clear();
5427 if let Some(c) = cap.as_deref_mut() {
5428 c.needs_full_render = true;
5429 }
5430 }
5431 if render_success
5432 && let Some(changed) =
5433 changed.filter(|_| render_age > 1)
5434 {
5435 damage_rects = changed;
5436 }
5437 }
5438 Err(e) => eprintln!("Render error: {:?}", e),
5439 }
5440 if let (Some(id), Some(c)) = (slot_id, cap.as_deref_mut()) {
5441 c.render_seq += 1;
5442 c.pool_last_render[id] =
5443 if render_success { c.render_seq } else { 0 };
5444 }
5445 if let Some(c) = cap {
5446 if !damage_rects.is_empty() {
5447 c.content_gen += 1;
5448 }
5449 if let Some((id, ref mut buf)) = pool_slot {
5450 if render_success && take_screenshot {
5456 screenshot_result = read_back_rgba_async(
5457 renderer,
5458 &mut state.screenshot_readbacks,
5459 &mut frame,
5460 width,
5461 height,
5462 )
5463 .map(|readback| readback.map(ShotPixels::Gpu));
5464 }
5465 if render_success
5466 && take_screenshot
5467 && screenshot_result.is_none()
5468 {
5469 let result = read_back_rgba_result(
5470 renderer, &mut frame, width, height, buf, true,
5471 );
5472 if result.is_ok() {
5473 c.pool_content_gen[id] = c.content_gen;
5474 } else {
5475 render_success = false;
5476 c.needs_full_render = true;
5477 }
5478 screenshot_result =
5479 Some(result.map(|()| ShotPixels::Rendered));
5480 } else if render_success
5481 && c.pool_content_gen[id] != c.content_gen
5482 {
5483 read_back_rgba(
5484 renderer, &mut frame, width, height, buf,
5485 );
5486 c.pool_content_gen[id] = c.content_gen;
5487 }
5488 }
5489 }
5490 if render_success && pool_slot.is_none() && take_screenshot {
5491 screenshot_result = read_back_rgba_async(
5492 renderer,
5493 &mut state.screenshot_readbacks,
5494 &mut frame,
5495 width,
5496 height,
5497 )
5498 .map(|readback| readback.map(ShotPixels::Gpu));
5499 }
5500 if render_success
5501 && pool_slot.is_none()
5502 && take_screenshot
5503 && screenshot_result.is_none()
5504 {
5505 let mut pixels = computer_use::ScreenshotBuffer::with_len(
5506 screenshot_len(width, height).unwrap_or(0),
5507 );
5508 screenshot_result = Some(
5509 read_back_rgba_result(
5510 renderer,
5511 &mut frame,
5512 width,
5513 height,
5514 &mut pixels,
5515 true,
5516 )
5517 .map(|()| ShotPixels::Owned(pixels)),
5518 );
5519 }
5520 }
5521 Err(e) => eprintln!("Failed to bind buffer: {:?}", e),
5522 }
5523 }
5524 }
5525 }
5526 }
5527 } else if !host_mode && let Some(renderer) = state.pixman_renderer.as_mut() {
5528 let mut cap = node.capture.as_mut();
5529 let buf_age = match pool_slot {
5530 Some((id, _)) => cap
5531 .as_ref()
5532 .map(|c| {
5533 if c.pool_last_render[id] == 0 {
5534 0
5535 } else {
5536 (c.render_seq + 1 - c.pool_last_render[id]) as usize
5537 }
5538 })
5539 .unwrap_or(0),
5540 None => 0,
5541 };
5542 let scene = FrameScene {
5543 space: &state.space,
5544 wm: state.wm.as_ref(),
5545 cursor: pointer_local
5546 .filter(|_| state.render_cursor_on_framebuffer)
5547 .map(software_cursor),
5548 drag_icon,
5549 overlay: &node.overlay_state,
5550 output: &output,
5551 origin,
5552 size: (width, height),
5553 scale: output_scale_val,
5554 };
5555 let elements = composition_elements(renderer, &scene);
5556 let render_age = if node.overlay_state.is_animated() || needs_full {
5557 0
5558 } else {
5559 buf_age
5560 };
5561 let changed = node
5562 .content_tracker
5563 .get_or_insert_with(|| {
5564 OutputDamageTracker::from_mode_source(node.damage_tracker.mode().clone())
5565 })
5566 .damage_output(1, &elements)
5567 .ok()
5568 .map(|(damage, _)| damage.cloned().unwrap_or_default());
5569 let direct = match (direct_wanted, pool_slot.as_ref()) {
5572 (Ok(()), Some(_)) => scanout_candidate(&elements, (width, height), output_scale_val)
5573 .and_then(|s| scanout_shm(s, (width, height))),
5574 (Err(why), _) => Err(why),
5575 (Ok(()), None) => Err("no pool buffer takes the frame"),
5576 };
5577 if let Some(c) = cap.as_deref_mut() {
5578 let said = direct
5579 .as_ref()
5580 .map(|(_, what)| what.clone())
5581 .map_err(|why| *why);
5582 note_direct(c, node.id, &said);
5583 }
5584 match direct {
5585 Ok((client, _)) => {
5586 let full = Rectangle::<i32, Physical>::from_size((width, height).into());
5587 damage_rects = changed.unwrap_or_else(|| vec![full]);
5591 let _ = node.damage_tracker.damage_output(1, &elements);
5592 render_success = true;
5593 shm_hold = Some(client);
5594 if let Some(c) = cap.as_deref_mut() {
5595 c.render_seq += 1;
5596 if let Some((id, _)) = pool_slot {
5597 c.pool_last_render[id] = 0;
5599 }
5600 }
5601 }
5602 Err(_) => {
5603 let ptr = match pool_slot {
5604 Some((_, ref mut buf)) => buf.as_mut_ptr() as *mut u32,
5605 None => node.frame_buffer.as_mut_ptr() as *mut u32,
5606 };
5607 let mut image = unsafe {
5608 pixman::Image::from_raw_mut(
5609 pixman::FormatCode::A8R8G8B8,
5610 width as usize,
5611 height as usize,
5612 ptr,
5613 (width as usize) * 4,
5614 false,
5615 )
5616 .expect("Failed to create pixman image")
5617 };
5618 match renderer.bind(&mut image) {
5619 Ok(mut frame) => {
5620 match node.damage_tracker.render_output(
5621 renderer,
5622 &mut frame,
5623 render_age,
5624 &elements,
5625 [0.1, 0.1, 0.1, 1.0],
5626 ) {
5627 Ok(result) => {
5628 render_success = true;
5629 if let Some(c) = cap.as_deref_mut() {
5630 c.needs_full_render = false;
5631 }
5632 if let Some(damage) = result.damage {
5633 damage_rects = damage.clone();
5634 }
5635 if let Some(changed) = changed.filter(|_| render_age > 1) {
5636 damage_rects = changed;
5637 }
5638 }
5639 Err(e) => eprintln!("Render error: {:?}", e),
5640 }
5641 if let Some(c) = cap {
5642 c.render_seq += 1;
5643 if render_success && let Some((id, _)) = pool_slot {
5644 c.pool_last_render[id] = c.render_seq;
5645 }
5646 }
5647 }
5648 Err(e) => eprintln!("Failed to bind pixman image: {:?}", e),
5649 }
5650 }
5651 }
5652 }
5653
5654 if take_screenshot && render_success {
5655 if !host_mode && !state.use_gpu {
5656 screenshot_result = Some(Ok(ShotPixels::Rendered));
5657 }
5658 if !host_mode
5659 && matches!(&screenshot_result, Some(Ok(ShotPixels::Rendered)))
5660 && let Some((_, buf)) = pool_slot.as_ref()
5661 {
5662 screenshot_result = Some(screenshot_pixels(buf, width, height).map(ShotPixels::Owned));
5663 }
5664 }
5665 if render_success {
5666 node.target_seeded = gpu_slot.is_none();
5668 }
5669 let answer_now = node.capture.as_ref().is_none_or(|c| c.callbacks_owed) || host_mode;
5673 if render_success && drives_screen {
5674 let time = state.clock.now();
5675 let mut feedback = OutputPresentationFeedback::new(&output);
5678 for window in state.space.elements_for_output(&output) {
5679 if answer_now {
5680 window.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
5681 Some(output.clone())
5682 });
5683 }
5684 window.take_presentation_feedback(
5685 &mut feedback,
5686 |_, _| Some(output.clone()),
5687 |_, _| wp_presentation_feedback::Kind::empty(),
5688 );
5689 }
5690 for layer in layer_map_for_output(&output).layers() {
5695 if answer_now {
5696 layer.send_frame(&output, time, Some(Duration::ZERO), |_, _| {
5697 Some(output.clone())
5698 });
5699 }
5700 layer.take_presentation_feedback(
5701 &mut feedback,
5702 |_, _| Some(output.clone()),
5703 |_, _| wp_presentation_feedback::Kind::empty(),
5704 );
5705 }
5706 if answer_now && let Some(cap) = node.capture.as_mut() {
5707 cap.callbacks_owed = false;
5708 }
5709 send_cursor_frame(state, &output, time);
5710 let refresh = match node.capture.as_ref() {
5711 Some(c) => Refresh::Fixed(Duration::from_secs_f64(
5712 1.0 / c.settings.target_fps.max(1.0),
5713 )),
5714 None => Refresh::Unknown,
5715 };
5716 node.frame_seq += 1;
5717 feedback.presented(
5718 time,
5719 refresh,
5720 node.frame_seq,
5721 wp_presentation_feedback::Kind::Vsync,
5722 );
5723 }
5724 if render_success && !host_mode && copy_frame_wanted {
5726 service_copy_frames(
5727 state,
5728 node,
5729 gpu_slot.as_ref().map(|(_, dmabuf)| dmabuf),
5730 width,
5731 height,
5732 &damage_rects,
5733 );
5734 }
5735 if render_success
5736 && !hold_frame
5737 && let Some(cap) = node.capture.as_mut()
5738 {
5739 if let Some(pool) = cap.encode_pool.as_ref() {
5740 if let Some((id, buf)) = pool_slot.take() {
5741 cap.published += 1;
5742 if let Some(hold) = shm_hold.as_ref() {
5743 cap.scanout_inflight.push((cap.published, hold.clone()));
5744 }
5745 pool.publish(WlFrame {
5746 id,
5747 buf,
5748 source: None,
5749 scanout: false,
5750 shm: shm_hold.take(),
5751 frame_id: cap.frame_counter,
5752 damage: std::mem::take(&mut damage_rects),
5753 is_animated: node.overlay_state.is_animated(),
5754 captured_ns: new_stamp.unwrap_or(composite_ns),
5755 });
5756 cap.frame_counter = cap.frame_counter.wrapping_add(1);
5757 }
5758 } else if let Some(pool) = cap.gpu_pool.clone()
5759 && let Some((id, dmabuf)) = gpu_slot.take()
5760 {
5761 if render_sync
5763 .take()
5764 .is_some_and(|sync| !wait_render_fence(&sync, node.id))
5765 {
5766 pool.cancel(id, dmabuf);
5767 } else {
5768 cap.published += 1;
5769 if host_enc_retained {
5770 cap.host_retained_use = cap.published;
5771 }
5772 let scanout = scanout_hold.is_some();
5773 if let Some(hold) = scanout_hold.take() {
5774 cap.scanout_inflight.push((cap.published, hold));
5775 }
5776 pool.publish(WlFrame {
5777 id,
5778 buf: dmabuf,
5779 source: source_dmabuf.take(),
5780 scanout,
5781 shm: None,
5782 frame_id: cap.frame_counter,
5783 damage: std::mem::take(&mut damage_rects),
5784 is_animated: node.overlay_state.is_animated(),
5785 captured_ns: new_stamp.unwrap_or(composite_ns),
5786 });
5787 cap.frame_counter = cap.frame_counter.wrapping_add(1);
5788 }
5789 }
5790 }
5791 cancel_slots(node, &mut pool_slot, &mut gpu_slot);
5792 if take_screenshot
5793 && let Some(request) = state.pending_screenshots.take(node.id, Instant::now())
5794 {
5795 let result = screenshot_result
5796 .unwrap_or_else(|| Err("Screenshot render produced no pixels".to_string()))
5797 .and_then(|pixels| {
5798 if !render_success {
5799 return Err("Screenshot render failed".to_string());
5800 }
5801 let pixels = match pixels {
5802 ShotPixels::Owned(pixels) => computer_use::ScreenshotPixels::Ready(pixels),
5803 ShotPixels::Rendered => computer_use::ScreenshotPixels::Ready(
5804 screenshot_pixels(&node.frame_buffer, width, height)?,
5805 ),
5806 ShotPixels::Gpu(readback) => computer_use::ScreenshotPixels::Gpu(readback),
5807 };
5808 Ok(computer_use::ScreenshotFrame {
5809 pixels,
5810 width: width as u32,
5811 height: height as u32,
5812 format: screenshot_format,
5813 })
5814 });
5815 let result = if request.canceled.load(Ordering::Acquire) {
5816 Err("Screenshot canceled".to_string())
5817 } else if Instant::now() >= request.deadline {
5818 Err("Screenshot timed out".to_string())
5819 } else {
5820 result
5821 };
5822 let _ = request.resp.send(result);
5823 }
5824 false
5825}
5826
5827fn rects_overlap(a: (i32, i32, i32, i32), b: (i32, i32, i32, i32)) -> bool {
5831 let (ax, ay, aw, ah) = (
5832 i64::from(a.0),
5833 i64::from(a.1),
5834 i64::from(a.2),
5835 i64::from(a.3),
5836 );
5837 let (bx, by, bw, bh) = (
5838 i64::from(b.0),
5839 i64::from(b.1),
5840 i64::from(b.2),
5841 i64::from(b.3),
5842 );
5843 aw > 0
5844 && ah > 0
5845 && bw > 0
5846 && bh > 0
5847 && ax < bx + bw
5848 && bx < ax + aw
5849 && ay < by + bh
5850 && by < ay + ah
5851}
5852
5853type OutputOverlap = (u32, &'static str, (i32, i32, i32, i32));
5856
5857fn find_output_overlap(
5863 nodes: &[wayland::frontend::OutputNode],
5864 skip_id: Option<u32>,
5865 logical: (i32, i32, i32, i32),
5866 physical: (i32, i32, i32, i32),
5867) -> Option<OutputOverlap> {
5868 for n in nodes {
5869 if Some(n.id) == skip_id {
5870 continue;
5871 }
5872 if n.owner.is_some() {
5873 continue;
5874 }
5875 if let Some(geo) = n.logical_geometry() {
5876 let other = (geo.loc.x, geo.loc.y, geo.size.w, geo.size.h);
5877 if rects_overlap(logical, other) {
5878 return Some((n.id, "logical", other));
5879 }
5880 }
5881 if let Some(mode) = n.output.current_mode() {
5882 let other = (n.pos.0, n.pos.1, mode.size.w, mode.size.h);
5883 if rects_overlap(physical, other) {
5884 return Some((n.id, "physical", other));
5885 }
5886 }
5887 }
5888 None
5889}
5890
5891fn create_output_on(
5898 state: &mut AppState,
5899 id: u32,
5900 width: i32,
5901 height: i32,
5902 x: i32,
5903 y: i32,
5904 scale: f64,
5905) -> bool {
5906 if state.node_idx_for_id(id).is_some() || width <= 0 || height <= 0 || scale <= 0.0 {
5907 return false;
5908 }
5909 if let Some(host) = state.host.as_ref() {
5912 let capacity = host.output_count();
5913 if state.output_nodes.len() >= capacity {
5914 eprintln!(
5915 "[Wayland] CreateOutput {id}: rejected, the host compositor has {capacity} output(s) and all are backing displays."
5916 );
5917 return false;
5918 }
5919 }
5920 let logical_size = (
5921 (f64::from(width) / scale).round() as i32,
5922 (f64::from(height) / scale).round() as i32,
5923 );
5924 if let Some((oid, flavor, other)) = find_output_overlap(
5925 &state.output_nodes,
5926 None,
5927 (x, y, logical_size.0, logical_size.1),
5928 (x, y, width, height),
5929 ) {
5930 eprintln!(
5931 "[Wayland] CreateOutput {id}: rejected, {flavor} rect {}x{}+{x}+{y} overlaps output {oid} at {}x{}+{}+{}.",
5932 if flavor == "logical" {
5933 logical_size.0
5934 } else {
5935 width
5936 },
5937 if flavor == "logical" {
5938 logical_size.1
5939 } else {
5940 height
5941 },
5942 other.2,
5943 other.3,
5944 other.0,
5945 other.1,
5946 );
5947 return false;
5948 }
5949 let output = Output::new(
5950 format!("HEADLESS-{}", id + 1),
5951 PhysicalProperties {
5952 size: (width, height).into(),
5953 subpixel: Subpixel::Unknown,
5954 make: "Pixelflux".into(),
5955 model: "Virtual".into(),
5956 serial_number: format!("{:03}", id + 1),
5957 },
5958 );
5959 let mode = OutputMode {
5960 size: (width, height).into(),
5961 refresh: 60_000,
5962 };
5963 output.change_current_state(
5964 Some(mode),
5965 Some(Transform::Normal),
5966 Some(OutputScale::Fractional(scale)),
5967 Some((x, y).into()),
5968 );
5969 output.set_preferred(mode);
5970 let mut offscreen = None;
5971 if state.use_gpu {
5972 let Some(gbm) = state.gbm_device.as_mut() else {
5973 return false;
5974 };
5975 match alloc_render_target(gbm, width as u32, height as u32, GbmFormat::Argb8888) {
5976 Ok(bo) => {
5977 let dmabuf = create_dmabuf_from_bo(&bo);
5978 offscreen = Some((bo, dmabuf));
5979 }
5980 Err(e) => {
5981 eprintln!(
5982 "[Wayland] CreateOutput {id}: GBM allocation {width}x{height} failed ({e:?})."
5983 );
5984 return false;
5985 }
5986 }
5987 }
5988 state.space.map_output(&output, (x, y));
5989 let global = output.create_global::<AppState>(&state.dh);
5990 let damage_tracker = OutputDamageTracker::from_output(&output);
5991 if let Some(host) = state.host.as_ref() {
5992 host.set_layout(id, x, y);
5993 }
5994 println!("[Wayland] Output {id} created: {width}x{height} @ ({x}, {y}) scale {scale:.2}.");
5995 state.output_nodes.push(wayland::frontend::OutputNode {
5996 id,
5997 output,
5998 global: Some(global),
5999 owner: None,
6000 view_size: (width, height),
6001 view_scale: 0.0,
6002 pos: (x, y),
6003 damage_tracker,
6004 content_tracker: None,
6005 frame_buffer: vec![0u8; (width.max(0) as usize) * (height.max(0) as usize) * 4],
6006 offscreen_buffer: offscreen,
6007 undrawn_ticks: 0,
6008 overlay_state: OverlayState::default(),
6009 capture: None,
6010 frame_seq: 0,
6011 target_seeded: false,
6012 content_hold_until: None,
6013 });
6014 let mut counts: Vec<(u32, usize)> = Vec::new();
6019 for w in state.space.elements() {
6020 let oid = wayland::frontend::window_output_id(w);
6021 match counts.iter_mut().find(|(o, _)| *o == oid) {
6022 Some((_, c)) => *c += 1,
6023 None => counts.push((oid, 1)),
6024 }
6025 }
6026 let newest = |pred: &dyn Fn(&smithay::desktop::Window) -> bool| {
6027 state
6028 .space
6029 .elements()
6030 .filter(|w| pred(w))
6031 .max_by_key(|w| wayland::frontend::window_meta(w).map(|m| m.id).unwrap_or(0))
6032 .cloned()
6033 };
6034 let parked = |w: &smithay::desktop::Window| {
6035 wayland::frontend::window_meta(w)
6036 .map(|m| m.parked.load(std::sync::atomic::Ordering::Relaxed))
6037 .unwrap_or(false)
6038 };
6039 let adopt = newest(&|w| parked(w) && wayland::frontend::window_output_id(w) == id)
6043 .or_else(|| newest(&parked))
6044 .or_else(|| {
6045 newest(&|w| {
6046 let oid = wayland::frontend::window_output_id(w);
6047 counts.iter().any(|(o, c)| *o == oid && *c >= 2)
6048 })
6049 });
6050 if let Some(window) = adopt
6051 && !wayland::wm::active(state)
6052 {
6053 state.place_window_on_output(&window, id);
6054 crate::log::debug!(
6055 "[Wayland] Output {id}: adopted waiting window {}.",
6056 wayland::frontend::window_meta(&window)
6057 .map(|m| m.id)
6058 .unwrap_or(0)
6059 );
6060 }
6061 wayland::workspace::outputs_changed(state);
6062 wayland::wm::outputs_changed(state);
6063 wayland::xwm::publish_workarea(state);
6064 true
6065}
6066
6067fn reposition_output_on(state: &mut AppState, id: u32, x: i32, y: i32) -> bool {
6077 let Some(idx) = state.node_idx_for_id(id) else {
6078 return false;
6079 };
6080 let output = state.output_nodes[idx].output.clone();
6081 if state.output_nodes[idx].pos == (x, y) {
6082 return true;
6083 }
6084 if let Some(owner) = state.output_nodes[idx].owner {
6087 let base = state
6088 .node_idx_for_id(owner)
6089 .map(|o| state.output_nodes[o].pos)
6090 .unwrap_or((0, 0));
6091 let mode = output
6092 .current_mode()
6093 .map(|m| (m.size.w, m.size.h))
6094 .unwrap_or((0, 0));
6095 let size = state.output_nodes[idx]
6096 .capture
6097 .as_ref()
6098 .map(|c| (c.settings.width, c.settings.height))
6099 .unwrap_or(state.output_nodes[idx].view_size);
6100 if x < base.0 || y < base.1 || x - base.0 + size.0 > mode.0 || y - base.1 + size.1 > mode.1
6101 {
6102 eprintln!(
6103 "[Wayland] RepositionOutput {id}: rejected, {}x{}+{x}+{y} leaves output \
6104 {owner}'s {}x{}.",
6105 size.0, size.1, mode.0, mode.1
6106 );
6107 return false;
6108 }
6109 state.output_nodes[idx].pos = (x, y);
6110 if let Some(cap) = state.output_nodes[idx].capture.as_mut() {
6111 cap.needs_full_render = true;
6112 }
6113 println!("[Wayland] View {id} moved to ({x}, {y}).");
6114 return true;
6115 }
6116 let logical_size = state.output_nodes[idx]
6117 .logical_geometry()
6118 .map(|g| (g.size.w, g.size.h))
6119 .unwrap_or((0, 0));
6120 let physical_size = output
6121 .current_mode()
6122 .map(|m| (m.size.w, m.size.h))
6123 .unwrap_or((0, 0));
6124 if let Some((oid, flavor, other)) = find_output_overlap(
6125 &state.output_nodes,
6126 Some(id),
6127 (x, y, logical_size.0, logical_size.1),
6128 (x, y, physical_size.0, physical_size.1),
6129 ) {
6130 eprintln!(
6131 "[Wayland] RepositionOutput {id}: rejected, {flavor} rect {}x{}+{x}+{y} overlaps output {oid} at {}x{}+{}+{}.",
6132 if flavor == "logical" {
6133 logical_size.0
6134 } else {
6135 physical_size.0
6136 },
6137 if flavor == "logical" {
6138 logical_size.1
6139 } else {
6140 physical_size.1
6141 },
6142 other.2,
6143 other.3,
6144 other.0,
6145 other.1,
6146 );
6147 return false;
6148 }
6149 let (old_x, old_y) = state.output_nodes[idx].pos;
6150 state.output_nodes[idx].pos = (x, y);
6151 for view in state
6155 .output_nodes
6156 .iter_mut()
6157 .filter(|n| n.owner == Some(id))
6158 {
6159 view.pos = (view.pos.0 + x - old_x, view.pos.1 + y - old_y);
6160 if let Some(cap) = view.capture.as_mut() {
6161 cap.needs_full_render = true;
6162 }
6163 }
6164 if let Some(host) = state.host.as_ref() {
6165 host.set_layout(id, x, y);
6166 }
6167 output.change_current_state(None, None, None, Some((x, y).into()));
6168 state.space.map_output(&output, (x, y));
6169 let windows: Vec<smithay::desktop::Window> = state
6170 .space
6171 .elements()
6172 .filter(|w| wayland::frontend::window_output_id(w) == id)
6173 .cloned()
6174 .collect();
6175 if !wayland::wm::active(state) {
6176 for window in &windows {
6177 state.space.map_element(window.clone(), (x, y), false);
6178 }
6179 }
6180 state.refocus_pointer();
6181 if let Some(cap) = state.output_nodes[idx].capture.as_mut() {
6182 cap.needs_full_render = true;
6183 }
6184 println!("[Wayland] Output {id} repositioned to ({x}, {y}).");
6185 wayland::wm::outputs_changed(state);
6186 wayland::xwm::publish_workarea(state);
6187 true
6188}
6189
6190fn destroy_output_on(state: &mut AppState, id: u32) -> bool {
6193 if id == 0 {
6194 return false;
6195 }
6196 let Some(_) = state.node_idx_for_id(id) else {
6197 return false;
6198 };
6199 state
6200 .pending_screenshots
6201 .fail_display(id, "Screenshot output was removed");
6202 stop_capture_on_display(state, id);
6203 if let Some(host) = state.host.as_ref() {
6204 host.idle_output(id);
6205 }
6206 wayland_owners().lock().unwrap().remove(&id);
6207 let windows: Vec<smithay::desktop::Window> = state
6209 .space
6210 .elements()
6211 .filter(|w| wayland::frontend::window_output_id(w) == id)
6212 .cloned()
6213 .collect();
6214 for window in &windows {
6215 if wayland::wm::active(state) {
6216 if let Some(meta) = wayland::frontend::window_meta(window) {
6217 meta.output.store(0, Ordering::Relaxed);
6218 }
6219 continue;
6220 }
6221 if state.would_cover_screen(window, 0) {
6225 state.park_window(window, id);
6226 } else {
6227 state.place_window_on_output(window, 0);
6228 }
6229 }
6230 for w in &state.pending_windows {
6231 if let Some(meta) = wayland::frontend::window_meta(w)
6232 && meta.output.load(Ordering::Relaxed) == id
6233 {
6234 meta.output.store(0, Ordering::Relaxed);
6235 }
6236 }
6237 for view in state.view_ids_of(id) {
6241 state
6242 .pending_screenshots
6243 .fail_display(view, "Screenshot output was removed");
6244 stop_capture_on_display(state, view);
6245 wayland_owners().lock().unwrap().remove(&view);
6246 if let Some(vidx) = state.node_idx_for_id(view) {
6247 state.output_nodes.remove(vidx);
6248 }
6249 }
6250 let idx = state.node_idx_for_id(id).unwrap();
6251 let node = state.output_nodes.remove(idx);
6252 if let Some(global) = node.global {
6253 state.space.unmap_output(&node.output);
6254 state.dh.remove_global::<AppState>(global);
6255 }
6256 println!(
6257 "[Wayland] Output {id} destroyed; {} window(s) relocated to primary.",
6258 windows.len()
6259 );
6260 wayland::workspace::outputs_changed(state);
6261 wayland::wm::outputs_changed(state);
6262 wayland::xwm::publish_workarea(state);
6263 true
6264}
6265fn windows_on_display(
6281 state: &AppState,
6282 display_id: u32,
6283 output: &Output,
6284) -> Vec<smithay::desktop::Window> {
6285 let on_output: Vec<smithay::desktop::Window> = if wayland::wm::active(state) {
6286 state.space.elements_for_output(output).cloned().collect()
6287 } else {
6288 state
6289 .space
6290 .elements()
6291 .filter(|window| wayland::frontend::window_output_id(window) == display_id)
6292 .cloned()
6293 .collect()
6294 };
6295 on_output
6296 .into_iter()
6297 .filter(|window| {
6298 !wayland::frontend::window_meta(window)
6299 .is_some_and(|meta| meta.parked.load(Ordering::Relaxed))
6300 })
6301 .collect()
6302}
6303
6304fn announce_scale_to_windows(state: &AppState, display_id: u32, output: &Output, scale: f64) {
6309 let scale = scale.max(0.1);
6310 for window in windows_on_display(state, display_id, output) {
6311 if let Some(surface) = window.wl_surface() {
6312 output.enter(&surface);
6313 with_states(&surface, |states| {
6314 smithay::wayland::compositor::send_surface_state(
6315 &surface,
6316 states,
6317 scale.ceil() as i32,
6318 Transform::Normal,
6319 );
6320 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
6321 fs.set_preferred_scale(scale);
6322 });
6323 });
6324 }
6325 }
6326}
6327
6328fn configure_windows_for_mode(
6329 state: &AppState,
6330 display_id: u32,
6331 output: &Output,
6332 width: i32,
6333 height: i32,
6334 scale: f64,
6335) {
6336 let scale = scale.max(0.1);
6337 let logical_width = (f64::from(width) / scale).round() as i32;
6338 let logical_height = (f64::from(height) / scale).round() as i32;
6339 announce_scale_to_windows(state, display_id, output, scale);
6340 for window in windows_on_display(state, display_id, output) {
6341 if let Some(toplevel) = window.toplevel() {
6342 toplevel.with_pending_state(|state| {
6343 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State;
6344 state.states.set(State::Fullscreen);
6345 state.states.set(State::Activated);
6346 state.size = Some((logical_width, logical_height).into());
6347 });
6348 toplevel.send_configure();
6349 }
6350 }
6351}
6352
6353fn resize_output_on(state: &mut AppState, id: u32, width: i32, height: i32, scale: f64) -> bool {
6354 if width <= 0 || height <= 0 || scale <= 0.0 {
6355 return false;
6356 }
6357 let Some(idx) = state.node_idx_for_id(id) else {
6358 return false;
6359 };
6360 if state.output_nodes[idx].owner.is_some() {
6361 eprintln!("[Wayland] ResizeOutput {id}: rejected, {id} is a view of another output.");
6362 return false;
6363 }
6364 let base = state.output_nodes[idx].pos;
6365 for view in state
6368 .output_nodes
6369 .iter()
6370 .filter(|n| n.owner == Some(id) && n.capture.is_some())
6371 {
6372 let (vw, vh) = view
6373 .capture
6374 .as_ref()
6375 .map(|c| (c.settings.width, c.settings.height))
6376 .unwrap_or(view.view_size);
6377 if view.pos.0 - base.0 + vw > width || view.pos.1 - base.1 + vh > height {
6378 eprintln!(
6379 "[Wayland] ResizeOutput {id}: rejected, view {} would fall outside {width}x{height}.",
6380 view.id
6381 );
6382 return false;
6383 }
6384 }
6385 let output = state.output_nodes[idx].output.clone();
6386 let refresh = output.current_mode().map(|m| m.refresh).unwrap_or(60_000);
6387 if output.current_mode().map(|m| (m.size.w, m.size.h)) == Some((width, height))
6388 && (output.current_scale().fractional_scale() - scale).abs() <= 0.001
6389 {
6390 return true;
6391 }
6392 let mut new_offscreen = None;
6396 if state.use_gpu {
6397 let Some(gbm) = state.gbm_device.as_mut() else {
6398 return false;
6399 };
6400 match alloc_render_target(gbm, width as u32, height as u32, GbmFormat::Argb8888) {
6401 Ok(bo) => {
6402 let dmabuf = create_dmabuf_from_bo(&bo);
6403 new_offscreen = Some((bo, dmabuf));
6404 }
6405 Err(e) => {
6406 eprintln!(
6407 "[Wayland] ResizeOutput {id}: GBM allocation {width}x{height} failed ({e:?})."
6408 );
6409 return false;
6410 }
6411 }
6412 }
6413 let mode = OutputMode {
6414 size: (width, height).into(),
6415 refresh,
6416 };
6417 output.change_current_state(
6418 Some(mode),
6419 Some(Transform::Normal),
6420 Some(OutputScale::Fractional(scale)),
6421 Some(Point::from(base)),
6422 );
6423 output.set_preferred(mode);
6424 state.space.map_output(&output, base);
6425 for cs in state
6428 .copy_sessions
6429 .iter()
6430 .filter(|cs| cs.output.upgrade().as_ref() == Some(&output))
6431 {
6432 if let Some(c) = wayland::frontend::output_capture_constraints(
6433 &output,
6434 state.gles_renderer.as_ref(),
6435 &state.render_node_path,
6436 ) {
6437 cs.session.update_constraints(c);
6438 }
6439 }
6440 let node = &mut state.output_nodes[idx];
6441 node.frame_buffer = vec![0u8; (width.max(0) as usize) * (height.max(0) as usize) * 4];
6442 node.view_size = (width, height);
6443 node.target_seeded = false;
6444 if let Some(off) = new_offscreen.take() {
6445 node.offscreen_buffer = Some(off);
6446 }
6447 for n in state
6448 .output_nodes
6449 .iter_mut()
6450 .filter(|n| n.id == id || n.owner == Some(id))
6451 {
6452 if let Some(cap) = n.capture.as_mut() {
6453 cap.needs_full_render = true;
6454 }
6455 }
6456 if wayland::wm::active(state) {
6457 announce_scale_to_windows(state, id, &output, scale);
6458 } else {
6459 configure_windows_for_mode(state, id, &output, width, height, scale);
6460 }
6461 layer_map_for_output(&output).arrange();
6465 wayland::wm::mark_dirty(state);
6466 println!("[Wayland] Output {id} resized to {width}x{height} scale {scale:.2}.");
6467 wayland::wm::outputs_changed(state);
6468 wayland::xwm::publish_workarea(state);
6469 true
6470}
6471
6472const DEFAULT_VIEW_ID: u32 = 1;
6475
6476fn create_view_on(
6484 state: &mut AppState,
6485 id: u32,
6486 owner: u32,
6487 x: i32,
6488 y: i32,
6489 width: i32,
6490 height: i32,
6491) -> bool {
6492 if state.node_idx_for_id(id).is_some() || width <= 0 || height <= 0 || x < 0 || y < 0 {
6493 return false;
6494 }
6495 let Some(oidx) = state.node_idx_for_id(owner) else {
6496 return false;
6497 };
6498 if state.output_nodes[oidx].owner.is_some() {
6499 eprintln!("[Wayland] CreateView {id}: rejected, output {owner} is itself a view.");
6500 return false;
6501 }
6502 let output = state.output_nodes[oidx].output.clone();
6503 let scale = output.current_scale().fractional_scale();
6504 let Some(mode) = output.current_mode() else {
6505 return false;
6506 };
6507 if x + width > mode.size.w || y + height > mode.size.h {
6508 eprintln!(
6509 "[Wayland] CreateView {id}: rejected, {width}x{height}+{x}+{y} leaves output \
6510 {owner}'s {}x{}.",
6511 mode.size.w, mode.size.h
6512 );
6513 return false;
6514 }
6515 let mut offscreen = None;
6516 if state.use_gpu {
6517 let Some(gbm) = state.gbm_device.as_mut() else {
6518 return false;
6519 };
6520 match alloc_render_target(gbm, width as u32, height as u32, GbmFormat::Argb8888) {
6521 Ok(bo) => {
6522 let dmabuf = create_dmabuf_from_bo(&bo);
6523 offscreen = Some((bo, dmabuf));
6524 }
6525 Err(e) => {
6526 eprintln!(
6527 "[Wayland] CreateView {id}: GBM allocation {width}x{height} failed ({e:?})."
6528 );
6529 return false;
6530 }
6531 }
6532 }
6533 let pos = state.output_nodes[oidx].pos;
6534 let origin = (pos.0 + x, pos.1 + y);
6535 let damage_tracker = OutputDamageTracker::new((width, height), scale, Transform::Normal);
6538 println!("[Wayland] View {id} created over output {owner}: {width}x{height} @ ({x}, {y}).");
6539 state.output_nodes.push(wayland::frontend::OutputNode {
6540 id,
6541 output,
6542 global: None,
6543 owner: Some(owner),
6544 view_size: (width, height),
6545 view_scale: 0.0,
6546 pos: origin,
6547 damage_tracker,
6548 content_tracker: None,
6549 frame_buffer: vec![0u8; (width.max(0) as usize) * (height.max(0) as usize) * 4],
6550 offscreen_buffer: offscreen,
6551 undrawn_ticks: 0,
6552 overlay_state: OverlayState::default(),
6553 capture: None,
6554 frame_seq: 0,
6555 target_seeded: false,
6556 content_hold_until: None,
6557 });
6558 true
6559}
6560
6561struct WaylandThreadConfig {
6564 command_rx: smithay::reexports::calloop::channel::Channel<ThreadCommand>,
6565 wake_rx: smithay::reexports::calloop::channel::Channel<()>,
6566 wake_tx: smithay::reexports::calloop::channel::Sender<()>,
6567 command_tx: smithay::reexports::calloop::channel::Sender<ThreadCommand>,
6568 initial_width: i32,
6569 initial_height: i32,
6570 explicit_dri_node: String,
6571 auto_gpu_selected: bool,
6572 cursor_size: i32,
6573 pointer_share: Arc<crate::wayland::frontend::PointerShare>,
6574}
6575
6576const EGL_LIBRARY: &str = "libEGL.so.1";
6578
6579fn egl_library() -> Result<(), String> {
6582 static LIBRARY: OnceLock<Result<libloading::Library, String>> = OnceLock::new();
6583 LIBRARY
6584 .get_or_init(|| {
6586 unsafe { libloading::Library::new(EGL_LIBRARY) }.map_err(|e| {
6587 match std::error::Error::source(&e) {
6588 Some(why) => format!("{e}: {why}"),
6589 None => e.to_string(),
6590 }
6591 })
6592 })
6593 .as_ref()
6594 .map(|_| ())
6595 .map_err(|e| format!("{EGL_LIBRARY} is not available: {e}"))
6596}
6597
6598pub(crate) fn egl_display<N>(native: N) -> Result<EGLDisplay, String>
6605where
6606 N: smithay::backend::egl::native::EGLNativeDisplay + 'static,
6607{
6608 egl_library()?;
6609 #[allow(clippy::disallowed_methods)]
6613 unsafe { EGLDisplay::new(native) }.map_err(|e| format!("{e}"))
6614}
6615
6616fn gpu_render_init(
6622 device_path: &std::path::Path,
6623) -> Result<(RawGbmDevice<File>, GlesRenderer), String> {
6624 let file = File::options()
6625 .read(true)
6626 .write(true)
6627 .open(device_path)
6628 .map_err(|e| format!("Failed to open render device: {}", e))?;
6629 let file_for_alloc = file
6630 .try_clone()
6631 .map_err(|e| format!("Failed to clone file for GBM Allocator: {}", e))?;
6632 let gbm_allocator =
6633 RawGbmDevice::new(file_for_alloc).map_err(|_| "Failed to create Raw GBM Device")?;
6634 let gbm = GbmDevice::new(file).map_err(|_| "Failed to create GBM device")?;
6635 let egl = egl_display(gbm).map_err(|e| format!("Failed to create EGL display: {e}"))?;
6636 let context = EGLContext::new(&egl).map_err(|_| "Failed to create EGL context")?;
6637 let renderer =
6638 unsafe { GlesRenderer::new(context) }.map_err(|_| "Failed to init GlesRenderer")?;
6639 Ok((gbm_allocator, renderer))
6640}
6641
6642struct GpuBringUp {
6645 gbm: RawGbmDevice<File>,
6646 renderer: GlesRenderer,
6647 formats: Vec<smithay::backend::allocator::Format>,
6648 node: DrmNode,
6649 target: (BufferObject<()>, Dmabuf),
6650}
6651
6652fn gpu_bring_up(
6659 device_path: &std::path::Path,
6660 width: u32,
6661 height: u32,
6662) -> Result<GpuBringUp, String> {
6663 let (gbm, renderer) = gpu_render_init(device_path)?;
6664 let formats = Bind::<Dmabuf>::supported_formats(&renderer)
6665 .ok_or("Failed to query formats")?
6666 .into_iter()
6667 .collect::<Vec<_>>();
6668 let node = DrmNode::from_path(device_path).map_err(|_| "Failed to create DrmNode")?;
6669 let bo = alloc_render_target(&gbm, width, height, GbmFormat::Argb8888)
6670 .map_err(|_| "Failed to allocate GBM buffer")?;
6671 let dmabuf = create_dmabuf_from_bo(&bo);
6672 Ok(GpuBringUp {
6673 gbm,
6674 renderer,
6675 formats,
6676 node,
6677 target: (bo, dmabuf),
6678 })
6679}
6680
6681fn gl_renderer_name(renderer: &mut GlesRenderer) -> String {
6685 renderer
6686 .with_context(|gl| unsafe {
6687 let ptr = gl.GetString(smithay::backend::renderer::gles::ffi::RENDERER);
6688 if ptr.is_null() {
6689 String::new()
6690 } else {
6691 std::ffi::CStr::from_ptr(ptr as *const std::ffi::c_char)
6692 .to_string_lossy()
6693 .into_owned()
6694 }
6695 })
6696 .unwrap_or_default()
6697}
6698
6699fn node_gpu_name(node_index: i32) -> String {
6703 static NAMES: OnceLock<Mutex<std::collections::HashMap<i32, String>>> = OnceLock::new();
6704 let mut names = NAMES.get_or_init(Default::default).lock().unwrap();
6705 names
6706 .entry(node_index)
6707 .or_insert_with(|| {
6708 let path = format!("/dev/dri/renderD{}", 128 + node_index);
6709 gpu_render_init(std::path::Path::new(&path))
6710 .map(|(_gbm, mut renderer)| gl_renderer_name(&mut renderer))
6711 .unwrap_or_default()
6712 })
6713 .clone()
6714}
6715
6716fn gpu_exposed() -> bool {
6722 std::path::Path::new("/dev/nvidiactl").exists()
6723 || std::fs::read_dir("/dev/dri")
6724 .into_iter()
6725 .flatten()
6726 .flatten()
6727 .any(|e| {
6728 let name = e.file_name();
6729 let name = name.to_string_lossy();
6730 name.starts_with("renderD") || name.starts_with("card")
6731 })
6732}
6733
6734fn run_wayland_thread(cfg: WaylandThreadConfig) {
6777 let WaylandThreadConfig {
6778 command_rx,
6779 wake_rx,
6780 wake_tx,
6781 command_tx,
6782 initial_width,
6783 initial_height,
6784 explicit_dri_node,
6785 auto_gpu_selected,
6786 cursor_size,
6787 pointer_share,
6788 } = cfg;
6789 let width: i32 = if initial_width > 0 {
6790 initial_width
6791 } else {
6792 1024
6793 };
6794 let height: i32 = if initial_height > 0 {
6795 initial_height
6796 } else {
6797 768
6798 };
6799
6800 let mut event_loop = match EventLoop::<AppState>::try_new() {
6801 Ok(l) => l,
6802 Err(e) => {
6803 eprintln!("[Wayland] compositor thread aborting: event loop init failed: {e}");
6804 return;
6805 }
6806 };
6807 let display: Display<AppState> = match Display::new() {
6808 Ok(d) => d,
6809 Err(e) => {
6810 eprintln!("[Wayland] compositor thread aborting: display init failed: {e}");
6811 return;
6812 }
6813 };
6814 let dh: DisplayHandle = display.handle();
6815 unsafe {
6816 if let Ok(lib) = libloading::Library::new("libwayland-server.so.0") {
6817 if let Ok(set_max) = lib.get::<unsafe extern "C" fn(*mut std::ffi::c_void, usize)>(
6818 b"wl_display_set_default_max_buffer_size\0",
6819 ) {
6820 set_max(
6821 dh.backend_handle().display_ptr() as *mut std::ffi::c_void,
6822 10 * 1024 * 1024,
6823 );
6824 }
6825 std::mem::forget(lib);
6826 }
6827 }
6828
6829 let dri_node = explicit_dri_node;
6830
6831 let mut use_gpu = !dri_node.is_empty();
6832 let render_node_path = dri_node.clone();
6833
6834 let mut gles_renderer = None;
6835 let mut pixman_renderer = None;
6836 let mut offscreen_buffer: Option<(BufferObject<()>, Dmabuf)> = None;
6837 let mut dmabuf_global = None;
6838 let mut gbm_device_raw = None;
6839 let mut dmabuf_state = DmabufState::new();
6840
6841 let mut gpu_success = false;
6842 if use_gpu {
6843 println!("[Wayland] Renderer: GL on {dri_node}.");
6844 let init_res = gpu_bring_up(std::path::Path::new(&dri_node), width as u32, height as u32)
6845 .map(|mut up| {
6846 if let Err(e) = up.renderer.bind_wl_display(&dh) {
6847 crate::log::debug!(
6848 "[Wayland] EGL did not bind to the Wayland display (optional): {:?}",
6849 e
6850 );
6851 }
6852 let feedback =
6853 DmabufFeedbackBuilder::new(up.node.dev_id(), up.formats.clone()).build();
6854 dmabuf_global = Some(if let Ok(default_feedback) = feedback {
6855 dmabuf_state
6856 .create_global_with_default_feedback::<AppState>(&dh, &default_feedback)
6857 } else {
6858 dmabuf_state.create_global::<AppState>(&dh, up.formats)
6859 });
6860 offscreen_buffer = Some(up.target);
6861 gbm_device_raw = Some(up.gbm);
6862 gles_renderer = Some(up.renderer);
6863 });
6864
6865 match init_res {
6866 Ok(()) => {
6867 gpu_success = true;
6868 report::set_renderer(report::Renderer {
6869 kind: "gl",
6870 node: dri_node.clone(),
6871 gpu: gles_renderer
6872 .as_mut()
6873 .map(gl_renderer_name)
6874 .unwrap_or_default(),
6875 reason: String::new(),
6876 });
6877 }
6878 Err(e) => {
6879 eprintln!(
6880 "[Wayland] GPU renderer failed to initialize ({e}); rendering in software (Pixman)."
6881 );
6882 report::set_renderer(report::Renderer {
6883 kind: "pixman",
6884 node: String::new(),
6885 gpu: String::new(),
6886 reason: format!("the GPU renderer did not initialize on {dri_node}: {e}"),
6887 });
6888 use_gpu = false;
6889 }
6890 }
6891 }
6892
6893 if !gpu_success {
6894 if dri_node.is_empty() {
6895 println!("[Wayland] Renderer: software (Pixman), no render node.");
6896 report::set_renderer(report::Renderer {
6897 kind: "pixman",
6898 node: String::new(),
6899 gpu: String::new(),
6900 reason: "no render node".to_string(),
6901 });
6902 }
6903 pixman_renderer = Some(PixmanRenderer::new().expect("Failed to init PixmanRenderer"));
6904 use_gpu = false;
6905 }
6906
6907 let compositor_state = CompositorState::new_v6::<AppState>(&dh);
6908 let image_capture_source_state = ImageCaptureSourceState::new();
6909 let output_capture_source_state = OutputCaptureSourceState::new::<AppState>(&dh);
6910 let image_copy_capture_state = ImageCopyCaptureState::new::<AppState>(&dh);
6911 let fractional_scale_state = FractionalScaleManagerState::new::<AppState>(&dh);
6912 let shm_state = ShmState::new::<AppState>(&dh, vec![]);
6913 let output_state = OutputManagerState::new_with_xdg_output::<AppState>(&dh);
6914 let mut seat_state = SeatState::new();
6915 let shell_state = XdgShellState::new::<AppState>(&dh);
6916 let space = Space::default();
6917 let layer_shell_state = WlrLayerShellState::new::<AppState>(&dh);
6918 let keyboard_shortcuts_inhibit_state =
6919 smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState::new::<AppState>(
6920 &dh,
6921 );
6922 let data_device_state = DataDeviceState::new::<AppState>(&dh);
6923 let data_control_state = DataControlState::new::<AppState, _>(&dh, None, |_| true);
6924 let ext_data_control_state = ExtDataControlState::new::<AppState, _>(&dh, None, |_| true);
6925 let cursor_shape_state = CursorShapeManagerState::new::<AppState>(&dh);
6926 let _vk_global = dh.create_global::<AppState, ZwpVirtualKeyboardManagerV1, _>(1, ());
6927 let pointer_warp_state = PointerWarpManager::new::<AppState>(&dh);
6928 let relative_pointer_state = RelativePointerManagerState::new::<AppState>(&dh);
6929 let pointer_constraints_state = PointerConstraintsState::new::<AppState>(&dh);
6930
6931 let foreign_toplevel_list = ForeignToplevelListState::new::<AppState>(&dh);
6932 let xdg_decoration_state = XdgDecorationState::new::<AppState>(&dh);
6933 let single_pixel_buffer = SinglePixelBufferState::new::<AppState>(&dh);
6934 let viewporter_state = ViewporterState::new::<AppState>(&dh);
6935 let presentation_state = PresentationState::new::<AppState>(&dh, 1);
6936 let xdg_activation_state = XdgActivationState::new::<AppState>(&dh);
6937 let primary_selection_state = PrimarySelectionState::new::<AppState>(&dh);
6938 let popups = PopupManager::default();
6939
6940 let mut seat = seat_state.new_wl_seat(&dh, "seat0");
6941 seat.add_keyboard(XkbConfig::default(), 600, 25)
6944 .expect("Failed to init keyboard");
6945 seat.add_pointer();
6946
6947 let (host_frame_tx, host_frame_rx) = smithay::reexports::calloop::channel::channel::<usize>();
6948
6949 let mut state = AppState {
6950 compositor_state,
6951 fractional_scale_state,
6952 viewporter_state,
6953 presentation_state,
6954 shm_state,
6955 single_pixel_buffer,
6956 dmabuf_state,
6957 dmabuf_global,
6958 ext_data_control_state,
6959 cursor_shape_state,
6960 image_capture_source_state,
6961 output_capture_source_state,
6962 image_copy_capture_state,
6963 copy_sessions: Vec::new(),
6964 output_state,
6965 seat_state,
6966 shell_state,
6967 layer_shell_state,
6968 space,
6969 data_device_state,
6970 data_control_state,
6971 dh: dh.clone(),
6972 seat,
6973 pointer_warp_state,
6974 relative_pointer_state,
6975 pointer_constraints_state,
6976 output_nodes: Vec::new(),
6977 pending_windows: Vec::new(),
6978 foreign_toplevel_list,
6979 keyboard_shortcuts_inhibit_state,
6980 xdg_decoration_state,
6981 xdg_activation_state,
6982 primary_selection_state,
6983 popups,
6984 gles_renderer,
6985 pixman_renderer,
6986 gbm_device: gbm_device_raw,
6987 settings: CaptureSettings {
6988 width,
6989 height,
6990 ..CaptureSettings::default()
6991 },
6992 cursor_callback_set: false,
6993 cursor_tx: wayland::cursor::spawn_cursor_worker(
6994 cursor_size,
6995 CaptureSettings::default().cursor_size_cap,
6996 ),
6997 clipboard_callback: None,
6998 pending_clipboard_read: None,
6999 clipboard_generation: Arc::new(std::sync::atomic::AtomicU64::new(0)),
7000 current_selection_mimes: Vec::new(),
7001 last_log_time: Instant::now(),
7002 start_time: Instant::now(),
7003 clock: Clock::new(),
7004 use_gpu,
7005 cursor_helper: Cursor::load(cursor_size),
7006 keymap_policy: wayland::keymap::KeymapPolicy::seat(),
7007 host: None,
7008 host_layout_pending: std::collections::HashMap::new(),
7009 host_frame_tx,
7010 host_mode_refusals: std::collections::HashMap::new(),
7011 current_cursor_icon: None,
7012 cursor_surface_pending: false,
7013 cursor_buffer: None,
7014 dnd_icon: None,
7015 render_cursor_on_framebuffer: false,
7016 render_node_path,
7017 auto_gpu_selected,
7018 pending_screenshots: wayland::frontend::ScreenshotQueue::default(),
7019 screenshot_readbacks: ScreenshotReadbacks::default(),
7020 command_rx: None,
7021 last_input_at: None,
7022 pointer_motion_at: None,
7023 pointer_share,
7024 motion_applied: 0,
7025 input_interval: None,
7026 frame_idle_long: false,
7027 last_idle_service_at: None,
7028 deliver_reaper: Vec::new(),
7029 encode_reaper: Vec::new(),
7030 wm: None,
7031 foreign_toplevel: wayland::foreign_toplevel::ForeignToplevelState::new(&dh),
7032 workspace: wayland::workspace::WorkspaceState::new(&dh),
7033 xwm: wayland::xwm::XwmState::new(
7034 smithay::wayland::xwayland_shell::XWaylandShellState::new::<AppState>(&dh),
7035 ),
7036 loop_handle: event_loop.handle(),
7037 keyboard_focus: None,
7038 };
7039 {
7042 let initial_keymap = if let Some(kb) = state.seat.get_keyboard() {
7043 kb.with_xkb_state(&mut state, |context| match context.xkb().lock() {
7044 Ok(guard) => {
7045 let keymap = unsafe { guard.keymap() };
7046 keymap.get_as_string(smithay::input::keyboard::xkb::KEYMAP_FORMAT_TEXT_V1)
7047 }
7048 Err(_) => String::new(),
7049 })
7050 } else {
7051 String::new()
7052 };
7053 state.keymap_policy.rebuild_base(initial_keymap);
7054 }
7055
7056 let output = Output::new(
7057 "HEADLESS-1".into(),
7058 PhysicalProperties {
7059 size: (width, height).into(),
7060 subpixel: Subpixel::Unknown,
7061 make: "Pixelflux".into(),
7062 model: "Virtual".into(),
7063 serial_number: "001".into(),
7064 },
7065 );
7066 output.change_current_state(
7067 Some(OutputMode {
7068 size: (width, height).into(),
7069 refresh: 60_000,
7070 }),
7071 Some(Transform::Normal),
7072 Some(OutputScale::Fractional(1.0)),
7073 Some((0, 0).into()),
7074 );
7075 output.set_preferred(OutputMode {
7076 size: (width, height).into(),
7077 refresh: 60_000,
7078 });
7079 state.space.map_output(&output, (0, 0));
7080 let global = output.create_global::<AppState>(&dh);
7081 let damage_tracker = OutputDamageTracker::from_output(&output);
7082 state.output_nodes.push(wayland::frontend::OutputNode {
7083 id: 0,
7084 output,
7085 global: Some(global),
7086 owner: None,
7087 view_size: (width, height),
7088 view_scale: 0.0,
7089 pos: (0, 0),
7090 damage_tracker,
7091 content_tracker: None,
7092 frame_buffer: vec![0u8; (width.max(0) as usize) * (height.max(0) as usize) * 4],
7093 offscreen_buffer,
7094 undrawn_ticks: 0,
7095 overlay_state: OverlayState::default(),
7096 capture: None,
7097 frame_seq: 0,
7098 target_seeded: false,
7099 content_hold_until: None,
7100 });
7101 create_view_on(&mut state, DEFAULT_VIEW_ID, 0, 0, 0, width, height);
7105
7106 fn note_pointer_motion(state: &mut AppState) {
7114 let now = Instant::now();
7115 if let Some(prev) = state.pointer_motion_at {
7116 let cap = frame_period(state.settings.target_fps) * 4;
7117 let dt = now.saturating_duration_since(prev).min(cap);
7118 state.input_interval = Some(match state.input_interval {
7119 None => dt,
7120 Some(avg) => avg.mul_f64(0.875) + dt.mul_f64(0.125),
7121 });
7122 }
7123 state.pointer_motion_at = Some(now);
7124 }
7125
7126 fn drain_thread_commands(state: &mut AppState) -> bool {
7127 let Some(rx) = state.command_rx.take() else {
7128 return false;
7129 };
7130 let mut had_input = false;
7131 while let Ok(cmd) = rx.try_recv() {
7132 had_input |= matches!(
7133 cmd,
7134 ThreadCommand::KeyboardKey { .. }
7135 | ThreadCommand::KeyboardKeys { .. }
7136 | ThreadCommand::PointerMotion { .. }
7137 | ThreadCommand::PointerRelativeMotion { .. }
7138 | ThreadCommand::PointerButton { .. }
7139 | ThreadCommand::PointerAxis { .. }
7140 | ThreadCommand::PointerScroll { .. }
7141 | ThreadCommand::PointerScrollEnd
7142 );
7143 if matches!(
7144 cmd,
7145 ThreadCommand::PointerMotion { .. } | ThreadCommand::PointerRelativeMotion { .. }
7146 ) {
7147 note_pointer_motion(state);
7148 }
7149 handle_thread_command(state, cmd);
7150 }
7151 if had_input {
7152 state.last_input_at = Some(Instant::now());
7153 }
7154 state.command_rx = Some(rx);
7155 had_input
7156 }
7157
7158 fn send_idle_frame_callbacks(state: &mut AppState) {
7165 let time = state.clock.now();
7166 for node in state.output_nodes.iter().filter(|n| n.owner.is_none()) {
7167 let mut feedback = OutputPresentationFeedback::new(&node.output);
7168 for window in state.space.elements_for_output(&node.output) {
7169 window.send_frame(&node.output, time, Some(Duration::ZERO), |_, _| {
7170 Some(node.output.clone())
7171 });
7172 window.take_presentation_feedback(
7173 &mut feedback,
7174 |_, _| Some(node.output.clone()),
7175 |_, _| wp_presentation_feedback::Kind::empty(),
7176 );
7177 }
7178 for layer in layer_map_for_output(&node.output).layers() {
7179 layer.send_frame(&node.output, time, Some(Duration::ZERO), |_, _| {
7180 Some(node.output.clone())
7181 });
7182 layer.take_presentation_feedback(
7183 &mut feedback,
7184 |_, _| Some(node.output.clone()),
7185 |_, _| wp_presentation_feedback::Kind::empty(),
7186 );
7187 }
7188 feedback.discarded();
7189 }
7190 if let Some(output) = state.primary_output() {
7191 send_cursor_frame(state, output, time);
7192 }
7193 if let Some(renderer) = state.gles_renderer.as_mut() {
7194 let _ = renderer.cleanup_texture_cache();
7195 }
7196 }
7197
7198 fn handle_thread_command(state: &mut AppState, cmd: ThreadCommand) {
7199 match cmd {
7200 ThreadCommand::StartCapture {
7201 display_id,
7202 callback,
7203 settings,
7204 } => {
7205 start_capture_on_display(state, display_id, callback, settings);
7206 }
7207 ThreadCommand::StopCapture { display_id } => {
7208 stop_capture_on_display(state, display_id);
7213 }
7214 ThreadCommand::CreateOutput {
7215 id,
7216 width,
7217 height,
7218 x,
7219 y,
7220 scale,
7221 reply,
7222 } => {
7223 let _ = reply.send(create_output_on(state, id, width, height, x, y, scale));
7224 }
7225 ThreadCommand::CreateView {
7226 id,
7227 owner,
7228 x,
7229 y,
7230 width,
7231 height,
7232 reply,
7233 } => {
7234 let _ = reply.send(create_view_on(state, id, owner, x, y, width, height));
7235 }
7236 ThreadCommand::ResizeOutput {
7237 id,
7238 width,
7239 height,
7240 scale,
7241 reply,
7242 } => {
7243 let _ = reply.send(resize_output_on(state, id, width, height, scale));
7244 }
7245 ThreadCommand::DestroyOutput { id, reply } => {
7246 let _ = reply.send(destroy_output_on(state, id));
7247 }
7248 ThreadCommand::OutputCapacity { reply } => {
7249 let _ = reply.send(state.host.as_ref().map_or(-1, |h| h.output_count() as i64));
7250 }
7251 ThreadCommand::RepositionOutput { id, x, y, reply } => {
7252 let _ = reply.send(reposition_output_on(state, id, x, y));
7253 }
7254 ThreadCommand::ListOutputs { reply } => {
7255 let list = state
7256 .output_nodes
7257 .iter()
7258 .map(|n| {
7259 let (w, h) = if n.owner.is_some() {
7262 n.capture
7263 .as_ref()
7264 .map(|c| (c.settings.width, c.settings.height))
7265 .unwrap_or(n.view_size)
7266 } else {
7267 n.output
7268 .current_mode()
7269 .map(|m| (m.size.w, m.size.h))
7270 .unwrap_or((0, 0))
7271 };
7272 (
7273 n.id,
7274 n.pos.0,
7275 n.pos.1,
7276 w,
7277 h,
7278 n.output.current_scale().fractional_scale(),
7279 n.capture.is_some(),
7280 )
7281 })
7282 .collect();
7283 let _ = reply.send(list);
7284 }
7285 ThreadCommand::MoveWindowToOutput {
7286 window_id,
7287 output_id,
7288 reply,
7289 } => {
7290 let window = state
7291 .space
7292 .elements()
7293 .find(|w| {
7294 wayland::frontend::window_meta(w)
7295 .map(|m| m.id == window_id)
7296 .unwrap_or(false)
7297 })
7298 .cloned();
7299 let ok = match window {
7300 Some(w) => state.place_window_on_output(&w, output_id),
7301 None => false,
7302 };
7303 let _ = reply.send(ok);
7304 }
7305 ThreadCommand::DesktopView { reply } => {
7306 let _ = reply.send(wayland::wm::view(state));
7307 }
7308 ThreadCommand::WindowRequest { id, request, reply } => {
7309 let known = wayland::wm::rect_of(state, id).is_some();
7310 let _ = reply.send(known && wayland::wm::request(state, id, request));
7311 }
7312 ThreadCommand::ListWindows { reply } => {
7313 use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
7314 let mut list = Vec::new();
7315 for window in state.space.elements() {
7316 let Some(meta) = wayland::frontend::window_meta(window) else {
7317 continue;
7318 };
7319 let (title, app_id) = window
7320 .toplevel()
7321 .map(|tl| {
7322 with_states(tl.wl_surface(), |states| {
7323 states
7324 .data_map
7325 .get::<XdgToplevelSurfaceData>()
7326 .map(|d| {
7327 let a = d.lock().unwrap();
7328 (
7329 a.title.clone().unwrap_or_default(),
7330 a.app_id.clone().unwrap_or_default(),
7331 )
7332 })
7333 .unwrap_or_default()
7334 })
7335 })
7336 .unwrap_or_default();
7337 list.push((
7338 meta.id,
7339 title,
7340 app_id,
7341 meta.output.load(Ordering::Relaxed),
7342 meta.parked.load(Ordering::Relaxed),
7343 ));
7344 }
7345 let _ = reply.send(list);
7346 }
7347 ThreadCommand::InstallWindowPolicy(policy) => {
7348 wayland::wm::install(state, policy);
7349 }
7350 ThreadCommand::StartXwayland { notify } => {
7351 let handle = state.loop_handle.clone();
7352 wayland::xwm::start(state, handle, notify);
7353 }
7354 ThreadCommand::SetClipboardCallback(cb) => {
7355 state.clipboard_callback = Some(cb);
7356 state.pending_clipboard_read = (!state.current_selection_mimes.is_empty())
7361 .then(|| state.current_selection_mimes.clone());
7362 }
7363 ThreadCommand::SetClipboard { entries } => {
7364 let mut mimes: Vec<String> = Vec::new();
7366 for (mime, _) in &entries {
7367 if !mime.starts_with("text/plain") {
7368 mimes.push(mime.clone());
7369 } else if !mimes.iter().any(|m| m == "TEXT") {
7370 mimes.extend(
7371 [
7372 "text/plain;charset=utf-8",
7373 "UTF8_STRING",
7374 "text/plain",
7375 "STRING",
7376 "TEXT",
7377 ]
7378 .iter()
7379 .map(|s| s.to_string()),
7380 );
7381 }
7382 }
7383 let payload = std::sync::Arc::new(entries);
7384 smithay::wayland::selection::data_device::set_data_device_selection(
7385 &state.dh,
7386 &state.seat,
7387 mimes.clone(),
7388 payload.clone(),
7389 );
7390 smithay::wayland::selection::primary_selection::set_primary_selection(
7393 &state.dh,
7394 &state.seat,
7395 mimes,
7396 payload,
7397 );
7398 state.current_selection_mimes.clear();
7401 }
7402 ThreadCommand::SetCursorCallback(cb) => {
7403 state.cursor_callback_set = cb.is_some();
7404 let _ = state.cursor_tx.send(CursorJob::SetCallback(cb));
7405 if state.cursor_callback_set {
7406 let icon = state
7410 .current_cursor_icon
7411 .clone()
7412 .unwrap_or(CursorImageStatus::Named(Default::default()));
7413 state.send_cursor_image(&icon);
7414 }
7415 }
7416 ThreadCommand::KeyboardKeys { events } => {
7417 for (scancode, key_state_val) in events {
7418 if let Some(host) = state.host.as_ref() {
7419 host.key(scancode, key_state_val > 0);
7420 continue;
7421 }
7422 let key_state = if key_state_val > 0 {
7423 KeyState::Pressed
7424 } else {
7425 KeyState::Released
7426 };
7427 let serial = next_serial();
7428 let time = wayland_time();
7429 if let Some(keyboard) = state.seat.get_keyboard() {
7430 let pressed = key_state == KeyState::Pressed;
7431 keyboard.input(
7432 state,
7433 Keycode::new(scancode),
7434 key_state,
7435 serial,
7436 time,
7437 |state, _, handle| wayland::wm::key_filter(state, handle, pressed),
7438 );
7439 }
7440 }
7441 }
7442 ThreadCommand::KeyboardKey {
7443 scancode,
7444 state: key_state_val,
7445 } => {
7446 if let Some(host) = state.host.as_ref() {
7447 host.key(scancode, key_state_val > 0);
7448 return;
7449 }
7450 let key_state = if key_state_val > 0 {
7451 KeyState::Pressed
7452 } else {
7453 KeyState::Released
7454 };
7455 let serial = next_serial();
7456 let time = wayland_time();
7457 if let Some(keyboard) = state.seat.get_keyboard() {
7458 let pressed = key_state == KeyState::Pressed;
7459 keyboard.input(
7460 state,
7461 Keycode::new(scancode),
7462 key_state,
7463 serial,
7464 time,
7465 |state, _, handle| wayland::wm::key_filter(state, handle, pressed),
7466 );
7467 }
7468 }
7469 ThreadCommand::SetKeymapString(text) => {
7470 if state.keymap_policy.rebuild_base(text) {
7473 state.apply_keymap_policy();
7474 } else {
7475 eprintln!(
7476 "[Wayland] set_keymap_string: keymap failed to compile; keeping current keymap."
7477 );
7478 }
7479 }
7480 ThreadCommand::SetXkbLayout {
7481 rules,
7482 model,
7483 layout,
7484 variant,
7485 options,
7486 reply,
7487 } => {
7488 match crate::wayland::keymap::compile_rmlvo(
7489 &rules, &model, &layout, &variant, &options,
7490 ) {
7491 Some(text) => {
7492 state.keymap_policy.rebuild_base(text);
7493 state.apply_keymap_policy();
7494 let _ = reply.send(true);
7495 }
7496 None => {
7497 eprintln!(
7498 "[Wayland] set_xkb_layout: RMLVO ({rules:?}, {model:?}, {layout:?}, {variant:?}, {options:?}) failed to compile."
7499 );
7500 let _ = reply.send(false);
7501 }
7502 }
7503 }
7504 ThreadCommand::BindKeysyms { keysyms, reply } => {
7505 let _ = reply.send(state.bind_keysyms(&keysyms));
7506 }
7507 ThreadCommand::SetKeymapOverlay { binds } => {
7508 if state.keymap_policy.has_base() {
7509 state.keymap_policy.set_manual_overlay(&binds);
7510 state.apply_keymap_policy();
7511 } else {
7512 eprintln!("[Wayland] set_keymap_overlay: no base keymap to splice onto.");
7513 }
7514 }
7515 ThreadCommand::GetKeyboardState { reply } => {
7516 let (pressed, mods) = state
7517 .seat
7518 .get_keyboard()
7519 .map(|kb| {
7520 let pressed: Vec<u32> = kb.pressed_keys().iter().map(|c| c.raw()).collect();
7521 let m = kb.modifier_state();
7522 let mask = u32::from(m.ctrl)
7523 | u32::from(m.shift) << 1
7524 | u32::from(m.alt) << 2
7525 | u32::from(m.logo) << 3
7526 | u32::from(m.caps_lock) << 4
7527 | u32::from(m.num_lock) << 5
7528 | u32::from(m.iso_level3_shift) << 6
7529 | u32::from(m.iso_level5_shift) << 7;
7530 (pressed, mask)
7531 })
7532 .unwrap_or_default();
7533 let _ = reply.send((pressed, mods));
7534 }
7535 ThreadCommand::Barrier { reply } => {
7536 for join in state.encode_reaper.drain(..) {
7544 let _ = join.join();
7545 }
7546 for join in state.deliver_reaper.drain(..) {
7547 let _ = join.join();
7548 }
7549 let _ = reply.send(());
7550 }
7551 ThreadCommand::GetXkbKeymap { reply } => {
7552 let mut keymap_str = String::new();
7553 if let Some(keyboard) = state.seat.get_keyboard() {
7554 keymap_str =
7555 keyboard.with_xkb_state(state, |context| match context.xkb().lock() {
7556 Ok(guard) => {
7557 let keymap = unsafe { guard.keymap() };
7558 keymap.get_as_string(
7559 smithay::input::keyboard::xkb::KEYMAP_FORMAT_TEXT_V1,
7560 )
7561 }
7562 Err(_) => String::new(),
7563 });
7564 }
7565 let _ = reply.send(keymap_str);
7566 }
7567 ThreadCommand::PointerMotion { x, y, motion } => {
7568 state.motion_applied = state.motion_applied.max(motion);
7569 if let Some(host) = state.host.as_ref() {
7570 crate::wayland::pwcapture::note_host_motion(motion);
7571 host.pointer_motion_abs(x, y);
7572 return;
7573 }
7574 let serial = next_serial();
7575 let time = wayland_time();
7576 let p = state.layout_physical_to_logical(x, y);
7581
7582 if let Some(pointer) = state.seat.get_pointer() {
7583 let under = state.pointer_target_under(p);
7584 if wayland::wm::pointer(
7585 state,
7586 crate::wm::PointerKind::Motion,
7587 p,
7588 wayland::wm::under_of(state, &under),
7589 ) == crate::wm::Verdict::Consumed
7590 {
7591 return;
7592 }
7593
7594 state.release_grab_across_screens(&pointer, &under, serial, time);
7595 let entered = pointer.current_focus() != under.as_ref().map(|(t, _)| t.clone());
7596 pointer.motion(
7597 state,
7598 under.clone(),
7599 &MotionEvent {
7600 location: p,
7601 serial,
7602 time,
7603 },
7604 );
7605 if entered && under.is_some() {
7610 pointer.motion(
7611 state,
7612 under.clone(),
7613 &MotionEvent {
7614 location: p,
7615 serial: next_serial(),
7616 time,
7617 },
7618 );
7619 }
7620 pointer.frame(state);
7621 state.activate_constraint_under(&pointer, &under, p);
7622 }
7623 }
7624 ThreadCommand::PointerRelativeMotion { dx, dy, motion } => {
7625 state.motion_applied = state.motion_applied.max(motion);
7626 if let Some(host) = state.host.as_ref() {
7627 crate::wayland::pwcapture::note_host_motion(motion);
7628 host.pointer_motion_rel(dx, dy);
7629 return;
7630 }
7631 let via_fake_input = crate::wayland::ficlient::pointer_motion_rel(dx, dy);
7639 let time = wayland_time();
7640 let serial = next_serial();
7641
7642 if let Some(pointer) = state.seat.get_pointer() {
7643 let current_pos =
7648 wayland::wm::judged_location(state, pointer.current_location());
7649 let event = RelativeMotionEvent {
7650 time,
7651 delta: (dx, dy).into(),
7652 delta_unaccel: (dx, dy).into(),
7653 };
7654 let resting = state
7655 .space
7656 .element_under(current_pos)
7657 .map(|(window, loc)| (FocusTarget::Window(window.clone()), loc.to_f64()));
7658 if state.pointer_locked(&pointer, &resting, current_pos) {
7662 if !via_fake_input {
7663 pointer.relative_motion(state, resting, &event);
7664 }
7665 pointer.frame(state);
7666 return;
7667 }
7668 let new_pos =
7669 state.clamp_logical((current_pos.x + dx, current_pos.y + dy).into());
7670
7671 let under = state.pointer_target_under(new_pos);
7672 if wayland::wm::pointer(
7673 state,
7674 crate::wm::PointerKind::Motion,
7675 new_pos,
7676 wayland::wm::under_of(state, &under),
7677 ) == crate::wm::Verdict::Consumed
7678 {
7679 return;
7680 }
7681
7682 state.release_grab_across_screens(&pointer, &under, serial, time);
7683 let entered = pointer.current_focus() != under.as_ref().map(|(t, _)| t.clone());
7684 pointer.motion(
7685 state,
7686 under.clone(),
7687 &MotionEvent {
7688 location: new_pos,
7689 serial,
7690 time,
7691 },
7692 );
7693 if entered && under.is_some() {
7696 pointer.motion(
7697 state,
7698 under.clone(),
7699 &MotionEvent {
7700 location: new_pos,
7701 serial: next_serial(),
7702 time,
7703 },
7704 );
7705 }
7706
7707 if !via_fake_input {
7708 pointer.relative_motion(state, under.clone(), &event);
7709 }
7710
7711 pointer.frame(state);
7712 state.activate_constraint_under(&pointer, &under, new_pos);
7713 }
7714 }
7715 ThreadCommand::PointerButton {
7716 btn,
7717 state: btn_state_val,
7718 } => {
7719 if let Some(host) = state.host.as_ref() {
7720 host.pointer_button(btn, btn_state_val > 0);
7721 return;
7722 }
7723 let serial = next_serial();
7724 let time = wayland_time();
7725 let button_state = if btn_state_val > 0 {
7726 smithay::backend::input::ButtonState::Pressed
7727 } else {
7728 smithay::backend::input::ButtonState::Released
7729 };
7730
7731 state.refocus_pointer();
7732 if let Some(pointer) = state.seat.get_pointer() {
7733 let pos = wayland::wm::judged_location(state, pointer.current_location());
7734 let under = state.pointer_target_under(pos);
7735 let kind = crate::wm::PointerKind::Button {
7736 button: btn,
7737 pressed: btn_state_val > 0,
7738 };
7739 if wayland::wm::pointer(state, kind, pos, wayland::wm::under_of(state, &under))
7740 == crate::wm::Verdict::Consumed
7741 {
7742 return;
7743 }
7744 if button_state == smithay::backend::input::ButtonState::Pressed
7745 && !wayland::wm::active(state)
7746 {
7747 let target_window = state.space.element_under(pos).map(|(w, _)| w.clone());
7748
7749 if let Some(window) = target_window {
7750 state.space.raise_element(&window, true);
7751 if let Some(keyboard) = state.seat.get_keyboard() {
7752 keyboard.set_focus(
7753 state,
7754 Some(FocusTarget::Window(window)),
7755 serial,
7756 );
7757 }
7758 }
7759 }
7760 let button = btn;
7761 pointer.button(
7762 state,
7763 &ButtonEvent {
7764 button,
7765 state: button_state,
7766 serial,
7767 time,
7768 },
7769 );
7770 pointer.frame(state);
7771 }
7772 }
7773 ThreadCommand::PointerAxis { x, y } => {
7774 if let Some(host) = state.host.as_ref() {
7775 host.pointer_axis(x, y);
7776 return;
7777 }
7778 let time = wayland_time();
7779 state.refocus_pointer();
7780 if let Some(pointer) = state.seat.get_pointer() {
7781 let pos = wayland::wm::judged_location(state, pointer.current_location());
7782 let under = state.pointer_target_under(pos);
7783 let kind = crate::wm::PointerKind::Axis {
7784 horizontal: x,
7785 vertical: y,
7786 };
7787 if wayland::wm::pointer(state, kind, pos, wayland::wm::under_of(state, &under))
7788 == crate::wm::Verdict::Consumed
7789 {
7790 return;
7791 }
7792 let mut frame = AxisFrame::new(time).source(AxisSource::Wheel);
7793
7794 if x != 0.0 {
7795 frame = frame
7796 .value(Axis::Horizontal, x)
7797 .v120(Axis::Horizontal, (x * SCROLL_V120_PER_UNIT) as i32);
7798 }
7799
7800 if y != 0.0 {
7801 frame = frame
7802 .value(Axis::Vertical, y)
7803 .v120(Axis::Vertical, (y * SCROLL_V120_PER_UNIT) as i32);
7804 }
7805
7806 if x != 0.0 || y != 0.0 {
7807 pointer.axis(state, frame);
7808 pointer.frame(state);
7809 }
7810 }
7811 }
7812 ThreadCommand::PointerScroll { dx, dy } => {
7813 if let Some(host) = state.host.as_ref() {
7814 host.pointer_scroll(dx, dy);
7815 return;
7816 }
7817 let time = wayland_time();
7818 state.refocus_pointer();
7819 if let Some(pointer) = state.seat.get_pointer()
7820 && (dx != 0.0 || dy != 0.0)
7821 {
7822 let pos = wayland::wm::judged_location(state, pointer.current_location());
7823 let under = state.pointer_target_under(pos);
7824 let kind = crate::wm::PointerKind::Axis {
7825 horizontal: dx,
7826 vertical: dy,
7827 };
7828 if wayland::wm::pointer(state, kind, pos, wayland::wm::under_of(state, &under))
7829 == crate::wm::Verdict::Consumed
7830 {
7831 return;
7832 }
7833 let (_, _, scale) = state.layout_logical_to_physical_scaled(pos);
7834 let mut frame = AxisFrame::new(time).source(AxisSource::Finger);
7835 if dx != 0.0 {
7836 frame = frame.value(Axis::Horizontal, dx / scale);
7837 }
7838 if dy != 0.0 {
7839 frame = frame.value(Axis::Vertical, dy / scale);
7840 }
7841 pointer.axis(state, frame);
7842 pointer.frame(state);
7843 }
7844 }
7845 ThreadCommand::PointerScrollEnd => {
7846 if let Some(host) = state.host.as_ref() {
7847 host.pointer_scroll_end();
7848 return;
7849 }
7850 let time = wayland_time();
7851 if let Some(pointer) = state.seat.get_pointer() {
7852 let frame = AxisFrame::new(time)
7853 .source(AxisSource::Finger)
7854 .stop(Axis::Horizontal)
7855 .stop(Axis::Vertical);
7856 pointer.axis(state, frame);
7857 pointer.frame(state);
7858 }
7859 }
7860 ThreadCommand::UpdateCursorConfig {
7861 render_on_framebuffer,
7862 } => {
7863 state.render_cursor_on_framebuffer = render_on_framebuffer;
7864 if let Some(host) = state.host.as_ref() {
7865 host.set_cursor_painting(render_on_framebuffer);
7866 }
7867 }
7868 ThreadCommand::SetCursorSize { size, reply } => {
7869 if size <= 0 {
7870 let _ = reply.send(false);
7871 } else {
7872 state.cursor_helper = Cursor::load(size);
7873 let _ = state.cursor_tx.send(CursorJob::SetSize(size));
7874 for node in state.output_nodes.iter_mut() {
7877 if let Some(cap) = node.capture.as_mut() {
7878 cap.needs_full_render = true;
7879 }
7880 }
7881 let _ = reply.send(true);
7882 }
7883 }
7884 ThreadCommand::RequestIdr { display_id } => {
7885 if let Some(idx) = state.node_idx_for_id(display_id)
7886 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7887 {
7888 cap.request_idr();
7889 }
7890 }
7891 ThreadCommand::InvalidateReference {
7892 display_id,
7893 frame_id,
7894 withheld,
7895 } => {
7896 if let Some(idx) = state.node_idx_for_id(display_id)
7897 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7898 {
7899 cap.invalidate_reference(frame_id, withheld);
7900 }
7901 }
7902 ThreadCommand::AcknowledgeReference {
7903 display_id,
7904 frame_id,
7905 held,
7906 } => {
7907 if let Some(idx) = state.node_idx_for_id(display_id)
7908 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7909 {
7910 cap.acknowledge_reference(frame_id, held);
7911 }
7912 }
7913 ThreadCommand::UpdateLinkRate {
7914 display_id,
7915 link_kbps,
7916 } => {
7917 if let Some(idx) = state.node_idx_for_id(display_id)
7918 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7919 {
7920 cap.settings.video_link_kbps = link_kbps;
7921 cap.encode_controls
7922 .link_kbps
7923 .store(link_kbps, Ordering::Relaxed);
7924 if display_id == 0 {
7925 state.settings.video_link_kbps = link_kbps;
7926 }
7927 }
7928 }
7929 ThreadCommand::UpdateRate {
7930 display_id,
7931 bitrate_kbps,
7932 vbv_multiplier,
7933 fps,
7934 } => {
7935 if let Some(idx) = state.node_idx_for_id(display_id)
7936 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7937 {
7938 if let Some(b) = bitrate_kbps {
7939 cap.settings.video_bitrate_kbps = b;
7940 }
7941 if let Some(v) = vbv_multiplier {
7942 cap.settings.video_vbv_multiplier = v;
7943 }
7944 if let Some(f) = fps
7945 && f > 0.0
7946 {
7947 cap.settings.target_fps = f;
7948 if let Some(host) = state.host.as_ref() {
7949 host.set_fps(display_id, f);
7950 }
7951 }
7952 let c = &cap.encode_controls;
7953 c.bitrate_kbps
7954 .store(cap.settings.video_bitrate_kbps, Ordering::Relaxed);
7955 c.vbv_mult_milli.store(
7956 (cap.settings.video_vbv_multiplier * 1000.0).round() as i32,
7957 Ordering::Relaxed,
7958 );
7959 c.fps_bits.store(
7960 cap.settings.target_fps.max(1.0).to_bits(),
7961 Ordering::Relaxed,
7962 );
7963 c.rate_dirty.store(true, Ordering::Release);
7964 if display_id == 0 {
7965 state.settings.video_bitrate_kbps = cap.settings.video_bitrate_kbps;
7966 state.settings.video_vbv_multiplier = cap.settings.video_vbv_multiplier;
7967 state.settings.target_fps = cap.settings.target_fps;
7968 }
7969 }
7970 }
7971 ThreadCommand::UpdateTunables {
7972 display_id,
7973 tunables: t,
7974 } => {
7975 state.render_cursor_on_framebuffer = t.capture_cursor;
7976 if let Some(host) = state.host.as_ref() {
7977 host.set_cursor_painting(t.capture_cursor);
7978 }
7979 let _ = state
7980 .cursor_tx
7981 .send(CursorJob::SetSizeCap(t.cursor_size_cap));
7982 if display_id == 0 {
7983 t.apply_to(&mut state.settings);
7984 }
7985 if let Some(idx) = state.node_idx_for_id(display_id)
7986 && let Some(cap) = state.output_nodes[idx].capture.as_mut()
7987 {
7988 t.apply_to(&mut cap.settings);
7989 *cap.encode_controls.tunables.lock().unwrap() = Some(t);
7990 cap.encode_controls
7991 .tunables_dirty
7992 .store(true, Ordering::Release);
7993 }
7994 }
7995 ThreadCommand::CuScreenshot {
7996 display_id,
7997 resp,
7998 canceled,
7999 deadline,
8000 } => {
8001 if state.node_idx_for_id(display_id).is_some() {
8002 state.pending_screenshots.enqueue(
8003 wayland::frontend::ScreenshotRequest {
8004 display_id,
8005 resp,
8006 canceled,
8007 deadline,
8008 },
8009 Instant::now(),
8010 );
8011 } else {
8012 let _ = resp.send(Err(format!("Unknown display: {display_id}")));
8013 }
8014 }
8015 ThreadCommand::CuCursorPosition { resp } => {
8016 let pos = state
8017 .pointer_shown_at()
8018 .unwrap_or_else(|| (0.0f64, 0.0f64).into());
8019 let _ = resp.send(state.layout_logical_to_physical(pos));
8020 }
8021 ThreadCommand::CuGetInfo { display_id, resp } => {
8022 match state.host_layout_pending.get_mut(&display_id) {
8026 Some(p) => p.geometry_waiters.push(resp),
8027 None => {
8028 let _ = resp.send(realized_geometry(state, display_id));
8029 }
8030 }
8031 }
8032 }
8033 }
8034
8035 state.command_rx = Some(command_rx);
8036 event_loop
8037 .handle()
8038 .insert_source(wake_rx, |_, _, state| {
8039 let had_input = drain_thread_commands(state);
8040 if wayland::wm::tick_due(state, Instant::now()) {
8041 wayland::wm::tick(state, Instant::now());
8042 }
8043 if state.frame_idle_long
8048 && state
8049 .last_input_at
8050 .is_some_and(|t| t.elapsed() < Duration::from_millis(50))
8051 && state
8052 .last_idle_service_at
8053 .is_none_or(|t| t.elapsed() >= Duration::from_millis(16))
8054 {
8055 state.last_idle_service_at = Some(Instant::now());
8056 send_idle_frame_callbacks(state);
8057 }
8058 if had_input
8062 && state.host.is_none()
8063 && state.output_nodes.iter().any(|n| n.capture.is_some())
8064 {
8065 for node in &mut state.output_nodes {
8067 if let Some(cap) = node.capture.as_mut() {
8068 cap.change_pending = true;
8069 }
8070 }
8071 state.space.refresh();
8072 render_pass(state, TickTrigger::Input);
8073 } else if !state.pending_screenshots.is_empty()
8074 && state.output_nodes.iter().all(|n| n.capture.is_none())
8075 {
8076 state.space.refresh();
8079 render_pass(state, TickTrigger::Timer);
8080 }
8081 })
8082 .unwrap();
8083
8084 event_loop
8085 .handle()
8086 .insert_source(host_frame_rx, |event, _, state| {
8087 let smithay::reexports::calloop::channel::Event::Msg(index) = event else {
8091 return;
8092 };
8093 let Some(id) = state
8094 .host
8095 .as_ref()
8096 .filter(|h| h.has_queued_frame(index))
8097 .and_then(|h| h.display_for_output(index))
8098 else {
8099 return;
8100 };
8101 if let Some(cap) = state
8103 .output_nodes
8104 .iter_mut()
8105 .find(|n| n.id == id)
8106 .and_then(|n| n.capture.as_mut())
8107 {
8108 cap.change_pending = true;
8109 }
8110 state.space.refresh();
8111 render_display(state, id, TickTrigger::HostFrame);
8112 })
8113 .unwrap();
8114
8115 let source = match ListeningSocketSource::new_auto() {
8116 Ok(s) => s,
8117 Err(e) => {
8118 eprintln!(
8119 "[Wayland] compositor thread aborting: could not bind a wayland-N socket (XDG_RUNTIME_DIR unset/full?): {e}"
8120 );
8121 return;
8122 }
8123 };
8124 let socket_name = source.socket_name().to_string_lossy().into_owned();
8125 println!("[Wayland] Socket listening on: {:?}", socket_name);
8126 unsafe { std::env::set_var("WAYLAND_DISPLAY", &socket_name) };
8132 publish_socket_name(&socket_name);
8133
8134 event_loop
8135 .handle()
8136 .insert_source(source, |client_stream, _, state| {
8137 if let Err(err) = state
8138 .dh
8139 .insert_client(client_stream, Arc::new(ClientState::default()))
8140 {
8141 eprintln!("Error adding wayland client: {:?}", err);
8142 }
8143 })
8144 .expect("Failed to init wayland socket source");
8145
8146 let timer = Timer::immediate();
8147 event_loop
8148 .handle()
8149 .insert_source(timer, move |_, _, state| {
8150 reap_dead_host(state);
8153 drain_thread_commands(state);
8154 reconcile_host_layouts(state);
8155 {
8156 let now = Instant::now();
8157 let handle = state.loop_handle.clone();
8158 wayland::xwm::maintain(state, &handle, now);
8159 if wayland::wm::tick_due(state, now) {
8160 wayland::wm::tick(state, now);
8161 }
8162 }
8163 let mut i = 0;
8168 while i < state.deliver_reaper.len() {
8169 if state.deliver_reaper[i].is_finished() {
8170 let _ = state.deliver_reaper.swap_remove(i).join();
8171 } else {
8172 i += 1;
8173 }
8174 }
8175 let mut i = 0;
8176 while i < state.encode_reaper.len() {
8177 if state.encode_reaper[i].is_finished() {
8178 let _ = state.encode_reaper.swap_remove(i).join();
8179 } else {
8180 i += 1;
8181 }
8182 }
8183 if state.screenshot_readbacks.has_idle(Instant::now())
8184 && let Some(renderer) = state.gles_renderer.as_mut()
8185 {
8186 let readbacks = &mut state.screenshot_readbacks;
8187 let _ = renderer.with_context(|gl| readbacks.reap_idle(gl, Instant::now()));
8188 }
8189 let loop_start_time = Instant::now();
8190 state.space.refresh();
8191
8192 let now = Instant::now();
8193 let elapsed = now.duration_since(state.last_log_time).as_secs_f64();
8194 if elapsed >= 1.0 {
8195 let mut mem: Option<(usize, u64)> = None;
8198 for node in &state.output_nodes {
8199 let Some(cap) = node.capture.as_ref() else { continue };
8200 let frames = cap.encode_stats.frames.swap(0, Ordering::Relaxed);
8201 let stripes = cap.encode_stats.stripes.swap(0, Ordering::Relaxed);
8202 if crate::log::debug_enabled() {
8203 let actual_fps = f64::from(frames) / elapsed;
8204 let stripes_per_sec = f64::from(stripes) / elapsed;
8205 let mode_str = cap.encode_stats.desc.lock().unwrap().clone();
8206 let n_stripes = cap.encode_stats.n_stripes.load(Ordering::Relaxed);
8207 let (current_rss, shm_usage) = *mem
8208 .get_or_insert_with(|| (get_process_rss_bytes(), get_shm_usage_bytes()));
8209
8210 println!("Display: {} Res: {}x{} Mode: {} Stripes: {} EncFPS: {:.2} EncStripes/s: {:.2} Mem: {}MB SHM: {}MB",
8211 node.id, cap.settings.width, cap.settings.height, mode_str, n_stripes, actual_fps, stripes_per_sec, current_rss / 1024 / 1024, shm_usage / 1024 / 1024);
8212 }
8213 }
8214 state.last_log_time = now;
8215 }
8216
8217 let any_capturing = state.output_nodes.iter().any(|n| n.capture.is_some());
8218 let any_copy_frame = state.copy_sessions.iter().any(|cs| cs.pending.is_some());
8219 state.pending_screenshots.prune(Instant::now());
8220 if !any_capturing && state.pending_screenshots.is_empty() && !any_copy_frame {
8221 state.last_idle_service_at = Some(Instant::now());
8232 send_idle_frame_callbacks(state);
8233 let post_input = state
8241 .last_input_at
8242 .is_some_and(|t| t.elapsed() < Duration::from_secs(1));
8243 let idle = if !state.copy_sessions.is_empty() || post_input {
8244 Duration::from_millis(16)
8245 } else {
8246 IDLE_FRAME_INTERVAL
8247 };
8248 let idle = match wayland::wm::next_wake(state) {
8249 Some(at) => idle.min(at.saturating_duration_since(Instant::now()).max(Duration::from_millis(1))),
8250 None => idle,
8251 };
8252 state.frame_idle_long = idle == IDLE_FRAME_INTERVAL;
8253 return TimeoutAction::ToDuration(idle);
8254 }
8255 state.frame_idle_long = false;
8256
8257 answer_frame_callbacks(state);
8258 serve_changes(state);
8259 let pool_busy = render_pass(state, TickTrigger::Timer);
8260 answer_frame_callbacks(state);
8261 if pool_busy {
8262 return TimeoutAction::ToDuration(Duration::from_millis(1));
8263 }
8264 let now = Instant::now();
8268 let next_due = state
8269 .output_nodes
8270 .iter()
8271 .filter_map(|n| n.capture.as_ref())
8272 .map(|cap| cap.pace.next_wake(capture_period(cap), now, cap.change_pending))
8273 .min()
8274 .unwrap_or(loop_start_time + frame_period(state.settings.target_fps));
8275 let next_due = match wayland::wm::next_wake(state) {
8276 Some(at) => next_due.min(at),
8277 None => next_due,
8278 };
8279 let wait = next_due.saturating_duration_since(now);
8280 TimeoutAction::ToDuration(if wait.is_zero() { Duration::from_millis(1) } else { wait })
8284 })
8285 .expect("Failed to init capture timer");
8286
8287 event_loop
8288 .handle()
8289 .insert_source(
8290 Generic::new(display, Interest::READ, Mode::Level),
8291 |_, display, _state| {
8292 if let Err(e) = unsafe { display.get_mut().dispatch_clients(_state) } {
8295 eprintln!("[Wayland] client dispatch error: {e:?}");
8296 }
8297 Ok(PostAction::Continue)
8298 },
8299 )
8300 .unwrap();
8301
8302 crate::computer_use::register_wayland_backend(command_tx, wake_tx);
8303 #[cfg(feature = "computer-use-server")]
8304 crate::computer_use::server::spawn_cu_from_env();
8305 crate::wayland::ficlient::arm(crate::computer_use::app_wayland_socket_path());
8306
8307 let ran = event_loop.run(None, &mut state, |state| {
8308 serve_changes(state);
8309 state.process_pending_clipboard_read();
8310 state.flush_pending_cursor();
8311 if state.host.is_none()
8312 && let Some(at) = state.pointer_shown_at()
8313 {
8314 let (x, y, scale) = state.layout_logical_to_physical_scaled(at);
8315 state
8316 .pointer_share
8317 .publish(crate::wayland::frontend::PointerAt {
8318 x,
8319 y,
8320 scale,
8321 motion: state.motion_applied,
8322 });
8323 }
8324 let _ = state.dh.flush_clients();
8325 });
8326 if let Err(e) = ran {
8327 eprintln!("[Wayland] compositor event loop ended: {e}");
8328 }
8329}
8330
8331pub struct Compositor {
8338 tx: smithay::reexports::calloop::channel::Sender<ThreadCommand>,
8339 pointer: Arc<crate::wayland::frontend::PointerShare>,
8341 motions: AtomicU64,
8343 wake_tx: smithay::reexports::calloop::channel::Sender<()>,
8347}
8348
8349impl Compositor {
8350 fn send(&self, cmd: ThreadCommand) -> Result<(), String> {
8351 self.tx.send(cmd).map_err(|e| e.to_string())?;
8352 let _ = self.wake_tx.send(());
8353 Ok(())
8354 }
8355
8356 pub fn set_cursor_sink(
8358 &self,
8359 sink: Option<Arc<dyn crate::sink::CursorSink>>,
8360 ) -> Result<(), Error> {
8361 self.send(ThreadCommand::SetCursorCallback(sink))
8362 .map_err(|e| Error::Compositor(format!("Failed to set cursor callback: {e}")))
8363 }
8364
8365 pub fn install_window_policy(
8368 &self,
8369 policy: Box<dyn crate::wm::WindowPolicy>,
8370 ) -> Result<(), Error> {
8371 self.send(ThreadCommand::InstallWindowPolicy(policy))
8372 .map_err(|e| Error::Compositor(format!("Failed to install the window policy: {e}")))
8373 }
8374
8375 pub fn start_xwayland(
8378 &self,
8379 notify: Option<std::sync::mpsc::Sender<u32>>,
8380 ) -> Result<(), Error> {
8381 self.send(ThreadCommand::StartXwayland { notify })
8382 .map_err(|e| Error::Compositor(format!("Failed to start Xwayland: {e}")))
8383 }
8384
8385 pub fn set_clipboard_sink(
8387 &self,
8388 sink: Arc<dyn crate::sink::ClipboardSink>,
8389 ) -> Result<(), Error> {
8390 self.send(ThreadCommand::SetClipboardCallback(sink))
8391 .map_err(|e| Error::Compositor(format!("Failed to set clipboard callback: {e}")))
8392 }
8393}
8394
8395impl Compositor {
8396 pub fn new(
8401 width: i32,
8402 height: i32,
8403 dri_node: String,
8404 auto_gpu_selected: bool,
8405 cursor_size: i32,
8406 ) -> Self {
8407 let (tx, rx) = smithay::reexports::calloop::channel::channel();
8408 let (wake_tx, wake_rx) = smithay::reexports::calloop::channel::channel();
8409 let cu_tx = tx.clone();
8410 let cu_wake_tx = wake_tx.clone();
8411 let pointer = Arc::new(crate::wayland::frontend::PointerShare::default());
8412 let pointer_share = pointer.clone();
8413 thread::spawn(move || {
8414 crate::boost_thread_priority(-15);
8415 run_wayland_thread(WaylandThreadConfig {
8416 command_rx: rx,
8417 wake_rx,
8418 wake_tx: cu_wake_tx,
8419 command_tx: cu_tx,
8420 initial_width: width,
8421 initial_height: height,
8422 explicit_dri_node: dri_node,
8423 auto_gpu_selected,
8424 cursor_size,
8425 pointer_share,
8426 });
8427 });
8428 Compositor {
8429 tx,
8430 wake_tx,
8431 pointer,
8432 motions: AtomicU64::new(0),
8433 }
8434 }
8435
8436 pub fn pointer_location(&self) -> Option<(f64, f64, f64, u64)> {
8446 let host = *crate::wayland::pwcapture::HOST_POINTER.lock().unwrap();
8447 crate::wayland::pwcapture::prefer_host_pointer(host, || self.pointer.read())
8448 .map(|at| (at.x, at.y, at.scale, at.motion))
8449 }
8450
8451 pub fn start_capture(
8458 &self,
8459 sink: Arc<dyn crate::sink::FrameSink>,
8460 settings: CaptureSettings,
8461 ) -> Result<(), Error> {
8462 let display_id = settings.display_id;
8463
8464 SHUTDOWN.store(false, Ordering::Relaxed);
8465 self.send(ThreadCommand::StartCapture {
8466 display_id,
8467 callback: Some(sink),
8468 settings,
8469 })
8470 .map_err(|e| Error::Compositor(format!("Failed to send start command: {e}")))?;
8471 Ok(())
8472 }
8473
8474 pub fn stop_capture(&self, display_id: u32) -> Result<(), Error> {
8476 self.send(ThreadCommand::StopCapture { display_id })
8477 .map_err(|e| Error::Compositor(format!("Failed to send stop command: {e}")))?;
8478 Ok(())
8479 }
8480
8481 pub fn create_output(
8488 &self,
8489 id: u32,
8490 width: i32,
8491 height: i32,
8492 x: i32,
8493 y: i32,
8494 scale: f64,
8495 ) -> Result<bool, Error> {
8496 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8497 self.send(ThreadCommand::CreateOutput {
8498 id,
8499 width,
8500 height,
8501 x,
8502 y,
8503 scale,
8504 reply: reply_tx,
8505 })
8506 .map_err(|e| Error::Compositor(format!("Failed to create output: {e}")))?;
8507 Ok(reply_rx
8508 .recv_timeout(Duration::from_secs(2))
8509 .unwrap_or(false))
8510 }
8511
8512 pub fn create_view(
8521 &self,
8522 id: u32,
8523 owner: u32,
8524 x: i32,
8525 y: i32,
8526 width: i32,
8527 height: i32,
8528 ) -> Result<bool, Error> {
8529 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8530 self.send(ThreadCommand::CreateView {
8531 id,
8532 owner,
8533 x,
8534 y,
8535 width,
8536 height,
8537 reply: reply_tx,
8538 })
8539 .map_err(|e| Error::Compositor(format!("Failed to create view: {e}")))?;
8540 Ok(reply_rx
8541 .recv_timeout(Duration::from_secs(2))
8542 .unwrap_or(false))
8543 }
8544
8545 pub fn resize_output(
8549 &self,
8550 id: u32,
8551 width: i32,
8552 height: i32,
8553 scale: f64,
8554 ) -> Result<bool, Error> {
8555 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8556 self.send(ThreadCommand::ResizeOutput {
8557 id,
8558 width,
8559 height,
8560 scale,
8561 reply: reply_tx,
8562 })
8563 .map_err(|e| Error::Compositor(format!("Failed to resize output: {e}")))?;
8564 Ok(reply_rx
8565 .recv_timeout(Duration::from_secs(2))
8566 .unwrap_or(false))
8567 }
8568
8569 pub fn destroy_output(&self, id: u32) -> Result<bool, Error> {
8572 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8573 self.send(ThreadCommand::DestroyOutput {
8574 id,
8575 reply: reply_tx,
8576 })
8577 .map_err(|e| Error::Compositor(format!("Failed to destroy output: {e}")))?;
8578 Ok(reply_rx
8579 .recv_timeout(Duration::from_secs(2))
8580 .unwrap_or(false))
8581 }
8582
8583 pub fn reposition_output(&self, id: u32, x: i32, y: i32) -> Result<bool, Error> {
8589 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8590 self.send(ThreadCommand::RepositionOutput {
8591 id,
8592 x,
8593 y,
8594 reply: reply_tx,
8595 })
8596 .map_err(|e| Error::Compositor(format!("Failed to reposition output: {e}")))?;
8597 Ok(reply_rx
8598 .recv_timeout(Duration::from_secs(2))
8599 .unwrap_or(false))
8600 }
8601
8602 pub fn list_outputs(&self) -> Result<Vec<OutputDesc>, Error> {
8605 let (reply_tx, reply_rx) = std::sync::mpsc::channel();
8606 self.send(ThreadCommand::ListOutputs { reply: reply_tx })
8607 .map_err(|e| Error::Compositor(format!("Failed to list outputs: {e}")))?;
8608 Ok(reply_rx
8609 .recv_timeout(Duration::from_secs(2))
8610 .unwrap_or_default())
8611 }
8612
8613 pub fn output_capacity(&self) -> Result<i64, Error> {
8618 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<i64>();
8619 self.send(ThreadCommand::OutputCapacity { reply: reply_tx })
8620 .map_err(|e| Error::Compositor(format!("Failed to query output capacity: {e}")))?;
8621 Ok(reply_rx.recv_timeout(Duration::from_secs(2)).unwrap_or(0))
8622 }
8623
8624 pub fn move_window_to_output(&self, window_id: u32, output_id: u32) -> Result<bool, Error> {
8627 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8628 self.send(ThreadCommand::MoveWindowToOutput {
8629 window_id,
8630 output_id,
8631 reply: reply_tx,
8632 })
8633 .map_err(|e| Error::Compositor(format!("Failed to move window: {e}")))?;
8634 Ok(reply_rx
8635 .recv_timeout(Duration::from_secs(2))
8636 .unwrap_or(false))
8637 }
8638
8639 pub fn list_windows(&self) -> Result<Vec<WindowDesc>, Error> {
8643 let (reply_tx, reply_rx) = std::sync::mpsc::channel();
8644 self.send(ThreadCommand::ListWindows { reply: reply_tx })
8645 .map_err(|e| Error::Compositor(format!("Failed to list windows: {e}")))?;
8646 Ok(reply_rx
8647 .recv_timeout(Duration::from_secs(2))
8648 .unwrap_or_default())
8649 }
8650
8651 pub fn desktop_view(&self) -> Result<Option<wm::DesktopView>, Error> {
8654 let (reply_tx, reply_rx) = std::sync::mpsc::channel();
8655 self.send(ThreadCommand::DesktopView { reply: reply_tx })
8656 .map_err(|e| Error::Compositor(format!("Failed to describe the desktop: {e}")))?;
8657 Ok(reply_rx
8658 .recv_timeout(Duration::from_secs(2))
8659 .unwrap_or_default())
8660 }
8661
8662 pub fn window_request(&self, id: wm::WindowId, request: wm::Request) -> Result<bool, Error> {
8667 let (reply_tx, reply_rx) = std::sync::mpsc::channel();
8668 self.send(ThreadCommand::WindowRequest {
8669 id,
8670 request,
8671 reply: reply_tx,
8672 })
8673 .map_err(|e| Error::Compositor(format!("Failed to send a window request: {e}")))?;
8674 Ok(reply_rx
8675 .recv_timeout(Duration::from_secs(2))
8676 .unwrap_or(false))
8677 }
8678
8679 pub fn set_cursor_size(&self, size: i32) -> Result<bool, Error> {
8683 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8684 self.send(ThreadCommand::SetCursorSize {
8685 size,
8686 reply: reply_tx,
8687 })
8688 .map_err(|e| Error::Compositor(format!("Failed to set cursor size: {e}")))?;
8689 Ok(reply_rx
8690 .recv_timeout(Duration::from_secs(2))
8691 .unwrap_or(false))
8692 }
8693
8694 pub fn set_clipboard(&self, entries: Vec<(String, Vec<u8>)>) -> Result<(), Error> {
8699 self.send(ThreadCommand::SetClipboard { entries })
8700 .map_err(|e| Error::Compositor(format!("Failed to set clipboard: {e}")))?;
8701 Ok(())
8702 }
8703
8704 pub fn inject_key(&self, scancode: u32, state: u32) -> Result<(), Error> {
8705 self.send(ThreadCommand::KeyboardKey { scancode, state })
8706 .map_err(|e| Error::Compositor(format!("Failed to inject key: {e}")))?;
8707 Ok(())
8708 }
8709
8710 pub fn inject_keys(&self, events: Vec<(u32, u32)>) -> Result<(), Error> {
8713 if events.is_empty() {
8714 return Ok(());
8715 }
8716 self.send(ThreadCommand::KeyboardKeys { events })
8717 .map_err(|e| Error::Compositor(format!("Failed to inject keys: {e}")))?;
8718 Ok(())
8719 }
8720
8721 pub fn set_keymap_overlay(&self, binds: Vec<(u32, u32)>) -> Result<(), Error> {
8731 self.send(ThreadCommand::SetKeymapOverlay { binds })
8732 .map_err(|e| Error::Compositor(format!("Failed to set overlay: {e}")))?;
8733 Ok(())
8734 }
8735
8736 pub fn set_keymap_string(&self, text: String) -> Result<(), Error> {
8737 self.send(ThreadCommand::SetKeymapString(text))
8738 .map_err(|e| Error::Compositor(format!("Failed to set keymap: {e}")))?;
8739 Ok(())
8740 }
8741
8742 pub fn get_xkb_keymap_string(&self) -> Result<String, Error> {
8749 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<String>();
8750 self.send(ThreadCommand::GetXkbKeymap { reply: reply_tx })
8751 .map_err(|e| Error::Compositor(format!("Failed to request keymap: {e}")))?;
8752 let result = reply_rx.recv_timeout(Duration::from_secs(2));
8753 match result {
8754 Ok(s) => Ok(s),
8755 Err(_) => Ok(String::new()),
8756 }
8757 }
8758
8759 pub fn inject_mouse_move(&self, x: f64, y: f64) -> Result<u64, Error> {
8762 let motion = self.motions.fetch_add(1, Ordering::Relaxed) + 1;
8763 self.send(ThreadCommand::PointerMotion { x, y, motion })
8764 .map_err(|e| Error::Compositor(format!("Failed to inject motion: {e}")))?;
8765 Ok(motion)
8766 }
8767
8768 pub fn inject_relative_mouse_move(&self, dx: f64, dy: f64) -> Result<u64, Error> {
8774 let motion = self.motions.fetch_add(1, Ordering::Relaxed) + 1;
8775 self.send(ThreadCommand::PointerRelativeMotion { dx, dy, motion })
8776 .map_err(|e| Error::Compositor(format!("Failed to inject relative motion: {e}")))?;
8777 Ok(motion)
8778 }
8779
8780 pub fn inject_mouse_button(&self, btn: u32, state: u32) -> Result<(), Error> {
8781 self.send(ThreadCommand::PointerButton { btn, state })
8782 .map_err(|e| Error::Compositor(format!("Failed to inject button: {e}")))?;
8783 Ok(())
8784 }
8785
8786 pub fn inject_mouse_scroll(&self, x: f64, y: f64) -> Result<(), Error> {
8787 self.send(ThreadCommand::PointerAxis { x, y })
8788 .map_err(|e| Error::Compositor(format!("Failed to inject axis: {e}")))?;
8789 Ok(())
8790 }
8791
8792 pub fn inject_finger_scroll(&self, dx: f64, dy: f64) -> Result<(), Error> {
8795 self.send(ThreadCommand::PointerScroll { dx, dy })
8796 .map_err(|e| Error::Compositor(format!("Failed to inject finger scroll: {e}")))?;
8797 Ok(())
8798 }
8799
8800 pub fn inject_finger_scroll_end(&self) -> Result<(), Error> {
8802 self.send(ThreadCommand::PointerScrollEnd)
8803 .map_err(|e| Error::Compositor(format!("Failed to end finger scroll: {e}")))?;
8804 Ok(())
8805 }
8806
8807 pub fn set_cursor_rendering(&self, enabled: bool) -> Result<(), Error> {
8808 self.send(ThreadCommand::UpdateCursorConfig {
8809 render_on_framebuffer: enabled,
8810 })
8811 .map_err(|e| Error::Compositor(format!("Failed to set cursor config: {e}")))?;
8812 Ok(())
8813 }
8814
8815 pub fn request_idr_frame(&self, display_id: u32) -> Result<(), Error> {
8820 self.send(ThreadCommand::RequestIdr { display_id })
8821 .map_err(|e| Error::Compositor(format!("Failed to request IDR: {e}")))?;
8822 Ok(())
8823 }
8824
8825 pub fn invalidate_reference(
8830 &self,
8831 frame_id: u16,
8832 display_id: u32,
8833 withheld: bool,
8834 ) -> Result<(), Error> {
8835 self.send(ThreadCommand::InvalidateReference {
8836 display_id,
8837 frame_id,
8838 withheld,
8839 })
8840 .map_err(|e| Error::Compositor(format!("Failed to invalidate reference: {e}")))
8841 }
8842
8843 pub fn acknowledge_reference(
8848 &self,
8849 frame_id: u16,
8850 display_id: u32,
8851 held: bool,
8852 ) -> Result<(), Error> {
8853 self.send(ThreadCommand::AcknowledgeReference {
8854 display_id,
8855 frame_id,
8856 held,
8857 })
8858 .map_err(|e| Error::Compositor(format!("Failed to acknowledge reference: {e}")))
8859 }
8860
8861 pub fn update_rate(
8864 &self,
8865 bitrate_kbps: Option<i32>,
8866 vbv_multiplier: Option<f64>,
8867 fps: Option<f64>,
8868 display_id: u32,
8869 ) -> Result<(), Error> {
8870 self.send(ThreadCommand::UpdateRate {
8871 display_id,
8872 bitrate_kbps,
8873 vbv_multiplier,
8874 fps,
8875 })
8876 .map_err(|e| Error::Compositor(format!("Failed to update rate: {e}")))?;
8877 Ok(())
8878 }
8879
8880 pub fn set_xkb_layout(
8884 &self,
8885 layout: String,
8886 variant: String,
8887 options: String,
8888 model: String,
8889 rules: String,
8890 ) -> Result<bool, Error> {
8891 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<bool>();
8892 self.send(ThreadCommand::SetXkbLayout {
8893 rules,
8894 model,
8895 layout,
8896 variant,
8897 options,
8898 reply: reply_tx,
8899 })
8900 .map_err(|e| Error::Compositor(format!("Failed to set layout: {e}")))?;
8901 Ok(reply_rx
8902 .recv_timeout(Duration::from_secs(2))
8903 .unwrap_or(false))
8904 }
8905
8906 pub fn get_keyboard_state(&self) -> Result<(Vec<u32>, u32), Error> {
8909 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<(Vec<u32>, u32)>();
8910 self.send(ThreadCommand::GetKeyboardState { reply: reply_tx })
8911 .map_err(|e| Error::Compositor(format!("Failed to read keyboard state: {e}")))?;
8912 Ok(reply_rx
8913 .recv_timeout(Duration::from_secs(2))
8914 .unwrap_or_default())
8915 }
8916
8917 pub fn get_realized_geometry(&self, display_id: u32) -> Result<Option<(i32, i32, f64)>, Error> {
8928 let (reply_tx, reply_rx) = std::sync::mpsc::channel::<(i32, i32, f64)>();
8929 self.send(ThreadCommand::CuGetInfo {
8930 display_id,
8931 resp: reply_tx,
8932 })
8933 .map_err(|e| Error::Compositor(format!("Failed to read geometry: {e}")))?;
8934 Ok(reply_rx.recv_timeout(GEOMETRY_BARRIER_TIMEOUT).ok())
8935 }
8936
8937 pub fn capture_state(&self, display_id: u32) -> (String, Option<String>) {
8944 wayland_capture_state(display_id)
8945 }
8946}
8947
8948const GEOMETRY_BARRIER_TIMEOUT: Duration = Duration::from_secs(6);
8953
8954fn wayland_capture_state(display_id: u32) -> (String, Option<String>) {
8957 let running = wayland_alive().lock().unwrap().contains(&display_id);
8958 let last_error = wayland_capture_err()
8959 .lock()
8960 .unwrap()
8961 .get(&display_id)
8962 .cloned();
8963 let state = if running {
8964 "running"
8965 } else if last_error.is_some() {
8966 "failed"
8967 } else {
8968 "idle"
8969 };
8970 (state.to_string(), last_error)
8971}
8972
8973impl Compositor {
8974 pub fn update_tunables(&self, display_id: u32, t: LiveTunables) -> Result<(), Error> {
8976 self.send(ThreadCommand::UpdateTunables {
8977 display_id,
8978 tunables: t,
8979 })
8980 .map_err(|e| Error::Compositor(format!("Failed to update tunables: {e}")))
8981 }
8982}
8983
8984use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, Ordering};
8985use std::sync::{Condvar, Mutex, OnceLock};
8986
8987use crate::encoders::software::{EncodedStripe, FrameTiming};
8988
8989static WAYLAND_BACKEND: OnceLock<Mutex<Option<Arc<Compositor>>>> = OnceLock::new();
8991static WAYLAND_SOCKET_NAME: Mutex<Option<String>> = Mutex::new(None);
8996static WAYLAND_SOCKET_CV: Condvar = Condvar::new();
8997
8998fn publish_socket_name(name: &str) {
8999 *WAYLAND_SOCKET_NAME.lock().unwrap() = Some(name.to_string());
9000 WAYLAND_SOCKET_CV.notify_all();
9001}
9002
9003fn wait_socket_name(timeout: Duration) -> Option<String> {
9005 let deadline = Instant::now() + timeout;
9006 let mut g = WAYLAND_SOCKET_NAME.lock().unwrap();
9007 loop {
9008 if let Some(name) = g.as_ref() {
9009 return Some(name.clone());
9010 }
9011 let now = Instant::now();
9012 if now >= deadline {
9013 return None;
9014 }
9015 let (gg, _) = WAYLAND_SOCKET_CV.wait_timeout(g, deadline - now).unwrap();
9016 g = gg;
9017 }
9018}
9019static PENDING_CURSOR_CALLBACK: Mutex<Option<Arc<dyn crate::sink::CursorSink>>> = Mutex::new(None);
9023static CURSOR_REGISTRATIONS: Mutex<Vec<(u64, Arc<dyn crate::sink::CursorSink>)>> =
9027 Mutex::new(Vec::new());
9028pub(crate) static SHUTDOWN: AtomicBool = AtomicBool::new(false);
9033static WAYLAND_OWNERS: OnceLock<Mutex<std::collections::HashMap<u32, u64>>> = OnceLock::new();
9037
9038fn wayland_owners() -> &'static Mutex<std::collections::HashMap<u32, u64>> {
9039 WAYLAND_OWNERS.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
9040}
9041
9042static WAYLAND_ALIVE_DISPLAYS: OnceLock<Mutex<std::collections::HashSet<u32>>> = OnceLock::new();
9046
9047fn wayland_alive() -> &'static Mutex<std::collections::HashSet<u32>> {
9048 WAYLAND_ALIVE_DISPLAYS.get_or_init(|| Mutex::new(std::collections::HashSet::new()))
9049}
9050static WAYLAND_CAPTURE_ERR: OnceLock<Mutex<std::collections::HashMap<u32, String>>> =
9056 OnceLock::new();
9057
9058fn wayland_capture_err() -> &'static Mutex<std::collections::HashMap<u32, String>> {
9059 WAYLAND_CAPTURE_ERR.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
9060}
9061
9062static WAYLAND_ACTIVE_CODEC: OnceLock<Mutex<std::collections::HashMap<u32, u32>>> = OnceLock::new();
9064
9065fn wayland_active_codec() -> &'static Mutex<std::collections::HashMap<u32, u32>> {
9068 WAYLAND_ACTIVE_CODEC.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
9069}
9070
9071fn set_wayland_active_codec(display_id: u32, codec: Option<Codec>) {
9072 let mut map = wayland_active_codec().lock().unwrap();
9073 match codec {
9074 Some(c) => {
9075 map.insert(display_id, c.id());
9076 }
9077 None => {
9078 map.remove(&display_id);
9079 }
9080 }
9081}
9082
9083fn set_wayland_capture_err(display_id: u32, err: Option<String>) {
9084 let mut map = wayland_capture_err().lock().unwrap();
9085 match err {
9086 Some(e) => {
9087 map.insert(display_id, e);
9088 }
9089 None => {
9090 map.remove(&display_id);
9091 }
9092 }
9093}
9094static NEXT_CAPTURE_ID: AtomicU64 = AtomicU64::new(1);
9098static LIVE_X11: OnceLock<Mutex<Vec<Arc<crate::x11::Controls>>>> = OnceLock::new();
9102
9103fn live_x11() -> &'static Mutex<Vec<Arc<crate::x11::Controls>>> {
9104 LIVE_X11.get_or_init(|| Mutex::new(Vec::new()))
9105}
9106
9107pub(crate) fn unpremultiply_rgba(image: &mut image::RgbaImage) {
9114 for p in image.pixels_mut() {
9115 let a = u32::from(p.0[3]);
9116 if a == 0 {
9117 p.0 = [0, 0, 0, 0];
9118 } else if a < 255 {
9119 for c in &mut p.0[..3] {
9120 *c = ((u32::from(*c) * 255 + a / 2) / a).min(255) as u8;
9121 }
9122 }
9123 }
9124}
9125
9126const STOP_JOIN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
9131
9132pub(crate) fn join_within(handle: thread::JoinHandle<()>, what: &str) {
9136 let deadline = std::time::Instant::now() + STOP_JOIN_TIMEOUT;
9137 while !handle.is_finished() {
9138 if std::time::Instant::now() >= deadline {
9139 eprintln!(
9140 "[X11] the {what} thread did not end within {} s; it is left behind",
9141 STOP_JOIN_TIMEOUT.as_secs()
9142 );
9143 return;
9144 }
9145 thread::sleep(std::time::Duration::from_millis(10));
9146 }
9147 let _ = handle.join();
9148}
9149
9150#[cfg(test)]
9151mod gpu_bring_up_tests {
9152 use super::*;
9153
9154 #[test]
9158 #[ignore]
9159 fn gpu_bring_up_fails_whole_on_a_target_the_allocator_refuses() {
9160 let index = first_render_node_index().expect("needs a render node");
9161 let node = format!("/dev/dri/renderD{}", 128 + index);
9162 let path = std::path::Path::new(&node);
9163 let err = gpu_bring_up(path, 0, 0)
9164 .err()
9165 .expect("an empty target is refused");
9166 assert_eq!(err, "Failed to allocate GBM buffer");
9167 let up = gpu_bring_up(path, 64, 64).expect("a 64x64 target is taken");
9168 assert!(!up.formats.is_empty());
9169 assert_eq!((up.target.0.width(), up.target.0.height()), (64, 64));
9170 }
9171}
9172
9173#[cfg(test)]
9174mod join_tests {
9175 use super::*;
9176
9177 #[test]
9179 fn join_within_is_bounded() {
9180 let quick = thread::spawn(|| {});
9181 let started = std::time::Instant::now();
9182 join_within(quick, "quick");
9183 assert!(started.elapsed() < STOP_JOIN_TIMEOUT);
9184 let (tx, rx) = std::sync::mpsc::channel::<()>();
9185 let stuck = thread::spawn(move || {
9186 let _ = rx.recv();
9187 });
9188 let started = std::time::Instant::now();
9189 join_within(stuck, "stuck");
9190 assert!(started.elapsed() >= STOP_JOIN_TIMEOUT);
9191 drop(tx);
9192 }
9193}
9194
9195pub(crate) fn boost_thread_priority(nice: libc::c_int) {
9202 static RTKIT: OnceLock<zbus::Result<zbus::Connection>> = OnceLock::new();
9203 static REFUSED: std::sync::Once = std::sync::Once::new();
9204 let tid = unsafe { libc::syscall(libc::SYS_gettid) };
9205 if unsafe { libc::setpriority(libc::PRIO_PROCESS, tid as libc::id_t, nice) } == 0 {
9206 return;
9207 }
9208 let granted = RTKIT
9209 .get_or_init(|| zbus::block_on(zbus::Connection::system()))
9210 .as_ref()
9211 .map_err(|e| e.to_string())
9212 .and_then(|conn| {
9213 zbus::block_on(async {
9214 zbus::Proxy::new(
9215 conn,
9216 "org.freedesktop.RealtimeKit1",
9217 "/org/freedesktop/RealtimeKit1",
9218 "org.freedesktop.RealtimeKit1",
9219 )
9220 .await?
9221 .call_method("MakeThreadHighPriority", &(tid as u64, nice))
9222 .await
9223 })
9224 .map(|_| ())
9225 .map_err(|e| e.to_string())
9226 });
9227 if let Err(why) = granted {
9228 REFUSED.call_once(|| {
9229 eprintln!("[pixelflux] Thread priority {nice} refused and rtkit does not grant it; capture threads run at normal priority.");
9230 crate::log::debug!("[pixelflux] rtkit: {why}");
9231 });
9232 }
9233}
9234
9235fn wayland_update_rate(
9237 display_id: u32,
9238 bitrate_kbps: Option<i32>,
9239 vbv_multiplier: Option<f64>,
9240 fps: Option<f64>,
9241) {
9242 if let Some(slot) = WAYLAND_BACKEND.get()
9243 && let Some(be) = slot.lock().unwrap().as_ref()
9244 {
9245 let _ = be.update_rate(bitrate_kbps, vbv_multiplier, fps, display_id);
9246 }
9247}
9248
9249fn wayland_update_link_rate(display_id: u32, link_kbps: i32) {
9252 if let Some(slot) = WAYLAND_BACKEND.get()
9253 && let Some(be) = slot.lock().unwrap().as_ref()
9254 {
9255 let _ = be.send(ThreadCommand::UpdateLinkRate {
9256 display_id,
9257 link_kbps,
9258 });
9259 }
9260}
9261
9262fn wayland_update_tunables(display_id: u32, t: LiveTunables) {
9264 if let Some(slot) = WAYLAND_BACKEND.get()
9265 && let Some(be) = slot.lock().unwrap().as_ref()
9266 {
9267 let _ = be.update_tunables(display_id, t);
9268 }
9269}
9270
9271pub fn ensure_compositor(
9280 width: i32,
9281 height: i32,
9282 explicit_node: String,
9283 auto_gpu: String,
9284 fallback_node: String,
9285 cursor_size: i32,
9286) -> Result<Arc<Compositor>, Error> {
9287 let slot = WAYLAND_BACKEND.get_or_init(|| Mutex::new(None));
9288 let mut g = slot.lock().unwrap();
9289 if g.is_none() {
9290 let mut node = (!explicit_node.is_empty()).then_some(explicit_node);
9291 let mut auto_gpu_selected = false;
9292 if node.is_none()
9293 && let Some(request) = parse_auto_gpu(&auto_gpu)
9294 {
9295 match auto_select_render_node(request.as_deref()) {
9296 Some(picked) => {
9297 println!("[Wayland] AUTO_GPU selected {picked}.");
9298 node = Some(picked);
9299 auto_gpu_selected = true;
9300 }
9301 None => {
9302 if let Some(token) = request {
9303 eprintln!("[pixelflux] AUTO_GPU={token}: no matching GPU found.");
9304 }
9305 }
9306 }
9307 }
9308 let node = node.unwrap_or(fallback_node);
9309 let be = Arc::new(Compositor::new(
9310 width,
9311 height,
9312 node,
9313 auto_gpu_selected,
9314 cursor_size,
9315 ));
9316 if let Some(cb) = PENDING_CURSOR_CALLBACK.lock().unwrap().take() {
9317 let _ = be.set_cursor_sink(Some(cb));
9318 }
9319 *g = Some(be);
9320 }
9321 Ok(g.as_ref().unwrap().clone())
9322}
9323
9324pub fn install_window_policy(policy: Box<dyn crate::wm::WindowPolicy>) -> Result<(), Error> {
9326 running_compositor()
9327 .ok_or_else(|| Error::Compositor("no compositor is running".to_string()))?
9328 .install_window_policy(policy)
9329}
9330
9331pub fn desktop_view() -> Option<wm::DesktopView> {
9334 running_compositor()?.desktop_view().ok().flatten()
9335}
9336
9337pub fn window_request(id: wm::WindowId, request: wm::Request) -> bool {
9340 running_compositor().is_some_and(|be| be.window_request(id, request).unwrap_or(false))
9341}
9342
9343pub const XWAYLAND_VAR: &str = wayland::xwm::XWAYLAND_VAR;
9346
9347pub fn start_xwayland(notify: Option<std::sync::mpsc::Sender<u32>>) -> Result<(), Error> {
9349 running_compositor()
9350 .ok_or_else(|| Error::Compositor("no compositor is running".to_string()))?
9351 .start_xwayland(notify)
9352}
9353
9354pub fn running_compositor() -> Option<Arc<Compositor>> {
9358 let slot = WAYLAND_BACKEND.get()?;
9359 let g = slot.lock().unwrap();
9360 g.as_ref().cloned()
9361}
9362
9363struct ScState {
9367 backend: u8,
9369 cursor_ref: bool,
9377 controls: Option<Arc<crate::x11::Controls>>,
9378 starting: Option<Arc<crate::x11::Controls>>,
9384 handle: Option<thread::JoinHandle<()>>,
9385 cap_thread_id: Option<thread::ThreadId>,
9386 encode_thread_id: Option<thread::ThreadId>,
9389 encode_tid_rx: Option<std::sync::mpsc::Receiver<thread::ThreadId>>,
9397 deliver_handle: Option<thread::JoinHandle<()>>,
9402 deliver_thread_id: Option<thread::ThreadId>,
9403 wl_display: u32,
9405 err: Option<Arc<Mutex<Option<String>>>>,
9409}
9410
9411impl ScState {
9412 fn x11_controls(&self) -> Option<Arc<crate::x11::Controls>> {
9415 match self.backend {
9416 1 => self.controls.clone(),
9417 0 => self.starting.clone(),
9418 _ => None,
9419 }
9420 }
9421}
9422
9423#[derive(Clone, Debug)]
9430pub struct StreamInfo {
9431 pub info: report::StreamInfo,
9432 pub renderer: Option<report::Renderer>,
9433}
9434
9435#[derive(Clone, Copy, Debug)]
9438pub struct StreamStats {
9439 pub totals: report::StreamTotals,
9440 pub peaks: report::StreamPeaks,
9441}
9442
9443pub struct ScreenCapture {
9444 id: u64,
9445 inner: Mutex<ScState>,
9446}
9447
9448impl Default for ScreenCapture {
9449 fn default() -> Self {
9450 Self::new()
9451 }
9452}
9453
9454impl ScreenCapture {
9455 pub fn set_cursor_sink(
9463 &self,
9464 sink: Option<Arc<dyn crate::sink::CursorSink>>,
9465 ) -> Result<(), Error> {
9466 let sink = {
9467 let mut regs = CURSOR_REGISTRATIONS.lock().unwrap();
9468 regs.retain(|(id, _)| *id != self.id);
9469 if let Some(cb) = sink {
9470 regs.push((self.id, cb));
9471 }
9472 regs.last().map(|(_, cb)| cb.clone())
9473 };
9474 crate::x11::cursor::set_callback(sink.clone());
9475 let slot = WAYLAND_BACKEND.get_or_init(|| Mutex::new(None));
9476 let g = slot.lock().unwrap();
9477 match g.as_ref() {
9478 Some(be) => be.set_cursor_sink(sink),
9479 None => {
9480 *PENDING_CURSOR_CALLBACK.lock().unwrap() = sink;
9481 Ok(())
9482 }
9483 }
9484 }
9485
9486 fn hand_report(&self, report: encoders::reference::ReferenceReport) {
9489 use encoders::reference::ReferenceReport;
9490 let (backend, controls, did) = {
9491 let st = self.inner.lock().unwrap();
9492 (st.backend, st.x11_controls(), st.wl_display)
9493 };
9494 match backend {
9495 0 | 1 => {
9496 if let Some(c) = controls {
9497 c.reference_reports.lock().unwrap().push(report);
9498 }
9499 }
9500 2 => {
9501 if let Some(slot) = WAYLAND_BACKEND.get()
9502 && let Some(be) = slot.lock().unwrap().as_ref()
9503 {
9504 let _ = match report {
9505 ReferenceReport::Lost(frame_id) => {
9506 be.invalidate_reference(frame_id, did, false)
9507 }
9508 ReferenceReport::Withheld(frame_id) => {
9509 be.invalidate_reference(frame_id, did, true)
9510 }
9511 ReferenceReport::Held(frame_id) => {
9512 be.acknowledge_reference(frame_id, did, true)
9513 }
9514 ReferenceReport::Sent(frame_id) => {
9515 be.acknowledge_reference(frame_id, did, false)
9516 }
9517 };
9518 }
9519 }
9520 _ => {}
9521 }
9522 }
9523
9524 fn stop_internal(&self) -> Result<(), Error> {
9535 let (handle, deliver_handle, same_thread, backend, controls, wl_display, cursor_ref) = {
9536 let mut st = self.inner.lock().unwrap();
9537 if let Some(c) = &st.controls {
9538 c.stop.store(true, Ordering::Relaxed);
9539 }
9540 if let Some(c) = st.starting.take() {
9541 c.stop.store(true, Ordering::Relaxed);
9542 }
9543 let cur = Some(thread::current().id());
9544 if st.encode_thread_id.is_none()
9545 && let Some(rx) = st.encode_tid_rx.as_ref()
9546 && let Ok(id) = rx.try_recv()
9547 {
9548 st.encode_thread_id = Some(id);
9549 }
9550 let same = st.cap_thread_id == cur
9551 || st.encode_thread_id == cur
9552 || st.deliver_thread_id == cur;
9553 let controls = st.controls.take();
9554 let handle = st.handle.take();
9555 let deliver_handle = st.deliver_handle.take();
9556 let backend = st.backend;
9557 let cursor_ref = std::mem::take(&mut st.cursor_ref);
9558 let wl_display = st.wl_display;
9559 st.backend = 0;
9560 st.cap_thread_id = None;
9561 st.encode_thread_id = None;
9562 st.encode_tid_rx = None;
9563 st.deliver_thread_id = None;
9564 st.wl_display = 0;
9565 st.err = None;
9566 (
9567 handle,
9568 deliver_handle,
9569 same,
9570 backend,
9571 controls,
9572 wl_display,
9573 cursor_ref,
9574 )
9575 };
9576 if let Some(c) = &controls {
9577 live_x11().lock().unwrap().retain(|x| !Arc::ptr_eq(x, c));
9578 }
9579 if cursor_ref {
9580 crate::x11::cursor::release();
9581 }
9582 if backend == 2 {
9583 let did = wl_display;
9584 let owned = {
9585 let mut owners = wayland_owners().lock().unwrap();
9586 if owners.get(&did) == Some(&self.id) {
9587 owners.remove(&did);
9588 true
9589 } else {
9590 false
9591 }
9592 };
9593 if owned
9594 && let Some(slot) = WAYLAND_BACKEND.get()
9595 && let Some(be) = slot.lock().unwrap().as_ref()
9596 {
9597 let _ = be.stop_capture(did);
9598 }
9599 } else {
9600 if same_thread {
9601 drop(handle);
9603 drop(deliver_handle);
9604 } else {
9605 {
9606 if let Some(h) = handle {
9607 join_within(h, "capture");
9608 }
9609 if let Some(h) = deliver_handle {
9612 join_within(h, "delivery");
9613 }
9614 };
9615 }
9616 }
9617 Ok(())
9618 }
9619}
9620
9621impl ScreenCapture {
9622 fn report(&self) -> Option<Arc<report::StreamReport>> {
9624 let st = self.inner.lock().unwrap();
9625 match st.backend {
9626 1 => st.controls.as_ref().map(|c| c.report.clone()),
9627 2 => report::wayland_reports()
9628 .lock()
9629 .unwrap()
9630 .get(&st.wl_display)
9631 .cloned(),
9632 _ => None,
9633 }
9634 }
9635}
9636
9637impl ScreenCapture {
9638 pub fn new() -> Self {
9639 Self {
9640 id: NEXT_CAPTURE_ID.fetch_add(1, Ordering::Relaxed),
9641 inner: Mutex::new(ScState {
9642 backend: 0,
9643 cursor_ref: false,
9644 controls: None,
9645 starting: None,
9646 handle: None,
9647 cap_thread_id: None,
9648 encode_thread_id: None,
9649 encode_tid_rx: None,
9650 deliver_handle: None,
9651 deliver_thread_id: None,
9652 wl_display: 0,
9653 err: None,
9654 }),
9655 }
9656 }
9657
9658 pub fn active_codec(&self) -> Result<Option<String>, Error> {
9662 let (backend, controls, wl_display) = {
9663 let st = self.inner.lock().unwrap();
9664 (st.backend, st.controls.clone(), st.wl_display)
9665 };
9666 let id = match backend {
9667 1 => controls.map_or(u32::MAX, |c| c.codec.load(Ordering::Relaxed)),
9668 2 => {
9669 wayland_active_codec()
9670 .lock()
9671 .unwrap()
9672 .get(&wl_display)
9673 .copied()
9674 }
9675 .unwrap_or(u32::MAX),
9676 _ => u32::MAX,
9677 };
9678 Ok(Codec::from_id(id).map(|c| c.name().to_string()))
9679 }
9680
9681 pub fn stream_info(&self) -> Option<StreamInfo> {
9692 let report = self.report()?;
9693 let (info, renderer) = {
9694 let mut info = report.info();
9695 let renderer = (info.backend == "wayland").then(report::renderer);
9696 if info.hardware && info.gpu.is_empty() {
9697 info.gpu = match &renderer {
9698 Some(r) if render_node_index(&r.node) == Some(info.encode_node) => {
9699 r.gpu.clone()
9700 }
9701 _ => node_gpu_name(info.encode_node),
9702 };
9703 }
9704 (info, renderer)
9705 };
9706 Some(StreamInfo { info, renderer })
9707 }
9708
9709 pub fn stream_stats(&self) -> Option<StreamStats> {
9716 let report = self.report()?;
9717 let totals = report.totals();
9718 let peaks = report.take_peaks();
9719 Some(StreamStats { totals, peaks })
9720 }
9721
9722 pub fn stop_capture(&self) -> Result<(), Error> {
9723 self.stop_internal()
9724 }
9725
9726 pub fn request_idr_frame(&self) -> Result<(), Error> {
9727 let (backend, controls, did) = {
9728 let st = self.inner.lock().unwrap();
9729 (st.backend, st.x11_controls(), st.wl_display)
9730 };
9731 match backend {
9732 0 | 1 => {
9733 if let Some(c) = controls {
9734 c.force_idr.store(true, Ordering::Relaxed);
9735 }
9736 }
9737 2 => {
9738 if let Some(slot) = WAYLAND_BACKEND.get()
9739 && let Some(be) = slot.lock().unwrap().as_ref()
9740 {
9741 let _ = be.request_idr_frame(did);
9742 }
9743 }
9744 _ => {}
9745 }
9746 Ok(())
9747 }
9748
9749 pub fn invalidate_reference(&self, frame_id: u16, withheld: bool) -> Result<(), Error> {
9754 use encoders::reference::ReferenceReport;
9755 let report = if withheld {
9756 ReferenceReport::Withheld(frame_id)
9757 } else {
9758 ReferenceReport::Lost(frame_id)
9759 };
9760 self.hand_report(report);
9761 Ok(())
9762 }
9763
9764 pub fn acknowledge_reference(&self, frame_id: u16, held: bool) -> Result<(), Error> {
9769 use encoders::reference::ReferenceReport;
9770 let report = if held {
9771 ReferenceReport::Held(frame_id)
9772 } else {
9773 ReferenceReport::Sent(frame_id)
9774 };
9775 self.hand_report(report);
9776 Ok(())
9777 }
9778
9779 pub fn update_video_bitrate(&self, kbps: i32) -> Result<(), Error> {
9784 let (backend, controls, did) = {
9785 let st = self.inner.lock().unwrap();
9786 (st.backend, st.x11_controls(), st.wl_display)
9787 };
9788 match backend {
9789 0 | 1 => {
9790 if let Some(c) = &controls {
9791 c.bitrate_kbps.store(kbps, Ordering::Relaxed);
9792 c.rate_dirty.store(true, Ordering::Release);
9793 }
9794 }
9795 2 => wayland_update_rate(did, Some(kbps), None, None),
9796 _ => {}
9797 }
9798 Ok(())
9799 }
9800
9801 pub fn update_link_rate(&self, kbps: i32) -> Result<(), Error> {
9805 let (backend, controls, did) = {
9806 let st = self.inner.lock().unwrap();
9807 (st.backend, st.x11_controls(), st.wl_display)
9808 };
9809 match backend {
9810 0 | 1 => {
9811 if let Some(c) = &controls {
9812 c.link_kbps.store(kbps, Ordering::Relaxed);
9813 }
9814 }
9815 2 => wayland_update_link_rate(did, kbps),
9816 _ => {}
9817 }
9818 Ok(())
9819 }
9820
9821 pub fn update_framerate(&self, fps: f64) -> Result<(), Error> {
9822 let (backend, controls, did) = {
9823 let st = self.inner.lock().unwrap();
9824 (st.backend, st.x11_controls(), st.wl_display)
9825 };
9826 match backend {
9827 0 | 1 => {
9828 if let Some(c) = &controls {
9829 c.fps_bits.store(fps.max(1.0).to_bits(), Ordering::Relaxed);
9830 c.rate_dirty.store(true, Ordering::Release);
9831 crate::x11::follow_frame_rate();
9832 }
9833 }
9834 2 => wayland_update_rate(did, None, None, Some(fps)),
9835 _ => {}
9836 }
9837 Ok(())
9838 }
9839
9840 pub fn update_vbv_multiplier(&self, multiplier: f64) -> Result<(), Error> {
9842 let (backend, controls, did) = {
9843 let st = self.inner.lock().unwrap();
9844 (st.backend, st.x11_controls(), st.wl_display)
9845 };
9846 match backend {
9847 0 | 1 => {
9848 if let Some(c) = &controls {
9849 c.vbv_mult_milli
9850 .store((multiplier * 1000.0).round() as i32, Ordering::Relaxed);
9851 c.rate_dirty.store(true, Ordering::Release);
9852 }
9853 }
9854 2 => wayland_update_rate(did, None, Some(multiplier), None),
9855 _ => {}
9856 }
9857 Ok(())
9858 }
9859
9860 pub fn update_capture_region(
9866 &self,
9867 x: i32,
9868 y: i32,
9869 width: i32,
9870 height: i32,
9871 ) -> Result<(), Error> {
9872 let controls = {
9873 let st = self.inner.lock().unwrap();
9874 if st.backend == 2 {
9875 return Err(Error::Capture("update_capture_region is X11-only; on Wayland restart the capture with new dimensions".into()));
9876 }
9877 st.x11_controls()
9878 };
9879 if let Some(c) = &controls {
9880 *c.region.lock().unwrap() = (x.max(0), y.max(0), width, height);
9881 c.region_dirty.store(true, Ordering::Release);
9882 }
9883 Ok(())
9884 }
9885
9886 pub fn is_capturing(&self) -> bool {
9887 let st = self.inner.lock().unwrap();
9888 match st.backend {
9889 1 => st
9890 .controls
9891 .as_ref()
9892 .map(|c| !c.stop.load(Ordering::Relaxed))
9893 .unwrap_or(false),
9894 2 => {
9895 wayland_owners().lock().unwrap().get(&st.wl_display) == Some(&self.id)
9896 && wayland_alive().lock().unwrap().contains(&st.wl_display)
9897 }
9898 _ => false,
9899 }
9900 }
9901
9902 pub fn inject_key(&self, scancode: u32, state: u32) -> Result<(), Error> {
9903 running_compositor().map_or(Ok(()), |be| be.inject_key(scancode, state))
9904 }
9905
9906 pub fn inject_keys(&self, events: Vec<(u32, u32)>) -> Result<(), Error> {
9908 running_compositor().map_or(Ok(()), |be| be.inject_keys(events))
9909 }
9910
9911 pub fn set_keymap_string(&self, text: String) -> Result<(), Error> {
9912 running_compositor().map_or(Ok(()), |be| be.set_keymap_string(text))
9913 }
9914
9915 pub fn set_keymap_overlay(&self, binds: Vec<(u32, u32)>) -> Result<(), Error> {
9919 running_compositor().map_or(Ok(()), |be| be.set_keymap_overlay(binds))
9920 }
9921
9922 pub fn set_app_wayland_display(&self, display: String) {
9927 crate::computer_use::set_app_wayland_display(if display.is_empty() {
9928 None
9929 } else {
9930 Some(display)
9931 });
9932 }
9933
9934 pub fn type_text_wayland(&self, display: &str, text: &str) -> Result<(), Error> {
9940 {
9941 let path = crate::wayland::wlclient::socket_path(display)
9942 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
9943 crate::wayland::vkclient::type_text_to(&path, text)
9944 }
9945 .map_err(|e: String| {
9946 if e.contains("zwp_virtual_keyboard_manager_v1") {
9947 Error::Unavailable(e)
9948 } else {
9949 Error::Capture(e)
9950 }
9951 })
9952 }
9953
9954 pub fn type_keysyms_wayland(&self, display: &str, keysyms: &[u32]) -> Result<(), Error> {
9958 {
9959 let path = crate::wayland::wlclient::socket_path(display)
9960 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
9961 crate::wayland::vkclient::type_keysyms_to(&path, keysyms)
9962 }
9963 .map_err(|e: String| {
9964 if e.contains("zwp_virtual_keyboard_manager_v1") {
9965 Error::Unavailable(e)
9966 } else {
9967 Error::Capture(e)
9968 }
9969 })
9970 }
9971
9972 pub fn set_app_output_scale(
9977 &self,
9978 display: &str,
9979 scale: f64,
9980 index: usize,
9981 ) -> Result<bool, Error> {
9982 {
9983 let path = crate::wayland::wlclient::socket_path(display)
9984 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
9985 crate::wayland::outclient::set_output_scale(&path, index, scale)
9986 }
9987 .map(|outcome| matches!(outcome, crate::wayland::outclient::ScaleOutcome::Applied))
9988 .map_err(Error::Capture)
9989 }
9990
9991 pub fn set_app_screen_geometry(
10000 &self,
10001 display: &str,
10002 width: i32,
10003 height: i32,
10004 scale: f64,
10005 index: usize,
10006 ) -> Result<bool, Error> {
10007 {
10008 let path = crate::wayland::wlclient::socket_path(display)
10009 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10010 crate::wayland::outclient::set_screen_geometry(&path, index, (width, height), scale)
10011 }
10012 .map(|outcome| matches!(outcome, crate::wayland::outclient::ScaleOutcome::Applied))
10013 .map_err(Error::Capture)
10014 }
10015
10016 pub fn set_app_screen_layout(
10024 &self,
10025 display: &str,
10026 rects: Vec<(i32, i32, i32, i32)>,
10027 ) -> Result<usize, Error> {
10028 {
10029 let path = crate::wayland::wlclient::socket_path(display)
10030 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10031 let placed = crate::wayland::outclient::set_screen_layout(&path, rects.clone())?;
10032 if placed == 0 {
10033 crate::wayland::kdeclient::set_screen_layout(&path, &rects)
10034 } else {
10035 Ok(placed)
10036 }
10037 }
10038 .map_err(Error::Capture)
10039 }
10040
10041 pub fn list_app_screens(&self, display: &str) -> Result<Vec<crate::wayland::AppScreen>, Error> {
10048 {
10049 let path = crate::wayland::wlclient::socket_path(display)
10050 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10051 let screens = crate::wayland::outclient::list_screens(&path)?;
10052 if screens.is_empty() {
10053 crate::wayland::kdeclient::list_screens(&path)
10054 } else {
10055 Ok(screens)
10056 }
10057 }
10058 .map_err(Error::Capture)
10059 }
10060
10061 pub fn app_screen_control_offered(&self, display: &str) -> Result<bool, Error> {
10067 {
10068 let path = crate::wayland::wlclient::socket_path(display)
10069 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10070 crate::wayland::kdeclient::screen_control_offered(&path)
10071 }
10072 .map_err(Error::Capture)
10073 }
10074
10075 pub fn add_app_screen(
10080 &self,
10081 display: &str,
10082 name: &str,
10083 width: i32,
10084 height: i32,
10085 scale: f64,
10086 ) -> Result<(), Error> {
10087 {
10088 let path = crate::wayland::wlclient::socket_path(display)
10089 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10090 crate::wayland::kdeclient::add_screen(&path, name, (width, height), scale)
10091 }
10092 .map_err(Error::Capture)
10093 }
10094
10095 pub fn remove_app_screen(&self, name: &str) -> Result<bool, Error> {
10099 Ok(crate::wayland::kdeclient::remove_screen(name))
10100 }
10101
10102 pub fn hold_spare_app_screens(
10107 &self,
10108 display: &str,
10109 keep: usize,
10110 width: i32,
10111 height: i32,
10112 ) -> Result<usize, Error> {
10113 {
10114 let path = crate::wayland::wlclient::socket_path(display)
10115 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())?;
10116 crate::wayland::outclient::hold_spare_screens(&path, keep, (width, height))
10117 }
10118 .map_err(Error::Capture)
10119 }
10120
10121 pub fn clipboard_types_app(&self, display: &str) -> Result<Vec<String>, Error> {
10123 (crate::wayland::dcclient::list_types(&app_socket_path(display)?)).map_err(Error::Capture)
10124 }
10125
10126 pub fn clipboard_read_app(&self, display: &str, mime: &str) -> Result<Option<Vec<u8>>, Error> {
10129 let data = (crate::wayland::dcclient::read(&app_socket_path(display)?, mime))
10130 .map_err(Error::Capture)?;
10131 Ok(data)
10132 }
10133
10134 pub fn clipboard_write_app(
10137 &self,
10138 display: &str,
10139 entries: Vec<(String, Vec<u8>)>,
10140 ) -> Result<(), Error> {
10141 (crate::wayland::dcclient::write(&app_socket_path(display)?, entries))
10142 .map_err(Error::Capture)
10143 }
10144
10145 pub fn clipboard_served_app(&self, after: usize, timeout_s: f64) -> Vec<String> {
10149 let timeout = Duration::from_secs_f64(if timeout_s.is_finite() {
10150 timeout_s.clamp(0.0, 60.0)
10151 } else {
10152 0.0
10153 });
10154 crate::wayland::dcclient::served(after, timeout)
10155 }
10156
10157 pub fn clipboard_clear_app(&self, display: &str) -> Result<(), Error> {
10159 (crate::wayland::dcclient::clear(&app_socket_path(display)?)).map_err(Error::Capture)
10160 }
10161
10162 pub fn clipboard_unwatch_app(&self, display: &str) {
10164 if let Ok(path) = app_socket_path(display) {
10165 crate::wayland::dcclient::unwatch(&path);
10166 }
10167 }
10168
10169 pub fn inject_mouse_move(&self, x: f64, y: f64) -> Result<Option<u64>, Error> {
10171 running_compositor().map_or(Ok(None), |be| be.inject_mouse_move(x, y).map(Some))
10172 }
10173
10174 pub fn inject_relative_mouse_move(&self, dx: f64, dy: f64) -> Result<Option<u64>, Error> {
10176 running_compositor().map_or(Ok(None), |be| {
10177 be.inject_relative_mouse_move(dx, dy).map(Some)
10178 })
10179 }
10180
10181 pub fn inject_mouse_button(&self, btn: u32, state: u32) -> Result<(), Error> {
10182 running_compositor().map_or(Ok(()), |be| be.inject_mouse_button(btn, state))
10183 }
10184
10185 pub fn inject_mouse_scroll(&self, x: f64, y: f64) -> Result<(), Error> {
10186 running_compositor().map_or(Ok(()), |be| be.inject_mouse_scroll(x, y))
10187 }
10188
10189 pub fn inject_finger_scroll(&self, dx: f64, dy: f64) -> Result<(), Error> {
10191 running_compositor().map_or(Ok(()), |be| be.inject_finger_scroll(dx, dy))
10192 }
10193
10194 pub fn inject_finger_scroll_end(&self) -> Result<(), Error> {
10196 running_compositor().map_or(Ok(()), |be| be.inject_finger_scroll_end())
10197 }
10198
10199 pub fn pointer_location(&self) -> Option<(f64, f64, f64, u64)> {
10204 running_compositor().and_then(|be| be.pointer_location())
10205 }
10206
10207 pub fn set_cursor_rendering(&self, enabled: bool) -> Result<(), Error> {
10211 let controls = self.inner.lock().unwrap().x11_controls();
10212 if let Some(c) = &controls {
10213 c.capture_cursor.store(enabled, Ordering::Relaxed);
10214 return Ok(());
10215 }
10216 running_compositor().map_or(Ok(()), |be| be.set_cursor_rendering(enabled))
10217 }
10218
10219 pub fn get_xkb_keymap_string(&self) -> Result<String, Error> {
10220 running_compositor().map_or(Ok(String::new()), |be| be.get_xkb_keymap_string())
10221 }
10222
10223 pub fn set_clipboard(&self, entries: Vec<(String, Vec<u8>)>) -> Result<(), Error> {
10225 match running_compositor() {
10226 Some(be) => be.set_clipboard(entries),
10227 None => Err(Error::Capture("wayland backend not running".into())),
10228 }
10229 }
10230
10231 pub fn set_xkb_layout(
10234 &self,
10235 layout: String,
10236 variant: String,
10237 options: String,
10238 model: String,
10239 rules: String,
10240 ) -> Result<bool, Error> {
10241 running_compositor().map_or(Ok(false), |be| {
10242 be.set_xkb_layout(layout, variant, options, model, rules)
10243 })
10244 }
10245
10246 pub fn get_keyboard_state(&self) -> Result<(Vec<u32>, u32), Error> {
10248 running_compositor().map_or(Ok((Vec::new(), 0)), |be| be.get_keyboard_state())
10249 }
10250
10251 pub fn get_realized_geometry(&self, display_id: u32) -> Result<Option<(i32, i32, f64)>, Error> {
10256 running_compositor().map_or(Ok(None), |be| be.get_realized_geometry(display_id))
10257 }
10258
10259 pub fn capture_state(&self, display_id: u32) -> (String, Option<String>) {
10265 let (backend, running, err) = {
10266 let st = self.inner.lock().unwrap();
10267 let running = st
10268 .controls
10269 .as_ref()
10270 .map(|c| !c.stop.load(Ordering::Relaxed))
10271 .unwrap_or(false);
10272 let err = st
10273 .err
10274 .as_ref()
10275 .and_then(|e| e.lock().ok().and_then(|g| g.clone()));
10276 (st.backend, running, err)
10277 };
10278 match backend {
10279 1 => {
10280 let state = if running {
10281 "running"
10282 } else if err.is_some() {
10283 "failed"
10284 } else {
10285 "idle"
10286 };
10287 (state.to_string(), err)
10288 }
10289 2 => wayland_capture_state(display_id),
10290 _ => ("idle".to_string(), None),
10291 }
10292 }
10293
10294 pub fn create_output(
10297 &self,
10298 id: u32,
10299 width: i32,
10300 height: i32,
10301 x: i32,
10302 y: i32,
10303 scale: f64,
10304 ) -> Result<bool, Error> {
10305 running_compositor().map_or(Ok(false), |be| {
10306 be.create_output(id, width, height, x, y, scale)
10307 })
10308 }
10309
10310 pub fn create_view(
10313 &self,
10314 id: u32,
10315 owner: u32,
10316 x: i32,
10317 y: i32,
10318 width: i32,
10319 height: i32,
10320 ) -> Result<bool, Error> {
10321 running_compositor().map_or(Ok(false), |be| {
10322 be.create_view(id, owner, x, y, width, height)
10323 })
10324 }
10325
10326 pub fn resize_output(
10329 &self,
10330 id: u32,
10331 width: i32,
10332 height: i32,
10333 scale: f64,
10334 ) -> Result<bool, Error> {
10335 running_compositor().map_or(Ok(false), |be| be.resize_output(id, width, height, scale))
10336 }
10337
10338 pub fn destroy_output(&self, id: u32) -> Result<bool, Error> {
10340 running_compositor().map_or(Ok(false), |be| be.destroy_output(id))
10341 }
10342
10343 pub fn reposition_output(&self, id: u32, x: i32, y: i32) -> Result<bool, Error> {
10346 running_compositor().map_or(Ok(false), |be| be.reposition_output(id, x, y))
10347 }
10348
10349 pub fn set_cursor_size(&self, size: i32) -> Result<bool, Error> {
10352 running_compositor().map_or(Ok(false), |be| be.set_cursor_size(size))
10353 }
10354
10355 pub fn list_outputs(&self) -> Result<Vec<OutputDesc>, Error> {
10358 running_compositor().map_or(Ok(Vec::new()), |be| be.list_outputs())
10359 }
10360
10361 pub fn output_capacity(&self) -> Result<i64, Error> {
10363 running_compositor().map_or(Ok(-1), |be| be.output_capacity())
10364 }
10365
10366 pub fn move_window_to_output(&self, window_id: u32, output_id: u32) -> Result<bool, Error> {
10368 running_compositor().map_or(Ok(false), |be| {
10369 be.move_window_to_output(window_id, output_id)
10370 })
10371 }
10372
10373 pub fn list_windows(&self) -> Result<Vec<WindowDesc>, Error> {
10376 running_compositor().map_or(Ok(Vec::new()), |be| be.list_windows())
10377 }
10378
10379 pub fn start_capture(
10384 &self,
10385 sink: Arc<dyn crate::sink::FrameSink>,
10386 settings: CaptureSettings,
10387 ) -> Result<(), Error> {
10388 let rs = settings;
10389 crate::log::set_debug(rs.debug_logging);
10390 let display_id = rs.display_id;
10391 let wants_wayland = rs.wants_wayland();
10392 let live_wayland_restart = wants_wayland
10393 && {
10394 let st = self.inner.lock().unwrap();
10395 st.backend == 2 && st.wl_display == display_id
10396 }
10397 && wayland_owners().lock().unwrap().get(&display_id) == Some(&self.id)
10398 && wayland_alive().lock().unwrap().contains(&display_id);
10399 if !live_wayland_restart {
10400 self.stop_internal()?;
10401 }
10402
10403 if wants_wayland {
10404 let be = ensure_compositor(
10405 rs.width,
10406 rs.height,
10407 rs.render_node_path.clone(),
10408 rs.auto_gpu.clone(),
10409 rs.encode_node_path.clone(),
10410 rs.cursor_size,
10411 )?;
10412 be.start_capture(sink, rs)?;
10413 wayland_owners().lock().unwrap().insert(display_id, self.id);
10414 {
10415 let mut st = self.inner.lock().unwrap();
10416 st.backend = 2;
10417 st.wl_display = display_id;
10418 }
10419 return Ok(());
10420 }
10421
10422 SHUTDOWN.store(false, Ordering::Relaxed);
10426 let mut rs = rs;
10427 if rs.encode_node_index < -1 {
10428 if let Some(picked) = auto_render_node(&rs.auto_gpu)
10429 && let Some(idx) = render_node_index(&picked)
10430 {
10431 println!("[X11] AUTO_GPU selected {picked}.");
10432 rs.encode_node_index = idx;
10433 } else if let Some(idx) = first_render_node_index() {
10434 rs.encode_node_index = idx;
10435 }
10436 }
10437
10438 println!(
10439 "[X11] Configuring Output: {}x{} @ {:.2} FPS (Encode Node: {})",
10440 rs.width, rs.height, rs.target_fps, rs.encode_node_index
10441 );
10442
10443 let controls = Arc::new(crate::x11::Controls::new(&rs));
10444 crate::x11::cursor::set_size_cap(rs.cursor_size_cap);
10445 live_x11().lock().unwrap().push(controls.clone());
10446 if let Some(other) = self
10447 .inner
10448 .lock()
10449 .unwrap()
10450 .starting
10451 .replace(controls.clone())
10452 {
10453 other.stop.store(true, Ordering::Relaxed);
10454 }
10455 let c2 = controls.clone();
10456 let c3 = controls.clone();
10457 let cb = sink;
10458 let err_slot: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
10459 let err_slot2 = err_slot.clone();
10460
10461 let (deliver_tx, deliver_rx) = std::sync::mpsc::sync_channel::<Vec<EncodedStripe>>(1);
10466 let deliver_handle = thread::spawn(move || {
10467 crate::boost_thread_priority(-10);
10468 while let Ok(frame) = deliver_rx.recv() {
10469 if SHUTDOWN.load(Ordering::Relaxed) {
10470 continue;
10471 }
10472 cb.frames(frame);
10473 }
10474 });
10475 let deliver_thread_id = deliver_handle.thread().id();
10476
10477 let report = controls.report.clone();
10478 let on_frame = move |frame: Vec<EncodedStripe>| {
10479 report.tally(&frame);
10480 let _ = deliver_tx.send(frame);
10483 };
10484
10485 let (tid_tx, tid_rx) = std::sync::mpsc::channel();
10486 let (etid_tx, etid_rx) = std::sync::mpsc::channel();
10487 let handle = thread::spawn(move || {
10488 crate::boost_thread_priority(-15);
10489 let _ = tid_tx.send(thread::current().id());
10490 let res = crate::x11::run_capture(rs, c2, etid_tx, on_frame);
10491 c3.stop.store(true, Ordering::Release);
10492 if let Err(msg) = res {
10493 eprintln!("[X11] capture error: {msg}");
10494 if let Ok(mut g) = err_slot2.lock() {
10495 *g = Some(msg);
10496 }
10497 }
10498 c3.finished.store(true, Ordering::Release);
10501 });
10502 let (tid, etid_res, etid_rx) = {
10503 let tid = tid_rx.recv().ok();
10504 let etid_res = etid_rx.recv_timeout(std::time::Duration::from_secs(2));
10505 (tid, etid_res, etid_rx)
10506 };
10507 let mut late_etid_rx = None;
10508 let etid = match etid_res {
10509 Ok(id) => Some(id),
10510 Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
10511 let _ = handle.join();
10512 live_x11()
10513 .lock()
10514 .unwrap()
10515 .retain(|x| !Arc::ptr_eq(x, &controls));
10516 let mut st = self.inner.lock().unwrap();
10517 if !st
10518 .starting
10519 .as_ref()
10520 .is_some_and(|c| Arc::ptr_eq(c, &controls))
10521 {
10522 return Ok(());
10523 }
10524 st.starting = None;
10525 drop(st);
10526 let msg = err_slot
10527 .lock()
10528 .ok()
10529 .and_then(|g| g.clone())
10530 .unwrap_or_else(|| "X11 capture thread exited during start".to_string());
10531 return Err(Error::Capture(msg));
10532 }
10533 Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
10534 late_etid_rx = Some(etid_rx);
10538 None
10539 }
10540 };
10541 let mut st = self.inner.lock().unwrap();
10542 if !st
10543 .starting
10544 .as_ref()
10545 .is_some_and(|c| Arc::ptr_eq(c, &controls))
10546 {
10547 drop(st);
10548 live_x11()
10549 .lock()
10550 .unwrap()
10551 .retain(|x| !Arc::ptr_eq(x, &controls));
10552 {
10553 join_within(handle, "capture");
10554 join_within(deliver_handle, "delivery");
10555 };
10556 return Ok(());
10557 }
10558 st.starting = None;
10559 crate::x11::cursor::acquire();
10562 st.backend = 1;
10563 st.cursor_ref = true;
10564 st.controls = Some(controls);
10565 st.handle = Some(handle);
10566 st.cap_thread_id = tid;
10567 st.encode_thread_id = etid;
10568 st.encode_tid_rx = late_etid_rx;
10569 st.deliver_handle = Some(deliver_handle);
10570 st.deliver_thread_id = Some(deliver_thread_id);
10571 st.err = Some(err_slot);
10572 drop(st);
10573 Ok(())
10574 }
10575
10576 pub fn update_tunables(&self, settings: &CaptureSettings) -> Result<(), Error> {
10579 let rs = settings;
10580 let t = LiveTunables::from_settings(rs);
10581 let (backend, controls, did) = {
10582 let st = self.inner.lock().unwrap();
10583 (st.backend, st.x11_controls(), st.wl_display)
10584 };
10585 match backend {
10586 0 | 1 => {
10587 if let Some(c) = &controls {
10588 c.capture_cursor.store(t.capture_cursor, Ordering::Relaxed);
10589 *c.tunables.lock().unwrap() = Some(t);
10590 c.tunables_dirty.store(true, Ordering::Release);
10591 crate::x11::cursor::set_size_cap(rs.cursor_size_cap);
10592 }
10593 }
10594 2 => wayland_update_tunables(did, t),
10595 _ => {}
10596 }
10597 Ok(())
10598 }
10599
10600 pub fn set_clipboard_sink(
10602 &self,
10603 sink: Arc<dyn crate::sink::ClipboardSink>,
10604 ) -> Result<(), Error> {
10605 match running_compositor() {
10606 Some(be) => be.set_clipboard_sink(sink),
10607 None => Err(Error::Compositor("wayland backend not running".into())),
10608 }
10609 }
10610
10611 pub fn clipboard_watch_app(
10614 &self,
10615 display: &str,
10616 watch: Arc<dyn crate::sink::SelectionWatch>,
10617 ) -> Result<(), Error> {
10618 crate::wayland::dcclient::watch(&app_socket_path(display)?, watch).map_err(Error::Capture)
10619 }
10620}
10621
10622impl Drop for ScreenCapture {
10628 fn drop(&mut self) {
10629 if let Ok(mut st) = self.inner.lock() {
10630 if let Some(c) = &st.controls {
10631 c.stop.store(true, Ordering::Relaxed);
10632 }
10633 if st.backend == 2 && !crate::SHUTDOWN.load(Ordering::Relaxed) {
10636 let did = st.wl_display;
10637 let owned = {
10638 let mut owners = wayland_owners().lock().unwrap();
10639 if owners.get(&did) == Some(&self.id) {
10640 owners.remove(&did);
10641 true
10642 } else {
10643 false
10644 }
10645 };
10646 if owned
10647 && let Some(slot) = WAYLAND_BACKEND.get()
10648 && let Some(be) = slot.lock().unwrap().as_ref()
10649 {
10650 let _ = be.stop_capture(did);
10651 }
10652 }
10653 if std::mem::take(&mut st.cursor_ref) && !crate::SHUTDOWN.load(Ordering::Relaxed) {
10657 crate::x11::cursor::release();
10658 }
10659 }
10660 }
10661}
10662
10663fn app_socket_path(display: &str) -> Result<String, String> {
10667 crate::wayland::wlclient::socket_path(display)
10668 .ok_or_else(|| "XDG_RUNTIME_DIR is unset".to_string())
10669}
10670
10671pub fn shutdown() {
10683 SHUTDOWN.store(true, Ordering::Relaxed);
10684 crate::recorder::finalize_on_exit();
10685 *PENDING_CURSOR_CALLBACK.lock().unwrap() = None;
10686 crate::x11::cursor::shutdown();
10687 crate::wayland::dcclient::unwatch_all();
10688 let x11: Vec<Arc<crate::x11::Controls>> = live_x11().lock().unwrap().iter().cloned().collect();
10689 for c in &x11 {
10690 c.stop.store(true, Ordering::Relaxed);
10691 }
10692 if !x11.is_empty() {
10693 let deadline = Instant::now() + Duration::from_secs(2);
10694 for c in &x11 {
10695 while !c.finished.load(Ordering::Acquire) && Instant::now() < deadline {
10696 std::thread::sleep(Duration::from_millis(5));
10697 }
10698 }
10699 }
10700 let be = WAYLAND_BACKEND
10703 .get()
10704 .and_then(|slot| slot.lock().unwrap().as_ref().cloned());
10705 if let Some(be) = be {
10706 let mut displays: Vec<u32> = wayland_alive().lock().unwrap().iter().copied().collect();
10707 if !displays.contains(&0) {
10708 displays.push(0);
10709 }
10710 for did in displays {
10711 let _ = be.stop_capture(did);
10712 }
10713 let (ack_tx, ack_rx) = std::sync::mpsc::channel::<()>();
10714 if be.send(ThreadCommand::Barrier { reply: ack_tx }).is_ok() {
10715 let _ = ack_rx.recv_timeout(Duration::from_secs(2));
10716 }
10717 }
10718 wayland_owners().lock().unwrap().clear();
10719 wayland_alive().lock().unwrap().clear();
10720 std::thread::sleep(Duration::from_millis(50));
10721}
10722
10723pub fn start_recording(
10728 path: String,
10729 settings: Option<CaptureSettings>,
10730 audio_socket: &str,
10731) -> Result<crate::recorder::RecordingStatus, Error> {
10732 let mut opts = crate::recorder::RecordOptions::from_env(path);
10733 if !audio_socket.is_empty() {
10734 opts.audio_socket = audio_socket.to_string();
10735 }
10736 if let Some(rs) = settings {
10737 if rs.codec != Codec::H264 {
10738 return Err(Error::Settings(
10739 "recording requires H.264 capture settings (codec='h264')".into(),
10740 ));
10741 }
10742 opts.display_id = rs.display_id;
10743 if let Some(explicit) = rs.use_wayland {
10744 opts.backend = Some(if explicit {
10745 crate::recorder::PreferredBackend::Wayland
10746 } else {
10747 crate::recorder::PreferredBackend::X11
10748 });
10749 }
10750 opts.fps = 0.0;
10752 opts.bitrate_kbps = 0;
10753 opts.capture = Some(rs);
10754 }
10755 crate::recorder::start(opts).map_err(Error::Capture)
10756}
10757
10758pub fn stop_recording() -> Result<crate::recorder::RecordingStatus, Error> {
10760 crate::recorder::stop().map_err(Error::Capture)
10761}
10762
10763pub fn recording_status() -> Option<crate::recorder::RecordingStatus> {
10765 crate::recorder::status()
10766}
10767
10768pub fn ensure_wayland_display(
10772 width: i32,
10773 height: i32,
10774 render_node: String,
10775 auto_gpu: String,
10776 cursor_size: i32,
10777) -> Result<String, Error> {
10778 ensure_compositor(
10779 width,
10780 height,
10781 render_node,
10782 auto_gpu,
10783 String::new(),
10784 cursor_size,
10785 )?;
10786 Ok(wait_socket_name(Duration::from_secs(5)).unwrap_or_default())
10787}
10788
10789#[derive(Clone, Debug, PartialEq, Eq)]
10792pub struct WaylandGpuProbe {
10793 pub node: String,
10795 pub accelerated: bool,
10798 pub gpu: bool,
10800 pub renderer: String,
10802 pub error: String,
10804}
10805
10806pub fn probe_wayland_gpu(render_node: &str, auto_gpu: &str) -> WaylandGpuProbe {
10810 let node = if render_node.is_empty() {
10811 auto_render_node(auto_gpu)
10812 } else {
10813 Some(render_node.to_string())
10814 };
10815 let Some(node) = node else {
10816 return WaylandGpuProbe {
10817 node: String::new(),
10818 accelerated: false,
10819 gpu: gpu_exposed(),
10820 renderer: String::new(),
10821 error: "No render node".to_string(),
10822 };
10823 };
10824 let mut name = String::new();
10825 let result = gpu_render_init(std::path::Path::new(&node)).and_then(|(gbm, mut renderer)| {
10826 let bo = gbm
10827 .create_buffer_object::<()>(64, 64, GbmFormat::Argb8888, BufferObjectFlags::RENDERING)
10828 .map_err(|_| "Failed to allocate GBM buffer")?;
10829 bo.fd()
10830 .map_err(|e| format!("Failed to export dmabuf: {e:?}"))?;
10831 name = gl_renderer_name(&mut renderer);
10832 let lowered = name.to_lowercase();
10833 if ["llvmpipe", "softpipe", "swrast", "software rasterizer"]
10834 .iter()
10835 .any(|sw| lowered.contains(sw))
10836 {
10837 return Err(format!("Software rasterizer only ({name})"));
10838 }
10839 Ok(())
10840 });
10841 let error = result.err().unwrap_or_default();
10842 WaylandGpuProbe {
10843 node,
10844 accelerated: error.is_empty(),
10845 gpu: gpu_exposed(),
10846 renderer: name,
10847 error,
10848 }
10849}
10850
10851pub fn hardware_encoders(encode_node_index: i32, auto_gpu: &str) -> encoders::HardwareEncoders {
10855 let node = match encode_node_index {
10856 -1 => return Vec::new(),
10857 index if index < -1 => auto_render_node(auto_gpu)
10858 .and_then(|picked| render_node_index(&picked))
10859 .or_else(first_render_node_index)
10860 .unwrap_or(0),
10861 index => index,
10862 };
10863 encoders::hardware_encoders(node)
10864}
10865
10866pub fn wayland_display_name() -> Option<String> {
10869 running_compositor()?;
10870 wait_socket_name(Duration::from_secs(2))
10871}
10872
10873#[cfg(feature = "computer-use-server")]
10876pub fn start_computer_use(bind: &str, token: Option<&str>) -> Result<(), Error> {
10877 crate::computer_use::server::start_cu_server(bind, token).map_err(Error::Capture)
10878}
10879
10880pub fn screenshot_png(display: u32) -> Result<Vec<u8>, Error> {
10882 crate::computer_use::resolve_backend()
10883 .and_then(|b| b.screenshot_png(display))
10884 .map_err(Error::Capture)
10885}
10886
10887#[cfg(test)]
10888mod annexb_frame_type_tests {
10889 use crate::encoders::h264_frame_type as annexb_frame_type;
10894
10895 const IDR_AU: &[u8] = &[
10896 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0d, 0xac, 0xd9,
10897 0x41, 0x41, 0xfb, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0,
10898 0xf1, 0x42, 0x99, 0x60, 0x00, 0x00, 0x00, 0x01, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00,
10899 0x00, 0x01, 0x06, 0x05, 0xff, 0xff, 0xa8, 0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7,
10900 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20,
10901 0x63, 0x6f, 0x72, 0x65, 0x20, 0x31, 0x36, 0x34, 0x20, 0x72, 0x33, 0x30, 0x39, 0x35, 0x20,
10902 0x62, 0x61, 0x65, 0x65, 0x34, 0x30, 0x30, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32, 0x36, 0x34,
10903 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56, 0x43, 0x20, 0x63, 0x6f, 0x64,
10904 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32,
10905 0x30, 0x30, 0x33, 0x2d, 0x32, 0x30, 0x32, 0x32, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70,
10906 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c, 0x61, 0x6e,
10907 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x20,
10908 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61,
10909 0x63, 0x3d, 0x31, 0x20, 0x72, 0x65, 0x66, 0x3d, 0x33, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f,
10910 0x63, 0x6b, 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73,
10911 0x65, 0x3d, 0x30, 0x78, 0x33, 0x3a, 0x30, 0x78, 0x31, 0x31, 0x33, 0x20, 0x6d, 0x65, 0x3d,
10912 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x37, 0x20, 0x70, 0x73, 0x79,
10913 0x3d, 0x31, 0x20, 0x70, 0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a,
10914 0x30, 0x2e, 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x66, 0x3d,
10915 0x31, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x31, 0x36, 0x20, 0x63,
10916 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x6d, 0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c,
10917 0x6c, 0x69, 0x73, 0x3d, 0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x31, 0x20,
10918 0x63, 0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e, 0x65, 0x3d,
10919 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x70, 0x73, 0x6b, 0x69,
10920 0x70, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f,
10921 0x66, 0x66, 0x73, 0x65, 0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64,
10922 0x73, 0x3d, 0x37, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x5f, 0x74,
10923 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x31, 0x20, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x64,
10924 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x3d, 0x30,
10925 0x20, 0x64, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 0x6e, 0x74,
10926 0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c, 0x75, 0x72, 0x61,
10927 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d, 0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73,
10928 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30,
10929 0x20, 0x62, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x32, 0x20, 0x62, 0x5f, 0x70, 0x79,
10930 0x72, 0x61, 0x6d, 0x69, 0x64, 0x3d, 0x32, 0x20, 0x62, 0x5f, 0x61, 0x64, 0x61, 0x70, 0x74,
10931 0x3d, 0x31, 0x20, 0x62, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x3d, 0x30, 0x20, 0x64, 0x69, 0x72,
10932 0x65, 0x63, 0x74, 0x3d, 0x31, 0x20, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x3d, 0x31,
10933 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x67, 0x6f, 0x70, 0x3d, 0x31, 0x20, 0x77, 0x65, 0x69,
10934 0x67, 0x68, 0x74, 0x70, 0x3d, 0x32, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x3d, 0x31,
10935 0x32, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x37, 0x20,
10936 0x73, 0x63, 0x65, 0x6e, 0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x69, 0x6e, 0x74,
10937 0x72, 0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x72, 0x63,
10938 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x31, 0x32, 0x20, 0x72,
10939 0x63, 0x3d, 0x63, 0x72, 0x66, 0x20, 0x6d, 0x62, 0x74, 0x72, 0x65, 0x65, 0x3d, 0x31, 0x20,
10940 0x63, 0x72, 0x66, 0x3d, 0x32, 0x33, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d,
10941 0x30, 0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20, 0x71, 0x70,
10942 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73, 0x74, 0x65, 0x70, 0x3d, 0x34,
10943 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20,
10944 0x61, 0x71, 0x3d, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, 0x00, 0x00, 0x01, 0x65,
10945 0x88, 0x84, 0x00, 0x47, 0xda, 0xe3, 0x98, 0x5b, 0xd7, 0x57, 0xda, 0x42, 0x3e, 0x83, 0x89,
10946 0x96, 0xcb, 0xc5, 0x1b,
10947 ];
10948 const INTRA_AU: &[u8] = &[
10949 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x0d, 0xac, 0xd9,
10950 0x41, 0x41, 0xfb, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0xc0,
10951 0xf1, 0x42, 0x99, 0x60, 0x00, 0x00, 0x00, 0x01, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00,
10952 0x00, 0x01, 0x06, 0x06, 0x01, 0xc4, 0x80, 0x00, 0x00, 0x01, 0x41, 0x88, 0xc3, 0x05, 0xff,
10953 0xd4, 0x57, 0x6d, 0x62, 0x78, 0xad, 0x3e, 0x89, 0xb4, 0xb5, 0x2a, 0xde, 0xcb, 0x0c, 0x64,
10954 ];
10955 const P_AU: &[u8] = &[
10956 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x23, 0x6c, 0x45, 0x7f, 0xb7,
10957 0xe7, 0xf9, 0x23, 0x5b, 0xd9, 0xab, 0xc7, 0x7f, 0xcc, 0x03, 0xc4, 0xaa, 0x6f,
10958 ];
10959 const B_AU: &[u8] = &[
10960 0x00, 0x00, 0x01, 0x09, 0x50, 0x00, 0x00, 0x01, 0x41, 0x9e, 0x41, 0x78, 0x85, 0x3f, 0xfd,
10961 0xb1, 0x75, 0x99, 0x50, 0x2e, 0xe9, 0x5c, 0x1a, 0x0f, 0x32, 0x6a, 0xa7, 0x3d,
10962 ];
10963 const INTRA_VCL: &[u8] = &[
10964 0x00, 0x00, 0x01, 0x41, 0x88, 0xc3, 0x05, 0xff, 0xd4, 0x57, 0x6d, 0x62, 0x78, 0xad, 0x3e,
10965 0x89, 0xb4, 0xb5, 0x2a, 0xde, 0xcb, 0x0c, 0x64, 0x72, 0xe8, 0x3d, 0x54,
10966 ];
10967 const P_VCL: &[u8] = &[
10968 0x00, 0x00, 0x01, 0x41, 0x9a, 0x23, 0x6c, 0x45, 0x7f, 0xb7, 0xe7, 0xf9, 0x23, 0x5b, 0xd9,
10969 0xab, 0xc7, 0x7f, 0xcc, 0x03, 0xc4, 0xaa, 0x6f, 0x9a, 0xd8, 0xcf, 0x4a,
10970 ];
10971 const EPB_INTRA_VCL: &[u8] = &[
10975 0x00, 0x00, 0x01, 0x41, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x08, 0x88,
10976 ];
10977
10978 #[test]
10979 fn idr_is_key() {
10980 assert_eq!(annexb_frame_type(IDR_AU), 0x01);
10981 }
10982
10983 #[test]
10984 fn open_gop_intra_is_intra_not_key() {
10985 assert_eq!(annexb_frame_type(INTRA_AU), 0x02);
10986 }
10987
10988 #[test]
10989 fn predicted_frames_are_delta() {
10990 assert_eq!(annexb_frame_type(P_AU), 0x00);
10991 assert_eq!(annexb_frame_type(B_AU), 0x00);
10992 }
10993
10994 #[test]
10995 fn one_predicted_slice_downgrades_a_mixed_unit() {
10996 let mixed = [INTRA_VCL, P_VCL].concat();
10997 assert_eq!(annexb_frame_type(&mixed), 0x00);
10998 }
10999
11000 #[test]
11001 fn unparsable_slice_header_is_delta() {
11002 assert_eq!(annexb_frame_type(&[0x00, 0x00, 0x01, 0x41]), 0x00);
11003 assert_eq!(annexb_frame_type(&[]), 0x00);
11004 }
11005
11006 #[test]
11007 fn emulation_prevention_is_stripped_before_parsing() {
11008 assert_eq!(annexb_frame_type(EPB_INTRA_VCL), 0x02);
11009 }
11010}
11011
11012#[cfg(test)]
11013mod encode_node_tests {
11014 use super::{auto_render_node, render_node_index};
11017
11018 #[test]
11019 fn render_node_path_counts_from_128() {
11020 assert_eq!(render_node_index("/dev/dri/renderD128"), Some(0));
11021 assert_eq!(render_node_index("/dev/dri/renderD130"), Some(2));
11022 assert_eq!(render_node_index("/dev/dri/card0"), None);
11023 }
11024
11025 #[test]
11026 fn auto_gpu_off_picks_nothing() {
11027 assert_eq!(auto_render_node("false"), None);
11028 assert_eq!(auto_render_node(""), None);
11029 }
11030}
11031
11032#[cfg(test)]
11033mod shm_usage_tests {
11034 use super::shm_usage_in;
11037
11038 #[test]
11039 fn only_allocated_blocks_count() {
11040 let dir = std::env::temp_dir().join(format!("pixelflux-shm-{}", std::process::id()));
11041 std::fs::create_dir_all(&dir).unwrap();
11042 let path = dir.to_str().unwrap().to_string();
11043
11044 std::fs::File::create(dir.join("sparse"))
11045 .unwrap()
11046 .set_len(8 * 1024 * 1024 * 1024)
11047 .unwrap();
11048 let sparse = shm_usage_in(&path);
11049
11050 std::fs::write(dir.join("dense"), vec![0u8; 4 * 1024 * 1024]).unwrap();
11051 let dense = shm_usage_in(&path);
11052
11053 std::fs::remove_dir_all(&dir).ok();
11054 assert!(
11055 sparse < 1024 * 1024,
11056 "an 8 GiB sparse file counted {sparse} bytes"
11057 );
11058 assert!(
11059 dense - sparse >= 4 * 1024 * 1024,
11060 "a 4 MiB written file added only {} bytes",
11061 dense - sparse
11062 );
11063 }
11064}
11065
11066#[cfg(test)]
11067mod capture_state_tests {
11068 use super::{set_wayland_capture_err, wayland_alive, wayland_capture_state};
11072
11073 #[test]
11074 fn state_tracks_liveness_and_recorded_error() {
11075 let did = 987_654;
11077 set_wayland_capture_err(did, None);
11078 wayland_alive().lock().unwrap().remove(&did);
11079 assert_eq!(wayland_capture_state(did), ("idle".to_string(), None));
11080
11081 set_wayland_capture_err(did, Some("no output".to_string()));
11083 assert_eq!(
11084 wayland_capture_state(did),
11085 ("failed".to_string(), Some("no output".to_string()))
11086 );
11087
11088 wayland_alive().lock().unwrap().insert(did);
11090 set_wayland_capture_err(did, Some("using CPU encode".to_string()));
11091 assert_eq!(
11092 wayland_capture_state(did),
11093 ("running".to_string(), Some("using CPU encode".to_string()))
11094 );
11095
11096 wayland_alive().lock().unwrap().remove(&did);
11098 set_wayland_capture_err(did, None);
11099 assert_eq!(wayland_capture_state(did), ("idle".to_string(), None));
11100 }
11101}
11102
11103#[cfg(test)]
11104mod host_layout_tests {
11105 use super::host_layout_resolution;
11109
11110 #[test]
11111 fn the_capture_follows_the_mode_the_host_runs() {
11112 let want = (1920, 1080);
11113 assert_eq!(host_layout_resolution(want, Some(want)), None);
11115 assert_eq!(host_layout_resolution(want, None), None);
11117 assert_eq!(
11122 host_layout_resolution(want, Some((2560, 1440))),
11123 Some((2560, 1440))
11124 );
11125 assert_eq!(
11126 host_layout_resolution(want, Some((1280, 720))),
11127 Some((1280, 720))
11128 );
11129 }
11130}
11131
11132#[cfg(test)]
11133mod output_overlap_tests {
11134 use super::rects_overlap;
11138
11139 #[test]
11140 fn disjoint_rects_do_not_overlap() {
11141 assert!(!rects_overlap((0, 0, 100, 100), (200, 0, 100, 100)));
11142 assert!(!rects_overlap((0, 0, 100, 100), (0, 200, 100, 100)));
11143 }
11144
11145 #[test]
11146 fn touching_edges_do_not_overlap() {
11147 assert!(!rects_overlap((0, 0, 100, 100), (100, 0, 100, 100)));
11149 assert!(!rects_overlap((0, 0, 100, 100), (0, 100, 100, 100)));
11150 assert!(!rects_overlap((0, 0, 100, 100), (100, 100, 50, 50)));
11152 }
11153
11154 #[test]
11155 fn one_pixel_intrusion_overlaps() {
11156 assert!(rects_overlap((0, 0, 100, 100), (99, 0, 100, 100)));
11157 assert!(rects_overlap((0, 0, 100, 100), (0, 99, 100, 100)));
11158 }
11159
11160 #[test]
11161 fn containment_and_identity_overlap() {
11162 assert!(rects_overlap((0, 0, 100, 100), (25, 25, 10, 10)));
11163 assert!(rects_overlap((25, 25, 10, 10), (0, 0, 100, 100)));
11164 assert!(rects_overlap((5, 5, 50, 50), (5, 5, 50, 50)));
11165 }
11166
11167 #[test]
11168 fn empty_or_negative_rects_never_overlap() {
11169 assert!(!rects_overlap((10, 10, 0, 50), (0, 0, 100, 100)));
11170 assert!(!rects_overlap((10, 10, 50, 0), (0, 0, 100, 100)));
11171 assert!(!rects_overlap((10, 10, -5, 5), (0, 0, 100, 100)));
11172 assert!(!rects_overlap((0, 0, 100, 100), (10, 10, 0, 0)));
11173 }
11174
11175 #[test]
11176 fn negative_origins_overlap_correctly() {
11177 assert!(rects_overlap((-50, -50, 100, 100), (0, 0, 100, 100)));
11178 assert!(!rects_overlap((-100, -100, 100, 100), (0, 0, 100, 100)));
11179 }
11180
11181 #[test]
11182 fn extreme_coordinates_do_not_wrap() {
11183 assert!(!rects_overlap(
11184 (i32::MAX - 10, 0, 10, 10),
11185 (i32::MIN, 0, 10, 10)
11186 ));
11187 assert!(rects_overlap(
11188 (i32::MAX - 10, 0, 10, 10),
11189 (i32::MAX - 5, 0, 10, 10)
11190 ));
11191 }
11192}
11193
11194#[cfg(test)]
11195mod wl_frame_pool_tests {
11196 use super::*;
11201
11202 fn frame(id: usize, buf: Vec<u8>, n: u16) -> WlFrame {
11203 WlFrame {
11204 id,
11205 buf,
11206 source: None,
11207 scanout: false,
11208 shm: None,
11209 frame_id: n,
11210 damage: Vec::new(),
11211 is_animated: false,
11212 captured_ns: 0,
11213 }
11214 }
11215
11216 #[test]
11217 fn begin_gated_on_slot_and_free_list() {
11218 let p = WlFramePool::new(2, 16);
11219 let (a, abuf) = p.try_begin().expect("first buffer");
11220 let (b, bbuf) = p.try_begin().expect("second buffer");
11221 assert_ne!(a, b);
11222 assert!(p.try_begin().is_none(), "free list exhausted");
11223 p.publish(frame(a, abuf, 0));
11224 p.cancel(b, bbuf);
11225 assert!(p.try_begin().is_none(), "slot occupied blocks begin");
11226 let f = p.take().expect("published frame");
11227 assert_eq!(f.frame_id, 0);
11228 p.recycle(f.id, f.buf);
11229 assert!(p.try_begin().is_some(), "drained slot re-enables begin");
11230 }
11231
11232 #[test]
11233 fn frames_flow_in_order_and_buffers_recycle() {
11234 let p = Arc::new(WlFramePool::new(2, 4));
11235 let p2 = p.clone();
11236 let consumer = thread::spawn(move || {
11237 let mut seen = Vec::new();
11238 while let Some(f) = p2.take() {
11239 seen.push(f.frame_id);
11240 p2.recycle(f.id, f.buf);
11241 }
11242 seen
11243 });
11244 let mut published = 0u16;
11245 while published < 50 {
11246 if let Some((id, buf)) = p.try_begin() {
11247 p.publish(frame(id, buf, published));
11248 published += 1;
11249 } else {
11250 thread::sleep(Duration::from_micros(50));
11251 }
11252 }
11253 thread::sleep(Duration::from_millis(50));
11254 p.shutdown();
11255 let seen = consumer.join().unwrap();
11256 assert_eq!(seen, (0..50).collect::<Vec<u16>>(), "every frame, in order");
11257 }
11258
11259 #[test]
11260 fn shutdown_unblocks_take() {
11261 let p = Arc::new(WlFramePool::new(1, 4));
11262 let p2 = p.clone();
11263 let t = thread::spawn(move || p2.take());
11264 thread::sleep(Duration::from_millis(30));
11265 p.shutdown();
11266 assert!(t.join().unwrap().is_none(), "take returns None on shutdown");
11267 }
11268
11269 #[test]
11270 fn cancel_returns_buffer_for_reuse() {
11271 let p = WlFramePool::new(1, 8);
11272 let (id, buf) = p.try_begin().expect("buffer");
11273 assert!(p.try_begin().is_none());
11274 p.cancel(id, buf);
11275 assert!(p.try_begin().is_some(), "canceled reservation reusable");
11276 }
11277}
11278
11279#[cfg(test)]
11280mod auto_gpu_token_tests {
11281 use super::{CardIdentity, card_matches_token};
11286
11287 fn pci(driver: &str, vendor: u32) -> CardIdentity {
11288 CardIdentity {
11289 driver: driver.into(),
11290 pci_vendor: Some(vendor),
11291 compatibles: vec![],
11292 }
11293 }
11294
11295 fn dt(driver: &str, compatibles: &[&str]) -> CardIdentity {
11296 CardIdentity {
11297 driver: driver.into(),
11298 pci_vendor: None,
11299 compatibles: compatibles.iter().map(|c| c.to_string()).collect(),
11300 }
11301 }
11302
11303 #[test]
11304 fn driver_names_match_without_any_table() {
11305 assert!(card_matches_token("amdgpu", &pci("amdgpu", 0x1002)));
11306 assert!(card_matches_token(
11307 "panfrost",
11308 &dt("panfrost", &["rockchip,rk3399-mali"])
11309 ));
11310 assert!(card_matches_token("nouveau", &pci("nouveau", 0x10de)));
11311 assert!(!card_matches_token("i915", &pci("amdgpu", 0x1002)));
11312 }
11313
11314 #[test]
11315 fn vendor_names_and_raw_ids_match_pci_identity() {
11316 let nv = pci("nouveau", 0x10de);
11317 assert!(card_matches_token("nvidia", &nv));
11318 assert!(card_matches_token("0x10de", &nv));
11319 assert!(card_matches_token("10de", &nv));
11320 assert!(!card_matches_token("amd", &nv));
11321 assert!(card_matches_token("ati", &pci("radeon", 0x1002)));
11322 }
11323
11324 #[test]
11325 fn devicetree_prefixes_match_literally_and_via_aliases() {
11326 let mali = dt("panfrost", &["rockchip,rk3399-mali", "arm,mali-t860"]);
11327 assert!(card_matches_token("rockchip", &mali));
11328 assert!(card_matches_token("arm", &mali));
11329 assert!(card_matches_token("mali", &mali));
11330 let adreno = dt("msm", &["qcom,adreno-630", "qcom,adreno"]);
11331 assert!(card_matches_token("qcom", &adreno));
11332 assert!(card_matches_token("qualcomm", &adreno));
11333 assert!(card_matches_token("adreno", &adreno));
11334 assert!(!card_matches_token("brcm", &adreno));
11335 assert!(card_matches_token(
11336 "videocore",
11337 &dt("v3d", &["brcm,bcm2711-v3d"])
11338 ));
11339 }
11340
11341 #[test]
11342 fn missing_identity_fields_never_false_match() {
11343 let bare = CardIdentity {
11344 driver: String::new(),
11345 pci_vendor: None,
11346 compatibles: vec![],
11347 };
11348 for t in ["nvidia", "amdgpu", "0x10de", "qcom"] {
11349 assert!(!card_matches_token(t, &bare));
11350 }
11351 }
11352}