feat: add KWin/KDE Plasma screen capture via xdg-desktop-portal ScreenCast + PipeWire
Add a second capture backend for compositors without wlr-screencopy (KWin, GNOME, etc.) using the xdg-desktop-portal ScreenCast interface and PipeWire DMA-BUF streaming. New files: - src/backend_detect.rs: auto-detect wlr-screencopy vs portal backend - src/cap_portal.rs: Portal session setup + PipeWire DMA-BUF thread - src/state_portal.rs: StatePortal encoder pipeline (DMA-BUF → VAAPI) Changes: - Cargo.toml: add ashpd 0.13, tokio 1, pipewire 0.9, libspa 0.9, crossbeam-channel 0.5 - src/args.rs: add --backend CLI flag - src/avhw.rs: extract create_encoder() from inline State code - src/main.rs: route to portal or wlr-screencopy based on backend - src/state.rs: fix params.destroy() on dup failure, cleanup in_flight_surface on copy fail, use create_encoder() - tests/integration_test.rs: add --backend flag tests
This commit is contained in:
54
src/state.rs
54
src/state.rs
@@ -465,6 +465,8 @@ impl<S: CaptureSource> State<S> {
|
||||
let fd_dup = unsafe { libc::dup(obj.fd) };
|
||||
if fd_dup < 0 {
|
||||
tracing::error!("failed to dup dma-buf fd");
|
||||
// wayland-client does not auto-destroy params on Drop.
|
||||
params.destroy();
|
||||
self.errored = true;
|
||||
return;
|
||||
}
|
||||
@@ -553,8 +555,27 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_copy_fail(&mut self) {
|
||||
pub fn on_copy_fail(&mut self)
|
||||
where
|
||||
S::Frame: Default,
|
||||
{
|
||||
tracing::error!("compositor copy failed");
|
||||
let taken = mem::replace(&mut self.in_flight_surface, InFlightSurface::None);
|
||||
match taken {
|
||||
InFlightSurface::CopyQueued {
|
||||
buffer,
|
||||
frame,
|
||||
..
|
||||
} => {
|
||||
drop(buffer);
|
||||
if let EncConstructionStage::Streaming { cap, .. } = &mut self.stage {
|
||||
cap.on_done_with_frame(frame);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
self.in_flight_surface = other;
|
||||
}
|
||||
}
|
||||
self.errored = true;
|
||||
}
|
||||
|
||||
@@ -576,25 +597,19 @@ impl<S: CaptureSource> State<S> {
|
||||
};
|
||||
let (output_info, output, cap, screencopy_manager, dmabuf) = stage_data;
|
||||
let drm_path = self.resolve_drm_path();
|
||||
let bitrate = self.args.bitrate.unwrap_or_else(|| {
|
||||
let fps = self.args.fps as u64;
|
||||
2 * (width as u64) * (height as u64) * fps / 100
|
||||
});
|
||||
let gop_size = self.args.gop_size.unwrap_or(self.args.fps);
|
||||
let fps = self.args.fps;
|
||||
let (enc_w, enc_h) =
|
||||
transpose_if_transform_transposed(output_info.transform, width as i32, height as i32);
|
||||
let enc = match EncState::new(
|
||||
let bitrate = self.args.bitrate.unwrap_or_else(|| {
|
||||
2 * (width as u64) * (height as u64) * (fps as u64) / 100
|
||||
});
|
||||
let enc = match crate::avhw::create_encoder(
|
||||
&drm_path,
|
||||
Path::new(&self.args.output),
|
||||
width,
|
||||
height,
|
||||
enc_w as u32,
|
||||
enc_h as u32,
|
||||
bitrate,
|
||||
gop_size,
|
||||
fps,
|
||||
output_info.transform,
|
||||
self.args.bitrate,
|
||||
self.args.gop_size,
|
||||
) {
|
||||
Ok(enc) => enc,
|
||||
Err(e) => {
|
||||
@@ -1175,21 +1190,24 @@ impl<S: CaptureSource> Dispatch<ZwpLinuxBufferParamsV1, ()> for State<S> {
|
||||
}
|
||||
BufferParamsEvent::Failed => {
|
||||
tracing::error!("DMA-BUF buffer creation failed");
|
||||
state.errored = true;
|
||||
match mem::replace(&mut state.in_flight_surface, InFlightSurface::None) {
|
||||
let taken = mem::replace(&mut state.in_flight_surface, InFlightSurface::None);
|
||||
match taken {
|
||||
InFlightSurface::CopyQueued {
|
||||
surface: _,
|
||||
drm_map: _,
|
||||
frame: _,
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user