From eb0a1f330b7872e8bdd67c214ef40ce21cd4af24 Mon Sep 17 00:00:00 2001 From: tnfssc <29162020+tnfssc@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:20:32 +0530 Subject: [PATCH] fix(host-linux): send physical pixels to ghostty_surface_set_size GLArea::allocation() returns CSS/logical pixels, but ghostty_surface_set_size expects physical device pixels. On HiDPI screens this caused the terminal to render at a fraction of the GL viewport or with a zero-size grid. Changes: - Introduce gl_area_device_size() helper to convert logical allocation to device pixels (allocation * scale_factor). - Use explicit device_width/device_height parameter names in refresh_surface_display and add rustdoc to prevent accidental logical-pixel usage. - Add connect_scale_factor_notify handler with a last-device-size cache to avoid redundant refresh/render cycles when the notify fires without an actual size change. --- rust/limux-host-linux/src/terminal.rs | 62 +++++++++++++++++++++------ 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/rust/limux-host-linux/src/terminal.rs b/rust/limux-host-linux/src/terminal.rs index 4bd00101..d14a5af6 100644 --- a/rust/limux-host-linux/src/terminal.rs +++ b/rust/limux-host-linux/src/terminal.rs @@ -412,15 +412,29 @@ fn request_terminal_focus(gl_area: >k::GLArea, had_focus: &Cell) { gl_area.grab_focus(); } -fn refresh_surface_display(surface: ghostty_surface_t, gl_area: >k::GLArea) { +/// Convert a GLArea's logical (CSS-pixel) allocation to physical (device) pixels. +fn gl_area_device_size(gl_area: >k::GLArea) -> (u32, u32) { let alloc = gl_area.allocation(); - let w = alloc.width() as u32; - let h = alloc.height() as u32; - if w > 0 && h > 0 { + let scale = gl_area.scale_factor() as f64; + let w = (alloc.width() as f64 * scale).round() as u32; + let h = (alloc.height() as f64 * scale).round() as u32; + (w, h) +} + +/// Push the current content scale and physical size into Ghostty. +/// `device_width` and `device_height` must be in physical (device) pixels, +/// not CSS/logical pixels. +fn refresh_surface_display( + surface: ghostty_surface_t, + gl_area: >k::GLArea, + device_width: u32, + device_height: u32, +) { + if device_width > 0 && device_height > 0 { let scale = gl_area.scale_factor() as f64; unsafe { ghostty_surface_set_content_scale(surface, scale, scale); - ghostty_surface_set_size(surface, w, h); + ghostty_surface_set_size(surface, device_width, device_height); } } unsafe { ghostty_surface_refresh(surface) }; @@ -434,7 +448,8 @@ fn refresh_realized_surface_display(surface: ghostty_surface_t, gl_area: >k::G unsafe { ghostty_surface_display_realized(surface) }; } } - refresh_surface_display(surface, gl_area); + let (w, h) = gl_area_device_size(gl_area); + refresh_surface_display(surface, gl_area, w, h); } fn clear_ghostty_preedit(surface: ghostty_surface_t) { @@ -1380,13 +1395,11 @@ pub fn create_terminal( } } - // Set initial size — GLArea gives unscaled CSS pixels, - // Ghostty handles scaling internally via content_scale. - let alloc = gl_area.allocation(); - let w = alloc.width() as u32; - let h = alloc.height() as u32; + // Set initial size — GLArea allocation is CSS (logical) pixels, + // but ghostty_surface_set_size expects physical (device) pixels. + let (w, h) = gl_area_device_size(gl_area); if w > 0 && h > 0 { - refresh_surface_display(surface, gl_area); + refresh_surface_display(surface, gl_area, w, h); } let surface_key = surface as usize; @@ -1483,7 +1496,8 @@ pub fn create_terminal( let w = width as u32; let h = height as u32; if w > 0 && h > 0 { - refresh_surface_display(surface, gl_area); + // GLArea::resize passes physical (device) pixels directly. + refresh_surface_display(surface, gl_area, w, h); } } @@ -1496,6 +1510,28 @@ pub fn create_terminal( }); } + // On scale-factor change: update Ghostty's content scale and size. + // GTK4 usually emits resize when the backing surface resizes, but + // fractional-scale transitions can leave the allocation unchanged + // while the device-pixel size changes, so we refresh explicitly here. + { + let surface_cell = surface_cell.clone(); + let last_device_size: Rc>> = Rc::new(Cell::new(None)); + gl_area.connect_scale_factor_notify(move |gl_area| { + if let Some(surface) = *surface_cell.borrow() { + let (w, h) = gl_area_device_size(gl_area); + if w > 0 && h > 0 { + let size = (w, h); + if last_device_size.get() == Some(size) { + return; + } + last_device_size.set(Some(size)); + refresh_surface_display(surface, gl_area, w, h); + } + } + }); + } + // Keyboard input // // Send key events with the text field populated. Ghostty uses the