gpui(windows): Fix unwrap panic when monitor goes missing (#55630)

Fixes ZED-5K1

Release Notes:

- Fixed a panic on windows when a monitor disappears from windows
monitor enumeration

---------

Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
Lukas Wirth 2026-05-06 09:35:40 +02:00 committed by GitHub
parent fe1f7a60e4
commit f5945344cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 37 additions and 116 deletions

View file

@ -326,22 +326,22 @@ pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame);
/// An opaque identifier for a hardware display
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct DisplayId(pub(crate) u32);
pub struct DisplayId(pub(crate) u64);
impl DisplayId {
/// Create a new `DisplayId` from a raw platform display identifier.
pub fn new(id: u32) -> Self {
pub fn new(id: u64) -> Self {
Self(id)
}
}
impl From<u32> for DisplayId {
fn from(id: u32) -> Self {
impl From<u64> for DisplayId {
fn from(id: u64) -> Self {
Self(id)
}
}
impl From<DisplayId> for u32 {
impl From<DisplayId> for u64 {
fn from(id: DisplayId) -> Self {
id.0
}

View file

@ -768,7 +768,7 @@ impl LinuxClient for WaylandClient {
.outputs
.iter()
.find_map(|(object_id, output)| {
(object_id.protocol_id() == u32::from(id)).then(|| {
(object_id.protocol_id() as u64 == u64::from(id)).then(|| {
Rc::new(WaylandDisplay {
id: object_id.clone(),
name: output.name.clone(),
@ -810,11 +810,11 @@ impl LinuxClient for WaylandClient {
let parent = state.keyboard_focused_window.clone();
let target_output = params.display_id.and_then(|display_id| {
let target_protocol_id: u32 = display_id.into();
let target_protocol_id: u64 = display_id.into();
state
.wl_outputs
.iter()
.find(|(id, _)| id.protocol_id() == target_protocol_id)
.find(|(id, _)| id.protocol_id() as u64 == target_protocol_id)
.map(|(_, output)| output.clone())
});

View file

@ -25,7 +25,7 @@ impl Hash for WaylandDisplay {
impl PlatformDisplay for WaylandDisplay {
fn id(&self) -> DisplayId {
DisplayId::new(self.id.protocol_id())
DisplayId::new(self.id.protocol_id() as u64)
}
fn uuid(&self) -> anyhow::Result<Uuid> {

View file

@ -1567,7 +1567,7 @@ impl LinuxClient for X11Client {
X11Display::new(
&state.xcb_connection,
state.scale_factor,
u32::from(id) as usize,
u64::from(id) as usize,
)
.ok()?,
))

View file

@ -38,7 +38,7 @@ impl X11Display {
impl PlatformDisplay for X11Display {
fn id(&self) -> DisplayId {
DisplayId::new(self.x_screen_index as u32)
DisplayId::new(self.x_screen_index as u64)
}
fn uuid(&self) -> anyhow::Result<Uuid> {

View file

@ -343,7 +343,7 @@ impl rwh::HasDisplayHandle for X11Window {
};
let screen_id = {
let state = self.0.state.borrow();
u32::from(state.display.id()) as i32
u64::from(state.display.id()) as i32
};
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), screen_id);
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
@ -429,7 +429,7 @@ impl X11WindowState {
) -> anyhow::Result<Self> {
let x_screen_index = params
.display_id
.map_or(x_main_screen_index, |did| u32::from(did) as usize);
.map_or(x_main_screen_index, |did| u64::from(did) as usize);
let visual_set = find_visuals(xcb, x_screen_index);

View file

@ -73,7 +73,7 @@ unsafe extern "C" {
impl PlatformDisplay for MacDisplay {
fn id(&self) -> DisplayId {
DisplayId::new(self.0)
DisplayId::new(self.0 as u64)
}
fn uuid(&self) -> Result<Uuid> {

View file

@ -35,102 +35,18 @@ unsafe impl Sync for WindowsDisplay {}
impl WindowsDisplay {
pub(crate) fn new(display_id: DisplayId) -> Option<Self> {
let screen = available_monitors()
.into_iter()
.nth(u32::from(display_id) as _)?;
let info = get_monitor_info(screen).log_err()?;
let handle = HMONITOR(u64::from(display_id) as _);
let info = get_monitor_info(handle).log_err()?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(screen).log_err()?;
let scale_factor = get_scale_factor_for_monitor(handle).log_err()?;
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
Some(WindowsDisplay {
handle: screen,
display_id,
scale_factor,
bounds: Bounds {
origin: logical_point(
monitor_size.left as f32,
monitor_size.top as f32,
scale_factor,
),
size: physical_size.to_pixels(scale_factor),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(gpui::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
})
}
pub fn new_with_handle(monitor: HMONITOR) -> anyhow::Result<Self> {
let info = get_monitor_info(monitor)?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let display_id = available_monitors()
.iter()
.position(|handle| handle.0 == monitor.0)
.unwrap();
let scale_factor = get_scale_factor_for_monitor(monitor)?;
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
Ok(WindowsDisplay {
handle: monitor,
display_id: DisplayId::new(display_id as _),
scale_factor,
bounds: Bounds {
origin: logical_point(
monitor_size.left as f32,
monitor_size.top as f32,
scale_factor,
),
size: physical_size.to_pixels(scale_factor),
},
visible_bounds: Bounds {
origin: logical_point(work_area.left as f32, work_area.top as f32, scale_factor),
size: size(
(work_area.right - work_area.left) as f32 / scale_factor,
(work_area.bottom - work_area.top) as f32 / scale_factor,
)
.map(gpui::px),
},
physical_bounds: Bounds {
origin: point(monitor_size.left.into(), monitor_size.top.into()),
size: physical_size,
},
uuid,
})
}
fn new_with_handle_and_id(handle: HMONITOR, display_id: DisplayId) -> anyhow::Result<Self> {
let info = get_monitor_info(handle)?;
let monitor_size = info.monitorInfo.rcMonitor;
let work_area = info.monitorInfo.rcWork;
let uuid = generate_uuid(&info.szDevice);
let scale_factor = get_scale_factor_for_monitor(handle)?;
let physical_size = size(
(monitor_size.right - monitor_size.left).into(),
(monitor_size.bottom - monitor_size.top).into(),
);
Ok(WindowsDisplay {
handle,
display_id,
scale_factor,
@ -158,6 +74,10 @@ impl WindowsDisplay {
})
}
pub(crate) fn display_id_for_monitor(monitor: HMONITOR) -> DisplayId {
DisplayId::new(monitor.0 as u64)
}
pub fn primary_monitor() -> Option<Self> {
// https://devblogs.microsoft.com/oldnewthing/20070809-00/?p=25643
const POINT_ZERO: POINT = POINT { x: 0, y: 0 };
@ -169,7 +89,7 @@ impl WindowsDisplay {
);
return None;
}
WindowsDisplay::new_with_handle(monitor).log_err()
WindowsDisplay::new(Self::display_id_for_monitor(monitor))
}
/// Check if the center point of given bounds is inside this monitor
@ -183,7 +103,7 @@ impl WindowsDisplay {
if monitor.is_invalid() {
false
} else {
let Ok(display) = WindowsDisplay::new_with_handle(monitor) else {
let Some(display) = WindowsDisplay::new(Self::display_id_for_monitor(monitor)) else {
return false;
};
display.uuid == self.uuid
@ -193,11 +113,11 @@ impl WindowsDisplay {
pub fn displays() -> Vec<Rc<dyn PlatformDisplay>> {
available_monitors()
.into_iter()
.enumerate()
.filter_map(|(id, handle)| {
Some(Rc::new(
WindowsDisplay::new_with_handle_and_id(handle, DisplayId::new(id as _)).ok()?,
) as Rc<dyn PlatformDisplay>)
.filter_map(|handle| {
Some(
Rc::new(WindowsDisplay::new(Self::display_id_for_monitor(handle))?)
as Rc<dyn PlatformDisplay>,
)
})
.collect()
}

View file

@ -143,9 +143,9 @@ impl WindowsWindowInner {
// monitor is invalid, we do nothing.
if !monitor.is_invalid() && self.state.display.get().handle != monitor {
// we will get the same monitor if we only have one
self.state
.display
.set(WindowsDisplay::new_with_handle(monitor).log_err()?);
self.state.display.set(WindowsDisplay::new(
WindowsDisplay::display_id_for_monitor(monitor),
)?);
}
}
if let Some(mut callback) = self.state.callbacks.moved.take() {
@ -853,7 +853,7 @@ impl WindowsWindowInner {
log::error!("No monitor detected!");
return None;
}
let new_display = WindowsDisplay::new_with_handle(new_monitor).log_err()?;
let new_display = WindowsDisplay::new(WindowsDisplay::display_id_for_monitor(new_monitor))?;
self.state.display.set(new_display);
Some(0)
}

View file

@ -465,11 +465,12 @@ impl WindowsWindow {
let hinstance = get_module_handle();
let display = if let Some(display_id) = params.display_id {
// if we obtain a display_id, then this ID must be valid.
WindowsDisplay::new(display_id).unwrap()
WindowsDisplay::new(display_id)
} else {
WindowsDisplay::primary_monitor().unwrap()
};
None
}
.or_else(WindowsDisplay::primary_monitor)
.context("failed to find any monitor")?;
let appearance = system_appearance().unwrap_or_default();
let mut context = WindowCreateContext {
inner: None,