mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 13:31:03 +03:00
drm: advertise the displays of every GPU, not just the first card
A drmtap context is bound to a single DRM device, so the service enumerated one auto-detected card and advertised only its monitors. On a multi-GPU host every display driven by another card was invisible to the client, and its card-local CRTC id could not have been opened through the wrong device anyway. The service now enumerates every card (drmtap_list_devices, libdrmtap 0.4.15), opens one reader per device, and merges their displays into the one list, each tagged with its own card node and render node. DrmStart resolves the chosen index to that display's device + CRTC and the worker reopens the right card; the converter already binds the display's render node. Both new fields are serde(default) and empty means the single auto-detected device, so a pre-0.4.15 .so and a mismatched-build peer keep the previous behaviour exactly. Enumeration replaces the single-reader open in the pre-warm, the udev hotplug refresh, and the per-connection handshake, so a hotplug on any card is picked up and an all-monitors-off state now correctly publishes an empty list. The per-connection cache refresh re-enumerates all cards rather than only the connection's device, so serving one display never drops the others from the next handshake. Verified on a Jetson Orin (its two DRM devices, only card2 driving a display): list_devices reports card2/renderD129 with one display, enumeration produces exactly that display tagged to card2, and card1 (no active CRTC) is skipped - no phantom, no regression on the single-display case.
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
// before opening. The DRM_DEVICE env is intentionally NOT consulted here.
|
||||
|
||||
use super::drmtap_dl::{
|
||||
self, drmtap_config, drmtap_ctx, drmtap_cursor_info, drmtap_display, drmtap_dmabuf_desc,
|
||||
drmtap_frame_info, DrmtapLib,
|
||||
self, drmtap_config, drmtap_ctx, drmtap_cursor_info, drmtap_device, drmtap_display,
|
||||
drmtap_dmabuf_desc, drmtap_frame_info, DrmtapLib,
|
||||
};
|
||||
use hbb_common::log;
|
||||
use std::ffi::CString;
|
||||
@@ -48,6 +48,62 @@ pub struct DisplaySnapshot {
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
/// One capturable DRM device, from `list_devices`. On a multi-GPU host each card
|
||||
/// is a separate device; the caller opens one `DrmReader` per `path` to reach the
|
||||
/// displays every card drives.
|
||||
pub struct DrmDevice {
|
||||
/// KMS card node, e.g. `/dev/dri/card1`.
|
||||
pub path: String,
|
||||
/// Render node of this device, or empty if it has none.
|
||||
pub render_node: String,
|
||||
/// CRTCs actively scanning out on this device.
|
||||
pub display_count: u32,
|
||||
}
|
||||
|
||||
/// Copy a fixed C char array into a `String`, stopping at the first NUL WITHIN
|
||||
/// the array so a field libdrmtap failed to terminate cannot read past it (lossy
|
||||
/// on non-UTF-8, which a /dev path never is).
|
||||
fn cstr_field(buf: &[std::os::raw::c_char]) -> String {
|
||||
// c_char and u8 share size/alignment; reinterpret the exact-length slice.
|
||||
let bytes: &[u8] =
|
||||
unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };
|
||||
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
|
||||
String::from_utf8_lossy(&bytes[..end]).into_owned()
|
||||
}
|
||||
|
||||
/// Enumerate every DRM device with KMS resources (libdrmtap >= 0.4.15). Returns
|
||||
/// `None` when libdrmtap is unavailable or the `.so` predates the symbol, so the
|
||||
/// caller can fall back to a single auto-detected device. An empty `Vec` means
|
||||
/// the library is new enough but found nothing.
|
||||
pub fn list_devices() -> Option<Vec<DrmDevice>> {
|
||||
let lib = drmtap_dl::get()?;
|
||||
let f = lib.list_devices?;
|
||||
// A handful of GPUs at most; the array is small and stack-friendly.
|
||||
const MAX: usize = 16;
|
||||
let mut raw: [drmtap_device; MAX] = unsafe { std::mem::zeroed() };
|
||||
// SAFETY: `raw` is MAX valid, zeroed drmtap_device slots; the call fills up to
|
||||
// MAX and returns the count (negative on error).
|
||||
let n = unsafe { f(raw.as_mut_ptr(), MAX as std::os::raw::c_int) };
|
||||
if n < 0 {
|
||||
// An enumeration FAILURE, not "found nothing" -> None, so the caller keeps
|
||||
// its single-device auto-detect fallback rather than treating this as an
|
||||
// authoritative empty device list.
|
||||
log::warn!("drmtap_list_devices failed ({n}); using single-device auto-detect");
|
||||
return None;
|
||||
}
|
||||
let n = (n as usize).min(MAX);
|
||||
Some(
|
||||
raw[..n]
|
||||
.iter()
|
||||
.map(|d| DrmDevice {
|
||||
path: cstr_field(&d.path),
|
||||
render_node: cstr_field(&d.render_node),
|
||||
display_count: d.display_count,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true only if `path` canonicalizes to a node directly under /dev/dri/.
|
||||
/// This is the realpath gate the libdrmtap helper applied but the in-process
|
||||
/// (direct) path does not, so the service must apply it itself.
|
||||
|
||||
@@ -55,6 +55,19 @@ pub struct drmtap_display {
|
||||
pub active: c_int,
|
||||
}
|
||||
|
||||
// A capturable DRM device from `drmtap_list_devices` (libdrmtap >= 0.4.15).
|
||||
// Mirrors `drmtap_device` in include/drmtap.h EXACTLY (field order + widths);
|
||||
// its layout is FROZEN there for the same reason as drmtap_dmabuf_desc (written
|
||||
// into caller-owned storage). `path`/`render_node`/`driver` are NUL-terminated.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct drmtap_device {
|
||||
pub path: [c_char; 64],
|
||||
pub render_node: [c_char; 64],
|
||||
pub driver: [c_char; 32],
|
||||
pub display_count: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct drmtap_frame_info {
|
||||
pub data: *mut c_void,
|
||||
@@ -127,6 +140,10 @@ type FnVersion = unsafe extern "C" fn() -> c_int;
|
||||
type FnOpen = unsafe extern "C" fn(*const drmtap_config) -> *mut drmtap_ctx;
|
||||
type FnClose = unsafe extern "C" fn(*mut drmtap_ctx);
|
||||
type FnListDisplays = unsafe extern "C" fn(*mut drmtap_ctx, *mut drmtap_display, c_int) -> c_int;
|
||||
// libdrmtap >= 0.4.15. Enumerates every DRM device with KMS resources, so a
|
||||
// multi-GPU host can open one context per device instead of only advertising the
|
||||
// first card's displays. Bound as an Option; `None` on an older .so.
|
||||
type FnListDevices = unsafe extern "C" fn(*mut drmtap_device, c_int) -> c_int;
|
||||
type FnGrabMapped = unsafe extern "C" fn(*mut drmtap_ctx, *mut drmtap_frame_info) -> c_int;
|
||||
type FnFrameRelease = unsafe extern "C" fn(*mut drmtap_ctx, *mut drmtap_frame_info);
|
||||
type FnGetCursor = unsafe extern "C" fn(*mut drmtap_ctx, *mut drmtap_cursor_info) -> c_int;
|
||||
@@ -152,6 +169,9 @@ pub struct DrmtapLib {
|
||||
pub open: FnOpen,
|
||||
pub close: FnClose,
|
||||
pub list_displays: FnListDisplays,
|
||||
// libdrmtap >= 0.4.15; `None` on an older .so (the service then enumerates a
|
||||
// single auto-detected device, exactly as before).
|
||||
pub list_devices: Option<FnListDevices>,
|
||||
pub grab_mapped: FnGrabMapped,
|
||||
pub frame_release: FnFrameRelease,
|
||||
pub get_cursor: FnGetCursor,
|
||||
@@ -220,6 +240,8 @@ impl DrmtapLib {
|
||||
let open: FnOpen = *lib.get(b"drmtap_open").ok()?;
|
||||
let close: FnClose = *lib.get(b"drmtap_close").ok()?;
|
||||
let list_displays: FnListDisplays = *lib.get(b"drmtap_list_displays").ok()?;
|
||||
let list_devices: Option<FnListDevices> =
|
||||
lib.get(b"drmtap_list_devices").ok().map(|s| *s);
|
||||
let grab_mapped: FnGrabMapped = *lib.get(b"drmtap_grab_mapped").ok()?;
|
||||
let frame_release: FnFrameRelease = *lib.get(b"drmtap_frame_release").ok()?;
|
||||
let get_cursor: FnGetCursor = *lib.get(b"drmtap_get_cursor").ok()?;
|
||||
@@ -239,6 +261,7 @@ impl DrmtapLib {
|
||||
open,
|
||||
close,
|
||||
list_displays,
|
||||
list_devices,
|
||||
grab_mapped,
|
||||
frame_release,
|
||||
get_cursor,
|
||||
|
||||
Reference in New Issue
Block a user