diff --git a/apps/artcraft-launcher/src/main.rs b/apps/artcraft-launcher/src/main.rs
--- a/apps/artcraft-launcher/src/main.rs
+++ b/apps/artcraft-launcher/src/main.rs
@@ -33,7 +33,30 @@ const SETTINGS_KEY: &str = "settings";
 
 type Wake = Arc<OnceLock<Box<dyn Fn() + Send + Sync>>>;
 
+/// Resolves the X11 scale factor from `xrdb -query`, desktop environment variables, or XSETTINGS.
+#[cfg(any(all(unix, not(target_os = "macos")), test))]
+fn resolve_x11_scale_factor(xrdb_stdout: Option<&str>, xsettings: Option<&str>, env: impl Fn(&str) -> Option<String>) -> String {
+    artcraft_launcher_native::process::resolve_x11_scale_factor(xrdb_stdout, xsettings, env)
+}
+
+/// Ensures `WINIT_X11_SCALE_FACTOR` is populated with a validated desktop scale factor on X11.
+#[cfg(all(unix, not(target_os = "macos")))]
+fn ensure_x11_scale_factor() {
+    if std::env::var_os("DISPLAY").is_some()
+        && window_can_hide_here(|name| std::env::var_os(name))
+        && std::env::var_os("WINIT_X11_SCALE_FACTOR").is_none()
+    {
+        let scale = artcraft_launcher_native::process::cached_x11_scale_factor();
+        if let Ok(exe) = std::env::current_exe() {
+            use std::os::unix::process::CommandExt;
+            let _ = std::process::Command::new(exe).args(std::env::args_os().skip(1)).env("WINIT_X11_SCALE_FACTOR", scale).exec();
+        }
+    }
+}
+
 fn main() -> ExitCode {
+    #[cfg(all(unix, not(target_os = "macos")))]
+    ensure_x11_scale_factor();
     // First, so the panic report and every start-up warning are recorded (`logging`).
     let logs = logging::install();
     logging::install_panic_hook();
@@ -832,6 +855,37 @@ mod tests {
         assert!(window_can_hide(None));
     }
 
+    #[test]
+    fn x11_scale_factor_validates_inputs_and_preserves_xsettings_before_default() {
+        let none = |_: &str| None;
+        assert_eq!(resolve_x11_scale_factor(Some("Xft.dpi:\t96\n"), None, none), "1");
+        assert_eq!(resolve_x11_scale_factor(Some("Xft.dpi: 144\n"), None, none), "1.5");
+        // Malformed or non-normal Xft.dpi falls through to GDK_SCALE / QT_SCALE_FACTOR.
+        assert_eq!(resolve_x11_scale_factor(Some("Xft.dpi: inf\n"), None, |k| (k == "GDK_SCALE").then(|| "2".to_string())), "2");
+        // Empty or invalid GDK_SCALE falls through to valid QT_SCALE_FACTOR.
+        assert_eq!(
+            resolve_x11_scale_factor(None, None, |k| match k {
+                "GDK_SCALE" => Some(String::new()),
+                "QT_SCALE_FACTOR" => Some("1.25".to_string()),
+                _ => None,
+            }),
+            "1.25"
+        );
+        assert_eq!(
+            resolve_x11_scale_factor(None, None, |k| match k {
+                "GDK_SCALE" => Some("auto".to_string()),
+                "QT_SCALE_FACTOR" => Some("1.5".to_string()),
+                _ => None,
+            }),
+            "1.5"
+        );
+        // XSETTINGS Xft/DPI (1024-scaled) is preserved when xrdb and env vars are unset.
+        assert_eq!(resolve_x11_scale_factor(None, Some("Xft/DPI 196608\n"), none), "2");
+        assert_eq!(resolve_x11_scale_factor(None, Some("Gdk/WindowScalingFactor 2\nGdk/UnscaledDPI 98304\n"), none), "2");
+        // Malformed env and unset XSETTINGS fall back to "1".
+        assert_eq!(resolve_x11_scale_factor(None, None, |k| (k == "GDK_SCALE").then(|| "invalid".to_string())), "1");
+    }
+
     #[test]
     fn a_wayland_session_is_known_before_the_window_opens() {
         let env = |set: &'static [(&'static str, &'static str)]| move |name: &str| set.iter().find(|(n, _)| *n == name).map(|(_, v)| v.into());
diff --git a/crates/native/src/process.rs b/crates/native/src/process.rs
--- a/crates/native/src/process.rs
+++ b/crates/native/src/process.rs
@@ -18,6 +18,10 @@ pub fn command(program: impl AsRef<std::ffi::OsStr>) -> Command {
     }
     #[cfg(unix)]
     clean_appimage_env(&mut cmd);
+    #[cfg(all(unix, not(target_os = "macos")))]
+    if std::env::var_os("DISPLAY").is_some() && std::env::var_os("WINIT_X11_SCALE_FACTOR").is_none() {
+        cmd.env("WINIT_X11_SCALE_FACTOR", cached_x11_scale_factor());
+    }
     cmd
 }
 
@@ -446,6 +450,113 @@ fn pgrep_pattern(path: &str) -> String {
     pattern
 }
 
+/// Validates a candidate X11 scale factor so invalid, non-positive, or non-normal values never
+/// reach `WINIT_X11_SCALE_FACTOR` (which would panic inside `winit`).
+pub fn valid_x11_scale(scale: f64) -> Option<String> {
+    (scale.is_finite() && scale.is_normal() && scale > 0.0).then(|| scale.to_string())
+}
+
+/// Parses an XSETTINGS dump (`dump_xsettings` or `xsettingsd.conf`) for `Xft/DPI` or
+/// `Gdk/WindowScalingFactor` / `Gdk/UnscaledDPI`.
+pub fn parse_xsettings_scale(xsettings: &str) -> Option<String> {
+    let mut xft_dpi: Option<f64> = None;
+    let mut gdk_window_scale: Option<f64> = None;
+    let mut gdk_unscaled_dpi: Option<f64> = None;
+
+    for line in xsettings.lines() {
+        let line = line.trim();
+        if line.is_empty() || line.starts_with('#') {
+            continue;
+        }
+        let mut parts = line.split_whitespace();
+        let Some(key) = parts.next() else { continue };
+        let Some(val_str) = parts.next() else { continue };
+        let key = key.trim_end_matches(':');
+        let Ok(raw) = val_str.parse::<f64>() else { continue };
+        if !raw.is_finite() || raw <= 0.0 {
+            continue;
+        }
+        match key {
+            "Xft/DPI" => {
+                let dpi = if raw >= 1024.0 { raw / 1024.0 } else { raw };
+                xft_dpi = Some(dpi);
+            }
+            "Gdk/WindowScalingFactor" => {
+                gdk_window_scale = Some(raw);
+            }
+            "Gdk/UnscaledDPI" => {
+                let dpi = if raw >= 1024.0 { raw / 1024.0 } else { raw };
+                gdk_unscaled_dpi = Some(dpi);
+            }
+            _ => {}
+        }
+    }
+
+    if let Some(dpi) = xft_dpi
+        && let Some(scale) = valid_x11_scale(dpi / 96.0)
+    {
+        return Some(scale);
+    }
+    if let Some(win_scale) = gdk_window_scale {
+        let unscaled_factor = gdk_unscaled_dpi.map_or(1.0, |dpi| dpi / 96.0);
+        if let Some(scale) = valid_x11_scale(win_scale * unscaled_factor) {
+            return Some(scale);
+        }
+    }
+    if let Some(dpi) = gdk_unscaled_dpi {
+        return valid_x11_scale(dpi / 96.0);
+    }
+    None
+}
+
+/// Resolves the X11 scale factor from `xrdb -query` (`Xft.dpi`), `GDK_SCALE`, `QT_SCALE_FACTOR`,
+/// or XSETTINGS (`Xft/DPI`, `Gdk/WindowScalingFactor`), defaulting to `"1"` so `winit` does not
+/// compute an inflated DPI from aspect-ratio EDID millimeter placeholders (such as `160mm x 90mm`
+/// on 16:9 HDMI monitors).
+pub fn resolve_x11_scale_factor(xrdb_stdout: Option<&str>, xsettings: Option<&str>, env: impl Fn(&str) -> Option<String>) -> String {
+    let valid_env = |name: &str| env(name).and_then(|value| value.trim().parse::<f64>().ok().and_then(valid_x11_scale));
+    if let Some(text) = xrdb_stdout {
+        for line in text.lines() {
+            if let Some(rest) = line.strip_prefix("Xft.dpi:")
+                && let Ok(dpi) = rest.trim().parse::<f64>()
+                && let Some(scale) = valid_x11_scale(dpi / 96.0)
+            {
+                return scale;
+            }
+        }
+    }
+    if let Some(scale) = valid_env("GDK_SCALE").or_else(|| valid_env("QT_SCALE_FACTOR")) {
+        return scale;
+    }
+    if let Some(scale) = xsettings.and_then(parse_xsettings_scale) {
+        return scale;
+    }
+    "1".to_string()
+}
+
+/// Queries the host's X11 scale factor once with bounded timeouts and caches the resolved value.
+#[cfg(all(unix, not(target_os = "macos")))]
+pub fn cached_x11_scale_factor() -> &'static str {
+    static SCALE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
+    SCALE.get_or_init(|| {
+        if let Some(existing) = std::env::var("WINIT_X11_SCALE_FACTOR").ok().and_then(|v| v.trim().parse::<f64>().ok().and_then(valid_x11_scale)) {
+            return existing;
+        }
+        let probe = |prog: &str, args: &[&str]| -> Option<String> {
+            let mut cmd = Command::new(prog);
+            clean_appimage_env(&mut cmd);
+            cmd.args(args);
+            run_command(cmd, prog, Duration::from_millis(500)).ok()
+        };
+        let xrdb_out = probe("xrdb", &["-query"]);
+        let xsettings_out = probe("dump_xsettings", &[]).or_else(|| {
+            let config = dirs::config_dir().or_else(|| dirs::home_dir().map(|h| h.join(".config")))?;
+            std::fs::read_to_string(config.join("xsettingsd/xsettingsd.conf")).ok()
+        });
+        resolve_x11_scale_factor(xrdb_out.as_deref(), xsettings_out.as_deref(), |k| std::env::var(k).ok())
+    })
+}
+
 #[cfg(test)]
 #[cfg(unix)]
 mod tests {
