fix(flutter): make Adjust Window reliable across desktop platforms (#15853)

* fix(flutter): make Adjust Window reliable across desktop platforms

  - Fix incorrect sizing on scaled displays by calculating the target from the
    rendered canvas scale and platform-specific window coordinate units.
  - Fix adjustments using the wrong monitor by querying the current remote
    window's screen, with the main window as fallback.
  - Fix stale geometry after fullscreen or maximized transitions by refreshing
    metrics before calculating and applying the target frame.
  - Fix fullscreen availability checks on Windows and macOS by predicting the
    restored window borders and caching each macOS window's pre-fullscreen work area.
  - Fix incorrect Linux work areas by handling GNOME Wayland fractional scaling
    and caching compositor/X11 work-area measurements when visibleFrame is wrong.
  - Prevent unsafe adjustments by rejecting invalid, oversized, or implausibly
    small target frames.
  - Avoid failures during window teardown by skipping adjustment when the view,
    screen, or native window frame is unavailable.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): harden Adjust Window handling

  - Use the dynamic Linux resize edge when predicting restored window bounds.
  - Treat GNOME fractional-scaling lookup failures as unknown without repeating
    the lookup for the remote window.
  - Stop adjustment safely when native window calls fail during window teardown.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): correct Linux monitor selection

Update window_size to use monitor height for vertical bounds, preventing incorrect screen selection with vertically stacked displays.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* docs(flutter): simplify Linux screen handling comments

Keep the source rationale concise and move platform measurements and investigation details out of the implementation.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): align Adjust Window resize padding

Use the shared drag-to-resize padding for Linux restored-window predictions so menu validation matches the applied frame dimensions.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): remove Adjust Window screen fallback

Return null when the current window screen is unavailable instead of using the main window's scale factor and work area.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(linux): query Mutter monitor layout mode

  Use DisplayConfig.GetCurrentState instead of inferring scaling from
  experimental features, and handle Ubuntu's UI-scaled logical mode.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): use native maximized state for Wayland cache

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): allow Adjust Window to fill work area

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): avoid racing screen info updates

Signed-off-by: 21pages <sunboeasy@gmail.com>

* refactor(flutter): remove dead Adjust Window web plumbing

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): tolerate near-unity Wayland scale factors

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): harden window screen detection

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(linux): drop deprecated GNOME session detection

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix(flutter): remove GNOME monitor layout mode flutter cache

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages
2026-08-19 15:08:58 +08:00
committed by GitHub
parent 630b531108
commit 5679670506
11 changed files with 483 additions and 99 deletions

View File

@@ -2655,6 +2655,14 @@ pub fn main_get_common(key: String) -> String {
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
#[cfg(not(target_os = "linux"))]
return false.to_string();
} else if key == "gnome-monitor-layout-mode" {
#[cfg(target_os = "linux")]
return match crate::platform::linux::gnome_monitor_layout_mode() {
Some(mode) => mode.as_str().to_owned(),
None => String::new(),
};
#[cfg(not(target_os = "linux"))]
return String::new();
} else if key == "permanent-password-set" {
return ui_interface::is_permanent_password_set().to_string();
} else if key == "local-permanent-password-set" {

View File

@@ -96,6 +96,9 @@ lazy_static::lazy_static! {
};
static ref ACTIVE_USER_LOOKUP_CACHE: std::sync::Mutex<Option<ActiveUserLookupCache>> =
std::sync::Mutex::new(None);
static ref GNOME_MONITOR_LAYOUT_MODE_CACHE: std::sync::Mutex<
Option<(Instant, Option<GnomeMonitorLayoutMode>)>,
> = Default::default();
// https://github.com/rustdesk/rustdesk/issues/13705
// Check if `sudo -E` actually preserves environment.
//
@@ -128,6 +131,141 @@ lazy_static::lazy_static! {
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GnomeMonitorLayoutMode {
Logical,
Physical,
}
impl GnomeMonitorLayoutMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Logical => "logical",
Self::Physical => "physical",
}
}
}
fn gnome_monitor_layout_mode_from_value(value: u32) -> Option<GnomeMonitorLayoutMode> {
// Upstream: https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
// Ubuntu mode 3: https://git.launchpad.net/ubuntu/+source/mutter/tree/debian/patches/x11-Add-support-for-fractional-scaling-using-Randr.patch
match value {
1 | 3 => Some(GnomeMonitorLayoutMode::Logical),
2 => Some(GnomeMonitorLayoutMode::Physical),
_ => None,
}
}
pub fn gnome_monitor_layout_mode() -> Option<GnomeMonitorLayoutMode> {
if let Ok(cache) = GNOME_MONITOR_LAYOUT_MODE_CACHE.lock() {
if let Some((updated_at, result)) = *cache {
if updated_at.elapsed() < Duration::from_secs(10) {
return result;
}
}
}
let result = (|| {
let is_gnome_desktop = std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_default()
.split(':')
.any(|desktop| {
desktop.eq_ignore_ascii_case("gnome") || desktop.eq_ignore_ascii_case("unity")
});
let is_gnome_session = std::env::var("DESKTOP_SESSION")
.unwrap_or_default()
.to_ascii_lowercase();
if !is_gnome_desktop && !is_gnome_session.contains("gnome") {
return None;
}
use dbus::{arg::PropMap, blocking::BlockingSender};
let conn = match dbus::blocking::Connection::new_session() {
Ok(conn) => conn,
Err(err) => {
log::warn!("Failed to connect to the session bus for GNOME monitor layout: {err}");
return None;
}
};
let message = match dbus::Message::new_method_call(
"org.gnome.Mutter.DisplayConfig",
"/org/gnome/Mutter/DisplayConfig",
"org.gnome.Mutter.DisplayConfig",
"GetCurrentState",
) {
Ok(message) => message,
Err(err) => {
log::warn!("Failed to create GNOME monitor layout query: {err}");
return None;
}
};
let reply = match conn.send_with_reply_and_block(message, Duration::from_secs(2)) {
Ok(reply) => reply,
Err(err) => {
log::warn!("Failed to query GNOME monitor layout: {err}");
return None;
}
};
let mut args = reply.iter_init();
for _ in 0..3 {
if !args.next() {
log::warn!("GNOME monitor layout reply is missing properties");
return None;
}
}
let properties: PropMap = match args.read() {
Ok(properties) => properties,
Err(err) => {
log::warn!("Failed to read GNOME monitor layout properties: {err}");
return None;
}
};
let Some(value) = dbus::arg::prop_cast::<u32>(&properties, "layout-mode").copied() else {
log::warn!("GNOME monitor layout reply has no layout-mode");
return None;
};
let mode = gnome_monitor_layout_mode_from_value(value);
if mode.is_none() {
log::warn!("GNOME monitor layout reply has unknown layout-mode {value}");
}
mode
})();
if let Ok(mut cache) = GNOME_MONITOR_LAYOUT_MODE_CACHE.lock() {
*cache = Some((Instant::now(), result));
}
result
}
#[cfg(test)]
mod gnome_monitor_layout_tests {
use super::*;
#[test]
fn maps_logical_layouts() {
assert_eq!(
gnome_monitor_layout_mode_from_value(1),
Some(GnomeMonitorLayoutMode::Logical)
);
assert_eq!(
gnome_monitor_layout_mode_from_value(3),
Some(GnomeMonitorLayoutMode::Logical)
);
}
#[test]
fn maps_physical_layout() {
assert_eq!(
gnome_monitor_layout_mode_from_value(2),
Some(GnomeMonitorLayoutMode::Physical)
);
}
#[test]
fn rejects_unknown_layout() {
assert_eq!(gnome_monitor_layout_mode_from_value(4), None);
}
}
#[inline]
fn update_active_user_lookup_cache(desktop: &Desktop) {
if let Ok(mut cache) = ACTIVE_USER_LOOKUP_CACHE.lock() {