fix: macos, hidpi, resolutions (#11825)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-05-21 16:53:02 +08:00
committed by GitHub
parent b4a30cac73
commit 06ab987e32
5 changed files with 90 additions and 24 deletions

View File

@@ -3212,7 +3212,8 @@ class Display {
originalWidth == kVirtualDisplayResolutionValue && originalWidth == kVirtualDisplayResolutionValue &&
originalHeight == kVirtualDisplayResolutionValue; originalHeight == kVirtualDisplayResolutionValue;
bool get isOriginalResolution => bool get isOriginalResolution =>
width == originalWidth && height == originalHeight; width == (originalWidth * scale).round() &&
height == (originalHeight * scale).round();
} }
class Resolution { class Resolution {

View File

@@ -153,8 +153,28 @@ size_t bitDepth(CGDisplayModeRef mode) {
return depth; return depth;
} }
static bool isHiDPIMode(CGDisplayModeRef mode) {
// Check if the mode is HiDPI by comparing pixel width to width
// If pixel width is greater than width, it's a HiDPI mode
return CGDisplayModeGetPixelWidth(mode) > CGDisplayModeGetWidth(mode);
}
CFArrayRef getAllModes(CGDirectDisplayID display) {
// Create options dictionary to include HiDPI modes
CFMutableDictionaryRef options = CFDictionaryCreateMutable(
kCFAllocatorDefault,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
// Include HiDPI modes
CFDictionarySetValue(options, kCGDisplayShowDuplicateLowResolutionModes, kCFBooleanTrue);
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, options);
CFRelease(options);
return allModes;
}
extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) { extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); CFArrayRef allModes = getAllModes(display);
if (allModes == NULL) { if (allModes == NULL) {
return false; return false;
} }
@@ -163,12 +183,12 @@ extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) {
return true; return true;
} }
extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, uint32_t max, uint32_t *numModes) { extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_t *heights, bool *hidpis, uint32_t max, uint32_t *numModes) {
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display); CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
if (currentMode == NULL) { if (currentMode == NULL) {
return false; return false;
} }
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); CFArrayRef allModes = getAllModes(display);
if (allModes == NULL) { if (allModes == NULL) {
CGDisplayModeRelease(currentMode); CGDisplayModeRelease(currentMode);
return false; return false;
@@ -181,6 +201,7 @@ extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_
bitDepth(currentMode) == bitDepth(mode)) { bitDepth(currentMode) == bitDepth(mode)) {
widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode); widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode);
heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode); heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode);
hidpis[realNum] = isHiDPIMode(mode);
realNum++; realNum++;
} }
} }
@@ -201,7 +222,6 @@ extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t
return true; return true;
} }
static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
CGError rc; CGError rc;
CGDisplayConfigRef config; CGDisplayConfigRef config;
@@ -220,30 +240,48 @@ static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) {
return true; return true;
} }
extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height) extern "C" bool MacSetMode(CGDirectDisplayID display, uint32_t width, uint32_t height, bool tryHiDPI)
{ {
bool ret = false; bool ret = false;
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display); CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
if (currentMode == NULL) { if (currentMode == NULL) {
return ret; return ret;
} }
CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); CFArrayRef allModes = getAllModes(display);
if (allModes == NULL) { if (allModes == NULL) {
CGDisplayModeRelease(currentMode); CGDisplayModeRelease(currentMode);
return ret; return ret;
} }
int numModes = CFArrayGetCount(allModes); int numModes = CFArrayGetCount(allModes);
CGDisplayModeRef preferredHiDPIMode = NULL;
CGDisplayModeRef fallbackMode = NULL;
for (int i = 0; i < numModes; i++) { for (int i = 0; i < numModes; i++) {
CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i);
if (width == CGDisplayModeGetWidth(mode) && if (width == CGDisplayModeGetWidth(mode) &&
height == CGDisplayModeGetHeight(mode) && height == CGDisplayModeGetHeight(mode) &&
CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) && CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(mode) &&
bitDepth(currentMode) == bitDepth(mode)) { bitDepth(currentMode) == bitDepth(mode)) {
ret = setDisplayToMode(display, mode);
break; if (isHiDPIMode(mode)) {
preferredHiDPIMode = mode;
break;
} else {
fallbackMode = mode;
if (!tryHiDPI) {
break;
}
}
} }
} }
if (preferredHiDPIMode) {
ret = setDisplayToMode(display, preferredHiDPIMode);
} else if (fallbackMode) {
ret = setDisplayToMode(display, fallbackMode);
}
CGDisplayModeRelease(currentMode); CGDisplayModeRelease(currentMode);
CFRelease(allModes); CFRelease(allModes);
return ret; return ret;
} }

View File

@@ -54,12 +54,13 @@ extern "C" {
display: u32, display: u32,
widths: *mut u32, widths: *mut u32,
heights: *mut u32, heights: *mut u32,
hidpis: *mut BOOL,
max: u32, max: u32,
numModes: *mut u32, numModes: *mut u32,
) -> BOOL; ) -> BOOL;
fn majorVersion() -> u32; fn majorVersion() -> u32;
fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL; fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL;
fn MacSetMode(display: u32, width: u32, height: u32) -> BOOL; fn MacSetMode(display: u32, width: u32, height: u32, tryHiDPI: bool) -> BOOL;
} }
pub fn major_version() -> u32 { pub fn major_version() -> u32 {
@@ -895,27 +896,45 @@ pub fn resolutions(name: &str) -> Vec<Resolution> {
let mut num = 0; let mut num = 0;
unsafe { unsafe {
if YES == MacGetModeNum(display, &mut num) { if YES == MacGetModeNum(display, &mut num) {
let (mut widths, mut heights) = (vec![0; num as _], vec![0; num as _]); let (mut widths, mut heights, mut _hidpis) =
(vec![0; num as _], vec![0; num as _], vec![NO; num as _]);
let mut real_num = 0; let mut real_num = 0;
if YES if YES
== MacGetModes( == MacGetModes(
display, display,
widths.as_mut_ptr(), widths.as_mut_ptr(),
heights.as_mut_ptr(), heights.as_mut_ptr(),
_hidpis.as_mut_ptr(),
num, num,
&mut real_num, &mut real_num,
) )
{ {
if real_num <= num { if real_num <= num {
for i in 0..real_num { v = (0..real_num)
let resolution = Resolution { .map(|i| Resolution {
width: widths[i as usize] as _, width: widths[i as usize] as _,
height: heights[i as usize] as _, height: heights[i as usize] as _,
..Default::default() ..Default::default()
}; })
if !v.contains(&resolution) { .collect::<Vec<_>>();
v.push(resolution); // Sort by (w, h), desc
v.sort_by(|a, b| {
if a.width == b.width {
b.height.cmp(&a.height)
} else {
b.width.cmp(&a.width)
} }
});
// Remove duplicates
v.dedup_by(|a, b| a.width == b.width && a.height == b.height);
// Filter out the ones that are less than width 800 (800x600) if there are too many.
// We can also do this filtering on the client side, but it is better not to change the client side to reduce the impact.
if v.len() > 15 {
// Most width > 800, so it's ok to remove the small ones.
v.retain(|r| r.width >= 800);
}
if v.len() > 15 {
// Ignore if the length is still too long.
} }
} }
} }
@@ -943,7 +962,7 @@ pub fn current_resolution(name: &str) -> ResultType<Resolution> {
pub fn change_resolution_directly(name: &str, width: usize, height: usize) -> ResultType<()> { pub fn change_resolution_directly(name: &str, width: usize, height: usize) -> ResultType<()> {
let display = name.parse::<u32>().map_err(|e| anyhow!(e))?; let display = name.parse::<u32>().map_err(|e| anyhow!(e))?;
unsafe { unsafe {
if NO == MacSetMode(display, width as _, height as _) { if NO == MacSetMode(display, width as _, height as _, true) {
bail!("MacSetMode failed"); bail!("MacSetMode failed");
} }
} }

View File

@@ -3093,10 +3093,18 @@ impl Connection {
if virtual_display_manager::amyuni_idd::is_my_display(&name) { if virtual_display_manager::amyuni_idd::is_my_display(&name) {
record_changed = false; record_changed = false;
} }
#[cfg(not(target_os = "macos"))]
let scale = 1.0;
#[cfg(target_os = "macos")]
let scale = display.scale();
let original = (
((display.width() as f64) / scale).round() as _,
(display.height() as f64 / scale).round() as _,
);
if record_changed { if record_changed {
display_service::set_last_changed_resolution( display_service::set_last_changed_resolution(
&name, &name,
(display.width() as _, display.height() as _), original,
(r.width, r.height), (r.width, r.height),
); );
} }
@@ -4424,7 +4432,7 @@ mod raii {
*WALLPAPER_REMOVER.lock().unwrap() = None; *WALLPAPER_REMOVER.lock().unwrap() = None;
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
display_service::reset_resolutions(); display_service::restore_resolutions();
#[cfg(windows)] #[cfg(windows)]
let _ = virtual_display_manager::reset_all(); let _ = virtual_display_manager::reset_all();
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@@ -133,12 +133,13 @@ pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), cha
#[inline] #[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn reset_resolutions() { pub fn restore_resolutions() {
for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() { for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() {
let (w, h) = res.original; let (w, h) = res.original;
log::info!("Restore resolution of display '{}' to ({}, {})", name, w, h);
if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) { if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) {
log::error!( log::error!(
"Failed to reset resolution of display '{}' to ({},{}): {}", "Failed to restore resolution of display '{}' to ({},{}): {}",
name, name,
w, w,
h, h,
@@ -146,7 +147,7 @@ pub fn reset_resolutions() {
); );
} }
} }
// Can be cleared because reset resolutions is called when there is no client connected. // Can be cleared because restore resolutions is called when there is no client connected.
CHANGED_RESOLUTIONS.write().unwrap().clear(); CHANGED_RESOLUTIONS.write().unwrap().clear();
} }
@@ -404,7 +405,6 @@ fn no_displays(displays: &Vec<Display>) -> bool {
} }
} }
#[inline] #[inline]
#[cfg(not(windows))] #[cfg(not(windows))]
pub fn try_get_displays() -> ResultType<Vec<Display>> { pub fn try_get_displays() -> ResultType<Vec<Display>> {