From 80c14c91983446bb967f7445af582f2f29ea0bc8 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 11 Jun 2024 11:43:12 -0700 Subject: [PATCH] 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 --- 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 +- .../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 ++-- .../src/platform/linux/headless/client.rs | 14 +- crates/gpui/src/platform/linux/platform.rs | 40 +-- .../gpui/src/platform/linux/wayland/client.rs | 10 +- .../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 +- .../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 | 241 +++++++++++------- crates/zed/src/reliability.rs | 37 ++- crates/zed/src/zed.rs | 7 +- 50 files changed, 571 insertions(+), 550 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 920798ba75..a96bcab0bb 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index 48b22cc18a..f18e280256 100644 --- a/Cargo.toml +++ b/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" } diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index d8dc610b83..2361cbf4c8 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -97,7 +97,7 @@ pub fn open_prompt_library( }, |cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)), ) - }) + })? }) } } diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 566a4eb422..2dd3c0f81d 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/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 { 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()); diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index b502c2d1ee..4e0d67ae89 100644 --- a/crates/client/Cargo.toml +++ b/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 diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index feaa11dffb..7fec525c6f 100644 --- a/crates/client/src/telemetry.rs +++ b/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>, // Per app installation (different for dev, nightly, preview, and stable) session_id: Option, // Per app launch release_channel: Option<&'static str>, - app_metadata: AppMetadata, architecture: &'static str, events_queue: Vec, flush_events_task: Option>, @@ -48,6 +47,10 @@ struct TelemetryState { first_event_date_time: Option>, event_coalescer: EventCoalescer, max_queue_size: usize, + + os_name: String, + app_version: String, + os_version: Option, } #[cfg(debug_assertions)] @@ -71,6 +74,87 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy>> = 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, @@ -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), diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 8c269f5497..35178d84ae 100644 --- a/crates/collab/src/api/events.rs +++ b/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, diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 275ef01438..91a0d81066 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/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(); diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index b427926737..9970c1feee 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/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, cx: &mut AppContext) { @@ -27,16 +28,21 @@ pub fn init(app_state: &Arc, 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()) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index be718025d0..395d8e33b6 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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); diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 037cc2fc96..ff7ad73910 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/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); diff --git a/crates/extension/src/extension_store_test.rs b/crates/extension/src/extension_store_test.rs index ebbfb14cdd..009b66ccca 100644 --- a/crates/extension/src/extension_store_test.rs +++ b/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); diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index ed81926d69..e6d8ef973e 100644 --- a/crates/feedback/src/feedback.rs +++ b/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()); diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 42dc808b1f..0d269fcdb5 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/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) }); diff --git a/crates/feedback/src/system_specs.rs b/crates/feedback/src/system_specs.rs index bf7554cb64..e0448867ef 100644 --- a/crates/feedback/src/system_specs.rs +++ b/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, + os_name: String, + os_version: String, memory: u64, architecture: &'static str, commit_sha: Option, } impl SystemSpecs { - pub fn new(cx: &AppContext) -> Self { + pub fn new(cx: &AppContext) -> Task { 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, diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 06390b8b9e..ee72f08302 100644 --- a/crates/gpui/Cargo.toml +++ b/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" diff --git a/crates/gpui/examples/animation.rs b/crates/gpui/examples/animation.rs index a672d3b0d6..7d7b875f2b 100644 --- a/crates/gpui/examples/animation.rs +++ b/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(); }); } diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index 6a91f6ab41..96ab335b08 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/crates/gpui/examples/hello_world.rs @@ -34,6 +34,7 @@ fn main() { text: "World".into(), }) }, - ); + ) + .unwrap(); }); } diff --git a/crates/gpui/examples/image/image.rs b/crates/gpui/examples/image/image.rs index e1e82d8d4a..b6da594c7a 100644 --- a/crates/gpui/examples/image/image.rs +++ b/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(); }); } diff --git a/crates/gpui/examples/set_menus.rs b/crates/gpui/examples/set_menus.rs index 1a46d2e8a8..1e7790b7dd 100644 --- a/crates/gpui/examples/set_menus.rs +++ b/crates/gpui/examples/set_menus.rs @@ -29,7 +29,8 @@ fn main() { }]); cx.open_window(WindowOptions::default(), |cx| { cx.new_view(|_cx| SetMenus {}) - }); + }) + .unwrap(); }); } diff --git a/crates/gpui/examples/window_positioning.rs b/crates/gpui/examples/window_positioning.rs index da8742330b..7398b5aa9b 100644 --- a/crates/gpui/examples/window_positioning.rs +++ b/crates/gpui/examples/window_positioning.rs @@ -61,7 +61,8 @@ fn main() { cx.new_view(|_| WindowContent { text: format!("{:?}", screen.id()).into(), }) - }); + }) + .unwrap(); } }); } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ac33bd8363..224ef19b54 100644 --- a/crates/gpui/src/app.rs +++ b/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; pub struct AppContext { pub(crate) this: Weak, pub(crate) platform: Rc, - app_metadata: AppMetadata, text_system: Arc, 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, - ) -> WindowHandle { + ) -> anyhow::Result> { 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 { self.platform.path_for_auxiliary_executable(name) diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index a0e463d719..06e44e71a3 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/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. diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 61f44f4987..80ca8edf6a 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/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(); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 81ec52bfc7..aff166807b 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -486,6 +486,7 @@ mod test { focus_handle: cx.focus_handle(), }) }) + .unwrap() }); cx.update(|cx| { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index a932ac8513..7df9547b44 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -70,6 +70,19 @@ pub(crate) fn current_platform() -> Rc { } #[cfg(target_os = "linux")] pub(crate) fn current_platform() -> Rc { + 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 { 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 { @@ -106,14 +120,12 @@ pub(crate) trait Platform: 'static { fn displays(&self) -> Vec>; fn primary_display(&self) -> Option>; fn active_window(&self) -> Option; - fn can_open_windows(&self) -> anyhow::Result<()> { - Ok(()) - } + fn open_window( &self, handle: AnyWindowHandle, options: WindowParams, - ) -> Box; + ) -> anyhow::Result>; /// 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); fn on_validate_app_menu_command(&self, callback: Box bool>); - fn os_name(&self) -> &'static str; - fn os_version(&self) -> Result; - fn app_version(&self) -> Result; + fn compositor_name(&self) -> &'static str { + "" + } fn app_path(&self) -> Result; fn local_timezone(&self) -> UtcOffset; fn path_for_auxiliary_executable(&self, name: &str) -> Result; @@ -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, - - /// The current version of the application - pub app_version: Option, -} - #[derive(PartialEq, Eq, Hash, Clone)] pub(crate) enum AtlasKey { Glyph(RenderGlyphParams), diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index 86c15243f9..bb066684f8 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/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 { None } @@ -71,8 +67,14 @@ impl LinuxClient for HeadlessClient { &self, _handle: AnyWindowHandle, _params: WindowParams, - ) -> Box { - unimplemented!() + ) -> anyhow::Result> { + 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) {} diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index d6c041b65d..4a8c48a566 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/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(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R; fn displays(&self) -> Vec>; fn primary_display(&self) -> Option>; fn display(&self, id: DisplayId) -> Option>; - fn can_open_windows(&self) -> anyhow::Result<()> { - Ok(()) - } + fn open_window( &self, handle: AnyWindowHandle, options: WindowParams, - ) -> Box; + ) -> anyhow::Result>; 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 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) { use std::os::unix::process::CommandExt as _; @@ -245,7 +244,7 @@ impl Platform for P { &self, handle: AnyWindowHandle, options: WindowParams, - ) -> Box { + ) -> anyhow::Result> { self.open_window(handle, options) } @@ -369,23 +368,6 @@ impl Platform for P { }); } - fn os_name(&self) -> &'static str { - "Linux" - } - - fn os_version(&self) -> Result { - Ok(SemanticVersion::new(1, 0, 0)) - } - - fn app_version(&self) -> Result { - 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 { // get the path of the executable of the current process let exe_path = std::env::current_exe()?; @@ -510,6 +492,8 @@ impl Platform for P { fn read_from_clipboard(&self) -> Option { self.read_from_clipboard() } + + fn add_recent_document(&self, _path: &Path) {} } pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) { diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 3e82d32c02..e4e2beaae2 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -559,7 +559,7 @@ impl LinuxClient for WaylandClient { &self, handle: AnyWindowHandle, params: WindowParams, - ) -> Box { + ) -> anyhow::Result> { 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 for WaylandClientStatePtr { diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 972a32e0e4..d24c1b5a5e 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -107,7 +107,7 @@ impl WaylandWindowState { client: WaylandClientStatePtr, globals: Globals, options: WindowParams, - ) -> Self { + ) -> anyhow::Result { 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())) } } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index b542acfbc5..5aa4546e74 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/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(&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 { + ) -> anyhow::Result> { 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) { diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 7ebcbfa032..e16db4c3b8 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -217,7 +217,7 @@ impl X11WindowState { atoms: &XcbAtoms, scale_factor: f32, appearance: WindowAppearance, - ) -> Self { + ) -> anyhow::Result { 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 { @@ -432,8 +430,8 @@ impl X11Window { atoms: &XcbAtoms, scale_factor: f32, appearance: WindowAppearance, - ) -> Self { - Self(X11WindowStatePtr { + ) -> anyhow::Result { + 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) { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 81aacb3369..d6fc06b43c 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/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 { + 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 { + ) -> Result> { // 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 { - 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 { - 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 { unsafe { let bundle: id = NSBundle::mainBundle(); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 10c90d8c94..19b45a801f 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/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 { + ) -> anyhow::Result> { 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 bool>) {} - fn os_name(&self) -> &'static str { - "test" - } - - fn os_version(&self) -> Result { - Err(anyhow!("os_version called on TestPlatform")) - } - - fn app_version(&self) -> Result { - Err(anyhow!("app_version called on TestPlatform")) - } - fn app_path(&self) -> Result { unimplemented!() } diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 5333682fad..9079430979 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/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 { + ) -> Result> { 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 { - 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 { - 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 { Ok(std::env::current_exe()?) } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 78cb5dc115..0c19e76529 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -605,7 +605,7 @@ impl Window { handle: AnyWindowHandle, options: WindowOptions, cx: &mut AppContext, - ) -> Self { + ) -> Result { 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, diff --git a/crates/language_tools/src/lsp_log_tests.rs b/crates/language_tools/src/lsp_log_tests.rs index 6058f454b5..a6bc3112f6 100644 --- a/crates/language_tools/src/lsp_log_tests.rs +++ b/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); diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index d5051e4766..aa80c3353a 100644 --- a/crates/lsp/src/lsp.rs +++ b/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), diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index e975ec801a..89e6fc5d17 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -147,7 +147,8 @@ pub fn main() { cx, ) }) - }); + }) + .unwrap(); }); } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index d619a8f80f..d18a263c4d 100644 --- a/crates/project/src/project_tests.rs +++ b/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); }); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 035eca6ffd..e45db2ba86 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/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); diff --git a/crates/release_channel/src/lib.rs b/crates/release_channel/src/lib.rs index eeec61331f..b4fda53d79 100644 --- a/crates/release_channel/src/lib.rs +++ b/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::().0 + if cx.has_global::() { + cx.global::().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)) } diff --git a/crates/telemetry_events/src/telemetry_events.rs b/crates/telemetry_events/src/telemetry_events.rs index fd1ee54ba3..ff9baab486 100644 --- a/crates/telemetry_events/src/telemetry_events.rs +++ b/crates/telemetry_events/src/telemetry_events.rs @@ -160,7 +160,7 @@ pub struct HangReport { pub backtrace: Vec, pub app_version: Option, pub os_name: String, - pub os_version: Option, + pub os_version: Option, pub architecture: String, pub installation_id: Option, } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 39a6b27ce0..f1137d9029 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/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); }); diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 5145b8e264..ab313c5eea 100644 --- a/crates/welcome/src/welcome.rs +++ b/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, cx: &mut AppContext) { +pub fn show_welcome_view( + app_state: Arc, + cx: &mut AppContext, +) -> Task> { 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 { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f35933029d..d883fa4b7e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4853,18 +4853,16 @@ pub fn open_new( app_state: Arc, cx: &mut AppContext, init: impl FnOnce(&mut Workspace, &mut ViewContext) + 'static + Send, -) -> Task<()> { +) -> Task> { 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| { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c7b8d4ff01..5fe26cfaac 100644 --- a/crates/zed/Cargo.toml +++ b/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"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index df2aa9b5cf..9f8bbd8b12 100644 --- a/crates/zed/src/main.rs +++ b/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"]); + 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(); + 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, 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, 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, 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(); + 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?; - } + 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 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 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, 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, + 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<()> { diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index 81a9e53324..ca774c708c 100644 --- a/crates/zed/src/reliability.rs +++ b/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, session_id: String) { +pub fn init_panic_hook( + installation_id: Option, + 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, 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, 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(), }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index dea32ca1a1..5a80e99466 100644 --- a/crates/zed/src/zed.rs +++ b/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);