mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 08:21:02 +03:00
Preserve DRM cursor density during display cache contention
This commit is contained in:
@@ -248,15 +248,21 @@ pub fn wayland_failure_stamped() -> bool {
|
|||||||
LAST_FAILED_LOOKUP.lock().unwrap().is_some()
|
LAST_FAILED_LOOKUP.lock().unwrap().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cursor polls must neither start discovery nor wait for an in-progress display refresh.
|
|
||||||
#[cfg(feature = "drm")]
|
#[cfg(feature = "drm")]
|
||||||
pub fn get_cached_displays() -> Option<Arc<Displays>> {
|
pub enum CachedDisplays {
|
||||||
|
Busy,
|
||||||
|
Ready(Option<Arc<Displays>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cursor polls must neither wait for discovery nor mistake contention for missing metadata.
|
||||||
|
#[cfg(feature = "drm")]
|
||||||
|
pub fn get_cached_displays() -> CachedDisplays {
|
||||||
match DISPLAYS.try_lock() {
|
match DISPLAYS.try_lock() {
|
||||||
Ok(cache) => cache.clone(),
|
Ok(cache) => CachedDisplays::Ready(cache.clone()),
|
||||||
Err(std::sync::TryLockError::WouldBlock) => None,
|
Err(std::sync::TryLockError::WouldBlock) => CachedDisplays::Busy,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Failed to read cached Wayland displays: {}", err);
|
warn!("Failed to read cached Wayland displays: {}", err);
|
||||||
None
|
CachedDisplays::Ready(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,7 +541,8 @@ mod tests {
|
|||||||
let cache = DISPLAYS.lock().unwrap();
|
let cache = DISPLAYS.lock().unwrap();
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let reader = std::thread::spawn(move || {
|
let reader = std::thread::spawn(move || {
|
||||||
tx.send(get_cached_displays().is_none()).unwrap();
|
tx.send(matches!(get_cached_displays(), CachedDisplays::Busy))
|
||||||
|
.unwrap();
|
||||||
});
|
});
|
||||||
// Discovery owns this lock until it completes or times out.
|
// Discovery owns this lock until it completes or times out.
|
||||||
let result = rx.recv_timeout(Duration::from_secs(1));
|
let result = rx.recv_timeout(Duration::from_secs(1));
|
||||||
@@ -545,7 +552,7 @@ mod tests {
|
|||||||
|
|
||||||
clear_wayland_displays_cache();
|
clear_wayland_displays_cache();
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
assert!(get_cached_displays().is_none());
|
assert!(matches!(get_cached_displays(), CachedDisplays::Ready(None)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::{Arc, Condvar, Mutex};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
#[cfg(feature = "flutter")]
|
||||||
|
mod cursor_metadata;
|
||||||
|
|
||||||
const HANDSHAKE_TIMEOUT_MS: u64 = 3000;
|
const HANDSHAKE_TIMEOUT_MS: u64 = 3000;
|
||||||
const DRM_CONNECT_TIMEOUT_MS: u64 = 1000;
|
const DRM_CONNECT_TIMEOUT_MS: u64 = 1000;
|
||||||
/// The service may hold the list back while it wakes sleeping displays: ~3.6s (DRM_WAKE_*).
|
/// The service may hold the list back while it wakes sleeping displays: ~3.6s (DRM_WAKE_*).
|
||||||
@@ -978,30 +981,40 @@ pub fn drm_cursor() -> Option<DrmCursorData> {
|
|||||||
pub fn drm_cursor_snapshot<T>(
|
pub fn drm_cursor_snapshot<T>(
|
||||||
f: impl Fn(&DrmCursorData) -> T,
|
f: impl Fn(&DrmCursorData) -> T,
|
||||||
) -> Option<(T, Option<base::platform::linux::WaylandDisplayInfo>)> {
|
) -> Option<(T, Option<base::platform::linux::WaylandDisplayInfo>)> {
|
||||||
|
use scrap::wayland::display::{get_cached_displays, wayland_snapshot_generation};
|
||||||
|
|
||||||
// Keep cursor identity and output together, then release the map before DRM_STATE.
|
// Keep cursor identity and output together, then release the map before DRM_STATE.
|
||||||
let (value, display, hidden) = {
|
let (value, display, epoch, hidden) = {
|
||||||
let map = DRM_CURSOR.lock().unwrap();
|
let map = DRM_CURSOR.lock().unwrap();
|
||||||
let (display, (_, cursor)) = map
|
let (display, (epoch, cursor)) = map
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, (_, cursor))| cursor.id != scrap::drm_reader::HIDDEN_CURSOR_ID)
|
.find(|(_, (_, cursor))| cursor.id != scrap::drm_reader::HIDDEN_CURSOR_ID)
|
||||||
.or_else(|| map.iter().next())?;
|
.or_else(|| map.iter().next())?;
|
||||||
(
|
(
|
||||||
f(cursor),
|
f(cursor),
|
||||||
*display,
|
*display,
|
||||||
|
*epoch,
|
||||||
cursor.id == scrap::drm_reader::HIDDEN_CURSOR_ID,
|
cursor.id == scrap::drm_reader::HIDDEN_CURSOR_ID,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let monitor = if hidden {
|
let monitor = if hidden {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// Display discovery runs outside the cursor service; missing metadata means unknown DPI.
|
cursor_metadata::monitor(
|
||||||
scrap::wayland::display::get_cached_displays().and_then(|wayland| {
|
cursor_metadata::Context {
|
||||||
cursor_monitor(
|
display,
|
||||||
display.max(0) as usize,
|
epoch,
|
||||||
&DRM_STATE.lock().unwrap(),
|
layout_generation: wayland_snapshot_generation(),
|
||||||
&wayland.displays,
|
},
|
||||||
)
|
get_cached_displays(),
|
||||||
})
|
|wayland| {
|
||||||
|
cursor_monitor(
|
||||||
|
display.max(0) as usize,
|
||||||
|
&DRM_STATE.lock().unwrap(),
|
||||||
|
&wayland.displays,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
Some((value, monitor))
|
Some((value, monitor))
|
||||||
}
|
}
|
||||||
|
|||||||
123
src/server/drm_capturer/cursor_metadata.rs
Normal file
123
src/server/drm_capturer/cursor_metadata.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use base::platform::linux::WaylandDisplayInfo;
|
||||||
|
use scrap::wayland::display::{CachedDisplays, Displays};
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(super) struct Context {
|
||||||
|
pub display: i32,
|
||||||
|
pub epoch: u64,
|
||||||
|
pub layout_generation: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static LAST_MONITOR: Cell<Option<(Context, WaylandDisplayInfo)>> = const { Cell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn monitor(
|
||||||
|
context: Context,
|
||||||
|
snapshot: CachedDisplays,
|
||||||
|
resolve: impl FnOnce(&Displays) -> Option<WaylandDisplayInfo>,
|
||||||
|
) -> Option<WaylandDisplayInfo> {
|
||||||
|
// ID polling and bitmap retrieval run on the same cursor-service thread.
|
||||||
|
// Only contention may reuse metadata, and only for this output, stream and layout.
|
||||||
|
LAST_MONITOR.with(|last| match snapshot {
|
||||||
|
CachedDisplays::Busy => {
|
||||||
|
let cached = last.take();
|
||||||
|
let monitor = cached
|
||||||
|
.as_ref()
|
||||||
|
.filter(|(key, _)| *key == context)
|
||||||
|
.map(|(_, monitor)| monitor.clone());
|
||||||
|
last.set(cached);
|
||||||
|
monitor
|
||||||
|
}
|
||||||
|
CachedDisplays::Ready(displays) => {
|
||||||
|
let monitor = displays.as_deref().and_then(resolve);
|
||||||
|
// An accessible cache with no matching metadata invalidates the old density.
|
||||||
|
last.set(monitor.clone().map(|monitor| (context, monitor)));
|
||||||
|
monitor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
const CONTEXT: Context = Context {
|
||||||
|
display: 0,
|
||||||
|
epoch: 7,
|
||||||
|
layout_generation: 11,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn displays(logical_width: i32) -> CachedDisplays {
|
||||||
|
CachedDisplays::Ready(Some(Arc::new(Displays {
|
||||||
|
primary: 0,
|
||||||
|
displays: vec![WaylandDisplayInfo {
|
||||||
|
name: "DP-1".into(),
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1280,
|
||||||
|
height: 800,
|
||||||
|
logical_size: Some((logical_width, logical_width * 800 / 1280)),
|
||||||
|
refresh_rate: 60000,
|
||||||
|
transform: 0,
|
||||||
|
}],
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(context: Context, snapshot: CachedDisplays) -> Option<WaylandDisplayInfo> {
|
||||||
|
monitor(context, snapshot, |displays| {
|
||||||
|
displays.displays.first().cloned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn busy_cache_preserves_metadata_until_a_completed_read() {
|
||||||
|
for width in [640, 1280] {
|
||||||
|
let expected = read(CONTEXT, displays(width)).unwrap().logical_size;
|
||||||
|
for _ in 0..5 {
|
||||||
|
let retained = monitor(CONTEXT, CachedDisplays::Busy, |_| {
|
||||||
|
panic!("a busy cursor lookup must not resolve displays")
|
||||||
|
});
|
||||||
|
assert_eq!(retained.unwrap().logical_size, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn busy_cache_does_not_borrow_another_output_stream_or_layout() {
|
||||||
|
read(CONTEXT, displays(640));
|
||||||
|
for changed in [
|
||||||
|
Context {
|
||||||
|
display: 1,
|
||||||
|
..CONTEXT
|
||||||
|
},
|
||||||
|
Context {
|
||||||
|
epoch: 8,
|
||||||
|
..CONTEXT
|
||||||
|
},
|
||||||
|
Context {
|
||||||
|
layout_generation: 12,
|
||||||
|
..CONTEXT
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
assert!(read(changed, CachedDisplays::Busy).is_none());
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
read(CONTEXT, CachedDisplays::Busy).unwrap().logical_size,
|
||||||
|
Some((640, 400))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_or_unmatched_metadata_invalidates_retained_density() {
|
||||||
|
read(CONTEXT, displays(640));
|
||||||
|
assert!(read(CONTEXT, CachedDisplays::Ready(None)).is_none());
|
||||||
|
assert!(read(CONTEXT, CachedDisplays::Busy).is_none());
|
||||||
|
|
||||||
|
read(CONTEXT, displays(640));
|
||||||
|
assert!(monitor(CONTEXT, displays(640), |_| None).is_none());
|
||||||
|
assert!(read(CONTEXT, CachedDisplays::Busy).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user