Report Linux cursor density independently of the host UI

This commit is contained in:
fufesou
2026-09-13 17:48:03 +08:00
parent 9a1c1a1a36
commit 110ddb9a6c
3 changed files with 9 additions and 55 deletions

View File

@@ -25,7 +25,7 @@ inline = []
use_samplerate = ["samplerate"]
use_rubato = ["rubato"]
use_dasp = ["dasp"]
flutter = ["flutter_rust_bridge", "dep:x11rb"]
flutter = ["flutter_rust_bridge"]
default = ["use_dasp"]
hwcodec = ["scrap/hwcodec"]
vram = ["scrap/vram"]
@@ -40,7 +40,6 @@ drm-wake = ["drm"]
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
unix-file-copy-paste = [
"dep:x11-clipboard",
"dep:x11rb",
"dep:percent-encoding",
"dep:once_cell",
"clipboard/unix-file-copy-paste",
@@ -191,7 +190,7 @@ evdev = { git="https://github.com/rustdesk-org/evdev" }
dbus = "0.9"
dbus-crossroads = "0.5"
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
x11rb = {version = "0.12", features = ["all-extensions"]}
percent-encoding = {version = "2.3", optional = true}
once_cell = {version = "1.18", optional = true}
nix = { version = "0.29", features = ["term", "process"]}

View File

@@ -35,11 +35,7 @@ use std::{
use terminfo::{capability as cap, Database};
use wallpaper;
// Scope density lookup to Flutter host builds to preserve legacy capture
// behavior. These builds include density in cursor IDs to refresh on DPI
// changes; non-Flutter builds retain original IDs and unknown density.
// This gate describes the host binary, not the connected controller's UI.
#[cfg(feature = "flutter")]
// Cursor density is capture metadata, independent of either endpoint's UI.
mod cursor;
pub const PA_SAMPLE_RATE: u32 = 48000;
@@ -577,11 +573,8 @@ pub fn get_cursor() -> ResultType<Option<u64>> {
// polled there is a live session, which is the case the latch reads correctly.
#[cfg(feature = "drm")]
if !is_x11() {
#[cfg(feature = "flutter")]
let cursor = cursor::drm_snapshot(|c| c.id)?
.map(|(id, scale)| cursor::cache_id(id, scale));
#[cfg(not(feature = "flutter"))]
let cursor = crate::server::drm_capturer::drm_cursor_id();
if let Some(id) = cursor {
// In a mixed DRM + PipeWire session the DRM streams only cover the DRM-backed displays;
// when the pointer sits on a PipeWire-served display every DRM stream reports the hidden
@@ -610,7 +603,6 @@ pub fn get_cursor() -> ResultType<Option<u64>> {
}
}
});
#[cfg(feature = "flutter")]
let res = res.map(cursor::x11_cursor_id);
Ok(res)
}
@@ -624,13 +616,8 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
// agree anyway, since a caller that took the DRM branch there has to take it here.
#[cfg(feature = "drm")]
if !is_x11() {
#[cfg(feature = "flutter")]
let cursor = cursor::drm_snapshot(Clone::clone)?;
#[cfg(not(feature = "flutter"))]
let cursor = crate::server::drm_capturer::drm_cursor();
if let Some(c) = cursor {
#[cfg(feature = "flutter")]
let (c, scale) = c;
if let Some((c, scale)) = cursor {
// See get_cursor(): a hidden DRM sentinel is authoritative only in a pure-DRM session. In
// a mixed DRM + PipeWire session fall through so the PipeWire display's cursor is served
// by the normal path instead of being hidden everywhere.
@@ -638,27 +625,19 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
|| !crate::server::display_service::has_non_drm_backed_display()
{
let mut cd: CursorData = Default::default();
cd.id = c.id;
cd.id = cursor::cache_id(c.id, scale);
cd.scale = scale;
cd.width = c.width;
cd.height = c.height;
cd.hotx = c.hotx;
cd.hoty = c.hoty;
cd.colors = c.colors.into();
#[cfg(feature = "flutter")]
{
cd.id = cursor::cache_id(cd.id, scale);
cd.scale = scale;
}
return Ok(cd);
}
}
}
#[cfg(feature = "flutter")]
let scale = cursor::x11_cursor_scale();
#[cfg(feature = "flutter")]
let matches = |id| cursor::cache_id(id, scale) == hcursor;
#[cfg(not(feature = "flutter"))]
let matches = |id| id == hcursor;
let mut res = None;
DISPLAY.with(|conn| {
if let Ok(ref mut d) = conn.try_borrow_mut() {
@@ -673,10 +652,7 @@ pub fn get_cursor_data(hcursor: u64) -> ResultType<CursorData> {
cd.height = (*img).height as _;
// to-do: how about if it is 0
cd.id = hcursor;
#[cfg(feature = "flutter")]
{
cd.scale = scale;
}
cd.scale = scale;
let pixels =
std::slice::from_raw_parts((*img).pixels, (cd.width * cd.height) as _);
// cd.colors.resize(pixels.len() * 4, 0);

View File

@@ -14,7 +14,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
#[cfg(feature = "flutter")]
mod cursor_metadata;
const HANDSHAKE_TIMEOUT_MS: u64 = 3000;
@@ -958,26 +957,8 @@ fn fold_cursor_id(id: u64, t: i32) -> u64 {
}
}
fn with_drm_cursor<T>(f: impl Fn(&DrmCursorData) -> T) -> Option<T> {
let map = DRM_CURSOR.lock().unwrap();
map.values()
.map(|(_, c)| c)
.find(|c| c.id != scrap::drm_reader::HIDDEN_CURSOR_ID)
.or_else(|| map.values().map(|(_, c)| c).next())
.map(f)
}
pub fn drm_cursor_id() -> Option<u64> {
with_drm_cursor(|c| c.id)
}
/// Snapshot of the DRM hardware cursor, or None. The pixels are premultiplied ARGB and are passed
/// through as-is, like the XFixes path, so the client sees one cursor format from either backend.
pub fn drm_cursor() -> Option<DrmCursorData> {
with_drm_cursor(|c| c.clone())
}
#[cfg(feature = "flutter")]
/// Snapshot of the DRM hardware cursor and optional display metadata. Pixels retain the
/// premultiplied format used by the XFixes path.
pub fn drm_cursor_snapshot<T>(
f: impl Fn(&DrmCursorData) -> T,
) -> Option<(T, Option<base::platform::linux::WaylandDisplayInfo>)> {
@@ -1019,7 +1000,6 @@ pub fn drm_cursor_snapshot<T>(
Some((value, monitor))
}
#[cfg(feature = "flutter")]
fn cursor_monitor(
display: usize,
state: &ProbeState,
@@ -2332,7 +2312,6 @@ mod drm_capturer_tests {
assert!(m2[0].is_none() && m2[1].is_none());
}
#[cfg(feature = "flutter")]
#[test]
fn cursor_monitor_reserves_other_displays_before_guessing_density() {
let state = ProbeState::Available(