From a17f809d9f988b041658947e2deb2f8ababe7473 Mon Sep 17 00:00:00 2001 From: dailz Date: Mon, 13 Jul 2026 19:09:39 +0800 Subject: [PATCH] refactor(state): split 1598-LOC state.rs into directory + extract 13 Dispatch impls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 4a + 4b combined. - src/state.rs (1594 LOC) -> src/state/mod.rs (struct + inherent methods + types + helpers; 999 LOC) + src/state/dispatch/ (13 Dispatch impls across 6 files: registry.rs / wl_output.rs / dmabuf.rs / screencopy.rs / output_mgr.rs / buffer.rs). Per Oracle audit: orphan rule permits Dispatch impls in submodules because Dispatch is a foreign trait on local type State. All State fields the impls touch are already pub/pub(crate) — no visibility widening needed. Verification (all green): - cargo build / cargo build --release - cargo test (79 lib + 3 integration = 82 pass, 1 ignored — unchanged) - cargo clippy --all-targets -- -D warnings - cargo fmt --check - cargo check --bin vaapi_import_bench --bin sw_encode_bench --- src/state/dispatch/buffer.rs | 19 + src/state/dispatch/dmabuf.rs | 115 ++++++ src/state/dispatch/mod.rs | 6 + src/state/dispatch/output_mgr.rs | 109 ++++++ src/state/dispatch/registry.rs | 116 ++++++ src/state/dispatch/screencopy.rs | 93 +++++ src/state/dispatch/wl_output.rs | 133 +++++++ src/{state.rs => state/mod.rs} | 613 +------------------------------ 8 files changed, 600 insertions(+), 604 deletions(-) create mode 100644 src/state/dispatch/buffer.rs create mode 100644 src/state/dispatch/dmabuf.rs create mode 100644 src/state/dispatch/mod.rs create mode 100644 src/state/dispatch/output_mgr.rs create mode 100644 src/state/dispatch/registry.rs create mode 100644 src/state/dispatch/screencopy.rs create mode 100644 src/state/dispatch/wl_output.rs rename src/{state.rs => state/mod.rs} (60%) diff --git a/src/state/dispatch/buffer.rs b/src/state/dispatch/buffer.rs new file mode 100644 index 0000000..8b65729 --- /dev/null +++ b/src/state/dispatch/buffer.rs @@ -0,0 +1,19 @@ +use wayland_client::protocol::wl_buffer::WlBuffer; +use wayland_client::{Dispatch, Proxy, QueueHandle}; + +use crate::state::{CaptureSource, State}; + +impl Dispatch for State { + fn event( + _state: &mut Self, + _proxy: &WlBuffer, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + if let wayland_client::protocol::wl_buffer::Event::Release = event { + tracing::trace!("WlBuffer released"); + } + } +} diff --git a/src/state/dispatch/dmabuf.rs b/src/state/dispatch/dmabuf.rs new file mode 100644 index 0000000..625bbfc --- /dev/null +++ b/src/state/dispatch/dmabuf.rs @@ -0,0 +1,115 @@ +use std::mem; +use std::path::PathBuf; + +use wayland_client::{Dispatch, Proxy, QueueHandle}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::{ + Event as BufferParamsEvent, ZwpLinuxBufferParamsV1, +}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::{ + Event as DmabufFeedbackEvent, ZwpLinuxDmabufFeedbackV1, +}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::{ + Event as DmabufEvent, ZwpLinuxDmabufV1, +}; + +use crate::state::{CaptureSource, EncConstructionStage, InFlightSurface, State}; + +impl Dispatch for State { + fn event( + _state: &mut Self, + _proxy: &ZwpLinuxDmabufV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + DmabufEvent::Format { .. } => {} + DmabufEvent::Modifier { .. } => {} + _ => {} + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _proxy: &ZwpLinuxDmabufFeedbackV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + DmabufFeedbackEvent::MainDevice { device } => { + if device.len() >= 8 { + let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]); + let dev = u64::from_ne_bytes(dev_bytes); + let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32; + let path = PathBuf::from(format!("/dev/dri/renderD{}", minor)); + if path.exists() { + tracing::info!( + "Compositor DRM device: {} (dev_t: {})", + path.display(), + dev + ); + state.drm_device_from_compositor = Some(path); + } else { + tracing::warn!( + "Compositor reported DRM device {} (dev_t: {}) but path does not exist", + path.display(), + dev + ); + } + } else { + tracing::warn!( + "main_device event with unexpected data length: {}", + device.len() + ); + } + } + DmabufFeedbackEvent::FormatTable { .. } => {} + DmabufFeedbackEvent::Done => {} + DmabufFeedbackEvent::TrancheDone => {} + DmabufFeedbackEvent::TrancheTargetDevice { .. } => {} + DmabufFeedbackEvent::TrancheFormats { .. } => {} + DmabufFeedbackEvent::TrancheFlags { .. } => {} + _ => {} + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + proxy: &ZwpLinuxBufferParamsV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + BufferParamsEvent::Created { .. } => { + tracing::debug!("DMA-BUF buffer created"); + } + BufferParamsEvent::Failed => { + tracing::error!("DMA-BUF buffer creation failed"); + let taken = mem::replace(&mut state.in_flight_surface, InFlightSurface::None); + match taken { + InFlightSurface::CopyQueued { buffer, frame, .. } => { + drop(buffer); + if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { + cap.on_done_with_frame(frame); + } + } + other => { + state.in_flight_surface = other; + } + } + proxy.destroy(); + state.errored = true; + } + _ => {} + } + } +} diff --git a/src/state/dispatch/mod.rs b/src/state/dispatch/mod.rs new file mode 100644 index 0000000..b44bad9 --- /dev/null +++ b/src/state/dispatch/mod.rs @@ -0,0 +1,6 @@ +mod buffer; +mod dmabuf; +mod output_mgr; +mod registry; +mod screencopy; +mod wl_output; diff --git a/src/state/dispatch/output_mgr.rs b/src/state/dispatch/output_mgr.rs new file mode 100644 index 0000000..bb14959 --- /dev/null +++ b/src/state/dispatch/output_mgr.rs @@ -0,0 +1,109 @@ +use wayland_client::{event_created_child, Dispatch, Proxy, QueueHandle}; +use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{ + self, Event as WlrHeadEvent, ZwlrOutputHeadV1, +}; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::{ + self, Event as WlrOutputManagerEvent, ZwlrOutputManagerV1, +}; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1; + +use crate::state::{CaptureSource, EncConstructionStage, State, WlrHeadInfo}; + +impl Dispatch for State { + fn event( + _state: &mut Self, + _proxy: &ZxdgOutputManagerV1, + _event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _proxy: &ZwlrOutputManagerV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + WlrOutputManagerEvent::Head { head } => { + let _head: ZwlrOutputHeadV1 = head; + tracing::debug!("wlr output head advertised"); + } + WlrOutputManagerEvent::Done { .. } => { + if let EncConstructionStage::ProbingOutputs { + wlr_manager_done, + outputs, + .. + } = &mut state.stage + { + *wlr_manager_done = true; + let count = outputs.len(); + for idx in 0..count { + state.try_finalize_output(idx); + } + } + } + WlrOutputManagerEvent::Finished => { + tracing::warn!("zwlr_output_manager_v1::Finished received during probing"); + } + _ => {} + } + } + + event_created_child!(State, ZwlrOutputManagerV1, [ + zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()), + ]); +} + +impl Dispatch for State { + fn event( + state: &mut Self, + proxy: &ZwlrOutputHeadV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + WlrHeadEvent::Name { name } => { + if let EncConstructionStage::ProbingOutputs { + wlr_heads, + wlr_head_proxy_to_name, + .. + } = &mut state.stage + { + wlr_heads.entry(name.clone()).or_insert(WlrHeadInfo {}); + wlr_head_proxy_to_name.insert(proxy.id(), name); + } + } + WlrHeadEvent::Position { .. } => {} + WlrHeadEvent::Finished => { + tracing::debug!("zwlr_output_head_v1::Finished received"); + } + _ => {} + } + } + + event_created_child!(State, ZwlrOutputHeadV1, [ + zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()), + ]); +} + +impl Dispatch for State { + fn event( + _state: &mut Self, + _proxy: &ZwlrOutputModeV1, + _event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + } +} diff --git a/src/state/dispatch/registry.rs b/src/state/dispatch/registry.rs new file mode 100644 index 0000000..a8ebad4 --- /dev/null +++ b/src/state/dispatch/registry.rs @@ -0,0 +1,116 @@ +use wayland_client::globals::GlobalListContents; +use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::protocol::wl_registry::WlRegistry; +use wayland_client::{Dispatch, QueueHandle}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1; +use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1; +use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; + +use crate::state::{CaptureSource, EncConstructionStage, OutputId, PartialOutputInfo, State}; + +impl Dispatch for State { + fn event( + state: &mut Self, + registry: &WlRegistry, + event: wayland_client::protocol::wl_registry::Event, + _data: &GlobalListContents, + _conn: &wayland_client::Connection, + qhandle: &QueueHandle>, + ) { + use wayland_client::protocol::wl_registry::Event as RegistryEvent; + + match event { + RegistryEvent::Global { + name, + interface, + version, + } => match interface.as_str() { + "zwlr_screencopy_manager_v1" => { + let v = version.min(3); + tracing::debug!("Binding zwlr_screencopy_manager_v1 v{v} (name={name})"); + let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ()); + if let EncConstructionStage::ProbingOutputs { + screencopy_manager, .. + } = &mut state.stage + { + *screencopy_manager = Some(mgr); + } + } + "zwp_linux_dmabuf_v1" => { + let v = version.min(4); + tracing::debug!("Binding zwp_linux_dmabuf_v1 v{v} (name={name})"); + let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ()); + if let EncConstructionStage::ProbingOutputs { + dmabuf, + dmabuf_feedback, + .. + } = &mut state.stage + { + *dmabuf = Some(proxy.clone()); + if v >= 4 { + let feedback = proxy.get_default_feedback(qhandle, ()); + *dmabuf_feedback = Some(feedback); + } + } + } + "wl_output" => { + let v = version.min(4); + tracing::debug!("Binding wl_output v{v} (name={name})"); + let output: WlOutput = registry.bind(name, v, qhandle, OutputId(name)); + if let EncConstructionStage::ProbingOutputs { + outputs, + bound_outputs, + output_names, + xdg_output_manager, + .. + } = &mut state.stage + { + outputs.push(PartialOutputInfo::default()); + bound_outputs.push(output.clone()); + output_names.push(name); + if let Some(xdg_mgr) = xdg_output_manager { + let output_id = OutputId(name); + xdg_mgr.get_xdg_output(&output, qhandle, output_id); + } + } + } + "zxdg_output_manager_v1" => { + let v = version.min(3); + tracing::debug!("Binding zxdg_output_manager_v1 v{v} (name={name})"); + let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ()); + if let EncConstructionStage::ProbingOutputs { + bound_outputs, + xdg_output_manager, + output_names, + .. + } = &mut state.stage + { + for (i, output) in bound_outputs.iter().enumerate() { + let oname = output_names.get(i).copied().unwrap_or(0); + let output_id = OutputId(oname); + xdg_mgr.get_xdg_output(output, qhandle, output_id); + } + *xdg_output_manager = Some(xdg_mgr); + } + } + "zwlr_output_manager_v1" => { + let v = version.min(4); + tracing::debug!("Binding zwlr_output_manager_v1 v{v} (name={name})"); + let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ()); + if let EncConstructionStage::ProbingOutputs { + wlr_output_manager, .. + } = &mut state.stage + { + *wlr_output_manager = Some(mgr); + } + } + _ => {} + }, + RegistryEvent::GlobalRemove { name } => { + tracing::debug!("Global removed: name={name}"); + } + _ => {} + } + } +} diff --git a/src/state/dispatch/screencopy.rs b/src/state/dispatch/screencopy.rs new file mode 100644 index 0000000..fe72e41 --- /dev/null +++ b/src/state/dispatch/screencopy.rs @@ -0,0 +1,93 @@ +use wayland_client::{Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ + Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1, +}; +use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; + +use crate::cap_wlr_screencopy::CapWlrScreencopy; +use crate::state::{EncConstructionStage, InFlightSurface, State}; + +impl Dispatch for State { + fn event( + state: &mut Self, + proxy: &ZwlrScreencopyFrameV1, + event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + match event { + // SHM buffer offer — in v3 the compositor enumerates supported buffer + // types (buffer and/or linux_dmabuf) before buffer_done. We only + // support DMA-BUF, so just log and wait for linux_dmabuf / buffer_done. + ScreencopyFrameEvent::Buffer { .. } => { + tracing::debug!("Received SHM Buffer offer — only DMA-BUF capture is supported"); + } + ScreencopyFrameEvent::LinuxDmabuf { + format, + width, + height, + } => { + tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); + + if !matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { + tracing::warn!("Received LinuxDmabuf while no frame allocation was queued"); + return; + } + + if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { + state.negotiate_format(format, width, height); + if state.errored { + return; + } + } + if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { + cap.current_frame = Some(proxy.clone()); + } + state.on_frame_allocd((), format, width, height); + } + // v3 terminal event: all buffer offers have been enumerated. + // If still AllocQueued, the compositor never sent linux_dmabuf — + // DMA-BUF screencopy is unsupported, so we must error out. + ScreencopyFrameEvent::BufferDone => { + if matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { + tracing::error!( + "Compositor did not offer DMA-BUF screencopy (only SHM); \ + DMA-BUF capture is required" + ); + state.in_flight_surface = InFlightSurface::None; + proxy.destroy(); + state.errored = true; + } + } + ScreencopyFrameEvent::Ready { + tv_sec_hi, + tv_sec_lo, + tv_nsec, + } => { + let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64; + let tv_usec = tv_nsec / 1000; + tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}"); + state.on_copy_complete(tv_sec, tv_usec); + } + ScreencopyFrameEvent::Failed => { + tracing::error!("Screencopy frame failed"); + state.on_copy_fail(); + } + ScreencopyFrameEvent::Damage { .. } => {} + _ => {} + } + } +} + +impl Dispatch for State { + fn event( + _state: &mut Self, + _proxy: &ZwlrScreencopyManagerV1, + _event: ::Event, + _data: &(), + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + } +} diff --git a/src/state/dispatch/wl_output.rs b/src/state/dispatch/wl_output.rs new file mode 100644 index 0000000..6f49332 --- /dev/null +++ b/src/state/dispatch/wl_output.rs @@ -0,0 +1,133 @@ +use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::{Dispatch, Proxy, QueueHandle}; +use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{ + Event as XdgOutputEvent, ZxdgOutputV1, +}; + +use crate::state::{CaptureSource, EncConstructionStage, OutputId, State, Transform}; + +impl Dispatch for State { + fn event( + state: &mut Self, + _proxy: &WlOutput, + event: wayland_client::protocol::wl_output::Event, + data: &OutputId, + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + use wayland_client::protocol::wl_output::Event as OutputEvent; + use wayland_client::protocol::wl_output::Mode as WlMode; + use wayland_client::protocol::wl_output::Transform as WlTransform; + + let OutputId(target_name) = data; + let idx = match &state.stage { + EncConstructionStage::ProbingOutputs { output_names, .. } => { + output_names.iter().position(|&n| n == *target_name) + } + _ => None, + }; + let idx = match idx { + Some(i) => i, + None => return, + }; + + match event { + OutputEvent::Geometry { transform, .. } => { + let t = match transform { + wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal, + wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90, + wayland_client::WEnum::Value(WlTransform::_180) => Transform::Normal180, + wayland_client::WEnum::Value(WlTransform::_270) => Transform::Normal270, + wayland_client::WEnum::Value(WlTransform::Flipped) => Transform::Flipped, + wayland_client::WEnum::Value(WlTransform::Flipped90) => Transform::Flipped90, + wayland_client::WEnum::Value(WlTransform::Flipped180) => Transform::Flipped180, + wayland_client::WEnum::Value(WlTransform::Flipped270) => Transform::Flipped270, + _ => Transform::Normal, + }; + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.transform = Some(t); + } + } + } + OutputEvent::Mode { + width, + height, + flags, + .. + } => { + let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current)); + if is_current { + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.mode_size = Some((width, height)); + } + } + } + } + OutputEvent::Done => { + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.done_count += 1; + if info.done_count >= 1 { + state.try_finalize_output(idx); + } + } + } + } + OutputEvent::Name { name } => { + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.wl_name = Some(name); + } + } + } + _ => {} + } + } +} + +impl Dispatch for State { + fn event( + state: &mut Self, + _proxy: &ZxdgOutputV1, + event: ::Event, + data: &OutputId, + _conn: &wayland_client::Connection, + _qhandle: &QueueHandle>, + ) { + let target_name = data.0; + let idx = match &state.stage { + EncConstructionStage::ProbingOutputs { output_names, .. } => { + output_names.iter().position(|&n| n == target_name) + } + _ => None, + }; + let idx = match idx { + Some(i) => i, + None => return, + }; + + match event { + XdgOutputEvent::Name { name } => { + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.name = Some(name); + } + } + } + XdgOutputEvent::LogicalSize { .. } => {} + XdgOutputEvent::Done => { + if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { + if let Some(info) = outputs.get_mut(idx) { + info.done_count += 1; + if info.done_count >= 1 { + state.try_finalize_output(idx); + } + } + } + } + _ => {} + } + } +} diff --git a/src/state.rs b/src/state/mod.rs similarity index 60% rename from src/state.rs rename to src/state/mod.rs index f855702..4f8051b 100644 --- a/src/state.rs +++ b/src/state/mod.rs @@ -9,34 +9,16 @@ use std::time::Instant; use anyhow::Result; use wayland_client::backend::ObjectId; -use wayland_client::globals::{GlobalList, GlobalListContents}; +use wayland_client::globals::GlobalList; use wayland_client::protocol::wl_buffer::WlBuffer; use wayland_client::protocol::wl_output::WlOutput; -use wayland_client::protocol::wl_registry::WlRegistry; -use wayland_client::{event_created_child, Dispatch, Proxy, QueueHandle}; -use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::{ - Event as BufferParamsEvent, Flags as BufferParamsFlags, ZwpLinuxBufferParamsV1, -}; -use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::{ - Event as DmabufFeedbackEvent, ZwpLinuxDmabufFeedbackV1, -}; -use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::{ - Event as DmabufEvent, ZwpLinuxDmabufV1, -}; +use wayland_client::{Dispatch, QueueHandle}; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::Flags as BufferParamsFlags; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1; use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1; -use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{ - Event as XdgOutputEvent, ZxdgOutputV1, -}; -use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{ - self, Event as WlrHeadEvent, ZwlrOutputHeadV1, -}; -use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::{ - self, Event as WlrOutputManagerEvent, ZwlrOutputManagerV1, -}; -use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1; -use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ - Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1, -}; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1; +use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; use ffmpeg_next as ff; @@ -44,12 +26,13 @@ use ffmpeg_next::ffi; use crate::args::Args; use crate::avhw::{AvHwDevCtx, EncState, EncodedH264Frame, SwEncState}; -use crate::cap_wlr_screencopy::CapWlrScreencopy; use crate::fps_limit::FpsLimit; use crate::stats::{FrameTimings, PipelineStats}; use crate::transform::{transpose_if_transform_transposed, Transform}; use crate::webrtc::WebRtcState; +mod dispatch; + // --------------------------------------------------------------------------- // CaptureSource trait // --------------------------------------------------------------------------- @@ -1014,581 +997,3 @@ impl State { true } } - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - registry: &WlRegistry, - event: wayland_client::protocol::wl_registry::Event, - _data: &GlobalListContents, - _conn: &wayland_client::Connection, - qhandle: &QueueHandle>, - ) { - use wayland_client::protocol::wl_registry::Event as RegistryEvent; - - match event { - RegistryEvent::Global { - name, - interface, - version, - } => match interface.as_str() { - "zwlr_screencopy_manager_v1" => { - let v = version.min(3); - tracing::debug!("Binding zwlr_screencopy_manager_v1 v{v} (name={name})"); - let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ()); - if let EncConstructionStage::ProbingOutputs { - screencopy_manager, .. - } = &mut state.stage - { - *screencopy_manager = Some(mgr); - } - } - "zwp_linux_dmabuf_v1" => { - let v = version.min(4); - tracing::debug!("Binding zwp_linux_dmabuf_v1 v{v} (name={name})"); - let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ()); - if let EncConstructionStage::ProbingOutputs { - dmabuf, - dmabuf_feedback, - .. - } = &mut state.stage - { - *dmabuf = Some(proxy.clone()); - if v >= 4 { - let feedback = proxy.get_default_feedback(qhandle, ()); - *dmabuf_feedback = Some(feedback); - } - } - } - "wl_output" => { - let v = version.min(4); - tracing::debug!("Binding wl_output v{v} (name={name})"); - let output: WlOutput = registry.bind(name, v, qhandle, OutputId(name)); - if let EncConstructionStage::ProbingOutputs { - outputs, - bound_outputs, - output_names, - xdg_output_manager, - .. - } = &mut state.stage - { - outputs.push(PartialOutputInfo::default()); - bound_outputs.push(output.clone()); - output_names.push(name); - if let Some(xdg_mgr) = xdg_output_manager { - let output_id = OutputId(name); - xdg_mgr.get_xdg_output(&output, qhandle, output_id); - } - } - } - "zxdg_output_manager_v1" => { - let v = version.min(3); - tracing::debug!("Binding zxdg_output_manager_v1 v{v} (name={name})"); - let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ()); - if let EncConstructionStage::ProbingOutputs { - bound_outputs, - xdg_output_manager, - output_names, - .. - } = &mut state.stage - { - for (i, output) in bound_outputs.iter().enumerate() { - let oname = output_names.get(i).copied().unwrap_or(0); - let output_id = OutputId(oname); - xdg_mgr.get_xdg_output(output, qhandle, output_id); - } - *xdg_output_manager = Some(xdg_mgr); - } - } - "zwlr_output_manager_v1" => { - let v = version.min(4); - tracing::debug!("Binding zwlr_output_manager_v1 v{v} (name={name})"); - let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ()); - if let EncConstructionStage::ProbingOutputs { - wlr_output_manager, .. - } = &mut state.stage - { - *wlr_output_manager = Some(mgr); - } - } - _ => {} - }, - RegistryEvent::GlobalRemove { name } => { - tracing::debug!("Global removed: name={name}"); - } - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - _proxy: &WlOutput, - event: wayland_client::protocol::wl_output::Event, - data: &OutputId, - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - use wayland_client::protocol::wl_output::Event as OutputEvent; - use wayland_client::protocol::wl_output::Mode as WlMode; - use wayland_client::protocol::wl_output::Transform as WlTransform; - - let OutputId(target_name) = data; - let idx = match &state.stage { - EncConstructionStage::ProbingOutputs { output_names, .. } => { - output_names.iter().position(|&n| n == *target_name) - } - _ => None, - }; - let idx = match idx { - Some(i) => i, - None => return, - }; - - match event { - OutputEvent::Geometry { transform, .. } => { - let t = match transform { - wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal, - wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90, - wayland_client::WEnum::Value(WlTransform::_180) => Transform::Normal180, - wayland_client::WEnum::Value(WlTransform::_270) => Transform::Normal270, - wayland_client::WEnum::Value(WlTransform::Flipped) => Transform::Flipped, - wayland_client::WEnum::Value(WlTransform::Flipped90) => Transform::Flipped90, - wayland_client::WEnum::Value(WlTransform::Flipped180) => Transform::Flipped180, - wayland_client::WEnum::Value(WlTransform::Flipped270) => Transform::Flipped270, - _ => Transform::Normal, - }; - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.transform = Some(t); - } - } - } - OutputEvent::Mode { - width, - height, - flags, - .. - } => { - let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current)); - if is_current { - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.mode_size = Some((width, height)); - } - } - } - } - OutputEvent::Done => { - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.done_count += 1; - if info.done_count >= 1 { - state.try_finalize_output(idx); - } - } - } - } - OutputEvent::Name { name } => { - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.wl_name = Some(name); - } - } - } - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - _proxy: &ZxdgOutputV1, - event: ::Event, - data: &OutputId, - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - let target_name = data.0; - let idx = match &state.stage { - EncConstructionStage::ProbingOutputs { output_names, .. } => { - output_names.iter().position(|&n| n == target_name) - } - _ => None, - }; - let idx = match idx { - Some(i) => i, - None => return, - }; - - match event { - XdgOutputEvent::Name { name } => { - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.name = Some(name); - } - } - } - XdgOutputEvent::LogicalSize { .. } => {} - XdgOutputEvent::Done => { - if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { - if let Some(info) = outputs.get_mut(idx) { - info.done_count += 1; - if info.done_count >= 1 { - state.try_finalize_output(idx); - } - } - } - } - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - _state: &mut Self, - _proxy: &ZwpLinuxDmabufV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - DmabufEvent::Format { .. } => {} - DmabufEvent::Modifier { .. } => {} - _ => {} - } - } -} - -impl Dispatch for State { - fn event( - state: &mut Self, - _proxy: &ZwpLinuxDmabufFeedbackV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - DmabufFeedbackEvent::MainDevice { device } => { - if device.len() >= 8 { - let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]); - let dev = u64::from_ne_bytes(dev_bytes); - let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32; - let path = PathBuf::from(format!("/dev/dri/renderD{}", minor)); - if path.exists() { - tracing::info!( - "Compositor DRM device: {} (dev_t: {})", - path.display(), - dev - ); - state.drm_device_from_compositor = Some(path); - } else { - tracing::warn!( - "Compositor reported DRM device {} (dev_t: {}) but path does not exist", - path.display(), - dev - ); - } - } else { - tracing::warn!( - "main_device event with unexpected data length: {}", - device.len() - ); - } - } - DmabufFeedbackEvent::FormatTable { .. } => {} - DmabufFeedbackEvent::Done => {} - DmabufFeedbackEvent::TrancheDone => {} - DmabufFeedbackEvent::TrancheTargetDevice { .. } => {} - DmabufFeedbackEvent::TrancheFormats { .. } => {} - DmabufFeedbackEvent::TrancheFlags { .. } => {} - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - proxy: &ZwpLinuxBufferParamsV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - BufferParamsEvent::Created { .. } => { - tracing::debug!("DMA-BUF buffer created"); - } - BufferParamsEvent::Failed => { - tracing::error!("DMA-BUF buffer creation failed"); - let taken = mem::replace(&mut state.in_flight_surface, InFlightSurface::None); - match taken { - InFlightSurface::CopyQueued { buffer, frame, .. } => { - drop(buffer); - if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { - cap.on_done_with_frame(frame); - } - } - other => { - state.in_flight_surface = other; - } - } - proxy.destroy(); - state.errored = true; - } - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch for CapWlrScreencopy -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - proxy: &ZwlrScreencopyFrameV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - // SHM buffer offer — in v3 the compositor enumerates supported buffer - // types (buffer and/or linux_dmabuf) before buffer_done. We only - // support DMA-BUF, so just log and wait for linux_dmabuf / buffer_done. - ScreencopyFrameEvent::Buffer { .. } => { - tracing::debug!("Received SHM Buffer offer — only DMA-BUF capture is supported"); - } - ScreencopyFrameEvent::LinuxDmabuf { - format, - width, - height, - } => { - tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); - - if !matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { - tracing::warn!("Received LinuxDmabuf while no frame allocation was queued"); - return; - } - - if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { - state.negotiate_format(format, width, height); - if state.errored { - return; - } - } - if let EncConstructionStage::Streaming { cap, .. } = &mut state.stage { - cap.current_frame = Some(proxy.clone()); - } - state.on_frame_allocd((), format, width, height); - } - // v3 terminal event: all buffer offers have been enumerated. - // If still AllocQueued, the compositor never sent linux_dmabuf — - // DMA-BUF screencopy is unsupported, so we must error out. - ScreencopyFrameEvent::BufferDone => { - if matches!(state.in_flight_surface, InFlightSurface::AllocQueued) { - tracing::error!( - "Compositor did not offer DMA-BUF screencopy (only SHM); \ - DMA-BUF capture is required" - ); - state.in_flight_surface = InFlightSurface::None; - proxy.destroy(); - state.errored = true; - } - } - ScreencopyFrameEvent::Ready { - tv_sec_hi, - tv_sec_lo, - tv_nsec, - } => { - let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64; - let tv_usec = tv_nsec / 1000; - tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}"); - state.on_copy_complete(tv_sec, tv_usec); - } - ScreencopyFrameEvent::Failed => { - tracing::error!("Screencopy frame failed"); - state.on_copy_fail(); - } - ScreencopyFrameEvent::Damage { .. } => {} - _ => {} - } - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - _state: &mut Self, - _proxy: &ZxdgOutputManagerV1, - _event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - _proxy: &ZwlrOutputManagerV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - WlrOutputManagerEvent::Head { head } => { - let _head: ZwlrOutputHeadV1 = head; - tracing::debug!("wlr output head advertised"); - } - WlrOutputManagerEvent::Done { .. } => { - if let EncConstructionStage::ProbingOutputs { - wlr_manager_done, - outputs, - .. - } = &mut state.stage - { - *wlr_manager_done = true; - let count = outputs.len(); - for idx in 0..count { - state.try_finalize_output(idx); - } - } - } - WlrOutputManagerEvent::Finished => { - tracing::warn!("zwlr_output_manager_v1::Finished received during probing"); - } - _ => {} - } - } - - event_created_child!(State, ZwlrOutputManagerV1, [ - zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()), - ]); -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - state: &mut Self, - proxy: &ZwlrOutputHeadV1, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - match event { - WlrHeadEvent::Name { name } => { - if let EncConstructionStage::ProbingOutputs { - wlr_heads, - wlr_head_proxy_to_name, - .. - } = &mut state.stage - { - wlr_heads.entry(name.clone()).or_insert(WlrHeadInfo {}); - wlr_head_proxy_to_name.insert(proxy.id(), name); - } - } - WlrHeadEvent::Position { .. } => {} - WlrHeadEvent::Finished => { - tracing::debug!("zwlr_output_head_v1::Finished received"); - } - _ => {} - } - } - - event_created_child!(State, ZwlrOutputHeadV1, [ - zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()), - ]); -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - _state: &mut Self, - _proxy: &ZwlrOutputModeV1, - _event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - _state: &mut Self, - _proxy: &ZwlrScreencopyManagerV1, - _event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - } -} - -// --------------------------------------------------------------------------- -// Dispatch -// --------------------------------------------------------------------------- - -impl Dispatch for State { - fn event( - _state: &mut Self, - _proxy: &WlBuffer, - event: ::Event, - _data: &(), - _conn: &wayland_client::Connection, - _qhandle: &QueueHandle>, - ) { - if let wayland_client::protocol::wl_buffer::Event::Release = event { - tracing::trace!("WlBuffer released"); - } - } -}