Fix floating file chooser

Alvaro Parker created

Change summary

Cargo.lock                                       | 19 +++++++++++++++-
crates/gpui/Cargo.toml                           |  2 
crates/gpui/src/platform/linux/platform.rs       | 20 ++++++++++++++++++
crates/gpui/src/platform/linux/wayland/client.rs | 20 ++++++++++++++++++
crates/gpui/src/platform/linux/x11/client.rs     | 16 ++++++++++++++
crates/gpui/src/platform/linux/x11/window.rs     |  2 
6 files changed, 75 insertions(+), 4 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -777,6 +777,9 @@ dependencies = [
  "serde",
  "serde_repr",
  "url",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols 0.32.6",
  "zbus",
 ]
 
@@ -7120,7 +7123,7 @@ dependencies = [
  "wayland-backend",
  "wayland-client",
  "wayland-cursor",
- "wayland-protocols",
+ "wayland-protocols 0.31.2",
  "wayland-protocols-plasma",
  "windows 0.61.1",
  "windows-core 0.61.0",
@@ -18400,6 +18403,18 @@ dependencies = [
  "wayland-scanner",
 ]
 
+[[package]]
+name = "wayland-protocols"
+version = "0.32.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc"
+dependencies = [
+ "bitflags 2.9.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
 [[package]]
 name = "wayland-protocols-plasma"
 version = "0.2.0"
@@ -18409,7 +18424,7 @@ dependencies = [
  "bitflags 2.9.0",
  "wayland-backend",
  "wayland-client",
- "wayland-protocols",
+ "wayland-protocols 0.31.2",
  "wayland-scanner",
 ]
 

crates/gpui/Cargo.toml 🔗

@@ -38,7 +38,7 @@ wayland = [
     "blade-macros",
     "blade-util",
     "bytemuck",
-    "ashpd",
+    "ashpd/wayland",
     "cosmic-text",
     "font-kit",
     "calloop-wayland-source",

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

@@ -73,6 +73,15 @@ pub trait LinuxClient {
     fn active_window(&self) -> Option<AnyWindowHandle>;
     fn window_stack(&self) -> Option<Vec<AnyWindowHandle>>;
     fn run(&self);
+
+    #[cfg(any(feature = "wayland", feature = "x11"))]
+    fn window_identifier(
+        &self,
+    ) -> futures::channel::oneshot::Receiver<Option<ashpd::WindowIdentifier>> {
+        let (sources_tx, sources_rx) = futures::channel::oneshot::channel();
+        sources_tx.send(None).ok();
+        sources_rx
+    }
 }
 
 #[derive(Default)]
@@ -290,6 +299,9 @@ impl<P: LinuxClient + 'static> Platform for P {
         #[cfg(not(any(feature = "wayland", feature = "x11")))]
         let _ = (done_tx.send(Ok(None)), options);
 
+        #[cfg(any(feature = "wayland", feature = "x11"))]
+        let identifier = self.window_identifier();
+
         #[cfg(any(feature = "wayland", feature = "x11"))]
         self.foreground_executor()
             .spawn(async move {
@@ -298,8 +310,10 @@ impl<P: LinuxClient + 'static> Platform for P {
                 } else {
                     "Open File"
                 };
+                let identifier = identifier.await.ok().flatten();
 
                 let request = match ashpd::desktop::file_chooser::OpenFileRequest::default()
+                    .identifier(identifier)
                     .modal(true)
                     .title(title)
                     .accept_label(options.prompt.as_ref().map(crate::SharedString::as_str))
@@ -346,6 +360,9 @@ impl<P: LinuxClient + 'static> Platform for P {
         #[cfg(not(any(feature = "wayland", feature = "x11")))]
         let _ = (done_tx.send(Ok(None)), directory, suggested_name);
 
+        #[cfg(any(feature = "wayland", feature = "x11"))]
+        let identifier = self.window_identifier();
+
         #[cfg(any(feature = "wayland", feature = "x11"))]
         self.foreground_executor()
             .spawn({
@@ -353,8 +370,11 @@ impl<P: LinuxClient + 'static> Platform for P {
                 let suggested_name = suggested_name.map(|s| s.to_owned());
 
                 async move {
+                    let identifier = identifier.await.ok().flatten();
+
                     let mut request_builder =
                         ashpd::desktop::file_chooser::SaveFileRequest::default()
+                            .identifier(identifier)
                             .modal(true)
                             .title("Save File")
                             .current_folder(directory)

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

@@ -7,6 +7,7 @@ use std::{
     time::{Duration, Instant},
 };
 
+use ashpd::WindowIdentifier;
 use calloop::{
     EventLoop, LoopHandle,
     timer::{TimeoutAction, Timer},
@@ -858,6 +859,25 @@ impl LinuxClient for WaylandClient {
     fn compositor_name(&self) -> &'static str {
         "Wayland"
     }
+
+    fn window_identifier(&self) -> futures::channel::oneshot::Receiver<Option<WindowIdentifier>> {
+        let (done_tx, done_rx) = futures::channel::oneshot::channel();
+        let client_state = self.0.borrow();
+        let executor = &client_state.common.foreground_executor;
+
+        if let Some(active_window) = client_state.keyboard_focused_window.as_ref() {
+            let surface = active_window.surface();
+            executor
+                .spawn(async move {
+                    let window_identifier = ashpd::WindowIdentifier::from_wayland(&surface).await;
+                    done_tx.send(window_identifier).ok();
+                })
+                .detach();
+        } else {
+            done_tx.send(None).ok();
+        }
+        done_rx
+    }
 }
 
 impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {

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

@@ -1,5 +1,6 @@
 use crate::{Capslock, xcb_flush};
 use anyhow::{Context as _, anyhow};
+use ashpd::WindowIdentifier;
 use calloop::{
     EventLoop, LoopHandle, RegistrationToken,
     generic::{FdWrapper, Generic},
@@ -1660,6 +1661,21 @@ impl LinuxClient for X11Client {
 
         Some(handles)
     }
+
+    fn window_identifier(&self) -> futures::channel::oneshot::Receiver<Option<WindowIdentifier>> {
+        let (done_tx, done_rx) = futures::channel::oneshot::channel();
+        let state = self.0.borrow();
+        if let Some(window) = state
+            .keyboard_focused_window
+            .and_then(|focused_window| state.windows.get(&focused_window))
+        {
+            let window_identifier = WindowIdentifier::from_xid(window.window.x_window as u64);
+            done_tx.send(Some(window_identifier)).ok();
+        } else {
+            done_tx.send(None).ok();
+        }
+        done_rx
+    }
 }
 
 impl X11ClientState {

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

@@ -284,7 +284,7 @@ pub(crate) struct X11WindowStatePtr {
     pub state: Rc<RefCell<X11WindowState>>,
     pub(crate) callbacks: Rc<RefCell<Callbacks>>,
     xcb: Rc<XCBConnection>,
-    x_window: xproto::Window,
+    pub(crate) x_window: xproto::Window,
 }
 
 impl rwh::HasWindowHandle for RawWindow {