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:
71
src/main.rs
71
src/main.rs
@@ -9,9 +9,12 @@ use wayland_client::Connection;
|
||||
|
||||
mod args;
|
||||
mod avhw;
|
||||
mod backend_detect;
|
||||
mod cap_portal;
|
||||
mod cap_wlr_screencopy;
|
||||
mod fps_limit;
|
||||
mod state;
|
||||
mod state_portal;
|
||||
mod transform;
|
||||
|
||||
use crate::args::Args;
|
||||
@@ -40,6 +43,19 @@ fn main() -> Result<()> {
|
||||
anyhow::bail!("HEVC not supported in MVP. Use --codec h264");
|
||||
}
|
||||
|
||||
let backend = crate::backend_detect::detect_backend(&args)?;
|
||||
|
||||
match backend {
|
||||
crate::backend_detect::CaptureBackend::WlrScreencopy => {
|
||||
run_wlr_screencopy(args)
|
||||
}
|
||||
crate::backend_detect::CaptureBackend::PortalPipeWire => {
|
||||
run_portal_pipewire(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_wlr_screencopy(args: Args) -> Result<()> {
|
||||
// Connect to Wayland compositor
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (gm, mut queue) = registry_queue_init::<State<CapWlrScreencopy>>(&conn)?;
|
||||
@@ -158,3 +174,58 @@ fn main() -> Result<()> {
|
||||
tracing::info!("Done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_portal_pipewire(args: Args) -> Result<()> {
|
||||
use crate::state_portal::StatePortal;
|
||||
|
||||
tracing::info!("Using Portal/PipeWire backend (KWin/KDE/GNOME)");
|
||||
|
||||
let mut state = StatePortal::new(args)?;
|
||||
|
||||
// Set up signal handling only (no Wayland fd needed)
|
||||
let mut signals = signal_hook_mio::v1_0::Signals::new(&[
|
||||
signal_hook::consts::SIGINT,
|
||||
signal_hook::consts::SIGTERM,
|
||||
])?;
|
||||
|
||||
let mut poll = mio::Poll::new()?;
|
||||
let mut events = mio::Events::with_capacity(8);
|
||||
|
||||
poll.registry().register(
|
||||
&mut signals,
|
||||
mio::Token(1),
|
||||
mio::Interest::READABLE,
|
||||
)?;
|
||||
|
||||
let mut running = true;
|
||||
while running {
|
||||
poll.poll(&mut events, Some(std::time::Duration::from_millis(10)))
|
||||
.unwrap_or_else(|e| {
|
||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
||||
return;
|
||||
}
|
||||
tracing::error!("poll failed: {e}");
|
||||
running = false;
|
||||
});
|
||||
|
||||
for event in &events {
|
||||
if event.token() == mio::Token(1) {
|
||||
tracing::info!("Received quit signal");
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Process all available PipeWire frames
|
||||
while state.poll_and_encode()? {}
|
||||
|
||||
if state.is_errored() {
|
||||
tracing::error!("Fatal error in portal state machine, exiting");
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Shutting down...");
|
||||
state.shutdown();
|
||||
tracing::info!("Done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user