Pull app / OS info out of GPUI, add Linux information, make fallible window initialization (#12869)

TODO:
- [x] Finish GPUI changes on other operating systems 

This is a largely internal change to how we report data to our
diagnostics and telemetry. This PR also includes an update to our blade
backend which allows us to report errors in a more useful way when
failing to initialize blade.


Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Mikayla Maki
2024-06-11 11:43:12 -07:00
committed by GitHub
co-authored by Conrad Irwin
parent ec9e700e70
commit 80c14c9198
50 changed files with 571 additions and 550 deletions
@@ -59,10 +59,6 @@ impl LinuxClient for HeadlessClient {
None
}
fn can_open_windows(&self) -> anyhow::Result<()> {
return Err(anyhow::anyhow!("neither DISPLAY, nor WAYLAND_DISPLAY found. You can still run zed for remote development with --dev-server-token."));
}
fn active_window(&self) -> Option<AnyWindowHandle> {
None
}
@@ -71,8 +67,14 @@ impl LinuxClient for HeadlessClient {
&self,
_handle: AnyWindowHandle,
_params: WindowParams,
) -> Box<dyn PlatformWindow> {
unimplemented!()
) -> anyhow::Result<Box<dyn PlatformWindow>> {
Err(anyhow::anyhow!(
"neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode"
))
}
fn compositor_name(&self) -> &'static str {
"headless"
}
fn set_cursor_style(&self, _style: CursorStyle) {}
+12 -28
View File
@@ -39,8 +39,8 @@ use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle,
DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers,
OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler,
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task,
WindowAppearance, WindowOptions, WindowParams,
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString,
Size, Task, WindowAppearance, WindowOptions, WindowParams,
};
use super::x11::X11Client;
@@ -54,18 +54,17 @@ pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn can_open_windows(&self) -> anyhow::Result<()> {
Ok(())
}
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow>;
) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle);
fn open_uri(&self, uri: &str);
fn write_to_primary(&self, item: ClipboardItem);
@@ -152,14 +151,14 @@ impl<P: LinuxClient + 'static> Platform for P {
});
}
fn can_open_windows(&self) -> anyhow::Result<()> {
self.can_open_windows()
}
fn quit(&self) {
self.with_common(|common| common.signal.stop());
}
fn compositor_name(&self) -> &'static str {
self.compositor_name()
}
fn restart(&self, binary_path: Option<PathBuf>) {
use std::os::unix::process::CommandExt as _;
@@ -245,7 +244,7 @@ impl<P: LinuxClient + 'static> Platform for P {
&self,
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
) -> anyhow::Result<Box<dyn PlatformWindow>> {
self.open_window(handle, options)
}
@@ -369,23 +368,6 @@ impl<P: LinuxClient + 'static> Platform for P {
});
}
fn os_name(&self) -> &'static str {
"Linux"
}
fn os_version(&self) -> Result<SemanticVersion> {
Ok(SemanticVersion::new(1, 0, 0))
}
fn app_version(&self) -> Result<SemanticVersion> {
const VERSION: Option<&str> = option_env!("RELEASE_VERSION");
if let Some(version) = VERSION {
version.parse()
} else {
Ok(SemanticVersion::new(1, 0, 0))
}
}
fn app_path(&self) -> Result<PathBuf> {
// get the path of the executable of the current process
let exe_path = std::env::current_exe()?;
@@ -510,6 +492,8 @@ impl<P: LinuxClient + 'static> Platform for P {
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard()
}
fn add_recent_document(&self, _path: &Path) {}
}
pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) {
@@ -559,7 +559,7 @@ impl LinuxClient for WaylandClient {
&self,
handle: AnyWindowHandle,
params: WindowParams,
) -> Box<dyn PlatformWindow> {
) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut();
let (window, surface_id) = WaylandWindow::new(
@@ -568,10 +568,10 @@ impl LinuxClient for WaylandClient {
WaylandClientStatePtr(Rc::downgrade(&self.0)),
params,
state.common.appearance,
);
)?;
state.windows.insert(surface_id, window.0.clone());
Box::new(window)
Ok(Box::new(window))
}
fn set_cursor_style(&self, style: CursorStyle) {
@@ -693,6 +693,10 @@ impl LinuxClient for WaylandClient {
.as_ref()
.map(|window| window.handle())
}
fn compositor_name(&self) -> &'static str {
"Wayland"
}
}
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {
@@ -107,7 +107,7 @@ impl WaylandWindowState {
client: WaylandClientStatePtr,
globals: Globals,
options: WindowParams,
) -> Self {
) -> anyhow::Result<Self> {
let bounds = options.bounds.map(|p| p.0 as u32);
let raw = RawWindow {
@@ -130,7 +130,7 @@ impl WaylandWindowState {
},
)
}
.unwrap(),
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
size: gpu::Extent {
@@ -141,7 +141,7 @@ impl WaylandWindowState {
transparent: options.window_background != WindowBackgroundAppearance::Opaque,
};
Self {
Ok(Self {
xdg_surface,
acknowledged_first_configure: false,
surface,
@@ -164,7 +164,7 @@ impl WaylandWindowState {
appearance,
handle,
active: false,
}
})
}
}
@@ -224,7 +224,7 @@ impl WaylandWindow {
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
) -> (Self, ObjectId) {
) -> anyhow::Result<(Self, ObjectId)> {
let surface = globals.compositor.create_surface(&globals.qh, ());
let xdg_surface = globals
.wm_base
@@ -267,14 +267,14 @@ impl WaylandWindow {
client,
globals,
params,
))),
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
});
// Kick things off
surface.commit();
(this, surface.id())
Ok((this, surface.id()))
}
}
+7 -3
View File
@@ -889,6 +889,10 @@ impl X11Client {
}
impl LinuxClient for X11Client {
fn compositor_name(&self) -> &'static str {
"X11"
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common)
}
@@ -929,7 +933,7 @@ impl LinuxClient for X11Client {
&self,
handle: AnyWindowHandle,
params: WindowParams,
) -> Box<dyn PlatformWindow> {
) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut();
let x_window = state.xcb_connection.generate_id().unwrap();
@@ -944,7 +948,7 @@ impl LinuxClient for X11Client {
&state.atoms,
state.scale_factor,
state.common.appearance,
);
)?;
let screen_resources = state
.xcb_connection
@@ -1012,7 +1016,7 @@ impl LinuxClient for X11Client {
};
state.windows.insert(x_window, window_ref);
Box::new(window)
Ok(Box::new(window))
}
fn set_cursor_style(&self, style: CursorStyle) {
+10 -12
View File
@@ -217,7 +217,7 @@ impl X11WindowState {
atoms: &XcbAtoms,
scale_factor: f32,
appearance: WindowAppearance,
) -> Self {
) -> anyhow::Result<Self> {
let x_screen_index = params
.display_id
.map_or(x_main_screen_index, |did| did.0 as usize);
@@ -249,8 +249,7 @@ impl X11WindowState {
xcb_connection
.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
.unwrap()
.check()
.unwrap();
.check()?;
id
};
@@ -282,8 +281,7 @@ impl X11WindowState {
&win_aux,
)
.unwrap()
.check()
.unwrap();
.check()?;
if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title {
@@ -346,7 +344,7 @@ impl X11WindowState {
},
)
}
.unwrap(),
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
@@ -356,7 +354,7 @@ impl X11WindowState {
transparent: params.window_background != WindowBackgroundAppearance::Opaque,
};
Self {
Ok(Self {
client,
executor,
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
@@ -370,7 +368,7 @@ impl X11WindowState {
appearance,
handle,
destroyed: false,
}
})
}
fn content_size(&self) -> Size<Pixels> {
@@ -432,8 +430,8 @@ impl X11Window {
atoms: &XcbAtoms,
scale_factor: f32,
appearance: WindowAppearance,
) -> Self {
Self(X11WindowStatePtr {
) -> anyhow::Result<Self> {
Ok(Self(X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new(
handle,
client,
@@ -445,11 +443,11 @@ impl X11Window {
atoms,
scale_factor,
appearance,
))),
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(),
x_window,
})
}))
}
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
+16 -38
View File
@@ -5,7 +5,7 @@ use crate::{
Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
WindowAppearance, WindowParams,
};
use anyhow::{anyhow, bail};
use anyhow::anyhow;
use block::ConcreteBlock;
use cocoa::{
appkit::{
@@ -367,6 +367,18 @@ impl MacPlatform {
}
}
}
fn os_version(&self) -> Result<SemanticVersion> {
unsafe {
let process_info = NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion();
Ok(SemanticVersion::new(
version.majorVersion as usize,
version.minorVersion as usize,
version.patchVersion as usize,
))
}
}
}
impl Platform for MacPlatform {
@@ -504,16 +516,16 @@ impl Platform for MacPlatform {
&self,
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
) -> Result<Box<dyn PlatformWindow>> {
// Clippy thinks that this evaluates to `()`, for some reason.
#[allow(clippy::unit_arg, clippy::clone_on_copy)]
let renderer_context = self.0.lock().renderer_context.clone();
Box::new(MacWindow::open(
Ok(Box::new(MacWindow::open(
handle,
options,
self.foreground_executor(),
renderer_context,
))
)))
}
fn window_appearance(&self) -> WindowAppearance {
@@ -705,40 +717,6 @@ impl Platform for MacPlatform {
self.0.lock().validate_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str {
"macOS"
}
fn os_version(&self) -> Result<SemanticVersion> {
unsafe {
let process_info = NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion();
Ok(SemanticVersion::new(
version.majorVersion as usize,
version.minorVersion as usize,
version.patchVersion as usize,
))
}
}
fn app_version(&self) -> Result<SemanticVersion> {
unsafe {
let bundle: id = NSBundle::mainBundle();
if bundle.is_null() {
Err(anyhow!("app is not running inside a bundle"))
} else {
let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
if version.is_null() {
bail!("bundle does not have version");
}
let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
let bytes = version.UTF8String() as *const u8;
let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
version.parse()
}
}
}
fn app_path(&self) -> Result<PathBuf> {
unsafe {
let bundle: id = NSBundle::mainBundle();
+3 -15
View File
@@ -3,7 +3,7 @@ use crate::{
Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance,
WindowParams,
};
use anyhow::{anyhow, Result};
use anyhow::Result;
use collections::VecDeque;
use futures::channel::oneshot;
use parking_lot::Mutex;
@@ -187,14 +187,14 @@ impl Platform for TestPlatform {
&self,
handle: AnyWindowHandle,
params: WindowParams,
) -> Box<dyn crate::PlatformWindow> {
) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
let window = TestWindow::new(
handle,
params,
self.weak.clone(),
self.active_display.clone(),
);
Box::new(window)
Ok(Box::new(window))
}
fn window_appearance(&self) -> WindowAppearance {
@@ -249,18 +249,6 @@ impl Platform for TestPlatform {
fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
fn os_name(&self) -> &'static str {
"test"
}
fn os_version(&self) -> Result<crate::SemanticVersion> {
Err(anyhow!("os_version called on TestPlatform"))
}
fn app_version(&self) -> Result<crate::SemanticVersion> {
Err(anyhow!("app_version called on TestPlatform"))
}
fn app_path(&self) -> Result<std::path::PathBuf> {
unimplemented!()
}
+2 -120
View File
@@ -16,17 +16,14 @@ use copypasta::{ClipboardContext, ClipboardProvider};
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use parking_lot::RwLock;
use semantic_version::SemanticVersion;
use smallvec::SmallVec;
use time::UtcOffset;
use windows::{
core::*,
Wdk::System::SystemServices::*,
Win32::{
Foundation::*,
Graphics::Gdi::*,
Security::Credentials::*,
Storage::FileSystem::*,
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
},
@@ -287,7 +284,7 @@ impl Platform for WindowsPlatform {
&self,
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
) -> Result<Box<dyn PlatformWindow>> {
let lock = self.state.borrow();
let window = WindowsWindow::new(
handle,
@@ -300,7 +297,7 @@ impl Platform for WindowsPlatform {
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);
Box::new(window)
Ok(Box::new(window))
}
// todo(windows)
@@ -464,121 +461,6 @@ impl Platform for WindowsPlatform {
self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str {
"Windows"
}
fn os_version(&self) -> Result<SemanticVersion> {
let mut info = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut info) };
if status.is_ok() {
Ok(SemanticVersion::new(
info.dwMajorVersion as _,
info.dwMinorVersion as _,
info.dwBuildNumber as _,
))
} else {
Err(anyhow::anyhow!(
"unable to get Windows version: {}",
std::io::Error::last_os_error()
))
}
}
fn app_version(&self) -> Result<SemanticVersion> {
let mut file_name_buffer = vec![0u16; MAX_PATH as usize];
let file_name = {
let mut file_name_buffer_capacity = MAX_PATH as usize;
let mut file_name_length;
loop {
file_name_length =
unsafe { GetModuleFileNameW(None, &mut file_name_buffer) } as usize;
if file_name_length < file_name_buffer_capacity {
break;
}
// buffer too small
file_name_buffer_capacity *= 2;
file_name_buffer = vec![0u16; file_name_buffer_capacity];
}
PCWSTR::from_raw(file_name_buffer[0..(file_name_length + 1)].as_ptr())
};
let version_info_block = {
let mut version_handle = 0;
let version_info_size =
unsafe { GetFileVersionInfoSizeW(file_name, Some(&mut version_handle)) } as usize;
if version_info_size == 0 {
log::error!(
"unable to get version info size: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("unable to get version info size"));
}
let mut version_data = vec![0u8; version_info_size + 2];
unsafe {
GetFileVersionInfoW(
file_name,
version_handle,
version_info_size as u32,
version_data.as_mut_ptr() as _,
)
}
.inspect_err(|_| {
log::error!(
"unable to retrieve version info: {}",
std::io::Error::last_os_error()
)
})?;
version_data
};
let version_info_raw = {
let mut buffer = unsafe { std::mem::zeroed() };
let mut size = 0;
let entry = "\\".encode_utf16().chain(Some(0)).collect_vec();
if !unsafe {
VerQueryValueW(
version_info_block.as_ptr() as _,
PCWSTR::from_raw(entry.as_ptr()),
&mut buffer,
&mut size,
)
}
.as_bool()
{
log::error!(
"unable to query version info data: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("the specified resource is not valid"));
}
if size == 0 {
log::error!(
"unable to query version info data: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("no value is available for the specified name"));
}
buffer
};
let version_info = unsafe { &*(version_info_raw as *mut VS_FIXEDFILEINFO) };
// https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
if version_info.dwSignature == 0xFEEF04BD {
return Ok(SemanticVersion::new(
((version_info.dwProductVersionMS >> 16) & 0xFFFF) as usize,
(version_info.dwProductVersionMS & 0xFFFF) as usize,
((version_info.dwProductVersionLS >> 16) & 0xFFFF) as usize,
));
} else {
log::error!(
"no version info present: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("no version info present"));
}
}
fn app_path(&self) -> Result<PathBuf> {
Ok(std::env::current_exe()?)
}