Add Wayland multi-monitor screen capture functionality (#12900)
* Add Wayland multi-monitor screen capture functionality * fix wayland capture issues by reverting to CapturerPtr, the problem was that calling Display::all in get_capturer_for_display was dropping the pipewire capturer and causing the video to freeze. * If running as AppImage or flatpak, ignore the 'multiple' argument * Comment out warning log with unclear purpose Comment out warning log with unclear purpose --------- Co-authored-by: fufesou <13586388+fufesou@users.noreply.github.com>
This commit is contained in:
@@ -661,7 +661,9 @@ fn on_create_session_response(
|
|||||||
Variant(Box::new("u3".to_string())),
|
Variant(Box::new("u3".to_string())),
|
||||||
);
|
);
|
||||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
||||||
// args.insert("multiple".into(), Variant(Box::new(true)));
|
if is_server_running() {
|
||||||
|
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||||
|
}
|
||||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||||
|
|
||||||
let path = portal.select_sources(ses.clone(), args)?;
|
let path = portal.select_sources(ses.clone(), args)?;
|
||||||
@@ -725,7 +727,9 @@ fn on_select_devices_response(
|
|||||||
Variant(Box::new("u3".to_string())),
|
Variant(Box::new("u3".to_string())),
|
||||||
);
|
);
|
||||||
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.ScreenCast.html
|
||||||
// args.insert("multiple".into(), Variant(Box::new(true)));
|
if is_server_running() {
|
||||||
|
args.insert("multiple".into(), Variant(Box::new(true)));
|
||||||
|
}
|
||||||
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32)));
|
||||||
|
|
||||||
let session = session.clone();
|
let session = session.clone();
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ fn get_capturer_monitor(
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
if !is_x11() {
|
if !is_x11() {
|
||||||
return super::wayland::get_capturer();
|
return super::wayland::get_capturer_for_display(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,11 +473,20 @@ fn run(vs: VideoService) -> ResultType<()> {
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
super::wayland::ensure_inited()?;
|
super::wayland::ensure_inited()?;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let _wayland_call_on_ret = SimpleCallOnReturn {
|
let _wayland_call_on_ret = {
|
||||||
b: true,
|
// Increment active display count when starting
|
||||||
f: Box::new(|| {
|
let _display_count = super::wayland::increment_active_display_count();
|
||||||
super::wayland::clear();
|
|
||||||
}),
|
SimpleCallOnReturn {
|
||||||
|
b: true,
|
||||||
|
f: Box::new(|| {
|
||||||
|
// Decrement active display count and only clear if this was the last display
|
||||||
|
let remaining_count = super::wayland::decrement_active_display_count();
|
||||||
|
if remaining_count == 0 {
|
||||||
|
super::wayland::clear();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use hbb_common::{
|
|||||||
platform::linux::{CMD_SH, DISTRO},
|
platform::linux::{CMD_SH, DISTRO},
|
||||||
};
|
};
|
||||||
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
use scrap::{is_cursor_embedded, set_map_err, Capturer, Display, Frame, TraitCapturer};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
|
|
||||||
@@ -15,14 +16,30 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref CAP_DISPLAY_INFO: RwLock<u64> = RwLock::new(0);
|
static ref CAP_DISPLAY_INFO: RwLock<HashMap<usize, u64>> = RwLock::new(HashMap::new());
|
||||||
|
static ref PIPEWIRE_INITIALIZED: RwLock<bool> = RwLock::new(false);
|
||||||
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
|
static ref LOG_SCRAP_COUNT: Mutex<u32> = Mutex::new(0);
|
||||||
|
static ref ACTIVE_DISPLAY_COUNT: RwLock<usize> = RwLock::new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
set_map_err(map_err_scrap);
|
set_map_err(map_err_scrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn increment_active_display_count() -> usize {
|
||||||
|
let mut count = ACTIVE_DISPLAY_COUNT.write().unwrap();
|
||||||
|
*count += 1;
|
||||||
|
*count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn decrement_active_display_count() -> usize {
|
||||||
|
let mut count = ACTIVE_DISPLAY_COUNT.write().unwrap();
|
||||||
|
if *count > 0 {
|
||||||
|
*count -= 1;
|
||||||
|
}
|
||||||
|
*count
|
||||||
|
}
|
||||||
|
|
||||||
fn map_err_scrap(err: String) -> io::Error {
|
fn map_err_scrap(err: String) -> io::Error {
|
||||||
// to-do: Handle error better, do not restart server
|
// to-do: Handle error better, do not restart server
|
||||||
if err.starts_with("Did not receive a reply") {
|
if err.starts_with("Did not receive a reply") {
|
||||||
@@ -70,7 +87,7 @@ impl Clone for CapturerPtr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TraitCapturer for CapturerPtr {
|
impl TraitCapturer for CapturerPtr {
|
||||||
fn frame<'a>(&'a mut self, timeout: Duration) -> io::Result<Frame<'a>> {
|
fn frame<'a>(&'a mut self, timeout: std::time::Duration) -> std::io::Result<Frame<'a>> {
|
||||||
unsafe { (*self.0).frame(timeout) }
|
unsafe { (*self.0).frame(timeout) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +110,7 @@ pub(super) fn is_inited() -> Option<Message> {
|
|||||||
if is_x11() {
|
if is_x11() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
|
if CAP_DISPLAY_INFO.read().unwrap().is_empty() {
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
let res = MessageBox {
|
let res = MessageBox {
|
||||||
msgtype: "nook-nocancel-hasclose".to_owned(),
|
msgtype: "nook-nocancel-hasclose".to_owned(),
|
||||||
@@ -126,6 +143,20 @@ fn get_max_desktop_resolution() -> Option<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_max_resolution_from_displays(displays: &[Display]) -> (i32, i32) {
|
||||||
|
// TODO: this doesn't work in most situations other than sharing all displays
|
||||||
|
// this is because the function only gets called with the displays being shared with pipewire
|
||||||
|
// the xrandr method does work otherwise we could get this correctly using xdg-output-unstable-v1 when xrandr isn't available
|
||||||
|
// log::warn!("using incorrect max resolution calculation uinput may not work correctly");
|
||||||
|
let (mut max_x, mut max_y) = (0, 0);
|
||||||
|
for d in displays {
|
||||||
|
let (x, y) = d.origin();
|
||||||
|
max_x = max_x.max(x + d.width() as i32);
|
||||||
|
max_y = max_y.max(y + d.height() as i32);
|
||||||
|
}
|
||||||
|
(max_x, max_y)
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn check_init() -> ResultType<()> {
|
pub(super) async fn check_init() -> ResultType<()> {
|
||||||
if !is_x11() {
|
if !is_x11() {
|
||||||
let mut minx = 0;
|
let mut minx = 0;
|
||||||
@@ -134,13 +165,19 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
let mut maxy = 0;
|
let mut maxy = 0;
|
||||||
let use_uinput = crate::input_service::wayland_use_uinput();
|
let use_uinput = crate::input_service::wayland_use_uinput();
|
||||||
|
|
||||||
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
|
if CAP_DISPLAY_INFO.read().unwrap().is_empty() {
|
||||||
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
|
||||||
if *lock == 0 {
|
if lock.is_empty() {
|
||||||
let mut all = Display::all()?;
|
// Check if PipeWire is already initialized to prevent duplicate recorder creation
|
||||||
|
if *PIPEWIRE_INITIALIZED.read().unwrap() {
|
||||||
|
log::warn!("wayland_diag: Preventing duplicate PipeWire initialization");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let all = Display::all()?;
|
||||||
|
*PIPEWIRE_INITIALIZED.write().unwrap() = true;
|
||||||
let num = all.len();
|
let num = all.len();
|
||||||
let primary = super::display_service::get_primary_2(&all);
|
let primary = super::display_service::get_primary_2(&all);
|
||||||
let current = primary;
|
|
||||||
super::display_service::check_update_displays(&all);
|
super::display_service::check_update_displays(&all);
|
||||||
let mut displays = super::display_service::get_sync_displays();
|
let mut displays = super::display_service::get_sync_displays();
|
||||||
for display in displays.iter_mut() {
|
for display in displays.iter_mut() {
|
||||||
@@ -152,35 +189,25 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
rects.push((d.origin(), d.width(), d.height()));
|
rects.push((d.origin(), d.width(), d.height()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let display = all.remove(current);
|
log::debug!("#displays={}, primary={}, rects: {:?}, cpus={}/{}", num, primary, rects, num_cpus::get_physical(), num_cpus::get());
|
||||||
let (origin, width, height) = (display.origin(), display.width(), display.height());
|
|
||||||
log::debug!(
|
|
||||||
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
|
|
||||||
num,
|
|
||||||
current,
|
|
||||||
&origin,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
num_cpus::get_physical(),
|
|
||||||
num_cpus::get(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if use_uinput {
|
if use_uinput {
|
||||||
let (max_width, max_height) = match get_max_desktop_resolution() {
|
let (max_width, max_height) = match get_max_desktop_resolution() {
|
||||||
Some(result) if !result.is_empty() => {
|
Some(result) if !result.is_empty() => {
|
||||||
let resolution: Vec<&str> = result.split(" ").collect();
|
let resolution: Vec<&str> = result.split(" ").collect();
|
||||||
let w: i32 = resolution[0].parse().unwrap_or(origin.0 + width as i32);
|
if let (Ok(w), Ok(h)) = (
|
||||||
let h: i32 = resolution[2]
|
resolution[0].parse::<i32>(),
|
||||||
.trim_end_matches(",")
|
resolution.get(2)
|
||||||
.parse()
|
.unwrap_or(&"0")
|
||||||
.unwrap_or(origin.1 + height as i32);
|
.trim_end_matches(",")
|
||||||
if w < origin.0 + width as i32 || h < origin.1 + height as i32 {
|
.parse::<i32>()
|
||||||
(origin.0 + width as i32, origin.1 + height as i32)
|
) {
|
||||||
} else {
|
|
||||||
(w, h)
|
(w, h)
|
||||||
|
} else {
|
||||||
|
calculate_max_resolution_from_displays(&all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (origin.0 + width as i32, origin.1 + height as i32),
|
_ => calculate_max_resolution_from_displays(&all),
|
||||||
};
|
};
|
||||||
|
|
||||||
minx = 0;
|
minx = 0;
|
||||||
@@ -189,19 +216,24 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
maxy = max_height;
|
maxy = max_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
let capturer = Box::into_raw(Box::new(
|
// Create individual CapDisplayInfo for each display with its own capturer
|
||||||
Capturer::new(display).with_context(|| "Failed to create capturer")?,
|
for (idx, display) in all.into_iter().enumerate() {
|
||||||
));
|
let capturer = Box::into_raw(Box::new(
|
||||||
let capturer = CapturerPtr(capturer);
|
Capturer::new(display).with_context(|| format!("Failed to create capturer for display {}", idx))?,
|
||||||
let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo {
|
));
|
||||||
rects,
|
let capturer = CapturerPtr(capturer);
|
||||||
displays,
|
|
||||||
num,
|
let cap_display_info = Box::into_raw(Box::new(CapDisplayInfo {
|
||||||
primary,
|
rects: rects.clone(),
|
||||||
current,
|
displays: displays.clone(),
|
||||||
capturer,
|
num,
|
||||||
}));
|
primary,
|
||||||
*lock = cap_display_info as _;
|
current: idx,
|
||||||
|
capturer,
|
||||||
|
}));
|
||||||
|
|
||||||
|
lock.insert(idx, cap_display_info as u64);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,9 +255,9 @@ pub(super) async fn check_init() -> ResultType<()> {
|
|||||||
|
|
||||||
pub(super) async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
pub(super) async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
||||||
check_init().await?;
|
check_init().await?;
|
||||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
let cap_map = CAP_DISPLAY_INFO.read().unwrap();
|
||||||
if addr != 0 {
|
if let Some(addr) = cap_map.values().next() {
|
||||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
let cap_display_info: *const CapDisplayInfo = *addr as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
let cap_display_info = &*cap_display_info;
|
let cap_display_info = &*cap_display_info;
|
||||||
Ok(cap_display_info.displays.clone())
|
Ok(cap_display_info.displays.clone())
|
||||||
@@ -236,9 +268,9 @@ pub(super) async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_primary() -> ResultType<usize> {
|
pub(super) fn get_primary() -> ResultType<usize> {
|
||||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
let cap_map = CAP_DISPLAY_INFO.read().unwrap();
|
||||||
if addr != 0 {
|
if let Some(addr) = cap_map.values().next() {
|
||||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
let cap_display_info: *const CapDisplayInfo = *addr as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
let cap_display_info = &*cap_display_info;
|
let cap_display_info = &*cap_display_info;
|
||||||
Ok(cap_display_info.primary)
|
Ok(cap_display_info.primary)
|
||||||
@@ -253,26 +285,29 @@ pub fn clear() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut write_lock = CAP_DISPLAY_INFO.write().unwrap();
|
let mut write_lock = CAP_DISPLAY_INFO.write().unwrap();
|
||||||
if *write_lock != 0 {
|
for (_, addr) in write_lock.iter() {
|
||||||
let cap_display_info: *mut CapDisplayInfo = *write_lock as _;
|
let cap_display_info: *mut CapDisplayInfo = *addr as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
let _box_capturer = Box::from_raw((*cap_display_info).capturer.0);
|
let _box_capturer = Box::from_raw((*cap_display_info).capturer.0);
|
||||||
let _box_cap_display_info = Box::from_raw(cap_display_info);
|
let _box_cap_display_info = Box::from_raw(cap_display_info);
|
||||||
*write_lock = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
write_lock.clear();
|
||||||
|
|
||||||
|
// Reset PipeWire initialization flag to allow recreation on next init
|
||||||
|
*PIPEWIRE_INITIALIZED.write().unwrap() = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
|
pub(super) fn get_capturer_for_display(display_idx: usize) -> ResultType<super::video_service::CapturerInfo> {
|
||||||
if is_x11() {
|
if is_x11() {
|
||||||
bail!("Do not call this function if not wayland");
|
bail!("Do not call this function if not wayland");
|
||||||
}
|
}
|
||||||
let addr = *CAP_DISPLAY_INFO.read().unwrap();
|
let cap_map = CAP_DISPLAY_INFO.read().unwrap();
|
||||||
if addr != 0 {
|
if let Some(addr) = cap_map.get(&display_idx) {
|
||||||
let cap_display_info: *const CapDisplayInfo = addr as _;
|
let cap_display_info: *const CapDisplayInfo = *addr as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
let cap_display_info = &*cap_display_info;
|
let cap_display_info = &*cap_display_info;
|
||||||
let rect = cap_display_info.rects[cap_display_info.current];
|
let rect = cap_display_info.rects[cap_display_info.current];
|
||||||
Ok(super::video_service::CapturerInfo {
|
Ok(super::video_service::CapturerInfo {
|
||||||
origin: rect.0,
|
origin: rect.0,
|
||||||
width: rect.1,
|
width: rect.1,
|
||||||
@@ -285,7 +320,7 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Failed to get capturer display info");
|
bail!("Failed to get capturer display info for display {}", display_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user