gpui: Try to notify when GPU init fails (#44487)

Nia and Cole Miller created

Hopefully addresses #43575. cc @cole-miller 

Release Notes:

- GPU initialization errors are more reliably reported

---------

Co-authored-by: Cole Miller <cole@zed.dev>

Change summary

crates/gpui/src/platform/linux/platform.rs       | 44 ++++++++++++++++++
crates/gpui/src/platform/linux/wayland/client.rs |  9 ++-
crates/gpui/src/platform/linux/x11/client.rs     |  6 +-
crates/zed/src/main.rs                           |  1 
4 files changed, 53 insertions(+), 7 deletions(-)

Detailed changes

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

@@ -43,6 +43,50 @@ pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
 const FILE_PICKER_PORTAL_MISSING: &str =
     "Couldn't open file picker due to missing xdg-desktop-portal implementation.";
 
+#[cfg(any(feature = "x11", feature = "wayland"))]
+pub trait ResultExt {
+    type Ok;
+
+    fn notify_err(self, msg: &'static str) -> Self::Ok;
+}
+
+#[cfg(any(feature = "x11", feature = "wayland"))]
+impl<T> ResultExt for anyhow::Result<T> {
+    type Ok = T;
+
+    fn notify_err(self, msg: &'static str) -> T {
+        match self {
+            Ok(v) => v,
+            Err(e) => {
+                use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
+                use futures::executor::block_on;
+
+                let proxy = block_on(NotificationProxy::new()).expect(msg);
+
+                let notification_id = "dev.zed.Oops";
+                block_on(
+                    proxy.add_notification(
+                        notification_id,
+                        Notification::new("Zed failed to launch")
+                            .body(Some(
+                                format!(
+                                    "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
+                                )
+                                .as_str(),
+                            ))
+                            .priority(Priority::High)
+                            .icon(ashpd::desktop::Icon::with_names(&[
+                                "dialog-question-symbolic",
+                            ])),
+                    )
+                ).expect(msg);
+
+                panic!("{msg}");
+            }
+        }
+    }
+}
+
 pub trait LinuxClient {
     fn compositor_name(&self) -> &'static str;
     fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;

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

@@ -17,7 +17,7 @@ use collections::HashMap;
 use filedescriptor::Pipe;
 use http_client::Url;
 use smallvec::SmallVec;
-use util::ResultExt;
+use util::ResultExt as _;
 use wayland_backend::client::ObjectId;
 use wayland_backend::protocol::WEnum;
 use wayland_client::event_created_child;
@@ -76,8 +76,8 @@ use crate::{
     FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
     LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
     MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
-    PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScrollDelta, ScrollWheelEvent,
-    Size, TouchPhase, WindowParams, point, px, size,
+    PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta,
+    ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
 };
 use crate::{
     LinuxDispatcher, RunnableVariant, TaskTiming,
@@ -531,7 +531,8 @@ impl WaylandClient {
             })
             .unwrap();
 
-        let gpu_context = BladeContext::new().expect("Unable to init GPU context");
+        // This could be unified with the notification handling in zed/main:fail_to_open_window.
+        let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
 
         let seat = seat.unwrap();
         let globals = Globals::new(

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

@@ -1,4 +1,4 @@
-use crate::{Capslock, LinuxDispatcher, RunnableVariant, TaskTiming, xcb_flush};
+use crate::{Capslock, LinuxDispatcher, ResultExt as _, RunnableVariant, TaskTiming, xcb_flush};
 use anyhow::{Context as _, anyhow};
 use ashpd::WindowIdentifier;
 use calloop::{
@@ -18,7 +18,7 @@ use std::{
     rc::{Rc, Weak},
     time::{Duration, Instant},
 };
-use util::ResultExt;
+use util::ResultExt as _;
 
 use x11rb::{
     connection::{Connection, RequestConnection},
@@ -437,7 +437,7 @@ impl X11Client {
             .to_string();
         let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
 
-        let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
+        let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
 
         let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
             .context("Failed to create resource database")?;

crates/zed/src/main.rs 🔗

@@ -130,6 +130,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
         process::exit(1);
     }
 
+    // Maybe unify this with gpui::platform::linux::platform::ResultExt::notify_err(..)?
     #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {
         use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};