Fix x11 pipewire screencast

This commit is contained in:
GK-Gaming 2026-07-31 23:30:24 +02:00
parent cae2d7c7e2
commit f47564eb65
3 changed files with 109 additions and 110 deletions

View File

@ -18,6 +18,9 @@ use wlx_capture::{
};
use wlx_common::{astr_containers::AStrMapExt, overlays::ToastTopic};
#[cfg(feature = "x11")]
use wlx_capture::xshm::XshmScreen;
use crate::{
backend::task::{OverlayTask, TaskType},
gui::panel::{GuiPanel, NewGuiPanelParams},
@ -112,6 +115,51 @@ impl ScreenCastBackend {
finalize_fn,
)
}
#[cfg(feature = "x11")]
pub fn new_x11pw(
m: &XshmScreen,
token: Option<Rc<str>>,
app: &mut AppState,
) -> anyhow::Result<Self> {
use glam::vec2;
fn finalize_fn(
name: Arc<str>,
logical_pos: Vec2,
logical_size: Vec2,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
app: &mut AppState,
) -> Box<dyn OverlayBackend> {
use wlx_capture::frame::Transform;
let mut backend =
ScreenBackend::new_raw(name, app.feats.xr_backend, CaptureType::PipeWire, capture);
backend.logical_pos = logical_pos;
backend.logical_size = logical_size;
backend.apply_mouse_transform_with_override(Transform::Undefined);
Box::new(backend)
}
let params = ScreenCastParams {
token,
embed_mouse: true,
screens_only: true,
persist: true,
allow_multiple: false,
};
Self::new_raw(
m.name.clone(),
format!("{} {}", m.monitor.x(), m.monitor.y()).into(),
vec2(m.monitor.x() as f32, m.monitor.y() as f32),
vec2(m.monitor.width() as f32, m.monitor.height() as f32),
params,
app,
finalize_fn,
)
}
pub(super) fn new_raw(
name: Arc<str>,

View File

@ -1,14 +1,15 @@
use std::sync::Arc;
use wgui::log::LogErr;
use glam::{DVec2, vec2};
use wlx_capture::{
WlxCapture,
frame::Transform,
xshm::{XshmCapture, XshmScreen},
};
use wlx_common::{astr_containers::AStrMapExt, config::CaptureMethod};
#[cfg(feature = "pipewire")]
use crate::state::save_pw_token_config;
use crate::{
overlays::screen::{backend::CaptureType, create_screen_from_backend},
state::{AppState, ScreenMeta},
@ -21,9 +22,7 @@ use super::{
};
#[cfg(feature = "pipewire")]
use wlx_capture::pipewire::{
PipewireStream, ScreenCastParams, capture::PipewireCapture, screen_cast_select_blocking,
};
use crate::{overlays::screen::pw::ScreenCastBackend, windowing::backend::OverlayBackend};
impl ScreenBackend {
pub fn new_xshm(screen: Arc<XshmScreen>, app: &AppState) -> Self {
@ -40,45 +39,43 @@ impl ScreenBackend {
}
}
#[cfg(feature = "pipewire")]
pub fn create_screen_renderer_x11pw(
output: &XshmScreen,
app: &mut AppState,
) -> anyhow::Result<Box<dyn OverlayBackend>> {
let display_name = &*output.name;
// Find existing token by display
let token = app
.session
.pw_tokens
.arc_get(display_name)
.map(|x| x.to_string().into());
if token.is_some() {
log::info!("Found existing Pipewire token for display {display_name}");
}
Ok(Box::new(
ScreenCastBackend::new_x11pw(output, token, app)
.log_err("Failed to create screen with screen cast backend")?,
))
}
#[cfg(feature = "pipewire")]
pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateData> {
use wlx_capture::xshm::xshm_get_monitors;
use wlx_common::astr_containers::AStrMapExt;
use crate::{overlays::screen::create_screen_from_backend, state::ScreenMeta};
use super::ScreenCreateData;
let pw_tokens_copy = app.session.pw_tokens.clone();
let params = ScreenCastParams {
token: app
.session
.pw_tokens
.arc_get("x11")
.map(|x| x.clone().into()),
embed_mouse: !app.session.config.double_cursor_fix,
allow_multiple: true,
persist: true,
screens_only: true,
};
// this one still blocks
let select_screen_result = screen_cast_select_blocking(params)?;
if let Some(restore_token) = select_screen_result.restore_token
&& app
.session
.pw_tokens
.arc_set("x11".into(), restore_token.clone())
{
log::info!("Adding Pipewire token {restore_token}");
}
if pw_tokens_copy != app.session.pw_tokens {
// Token list changed, re-create token config file
if let Err(err) = save_pw_token_config(app.session.pw_tokens.clone()) {
log::error!("Failed to save Pipewire token config: {err}");
}
if !matches!(
app.session.config.capture_method,
CaptureMethod::PipeWireCpu | CaptureMethod::PipeWire
) {
anyhow::bail!("Pipewire is not selected as backend");
}
let monitors = match xshm_get_monitors() {
@ -87,49 +84,39 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
anyhow::bail!(e.to_string());
}
};
log::info!("Got {} monitors", monitors.len());
log::info!("Got {} streams", select_screen_result.streams.len());
let mut extent = DVec2::ZERO;
let screens = select_screen_result
.streams
.into_iter()
.enumerate()
.map(|(i, s)| {
let m = best_match(&s, monitors.iter().map(AsRef::as_ref)).unwrap();
log::info!("Stream {i} is {}", m.name);
extent.x = extent.x.max((m.monitor.x() + m.monitor.width()) as _);
extent.y = extent.y.max((m.monitor.y() + m.monitor.height()) as _);
let mut screens = vec![];
for m in &monitors {
if app.screens.iter().any(|s| s.name == m.name) {
continue;
}
let mut backend = ScreenBackend::new_raw(
m.name.clone(),
app.feats.xr_backend,
CaptureType::PipeWire,
new_wlx_capture!(
app.gfx_extras.queue_capture,
PipewireCapture::new(m.name.clone(), s.node_id)
),
);
log::info!(
"{}: Init screen of res {}x{}, at {}x{}",
m.name,
m.monitor.width(),
m.monitor.height(),
m.monitor.x(),
m.monitor.y(),
);
backend.logical_pos = vec2(m.monitor.x() as f32, m.monitor.y() as f32);
backend.logical_size = vec2(m.monitor.width() as f32, m.monitor.height() as f32);
backend.apply_mouse_transform_with_override(Transform::Undefined);
extent.x = extent.x.max((m.monitor.x() + m.monitor.width()) as _);
extent.y = extent.y.max((m.monitor.y() + m.monitor.height()) as _);
let window_data = create_screen_from_backend(
m.name.clone(),
Transform::Normal,
&app.session,
Box::new(backend),
);
let backend = create_screen_renderer_x11pw(m, app)?;
let window_config =
create_screen_from_backend(m.name.clone(), Transform::Normal, &app.session, backend);
let meta = ScreenMeta {
name: m.name.clone(),
native_handle: 0,
};
let meta = ScreenMeta {
name: m.name.clone(),
native_handle: 0,
};
(meta, window_data)
})
.collect();
screens.push((meta, window_config));
}
log::info!("Got {} monitors", monitors.len());
app.hid_provider.inner.set_desktop_extent(extent);
app.hid_provider.inner.set_desktop_origin(DVec2::ZERO);
@ -137,34 +124,6 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
Ok(ScreenCreateData { screens })
}
#[cfg(feature = "pipewire")]
fn best_match<'a>(
stream: &PipewireStream,
mut streams: impl Iterator<Item = &'a XshmScreen>,
) -> Option<&'a XshmScreen> {
let mut best = streams.next();
log::debug!("stream: {:?}", stream.position);
log::debug!("first: {:?}", best.map(|b| &b.monitor));
let Some(position) = stream.position else {
return best;
};
let mut best_dist = best.map_or(i32::MAX, |b| {
(b.monitor.x() - position.0).abs() + (b.monitor.y() - position.1).abs()
});
for stream in streams {
log::debug!("checking: {:?}", stream.monitor);
let dist =
(stream.monitor.x() - position.0).abs() + (stream.monitor.y() - position.1).abs();
if dist < best_dist {
best = Some(stream);
best_dist = dist;
}
}
log::debug!("best: {:?}", best.map(|b| &b.monitor));
best
}
pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result<ScreenCreateData> {
use wlx_capture::xshm::xshm_get_monitors;

View File

@ -25,11 +25,7 @@ pub enum CaptureMethod {
Auto,
#[serde(alias = "pipewire")]
#[strum(props(
Text = "PipeWire GPU",
Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP",
Desktop = "Wayland"
))]
#[strum(props(Text = "PipeWire GPU", Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP"))]
PipeWire,
#[strum(props(
@ -40,11 +36,7 @@ pub enum CaptureMethod {
ScreenCopyGpu,
#[serde(alias = "pw-fallback")]
#[strum(props(
Text = "PipeWire CPU",
Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP",
Desktop = "Wayland"
))]
#[strum(props(Text = "PipeWire CPU", Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP"))]
PipeWireCpu,
#[serde(alias = "screencopy")]