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

Mikayla Maki and Conrad Irwin created

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>

Change summary

Cargo.lock                                                        |  10 
Cargo.toml                                                        |   7 
crates/assistant/src/prompt_library.rs                            |   2 
crates/channel/src/channel_store_tests.rs                         |   4 
crates/client/Cargo.toml                                          |   7 
crates/client/src/telemetry.rs                                    | 110 
crates/collab/src/api/events.rs                                   |   8 
crates/collab/src/tests/test_server.rs                            |   4 
crates/collab_ui/src/notifications/project_shared_notification.rs |  24 
crates/editor/src/editor_tests.rs                                 |   7 
crates/editor/src/inlay_hint_cache.rs                             |   4 
crates/extension/src/extension_store_test.rs                      |   4 
crates/feedback/src/feedback.rs                                   |  36 
crates/feedback/src/feedback_modal.rs                             |   4 
crates/feedback/src/system_specs.rs                               |  41 
crates/gpui/Cargo.toml                                            |   2 
crates/gpui/examples/animation.rs                                 |   3 
crates/gpui/examples/hello_world.rs                               |   3 
crates/gpui/examples/image/image.rs                               |   3 
crates/gpui/examples/set_menus.rs                                 |   3 
crates/gpui/examples/window_positioning.rs                        |   3 
crates/gpui/src/app.rs                                            |  65 
crates/gpui/src/app/async_context.rs                              |   2 
crates/gpui/src/app/test_context.rs                               |  33 
crates/gpui/src/interactive.rs                                    |   1 
crates/gpui/src/platform.rs                                       |  45 
crates/gpui/src/platform/linux/headless/client.rs                 |  14 
crates/gpui/src/platform/linux/platform.rs                        |  40 
crates/gpui/src/platform/linux/wayland/client.rs                  |  10 
crates/gpui/src/platform/linux/wayland/window.rs                  |  14 
crates/gpui/src/platform/linux/x11/client.rs                      |  10 
crates/gpui/src/platform/linux/x11/window.rs                      |  22 
crates/gpui/src/platform/mac/platform.rs                          |  54 
crates/gpui/src/platform/test/platform.rs                         |  18 
crates/gpui/src/platform/windows/platform.rs                      | 122 
crates/gpui/src/window.rs                                         |   8 
crates/language_tools/src/lsp_log_tests.rs                        |   4 
crates/lsp/src/lsp.rs                                             |   4 
crates/markdown/examples/markdown.rs                              |   3 
crates/project/src/project_tests.rs                               |   4 
crates/project_symbols/src/project_symbols.rs                     |   4 
crates/release_channel/src/lib.rs                                 |  21 
crates/telemetry_events/src/telemetry_events.rs                   |   2 
crates/vim/src/test/vim_test_context.rs                           |   4 
crates/welcome/src/welcome.rs                                     |  16 
crates/workspace/src/workspace.rs                                 |  24 
crates/zed/Cargo.toml                                             |   3 
crates/zed/src/main.rs                                            | 247 
crates/zed/src/reliability.rs                                     |  37 
crates/zed/src/zed.rs                                             |   7 
50 files changed, 574 insertions(+), 553 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1512,7 +1512,7 @@ dependencies = [
 [[package]]
 name = "blade-graphics"
 version = "0.4.0"
-source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
+source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
 dependencies = [
  "ash",
  "ash-window",
@@ -1542,7 +1542,7 @@ dependencies = [
 [[package]]
 name = "blade-macros"
 version = "0.2.1"
-source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
+source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1552,7 +1552,7 @@ dependencies = [
 [[package]]
 name = "blade-util"
 version = "0.1.0"
-source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d"
+source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
 dependencies = [
  "blade-graphics",
  "bytemuck",
@@ -2210,8 +2210,10 @@ dependencies = [
  "async-tungstenite",
  "chrono",
  "clock",
+ "cocoa",
  "collections",
  "feature_flags",
+ "fs",
  "futures 0.3.28",
  "gpui",
  "http 0.1.0",
@@ -2238,6 +2240,7 @@ dependencies = [
  "tiny_http",
  "url",
  "util",
+ "windows 0.56.0",
 ]
 
 [[package]]
@@ -13168,6 +13171,7 @@ version = "0.140.0"
 dependencies = [
  "activity_indicator",
  "anyhow",
+ "ashpd",
  "assets",
  "assistant",
  "audio",

Cargo.toml 🔗

@@ -268,14 +268,15 @@ async-tar = "0.4.2"
 async-trait = "0.1"
 async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
 bitflags = "2.4.2"
-blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
-blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
-blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" }
+blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
+blade-macros = { git = "https://github.com/zed-industries/blade", rev =  "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
+blade-util = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d"  }
 cap-std = "3.0"
 cargo_toml = "0.20"
 chrono = { version = "0.4", features = ["serde"] }
 clap = { version = "4.4", features = ["derive"] }
 clickhouse = { version = "0.11.6" }
+cocoa = "0.25"
 ctor = "0.2.6"
 signal-hook = "0.3.17"
 core-foundation = { version = "0.9.3" }

crates/channel/src/channel_store_tests.rs 🔗

@@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent;
 use super::*;
 use client::{test::FakeServer, Client, UserStore};
 use clock::FakeSystemClock;
-use gpui::{AppContext, Context, Model, TestAppContext};
+use gpui::{AppContext, Context, Model, SemanticVersion, TestAppContext};
 use http::FakeHttpClient;
 use rpc::proto::{self};
 use settings::SettingsStore;
@@ -340,7 +340,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
 fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
     let settings_store = SettingsStore::test(cx);
     cx.set_global(settings_store);
-    release_channel::init("0.0.0", cx);
+    release_channel::init(SemanticVersion::default(), cx);
     client::init_settings(cx);
 
     let clock = Arc::new(FakeSystemClock::default());

crates/client/Cargo.toml 🔗

@@ -24,6 +24,7 @@ chrono = { workspace = true, features = ["serde"] }
 clock.workspace = true
 collections.workspace = true
 feature_flags.workspace = true
+fs.workspace = true
 futures.workspace = true
 gpui.workspace = true
 http.workspace = true
@@ -60,6 +61,12 @@ settings = { workspace = true, features = ["test-support"] }
 util = { workspace = true, features = ["test-support"] }
 http = { workspace = true, features = ["test-support"] }
 
+[target.'cfg(target_os = "windows")'.dependencies]
+windows.workspace = true
+
+[target.'cfg(target_os = "macos")'.dependencies]
+cocoa.workspace = true
+
 [target.'cfg(target_os = "linux")'.dependencies]
 async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
 # This is an indirect dependency of async-tungstenite that is included

crates/client/src/telemetry.rs 🔗

@@ -4,7 +4,7 @@ use crate::{ChannelId, TelemetrySettings};
 use chrono::{DateTime, Utc};
 use clock::SystemClock;
 use futures::Future;
-use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
+use gpui::{AppContext, BackgroundExecutor, Task};
 use http::{self, HttpClient, HttpClientWithUrl, Method};
 use once_cell::sync::Lazy;
 use parking_lot::Mutex;
@@ -39,7 +39,6 @@ struct TelemetryState {
     installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
     session_id: Option<String>,        // Per app launch
     release_channel: Option<&'static str>,
-    app_metadata: AppMetadata,
     architecture: &'static str,
     events_queue: Vec<EventWrapper>,
     flush_events_task: Option<Task<()>>,
@@ -48,6 +47,10 @@ struct TelemetryState {
     first_event_date_time: Option<DateTime<Utc>>,
     event_coalescer: EventCoalescer,
     max_queue_size: usize,
+
+    os_name: String,
+    app_version: String,
+    os_version: Option<String>,
 }
 
 #[cfg(debug_assertions)]
@@ -71,6 +74,87 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
         })
 });
 
+pub fn os_name() -> String {
+    #[cfg(target_os = "macos")]
+    {
+        "macOS".to_string()
+    }
+    #[cfg(target_os = "linux")]
+    {
+        format!("Linux {}", gpui::guess_compositor())
+    }
+
+    #[cfg(target_os = "windows")]
+    {
+        "Windows".to_string()
+    }
+}
+
+/// Note: This might do blocking IO! Only call from background threads
+pub fn os_version() -> String {
+    #[cfg(target_os = "macos")]
+    {
+        use cocoa::base::nil;
+        use cocoa::foundation::NSProcessInfo;
+
+        unsafe {
+            let process_info = cocoa::foundation::NSProcessInfo::processInfo(nil);
+            let version = process_info.operatingSystemVersion();
+            gpui::SemanticVersion::new(
+                version.majorVersion as usize,
+                version.minorVersion as usize,
+                version.patchVersion as usize,
+            )
+            .to_string()
+        }
+    }
+    #[cfg(target_os = "linux")]
+    {
+        use std::path::Path;
+
+        let content = if let Ok(file) = std::fs::read_to_string(&Path::new("/etc/os-release")) {
+            file
+        } else if let Ok(file) = std::fs::read_to_string(&Path::new("/usr/lib/os-release")) {
+            file
+        } else {
+            log::error!("Failed to load /etc/os-release, /usr/lib/os-release");
+            "".to_string()
+        };
+        let mut name = "unknown".to_string();
+        let mut version = "unknown".to_string();
+
+        for line in content.lines() {
+            if line.starts_with("ID=") {
+                name = line.trim_start_matches("ID=").trim_matches('"').to_string();
+            }
+            if line.starts_with("VERSION_ID=") {
+                version = line
+                    .trim_start_matches("VERSION_ID=")
+                    .trim_matches('"')
+                    .to_string();
+            }
+        }
+
+        format!("{} {}", name, version)
+    }
+
+    #[cfg(target_os = "windows")]
+    {
+        let mut info = unsafe { std::mem::zeroed() };
+        let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut info) };
+        if status.is_ok() {
+            gpui::SemanticVersion::new(
+                info.dwMajorVersion as _,
+                info.dwMinorVersion as _,
+                info.dwBuildNumber as _,
+            )
+            .to_string()
+        } else {
+            "unknown".to_string()
+        }
+    }
+}
+
 impl Telemetry {
     pub fn new(
         clock: Arc<dyn SystemClock>,
@@ -84,7 +168,6 @@ impl Telemetry {
 
         let state = Arc::new(Mutex::new(TelemetryState {
             settings: *TelemetrySettings::get_global(cx),
-            app_metadata: cx.app_metadata(),
             architecture: env::consts::ARCH,
             release_channel,
             installation_id: None,
@@ -97,6 +180,10 @@ impl Telemetry {
             first_event_date_time: None,
             event_coalescer: EventCoalescer::new(clock.clone()),
             max_queue_size: MAX_QUEUE_LEN,
+
+            os_version: None,
+            os_name: os_name(),
+            app_version: release_channel::AppVersion::global(cx).to_string(),
         }));
 
         #[cfg(not(debug_assertions))]
@@ -168,6 +255,9 @@ impl Telemetry {
         let mut state = self.state.lock();
         state.installation_id = installation_id.map(|id| id.into());
         state.session_id = Some(session_id);
+        state.app_version = release_channel::AppVersion::global(cx).to_string();
+        state.os_name = os_version();
+
         drop(state);
 
         let this = self.clone();
@@ -445,20 +535,14 @@ impl Telemetry {
 
                     {
                         let state = this.state.lock();
+
                         let request_body = EventRequestBody {
                             installation_id: state.installation_id.as_deref().map(Into::into),
                             session_id: state.session_id.clone(),
                             is_staff: state.is_staff,
-                            app_version: state
-                                .app_metadata
-                                .app_version
-                                .unwrap_or_default()
-                                .to_string(),
-                            os_name: state.app_metadata.os_name.to_string(),
-                            os_version: state
-                                .app_metadata
-                                .os_version
-                                .map(|version| version.to_string()),
+                            app_version: state.app_version.clone(),
+                            os_name: state.os_name.clone(),
+                            os_version: state.os_version.clone(),
                             architecture: state.architecture.to_string(),
 
                             release_channel: state.release_channel.map(Into::into),

crates/collab/src/api/events.rs 🔗

@@ -308,6 +308,14 @@ pub async fn post_panic(
         .map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
     let panic = report.panic;
 
+    // better OS reporting for linux (because linux is hard):
+    // - Remove os_version/app_version/os_name from the gpui platform trait
+    // - Move platform processing data into client/telemetry
+    // - Duplicate some small code in macOS platform for a version check
+    // - Add GPUI API for reporting the selected platform integration
+    //  - macos-blade, macos-metal, linux-X11, linux-headless
+    // if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
+
     tracing::error!(
         service = "client",
         version = %panic.app_version,

crates/collab/src/tests/test_server.rs 🔗

@@ -161,7 +161,7 @@ impl TestServer {
             }
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             client::init_settings(cx);
         });
 
@@ -327,7 +327,7 @@ impl TestServer {
             }
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             client::init_settings(cx);
         });
         let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();

crates/collab_ui/src/notifications/project_shared_notification.rs 🔗

@@ -8,6 +8,7 @@ use settings::Settings;
 use std::sync::{Arc, Weak};
 use theme::ThemeSettings;
 use ui::{prelude::*, Button, Label};
+use util::ResultExt;
 use workspace::AppState;
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -27,16 +28,21 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 
             for screen in cx.displays() {
                 let options = notification_window_options(screen, window_size, cx);
-                let window = cx.open_window(options, |cx| {
-                    cx.new_view(|_| {
-                        ProjectSharedNotification::new(
-                            owner.clone(),
-                            *project_id,
-                            worktree_root_names.clone(),
-                            app_state.clone(),
-                        )
+                let Some(window) = cx
+                    .open_window(options, |cx| {
+                        cx.new_view(|_| {
+                            ProjectSharedNotification::new(
+                                owner.clone(),
+                                *project_id,
+                                worktree_root_names.clone(),
+                                app_state.clone(),
+                            )
+                        })
                     })
-                });
+                    .log_err()
+                else {
+                    continue;
+                };
                 notification_windows
                     .entry(*project_id)
                     .or_insert(Vec::new())

crates/editor/src/editor_tests.rs 🔗

@@ -10,7 +10,8 @@ use crate::{
 };
 use futures::StreamExt;
 use gpui::{
-    div, AssetSource, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions,
+    div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
+    WindowBounds, WindowOptions,
 };
 use indoc::indoc;
 use language::{
@@ -511,6 +512,7 @@ fn test_clone(cx: &mut TestAppContext) {
         .update(cx, |editor, cx| {
             cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
         })
+        .unwrap()
         .unwrap();
 
     let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
@@ -7659,6 +7661,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
             },
             |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
         )
+        .unwrap()
     });
 
     let is_still_following = Rc::new(RefCell::new(true));
@@ -12195,7 +12198,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
         let store = SettingsStore::test(cx);
         cx.set_global(store);
         theme::init(theme::LoadThemes::JustBase, cx);
-        release_channel::init("0.0.0", cx);
+        release_channel::init(SemanticVersion::default(), cx);
         client::init_settings(cx);
         language::init(cx);
         Project::init_settings(cx);

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -1268,7 +1268,7 @@ pub mod tests {
         ExcerptRange,
     };
     use futures::StreamExt;
-    use gpui::{Context, TestAppContext, WindowHandle};
+    use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
     use itertools::Itertools;
     use language::{
         language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
@@ -3361,7 +3361,7 @@ pub mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             client::init_settings(cx);
             language::init(cx);
             Project::init_settings(cx);

crates/extension/src/extension_store_test.rs 🔗

@@ -10,7 +10,7 @@ use async_compression::futures::bufread::GzipEncoder;
 use collections::BTreeMap;
 use fs::{FakeFs, Fs, RealFs};
 use futures::{io::BufReader, AsyncReadExt, StreamExt};
-use gpui::{Context, TestAppContext};
+use gpui::{Context, SemanticVersion, TestAppContext};
 use http::{FakeHttpClient, Response};
 use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
 use node_runtime::FakeNodeRuntime;
@@ -723,7 +723,7 @@ fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
-        release_channel::init("0.0.0", cx);
+        release_channel::init(SemanticVersion::default(), cx);
         theme::init(theme::LoadThemes::JustBase, cx);
         Project::init_settings(cx);
         ExtensionSettings::register(cx);

crates/feedback/src/feedback.rs 🔗

@@ -1,5 +1,6 @@
 use gpui::{actions, AppContext, ClipboardItem, PromptLevel};
 use system_specs::SystemSpecs;
+use util::ResultExt;
 use workspace::Workspace;
 
 pub mod feedback_modal;
@@ -38,25 +39,38 @@ pub fn init(cx: &mut AppContext) {
         feedback_modal::FeedbackModal::register(workspace, cx);
         workspace
             .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| {
-                let specs = SystemSpecs::new(&cx).to_string();
+                let specs = SystemSpecs::new(&cx);
 
-                let prompt = cx.prompt(
-                    PromptLevel::Info,
-                    "Copied into clipboard",
-                    Some(&specs),
-                    &["OK"],
-                );
-                cx.spawn(|_, _cx| async move {
-                    prompt.await.ok();
+                cx.spawn(|_, mut cx| async move {
+                    let specs = specs.await.to_string();
+
+                    cx.update(|cx| cx.write_to_clipboard(ClipboardItem::new(specs.clone())))
+                        .log_err();
+
+                    cx.prompt(
+                        PromptLevel::Info,
+                        "Copied into clipboard",
+                        Some(&specs),
+                        &["OK"],
+                    )
+                    .await
+                    .ok();
                 })
                 .detach();
-                cx.write_to_clipboard(ClipboardItem::new(specs.clone()));
             })
             .register_action(|_, _: &RequestFeature, cx| {
                 cx.open_url(request_feature_url());
             })
             .register_action(move |_, _: &FileBugReport, cx| {
-                cx.open_url(&file_bug_report_url(&SystemSpecs::new(&cx)));
+                let specs = SystemSpecs::new(&cx);
+                cx.spawn(|_, mut cx| async move {
+                    let specs = specs.await;
+                    cx.update(|cx| {
+                        cx.open_url(&file_bug_report_url(&specs));
+                    })
+                    .log_err();
+                })
+                .detach();
             })
             .register_action(move |_, _: &OpenZedRepo, cx| {
                 cx.open_url(zed_repo_url());

crates/feedback/src/feedback_modal.rs 🔗

@@ -141,15 +141,15 @@ impl FeedbackModal {
                 return;
             }
 
+            let system_specs = SystemSpecs::new(cx);
             cx.spawn(|workspace, mut cx| async move {
                 let markdown = markdown.await.log_err();
                 let buffer = project.update(&mut cx, |project, cx| {
                     project.create_local_buffer("", markdown, cx)
                 })?;
+                let system_specs = system_specs.await;
 
                 workspace.update(&mut cx, |workspace, cx| {
-                    let system_specs = SystemSpecs::new(cx);
-
                     workspace.toggle_modal(cx, move |cx| {
                         FeedbackModal::new(system_specs, project, buffer, cx)
                     });

crates/feedback/src/system_specs.rs 🔗

@@ -1,4 +1,5 @@
-use gpui::AppContext;
+use client::telemetry;
+use gpui::{AppContext, Task};
 use human_bytes::human_bytes;
 use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
 use serde::Serialize;
@@ -9,27 +10,23 @@ use sysinfo::{MemoryRefreshKind, RefreshKind, System};
 pub struct SystemSpecs {
     app_version: String,
     release_channel: &'static str,
-    os_name: &'static str,
-    os_version: Option<String>,
+    os_name: String,
+    os_version: String,
     memory: u64,
     architecture: &'static str,
     commit_sha: Option<String>,
 }
 
 impl SystemSpecs {
-    pub fn new(cx: &AppContext) -> Self {
+    pub fn new(cx: &AppContext) -> Task<Self> {
         let app_version = AppVersion::global(cx).to_string();
         let release_channel = ReleaseChannel::global(cx);
-        let os_name = cx.app_metadata().os_name;
+        let os_name = telemetry::os_name();
         let system = System::new_with_specifics(
             RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
         );
         let memory = system.total_memory();
         let architecture = env::consts::ARCH;
-        let os_version = cx
-            .app_metadata()
-            .os_version
-            .map(|os_version| os_version.to_string());
         let commit_sha = match release_channel {
             ReleaseChannel::Dev | ReleaseChannel::Nightly => {
                 AppCommitSha::try_global(cx).map(|sha| sha.0.clone())
@@ -37,24 +34,24 @@ impl SystemSpecs {
             _ => None,
         };
 
-        SystemSpecs {
-            app_version,
-            release_channel: release_channel.display_name(),
-            os_name,
-            os_version,
-            memory,
-            architecture,
-            commit_sha,
-        }
+        cx.background_executor().spawn(async move {
+            let os_version = telemetry::os_version();
+            SystemSpecs {
+                app_version,
+                release_channel: release_channel.display_name(),
+                os_name,
+                os_version,
+                memory,
+                architecture,
+                commit_sha,
+            }
+        })
     }
 }
 
 impl Display for SystemSpecs {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let os_information = match &self.os_version {
-            Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
-            None => format!("OS: {}", self.os_name),
-        };
+        let os_information = format!("OS: {} {}", self.os_name, self.os_version);
         let app_version_information = format!(
             "Zed: v{} ({})",
             self.app_version,

crates/gpui/Cargo.toml 🔗

@@ -87,7 +87,7 @@ cbindgen = "0.26.0"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 block = "0.1"
-cocoa = "0.25"
+cocoa.workspace = true
 core-foundation.workspace = true
 core-graphics = "0.23"
 core-text = "20.1"

crates/gpui/examples/animation.rs 🔗

@@ -76,6 +76,7 @@ fn main() {
             cx.open_window(options, |cx| {
                 cx.activate(false);
                 cx.new_view(|_cx| AnimationExample {})
-            });
+            })
+            .unwrap();
         });
 }

crates/gpui/examples/image/image.rs 🔗

@@ -93,6 +93,7 @@ fn main() {
                 local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()),
                 remote_resource: "https://picsum.photos/512/512".into(),
             })
-        });
+        })
+        .unwrap();
     });
 }

crates/gpui/src/app.rs 🔗

@@ -27,13 +27,12 @@ use util::ResultExt;
 
 use crate::{
     current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
-    AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context,
-    DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
-    Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
-    PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
-    RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer,
-    Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle,
-    WindowId,
+    AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
+    Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
+    Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
+    PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
+    SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
+    Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
 };
 
 mod async_context;
@@ -169,11 +168,6 @@ impl App {
         self
     }
 
-    /// Returns metadata associated with the application
-    pub fn metadata(&self) -> AppMetadata {
-        self.0.borrow().app_metadata.clone()
-    }
-
     /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
     pub fn background_executor(&self) -> BackgroundExecutor {
         self.0.borrow().background_executor.clone()
@@ -208,7 +202,6 @@ type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
 pub struct AppContext {
     pub(crate) this: Weak<AppCell>,
     pub(crate) platform: Rc<dyn Platform>,
-    app_metadata: AppMetadata,
     text_system: Arc<TextSystem>,
     flushing_effects: bool,
     pending_updates: usize,
@@ -261,17 +254,10 @@ impl AppContext {
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
         let entities = EntityMap::new();
 
-        let app_metadata = AppMetadata {
-            os_name: platform.os_name(),
-            os_version: platform.os_version().ok(),
-            app_version: platform.app_version().ok(),
-        };
-
         let app = Rc::new_cyclic(|this| AppCell {
             app: RefCell::new(AppContext {
                 this: this.clone(),
                 platform: platform.clone(),
-                app_metadata,
                 text_system,
                 actions: Rc::new(ActionRegistry::default()),
                 flushing_effects: false,
@@ -346,11 +332,6 @@ impl AppContext {
         self.platform.quit();
     }
 
-    /// Get metadata about the app and platform.
-    pub fn app_metadata(&self) -> AppMetadata {
-        self.app_metadata.clone()
-    }
-
     /// Schedules all windows in the application to be redrawn. This can be called
     /// multiple times in an update cycle and still result in a single redraw.
     pub fn refresh(&mut self) {
@@ -490,26 +471,26 @@ impl AppContext {
         &mut self,
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
-    ) -> WindowHandle<V> {
+    ) -> anyhow::Result<WindowHandle<V>> {
         self.update(|cx| {
             let id = cx.windows.insert(None);
             let handle = WindowHandle::new(id);
-            let mut window = Window::new(handle.into(), options, cx);
-            let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
-            window.root_view.replace(root_view.into());
-            cx.window_handles.insert(id, window.handle);
-            cx.windows.get_mut(id).unwrap().replace(window);
-            handle
+            match Window::new(handle.into(), options, cx) {
+                Ok(mut window) => {
+                    let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
+                    window.root_view.replace(root_view.into());
+                    cx.window_handles.insert(id, window.handle);
+                    cx.windows.get_mut(id).unwrap().replace(window);
+                    Ok(handle)
+                }
+                Err(e) => {
+                    cx.windows.remove(id);
+                    return Err(e);
+                }
+            }
         })
     }
 
-    /// Returns Ok() if the platform supports opening windows.
-    /// This returns false (for example) on linux when we could
-    /// not establish a connection to X or Wayland.
-    pub fn can_open_windows(&self) -> anyhow::Result<()> {
-        self.platform.can_open_windows()
-    }
-
     /// Instructs the platform to activate the application by bringing it to the foreground.
     pub fn activate(&self, ignoring_other_apps: bool) {
         self.platform.activate(ignoring_other_apps);
@@ -616,6 +597,12 @@ impl AppContext {
         self.platform.app_path()
     }
 
+    /// On Linux, returns the name of the compositor in use.
+    /// Is blank on other platforms.
+    pub fn compositor_name(&self) -> &'static str {
+        self.platform.compositor_name()
+    }
+
     /// Returns the file URL of the executable with the specified name in the application bundle
     pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
         self.platform.path_for_auxiliary_executable(name)

crates/gpui/src/app/async_context.rs 🔗

@@ -151,7 +151,7 @@ impl AsyncAppContext {
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
         let mut lock = app.borrow_mut();
-        Ok(lock.open_window(options, build_root_view))
+        lock.open_window(options, build_root_view)
     }
 
     /// Schedule a future to be polled in the background.

crates/gpui/src/app/test_context.rs 🔗

@@ -193,19 +193,22 @@ impl TestAppContext {
             },
             |cx| cx.new_view(build_window),
         )
+        .unwrap()
     }
 
     /// Adds a new window with no content.
     pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
         let mut cx = self.app.borrow_mut();
         let bounds = Bounds::maximized(None, &mut cx);
-        let window = cx.open_window(
-            WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(bounds)),
-                ..Default::default()
-            },
-            |cx| cx.new_view(|_| Empty),
-        );
+        let window = cx
+            .open_window(
+                WindowOptions {
+                    window_bounds: Some(WindowBounds::Windowed(bounds)),
+                    ..Default::default()
+                },
+                |cx| cx.new_view(|_| Empty),
+            )
+            .unwrap();
         drop(cx);
         let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
         cx.run_until_parked();
@@ -222,13 +225,15 @@ impl TestAppContext {
     {
         let mut cx = self.app.borrow_mut();
         let bounds = Bounds::maximized(None, &mut cx);
-        let window = cx.open_window(
-            WindowOptions {
-                window_bounds: Some(WindowBounds::Windowed(bounds)),
-                ..Default::default()
-            },
-            |cx| cx.new_view(build_root_view),
-        );
+        let window = cx
+            .open_window(
+                WindowOptions {
+                    window_bounds: Some(WindowBounds::Windowed(bounds)),
+                    ..Default::default()
+                },
+                |cx| cx.new_view(build_root_view),
+            )
+            .unwrap();
         drop(cx);
         let view = window.root_view(self).unwrap();
         let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();

crates/gpui/src/platform.rs 🔗

@@ -70,6 +70,19 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
 }
 #[cfg(target_os = "linux")]
 pub(crate) fn current_platform() -> Rc<dyn Platform> {
+    match guess_compositor() {
+        "Wayland" => Rc::new(WaylandClient::new()),
+        "X11" => Rc::new(X11Client::new()),
+        "Headless" => Rc::new(HeadlessClient::new()),
+        _ => unreachable!(),
+    }
+}
+
+/// Return which compositor we're guessing we'll use.
+/// Does not attempt to connect to the given compositor
+#[cfg(target_os = "linux")]
+#[inline]
+pub fn guess_compositor() -> &'static str {
     let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
     let x11_display = std::env::var_os("DISPLAY");
 
@@ -77,13 +90,14 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
     let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
 
     if use_wayland {
-        Rc::new(WaylandClient::new())
+        "Wayland"
     } else if use_x11 {
-        Rc::new(X11Client::new())
+        "X11"
     } else {
-        Rc::new(HeadlessClient::new())
+        "Headless"
     }
 }
+
 // todo("windows")
 #[cfg(target_os = "windows")]
 pub(crate) fn current_platform() -> Rc<dyn Platform> {
@@ -106,14 +120,12 @@ pub(crate) trait Platform: 'static {
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
     fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
     fn active_window(&self) -> Option<AnyWindowHandle>;
-    fn can_open_windows(&self) -> anyhow::Result<()> {
-        Ok(())
-    }
+
     fn open_window(
         &self,
         handle: AnyWindowHandle,
         options: WindowParams,
-    ) -> Box<dyn PlatformWindow>;
+    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
 
     /// Returns the appearance of the application's windows.
     fn window_appearance(&self) -> WindowAppearance;
@@ -143,9 +155,9 @@ pub(crate) trait Platform: 'static {
     fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
     fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 
-    fn os_name(&self) -> &'static str;
-    fn os_version(&self) -> Result<SemanticVersion>;
-    fn app_version(&self) -> Result<SemanticVersion>;
+    fn compositor_name(&self) -> &'static str {
+        ""
+    }
     fn app_path(&self) -> Result<PathBuf>;
     fn local_timezone(&self) -> UtcOffset;
     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
@@ -288,19 +300,6 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
     fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
 }
 
-/// Basic metadata about the current application and operating system.
-#[derive(Clone, Debug)]
-pub struct AppMetadata {
-    /// The name of the current operating system
-    pub os_name: &'static str,
-
-    /// The operating system's version
-    pub os_version: Option<SemanticVersion>,
-
-    /// The current version of the application
-    pub app_version: Option<SemanticVersion>,
-}
-
 #[derive(PartialEq, Eq, Hash, Clone)]
 pub(crate) enum AtlasKey {
     Glyph(RenderGlyphParams),

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -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) {}

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -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>) {

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -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 {

crates/gpui/src/platform/linux/wayland/window.rs 🔗

@@ -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()))
     }
 }
 

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -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) {

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -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) {

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -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();

crates/gpui/src/platform/test/platform.rs 🔗

@@ -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!()
     }

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -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()?)
     }

crates/gpui/src/window.rs 🔗

@@ -605,7 +605,7 @@ impl Window {
         handle: AnyWindowHandle,
         options: WindowOptions,
         cx: &mut AppContext,
-    ) -> Self {
+    ) -> Result<Self> {
         let WindowOptions {
             window_bounds,
             titlebar,
@@ -633,7 +633,7 @@ impl Window {
                 display_id,
                 window_background,
             },
-        );
+        )?;
         let display_id = platform_window.display().map(|display| display.id());
         let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
@@ -761,7 +761,7 @@ impl Window {
             platform_window.set_app_id(&app_id);
         }
 
-        Window {
+        Ok(Window {
             handle,
             removed: false,
             platform_window,
@@ -807,7 +807,7 @@ impl Window {
             focus_enabled: true,
             pending_input: None,
             prompt: None,
-        }
+        })
     }
     fn new_focus_listener(
         &mut self,

crates/language_tools/src/lsp_log_tests.rs 🔗

@@ -4,7 +4,7 @@ use crate::lsp_log::LogMenuItem;
 
 use super::*;
 use futures::StreamExt;
-use gpui::{Context, TestAppContext, VisualTestContext};
+use gpui::{Context, SemanticVersion, TestAppContext, VisualTestContext};
 use language::{
     tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName,
 };
@@ -105,7 +105,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
         theme::init(theme::LoadThemes::JustBase, cx);
-        release_channel::init("0.0.0", cx);
+        release_channel::init(SemanticVersion::default(), cx);
         language::init(cx);
         client::init_settings(cx);
         Project::init_settings(cx);

crates/lsp/src/lsp.rs 🔗

@@ -1317,7 +1317,7 @@ impl FakeLanguageServer {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::TestAppContext;
+    use gpui::{SemanticVersion, TestAppContext};
     use std::str::FromStr;
 
     #[ctor::ctor]
@@ -1330,7 +1330,7 @@ mod tests {
     #[gpui::test]
     async fn test_fake(cx: &mut TestAppContext) {
         cx.update(|cx| {
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
         });
         let (server, mut fake) = FakeLanguageServer::new(
             LanguageServerId(0),

crates/project/src/project_tests.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{Event, *};
 use fs::FakeFs;
 use futures::{future, StreamExt};
-use gpui::{AppContext, UpdateGlobal};
+use gpui::{AppContext, SemanticVersion, UpdateGlobal};
 use language::{
     language_settings::{AllLanguageSettings, LanguageSettingsContent},
     tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
@@ -5135,7 +5135,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        release_channel::init("0.0.0", cx);
+        release_channel::init(SemanticVersion::default(), cx);
         language::init(cx);
         Project::init_settings(cx);
     });

crates/project_symbols/src/project_symbols.rs 🔗

@@ -260,7 +260,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
 mod tests {
     use super::*;
     use futures::StreamExt;
-    use gpui::{TestAppContext, VisualContext};
+    use gpui::{SemanticVersion, TestAppContext, VisualContext};
     use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
     use project::FakeFs;
     use serde_json::json;
@@ -395,7 +395,7 @@ mod tests {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             language::init(cx);
             Project::init_settings(cx);
             workspace::init_settings(cx);

crates/release_channel/src/lib.rs 🔗

@@ -59,20 +59,21 @@ impl AppVersion {
     /// 1. the `ZED_APP_VERSION` environment variable,
     /// 2. the [`AppContext::app_metadata`],
     /// 3. the passed in `pkg_version`.
-    pub fn init(pkg_version: &str, cx: &mut AppContext) {
-        let version = if let Ok(from_env) = env::var("ZED_APP_VERSION") {
+    pub fn init(pkg_version: &str) -> SemanticVersion {
+        if let Ok(from_env) = env::var("ZED_APP_VERSION") {
             from_env.parse().expect("invalid ZED_APP_VERSION")
         } else {
-            cx.app_metadata()
-                .app_version
-                .unwrap_or_else(|| pkg_version.parse().expect("invalid version in Cargo.toml"))
-        };
-        cx.set_global(GlobalAppVersion(version))
+            pkg_version.parse().expect("invalid version in Cargo.toml")
+        }
     }
 
     /// Returns the global version number.
     pub fn global(cx: &AppContext) -> SemanticVersion {
-        cx.global::<GlobalAppVersion>().0
+        if cx.has_global::<GlobalAppVersion>() {
+            cx.global::<GlobalAppVersion>().0
+        } else {
+            SemanticVersion::default()
+        }
     }
 }
 
@@ -100,8 +101,8 @@ struct GlobalReleaseChannel(ReleaseChannel);
 impl Global for GlobalReleaseChannel {}
 
 /// Initializes the release channel.
-pub fn init(pkg_version: &str, cx: &mut AppContext) {
-    AppVersion::init(pkg_version, cx);
+pub fn init(app_version: SemanticVersion, cx: &mut AppContext) {
+    cx.set_global(GlobalAppVersion(app_version));
     cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
 }
 

crates/telemetry_events/src/telemetry_events.rs 🔗

@@ -160,7 +160,7 @@ pub struct HangReport {
     pub backtrace: Vec<BacktraceFrame>,
     pub app_version: Option<SemanticVersion>,
     pub os_name: String,
-    pub os_version: Option<SemanticVersion>,
+    pub os_version: Option<String>,
     pub architecture: String,
     pub installation_id: Option<String>,
 }

crates/vim/src/test/vim_test_context.rs 🔗

@@ -1,7 +1,7 @@
 use std::ops::{Deref, DerefMut};
 
 use editor::test::editor_lsp_test_context::EditorLspTestContext;
-use gpui::{Context, View, VisualContext};
+use gpui::{Context, SemanticVersion, View, VisualContext};
 use search::{project_search::ProjectSearchBar, BufferSearchBar};
 
 use crate::{state::Operator, *};
@@ -19,7 +19,7 @@ impl VimTestContext {
             search::init(cx);
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             command_palette::init(cx);
             crate::init(cx);
         });

crates/welcome/src/welcome.rs 🔗

@@ -5,7 +5,7 @@ use client::{telemetry::Telemetry, TelemetrySettings};
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
-    ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
+    ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
     WindowContext,
 };
 use settings::{Settings, SettingsStore};
@@ -36,19 +36,21 @@ pub fn init(cx: &mut AppContext) {
     base_keymap_picker::init(cx);
 }
 
-pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) {
+pub fn show_welcome_view(
+    app_state: Arc<AppState>,
+    cx: &mut AppContext,
+) -> Task<anyhow::Result<()>> {
     open_new(app_state, cx, |workspace, cx| {
         workspace.toggle_dock(DockPosition::Left, cx);
         let welcome_page = WelcomePage::new(workspace, cx);
         workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
         cx.focus_view(&welcome_page);
         cx.notify();
-    })
-    .detach();
 
-    db::write_and_log(cx, || {
-        KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
-    });
+        db::write_and_log(cx, || {
+            KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
+        });
+    })
 }
 
 pub struct WelcomePage {

crates/workspace/src/workspace.rs 🔗

@@ -4853,18 +4853,16 @@ pub fn open_new(
     app_state: Arc<AppState>,
     cx: &mut AppContext,
     init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
-) -> Task<()> {
+) -> Task<anyhow::Result<()>> {
     let task = Workspace::new_local(Vec::new(), app_state, None, cx);
     cx.spawn(|mut cx| async move {
-        if let Some((workspace, opened_paths)) = task.await.log_err() {
-            workspace
-                .update(&mut cx, |workspace, cx| {
-                    if opened_paths.is_empty() {
-                        init(workspace, cx)
-                    }
-                })
-                .log_err();
-        }
+        let (workspace, opened_paths) = task.await?;
+        workspace.update(&mut cx, |workspace, cx| {
+            if opened_paths.is_empty() {
+                init(workspace, cx)
+            }
+        })?;
+        Ok(())
     })
 }
 
@@ -4936,7 +4934,7 @@ pub fn join_hosted_project(
                         Workspace::new(Default::default(), project, app_state.clone(), cx)
                     })
                 })
-            })?
+            })??
         };
 
         workspace.update(&mut cx, |_, cx| {
@@ -5011,7 +5009,7 @@ pub fn join_dev_server_project(
                             Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
                         })
                     })
-                })?
+                })??
             }
         };
 
@@ -5074,7 +5072,7 @@ pub fn join_in_room_project(
                         Workspace::new(Default::default(), project, app_state.clone(), cx)
                     })
                 })
-            })?
+            })??
         };
 
         workspace.update(&mut cx, |workspace, cx| {

crates/zed/Cargo.toml 🔗

@@ -103,6 +103,9 @@ zed_actions.workspace = true
 [target.'cfg(target_os = "windows")'.build-dependencies]
 winresource = "0.1"
 
+[target.'cfg(target_os = "linux")'.dependencies]
+ashpd.workspace = true
+
 [dev-dependencies]
 call = { workspace = true, features = ["test-support"] }
 editor = { workspace = true, features = ["test-support"] }

crates/zed/src/main.rs 🔗

@@ -27,7 +27,7 @@ use log::LevelFilter;
 use assets::Assets;
 use node_runtime::RealNodeRuntime;
 use parking_lot::Mutex;
-use release_channel::AppCommitSha;
+use release_channel::{AppCommitSha, AppVersion};
 use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore};
 use simplelog::ConfigBuilder;
 use smol::process::Command;
@@ -56,21 +56,64 @@ use crate::zed::inline_completion_registry;
 static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
 
 fn fail_to_launch(e: anyhow::Error) {
+    eprintln!("Zed failed to launch: {:?}", e);
     App::new().run(move |cx| {
-        let window = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty));
-        window.update(cx, |_, cx| {
-            let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
-
-            cx.spawn(|_, mut cx| async move {
-                response.await?;
-                cx.update(|cx| {
-                    cx.quit()
-                })
-            }).detach_and_log_err(cx);
-        }).log_err();
+        if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
+            window.update(cx, |_, cx| {
+                let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
+
+                cx.spawn(|_, mut cx| async move {
+                    response.await?;
+                    cx.update(|cx| {
+                        cx.quit()
+                    })
+                }).detach_and_log_err(cx);
+            }).log_err();
+        } else {
+            fail_to_open_window(e, cx)
+        }
     })
 }
 
+fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
+    cx.update(|cx| fail_to_open_window(e, cx)).log_err();
+}
+
+fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
+    eprintln!("Zed failed to open a window: {:?}", e);
+    #[cfg(not(target_os = "linux"))]
+    {
+        process::exit(1);
+    }
+
+    #[cfg(target_os = "linux")]
+    {
+        use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
+        _cx.spawn(|_cx| async move {
+            let Ok(proxy) = NotificationProxy::new().await else {
+                process::exit(1);
+            };
+
+            let notification_id = "dev.zed.Oops";
+            proxy
+                .add_notification(
+                    notification_id,
+                    Notification::new("Zed failed to launch")
+                        .body(Some(format!("{:?}", e).as_str()))
+                        .priority(Priority::High)
+                        .icon(ashpd::desktop::Icon::with_names(&[
+                            "dialog-question-symbolic",
+                        ])),
+                )
+                .await
+                .ok();
+
+            process::exit(1);
+        })
+        .detach();
+    }
+}
+
 enum AppMode {
     Headless(DevServerToken),
     Ui,
@@ -122,10 +165,6 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
         }
     };
 
-    if let Err(err) = cx.can_open_windows() {
-        return Err(err);
-    }
-
     SystemAppearance::init(cx);
     load_embedded_fonts(cx);
 
@@ -256,7 +295,9 @@ fn main() {
         .ok()
         .unzip();
     let session_id = Uuid::new_v4().to_string();
-    reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
+
+    let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
+    reliability::init_panic_hook(installation_id.clone(), app_version, session_id.clone());
 
     let (open_listener, mut open_rx) = OpenListener::new();
 
@@ -319,14 +360,18 @@ fn main() {
         {
             cx.spawn({
                 let app_state = app_state.clone();
-                |cx| async move { restore_or_create_workspace(app_state, cx).await }
+                |mut cx| async move {
+                    if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
+                        fail_to_open_window_async(e, &mut cx)
+                    }
+                }
             })
             .detach();
         }
     });
 
     app.run(move |cx| {
-        release_channel::init(env!("CARGO_PKG_VERSION"), cx);
+        release_channel::init(app_version, cx);
         if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
             AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
         }
@@ -421,7 +466,11 @@ fn main() {
                     init_ui(app_state.clone(), cx).unwrap();
                     cx.spawn({
                         let app_state = app_state.clone();
-                        |cx| async move { restore_or_create_workspace(app_state, cx).await }
+                        |mut cx| async move {
+                            if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
+                                fail_to_open_window_async(e, &mut cx)
+                            }
+                        }
                     })
                     .detach();
                 }
@@ -448,13 +497,12 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
         let app_state = app_state.clone();
         cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
             .detach();
-        return;
     }
 
     if let Err(e) = init_ui(app_state.clone(), cx) {
-        log::error!("{}", e);
+        fail_to_open_window(e, cx);
         return;
-    }
+    };
 
     let mut task = None;
     if !request.open_paths.is_empty() {
@@ -478,48 +526,59 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
 
     if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
         cx.spawn(|mut cx| async move {
-            if let Some(task) = task {
-                task.await?;
-            }
-            let client = app_state.client.clone();
-            // we continue even if authentication fails as join_channel/ open channel notes will
-            // show a visible error message.
-            authenticate(client, &cx).await.log_err();
-
-            if let Some(channel_id) = request.join_channel {
-                cx.update(|cx| {
-                    workspace::join_channel(
-                        client::ChannelId(channel_id),
-                        app_state.clone(),
-                        None,
-                        cx,
-                    )
-                })?
-                .await?;
-            }
+            let result = maybe!(async {
+                if let Some(task) = task {
+                    task.await?;
+                }
+                let client = app_state.client.clone();
+                // we continue even if authentication fails as join_channel/ open channel notes will
+                // show a visible error message.
+                authenticate(client, &cx).await.log_err();
+
+                if let Some(channel_id) = request.join_channel {
+                    cx.update(|cx| {
+                        workspace::join_channel(
+                            client::ChannelId(channel_id),
+                            app_state.clone(),
+                            None,
+                            cx,
+                        )
+                    })?
+                    .await?;
+                }
 
-            let workspace_window =
-                workspace::get_any_active_workspace(app_state, cx.clone()).await?;
-            let workspace = workspace_window.root_view(&cx)?;
-
-            let mut promises = Vec::new();
-            for (channel_id, heading) in request.open_channel_notes {
-                promises.push(cx.update_window(workspace_window.into(), |_, cx| {
-                    ChannelView::open(
-                        client::ChannelId(channel_id),
-                        heading,
-                        workspace.clone(),
-                        cx,
-                    )
-                    .log_err()
-                })?)
+                let workspace_window =
+                    workspace::get_any_active_workspace(app_state, cx.clone()).await?;
+                let workspace = workspace_window.root_view(&cx)?;
+
+                let mut promises = Vec::new();
+                for (channel_id, heading) in request.open_channel_notes {
+                    promises.push(cx.update_window(workspace_window.into(), |_, cx| {
+                        ChannelView::open(
+                            client::ChannelId(channel_id),
+                            heading,
+                            workspace.clone(),
+                            cx,
+                        )
+                        .log_err()
+                    })?)
+                }
+                future::join_all(promises).await;
+                anyhow::Ok(())
+            })
+            .await;
+            if let Err(err) = result {
+                fail_to_open_window_async(err, &mut cx);
             }
-            future::join_all(promises).await;
-            anyhow::Ok(())
         })
-        .detach_and_log_err(cx);
+        .detach()
     } else if let Some(task) = task {
-        task.detach_and_log_err(cx)
+        cx.spawn(|mut cx| async move {
+            if let Err(err) = task.await {
+                fail_to_open_window_async(err, &mut cx);
+            }
+        })
+        .detach();
     }
 }
 
@@ -562,41 +621,39 @@ async fn installation_id() -> Result<(String, bool)> {
     Ok((installation_id, false))
 }
 
-async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
-    maybe!(async {
-        let restore_behaviour =
-            cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?;
-        let location = match restore_behaviour {
-            workspace::RestoreOnStartupBehaviour::LastWorkspace => {
-                workspace::last_opened_workspace_paths().await
-            }
-            _ => None,
-        };
-        if let Some(location) = location {
-            cx.update(|cx| {
-                workspace::open_paths(
-                    location.paths().as_ref(),
-                    app_state,
-                    workspace::OpenOptions::default(),
-                    cx,
-                )
-            })?
-            .await
-            .log_err();
-        } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
-            cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
-        } else {
-            cx.update(|cx| {
-                workspace::open_new(app_state, cx, |workspace, cx| {
-                    Editor::new_file(workspace, &Default::default(), cx)
-                })
-                .detach();
-            })?;
+async fn restore_or_create_workspace(
+    app_state: Arc<AppState>,
+    cx: &mut AsyncAppContext,
+) -> Result<()> {
+    let restore_behaviour = cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?;
+    let location = match restore_behaviour {
+        workspace::RestoreOnStartupBehaviour::LastWorkspace => {
+            workspace::last_opened_workspace_paths().await
         }
-        anyhow::Ok(())
-    })
-    .await
-    .log_err();
+        _ => None,
+    };
+    if let Some(location) = location {
+        cx.update(|cx| {
+            workspace::open_paths(
+                location.paths().as_ref(),
+                app_state,
+                workspace::OpenOptions::default(),
+                cx,
+            )
+        })?
+        .await?;
+    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
+        cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
+    } else {
+        cx.update(|cx| {
+            workspace::open_new(app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+        })?
+        .await?;
+    }
+
+    Ok(())
 }
 
 fn init_paths() -> anyhow::Result<()> {

crates/zed/src/reliability.rs 🔗

@@ -1,8 +1,9 @@
 use anyhow::{Context, Result};
 use backtrace::{self, Backtrace};
 use chrono::Utc;
+use client::telemetry;
 use db::kvp::KEY_VALUE_STORE;
-use gpui::{App, AppContext, SemanticVersion};
+use gpui::{AppContext, SemanticVersion};
 use http::Method;
 use isahc::config::Configurable;
 
@@ -26,9 +27,12 @@ use util::{paths, ResultExt};
 use crate::stdout_is_a_pty;
 static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
 
-pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
+pub fn init_panic_hook(
+    installation_id: Option<String>,
+    app_version: SemanticVersion,
+    session_id: String,
+) {
     let is_pty = stdout_is_a_pty();
-    let app_metadata = app.metadata();
 
     panic::set_hook(Box::new(move |info| {
         let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
@@ -64,14 +68,6 @@ pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: S
             std::process::exit(-1);
         }
 
-        let app_version = if let Some(version) = app_metadata.app_version {
-            version.to_string()
-        } else {
-            option_env!("CARGO_PKG_VERSION")
-                .unwrap_or("dev")
-                .to_string()
-        };
-
         let backtrace = Backtrace::new();
         let mut backtrace = backtrace
             .frames()
@@ -101,11 +97,8 @@ pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: S
             }),
             app_version: app_version.to_string(),
             release_channel: RELEASE_CHANNEL.display_name().into(),
-            os_name: app_metadata.os_name.into(),
-            os_version: app_metadata
-                .os_version
-                .as_ref()
-                .map(SemanticVersion::to_string),
+            os_name: telemetry::os_name(),
+            os_version: Some(telemetry::os_version()),
             architecture: env::consts::ARCH.into(),
             panicked_on: Utc::now().timestamp_millis(),
             backtrace,
@@ -182,7 +175,6 @@ pub fn monitor_main_thread_hangs(
     let foreground_executor = cx.foreground_executor();
     let background_executor = cx.background_executor();
     let telemetry_settings = *client::TelemetrySettings::get_global(cx);
-    let metadata = cx.app_metadata();
 
     // Initialize SIGUSR2 handler to send a backrace to a channel.
     let (backtrace_tx, backtrace_rx) = mpsc::channel();
@@ -258,9 +250,14 @@ pub fn monitor_main_thread_hangs(
         })
         .detach();
 
+    let app_version = release_channel::AppVersion::global(cx);
+    let os_name = client::telemetry::os_name();
+
     background_executor
         .clone()
         .spawn(async move {
+            let os_version = client::telemetry::os_version();
+
             loop {
                 while let Some(_) = backtrace_rx.recv().ok() {
                     if !telemetry_settings.diagnostics {
@@ -306,9 +303,9 @@ pub fn monitor_main_thread_hangs(
 
                     let report = HangReport {
                         backtrace,
-                        app_version: metadata.app_version,
-                        os_name: metadata.os_name.to_owned(),
-                        os_version: metadata.os_version,
+                        app_version: Some(app_version),
+                        os_name: os_name.clone(),
+                        os_version: Some(os_version.clone()),
                         architecture: env::consts::ARCH.into(),
                         installation_id: installation_id.clone(),
                     };

crates/zed/src/zed.rs 🔗

@@ -894,7 +894,7 @@ mod tests {
     use editor::{display_map::DisplayRow, scroll::Autoscroll, DisplayPoint, Editor};
     use gpui::{
         actions, Action, AnyWindowHandle, AppContext, AssetSource, BorrowAppContext, Entity,
-        TestAppContext, VisualTestContext, WindowHandle,
+        SemanticVersion, TestAppContext, VisualTestContext, WindowHandle,
     };
     use language::{LanguageMatcher, LanguageRegistry};
     use project::{Project, ProjectPath, WorktreeSettings};
@@ -1314,7 +1314,8 @@ mod tests {
                 Editor::new_file(workspace, &Default::default(), cx)
             })
         })
-        .await;
+        .await
+        .unwrap();
         cx.run_until_parked();
 
         let workspace = cx
@@ -3088,7 +3089,7 @@ mod tests {
             notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
             Project::init_settings(cx);
-            release_channel::init("0.0.0", cx);
+            release_channel::init(SemanticVersion::default(), cx);
             command_palette::init(cx);
             language::init(cx);
             editor::init(cx);