From 06ab987e326163652a733e62bde97efe6304d989 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 21 May 2025 16:53:02 +0800 Subject: [PATCH] fix: macos, hidpi, resolutions (#11825) Signed-off-by: fufesou --- flutter/lib/models/model.dart | 3 +- src/platform/macos.mm | 56 +++++++++++++++++++++++++++++------ src/platform/macos.rs | 35 +++++++++++++++++----- src/server/connection.rs | 12 ++++++-- src/server/display_service.rs | 8 ++--- 5 files changed, 90 insertions(+), 24 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index cc56249d1..d8d33048b 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -3212,7 +3212,8 @@ class Display { originalWidth == kVirtualDisplayResolutionValue && originalHeight == kVirtualDisplayResolutionValue; bool get isOriginalResolution => - width == originalWidth && height == originalHeight; + width == (originalWidth * scale).round() && + height == (originalHeight * scale).round(); } class Resolution { diff --git a/src/platform/macos.mm b/src/platform/macos.mm index 0f963b97f..27caf32a8 100644 --- a/src/platform/macos.mm +++ b/src/platform/macos.mm @@ -153,8 +153,28 @@ size_t bitDepth(CGDisplayModeRef mode) { 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) { - CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + CFArrayRef allModes = getAllModes(display); if (allModes == NULL) { return false; } @@ -163,12 +183,12 @@ extern "C" bool MacGetModeNum(CGDirectDisplayID display, uint32_t *numModes) { 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); if (currentMode == NULL) { return false; } - CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + CFArrayRef allModes = getAllModes(display); if (allModes == NULL) { CGDisplayModeRelease(currentMode); return false; @@ -181,6 +201,7 @@ extern "C" bool MacGetModes(CGDirectDisplayID display, uint32_t *widths, uint32_ bitDepth(currentMode) == bitDepth(mode)) { widths[realNum] = (uint32_t)CGDisplayModeGetWidth(mode); heights[realNum] = (uint32_t)CGDisplayModeGetHeight(mode); + hidpis[realNum] = isHiDPIMode(mode); realNum++; } } @@ -201,7 +222,6 @@ extern "C" bool MacGetMode(CGDirectDisplayID display, uint32_t *width, uint32_t return true; } - static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { CGError rc; CGDisplayConfigRef config; @@ -220,30 +240,48 @@ static bool setDisplayToMode(CGDirectDisplayID display, CGDisplayModeRef mode) { 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; CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display); if (currentMode == NULL) { return ret; } - CFArrayRef allModes = CGDisplayCopyAllDisplayModes(display, NULL); + CFArrayRef allModes = getAllModes(display); + if (allModes == NULL) { CGDisplayModeRelease(currentMode); return ret; } int numModes = CFArrayGetCount(allModes); + CGDisplayModeRef preferredHiDPIMode = NULL; + CGDisplayModeRef fallbackMode = NULL; for (int i = 0; i < numModes; i++) { CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); if (width == CGDisplayModeGetWidth(mode) && height == CGDisplayModeGetHeight(mode) && CGDisplayModeGetRefreshRate(currentMode) == CGDisplayModeGetRefreshRate(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); CFRelease(allModes); return ret; -} \ No newline at end of file +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 214ff03b3..4e6b35dac 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -54,12 +54,13 @@ extern "C" { display: u32, widths: *mut u32, heights: *mut u32, + hidpis: *mut BOOL, max: u32, numModes: *mut u32, ) -> BOOL; fn majorVersion() -> u32; 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 { @@ -895,27 +896,45 @@ pub fn resolutions(name: &str) -> Vec { let mut num = 0; unsafe { 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; if YES == MacGetModes( display, widths.as_mut_ptr(), heights.as_mut_ptr(), + _hidpis.as_mut_ptr(), num, &mut real_num, ) { if real_num <= num { - for i in 0..real_num { - let resolution = Resolution { + v = (0..real_num) + .map(|i| Resolution { width: widths[i as usize] as _, height: heights[i as usize] as _, ..Default::default() - }; - if !v.contains(&resolution) { - v.push(resolution); + }) + .collect::>(); + // 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 { pub fn change_resolution_directly(name: &str, width: usize, height: usize) -> ResultType<()> { let display = name.parse::().map_err(|e| anyhow!(e))?; unsafe { - if NO == MacSetMode(display, width as _, height as _) { + if NO == MacSetMode(display, width as _, height as _, true) { bail!("MacSetMode failed"); } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 120d83149..3176fe686 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -3093,10 +3093,18 @@ impl Connection { if virtual_display_manager::amyuni_idd::is_my_display(&name) { 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 { display_service::set_last_changed_resolution( &name, - (display.width() as _, display.height() as _), + original, (r.width, r.height), ); } @@ -4424,7 +4432,7 @@ mod raii { *WALLPAPER_REMOVER.lock().unwrap() = None; } #[cfg(not(any(target_os = "android", target_os = "ios")))] - display_service::reset_resolutions(); + display_service::restore_resolutions(); #[cfg(windows)] let _ = virtual_display_manager::reset_all(); #[cfg(target_os = "linux")] diff --git a/src/server/display_service.rs b/src/server/display_service.rs index 33f665018..6a52cbbea 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -133,12 +133,13 @@ pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), cha #[inline] #[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() { 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 _) { log::error!( - "Failed to reset resolution of display '{}' to ({},{}): {}", + "Failed to restore resolution of display '{}' to ({},{}): {}", name, w, 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(); } @@ -404,7 +405,6 @@ fn no_displays(displays: &Vec) -> bool { } } - #[inline] #[cfg(not(windows))] pub fn try_get_displays() -> ResultType> {