From 91f3afae21b44c88dc25fd704b3cdd6324786199 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Mon, 18 Jul 2022 21:42:45 -0500 Subject: [PATCH] Support open/save dialogs on wayland. --- druid-shell/Cargo.toml | 2 + druid-shell/src/backend/shared/mod.rs | 1 + .../backend/{x11/dialog.rs => shared/zbus.rs} | 17 ++++---- .../backend/wayland/surfaces/layershell.rs | 10 +++++ .../src/backend/wayland/surfaces/mod.rs | 4 +- .../src/backend/wayland/surfaces/popup.rs | 10 +++++ .../src/backend/wayland/surfaces/surface.rs | 41 ++++++++++++++++++- druid-shell/src/backend/wayland/window.rs | 10 ++--- druid-shell/src/backend/x11/mod.rs | 1 - druid-shell/src/backend/x11/window.rs | 16 ++++++-- druid-shell/src/window.rs | 2 +- 11 files changed, 90 insertions(+), 24 deletions(-) rename druid-shell/src/backend/{x11/dialog.rs => shared/zbus.rs} (90%) diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 560cb4cb43..79fa96140d 100755 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -26,6 +26,8 @@ x11 = [ "x11rb", ] wayland = [ + "ashpd", + "futures", "wayland-client", "wayland-protocols/client", "wayland-protocols/unstable_protocols", diff --git a/druid-shell/src/backend/shared/mod.rs b/druid-shell/src/backend/shared/mod.rs index 998b929f5b..71d2e9cd63 100644 --- a/druid-shell/src/backend/shared/mod.rs +++ b/druid-shell/src/backend/shared/mod.rs @@ -26,5 +26,6 @@ cfg_if::cfg_if! { pub(crate) use timer::*; pub(crate) mod xkb; pub(crate) mod linux; + pub(crate) mod zbus; } } diff --git a/druid-shell/src/backend/x11/dialog.rs b/druid-shell/src/backend/shared/zbus.rs similarity index 90% rename from druid-shell/src/backend/x11/dialog.rs rename to druid-shell/src/backend/shared/zbus.rs index b77bae59a2..45762029ab 100644 --- a/druid-shell/src/backend/x11/dialog.rs +++ b/druid-shell/src/backend/shared/zbus.rs @@ -5,12 +5,10 @@ use ashpd::{zbus, WindowIdentifier}; use futures::executor::block_on; use tracing::warn; -use crate::{FileDialogOptions, FileDialogToken, FileInfo}; - -use super::window::IdleHandle; +use crate::{FileDialogOptions, FileDialogToken, FileInfo, IdleHandle}; pub(crate) fn open_file( - window: u32, + window: WindowIdentifier, idle: IdleHandle, options: FileDialogOptions, ) -> FileDialogToken { @@ -18,7 +16,7 @@ pub(crate) fn open_file( } pub(crate) fn save_file( - window: u32, + window: WindowIdentifier, idle: IdleHandle, options: FileDialogOptions, ) -> FileDialogToken { @@ -26,7 +24,7 @@ pub(crate) fn save_file( } fn dialog( - window: u32, + id: WindowIdentifier, idle: IdleHandle, mut options: FileDialogOptions, open: bool, @@ -37,7 +35,6 @@ fn dialog( if let Err(e) = block_on(async { let conn = zbus::Connection::session().await?; let proxy = file_chooser::FileChooserProxy::new(&conn).await?; - let id = WindowIdentifier::from_xid(window as u64); let multi = options.multi_selection; let title_owned = options.title.take(); @@ -70,7 +67,7 @@ fn dialog( format: None, }) .collect(); - idle.add_idle_callback(move |handler| handler.open_files(tok, infos)); + idle.add_idle(move |handler| handler.open_files(tok, infos)); } else if !multi { if uris.len() > 2 { warn!( @@ -83,9 +80,9 @@ fn dialog( format: None, }); if open { - idle.add_idle_callback(move |handler| handler.open_file(tok, info)); + idle.add_idle(move |handler| handler.open_file(tok, info)); } else { - idle.add_idle_callback(move |handler| handler.save_as(tok, info)); + idle.add_idle(move |handler| handler.save_as(tok, info)); } } else { warn!("cannot save multiple paths"); diff --git a/druid-shell/src/backend/wayland/surfaces/layershell.rs b/druid-shell/src/backend/wayland/surfaces/layershell.rs index 44eea2c7e8..18975acf39 100644 --- a/druid-shell/src/backend/wayland/surfaces/layershell.rs +++ b/druid-shell/src/backend/wayland/surfaces/layershell.rs @@ -4,6 +4,8 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; +use crate::FileDialogOptions; +use crate::FileDialogToken; use super::super::error; use super::super::outputs; @@ -369,6 +371,14 @@ impl Handle for Surface { return self.inner.wl_surface.borrow().invalidate_rect(rect); } + fn open_file(&self, options: FileDialogOptions) -> Option { + self.inner.wl_surface.borrow().open_file(options) + } + + fn save_as(&self, options: FileDialogOptions) -> Option { + self.inner.wl_surface.borrow().save_as(options) + } + fn remove_text_field(&self, token: crate::TextFieldToken) { return self.inner.wl_surface.borrow().remove_text_field(token); } diff --git a/druid-shell/src/backend/wayland/surfaces/mod.rs b/druid-shell/src/backend/wayland/surfaces/mod.rs index 30f2882345..4edd710623 100644 --- a/druid-shell/src/backend/wayland/surfaces/mod.rs +++ b/druid-shell/src/backend/wayland/surfaces/mod.rs @@ -6,9 +6,9 @@ use wayland_protocols::xdg_shell::client::xdg_popup; use wayland_protocols::xdg_shell::client::xdg_positioner; use wayland_protocols::xdg_shell::client::xdg_surface; -use crate::kurbo; use crate::Scale; use crate::TextFieldToken; +use crate::{kurbo, FileDialogOptions, FileDialogToken}; use super::error; use super::outputs; @@ -63,6 +63,8 @@ pub trait Handle { fn request_anim_frame(&self); fn invalidate(&self); fn invalidate_rect(&self, rect: kurbo::Rect); + fn open_file(&self, options: FileDialogOptions) -> Option; + fn save_as(&self, options: FileDialogOptions) -> Option; fn remove_text_field(&self, token: TextFieldToken); fn set_focused_text_field(&self, active_field: Option); fn get_idle_handle(&self) -> idle::Handle; diff --git a/druid-shell/src/backend/wayland/surfaces/popup.rs b/druid-shell/src/backend/wayland/surfaces/popup.rs index 05090af61e..99b62bb311 100644 --- a/druid-shell/src/backend/wayland/surfaces/popup.rs +++ b/druid-shell/src/backend/wayland/surfaces/popup.rs @@ -5,6 +5,8 @@ use wayland_protocols::xdg_shell::client::xdg_surface; use crate::kurbo; use crate::window; +use crate::FileDialogOptions; +use crate::FileDialogToken; use super::error; use super::surface; @@ -205,6 +207,14 @@ impl Handle for Surface { self.inner.wl_surface.invalidate_rect(rect) } + fn open_file(&self, options: FileDialogOptions) -> Option { + self.inner.wl_surface.open_file(options) + } + + fn save_as(&self, options: FileDialogOptions) -> Option { + self.inner.wl_surface.save_as(options) + } + fn remove_text_field(&self, token: crate::TextFieldToken) { self.inner.wl_surface.remove_text_field(token) } diff --git a/druid-shell/src/backend/wayland/surfaces/surface.rs b/druid-shell/src/backend/wayland/surfaces/surface.rs index 25e6e67390..5c2aad7a9f 100644 --- a/druid-shell/src/backend/wayland/surfaces/surface.rs +++ b/druid-shell/src/backend/wayland/surfaces/surface.rs @@ -6,8 +6,9 @@ use wayland_protocols::xdg_shell::client::xdg_popup; use wayland_protocols::xdg_shell::client::xdg_positioner; use wayland_protocols::xdg_shell::client::xdg_surface; -use crate::kurbo; +use crate::backend::shared::zbus; use crate::window; +use crate::{kurbo, FileDialogOptions, FileDialogToken}; use crate::{piet::Piet, region::Region, scale::Scale, TextFieldToken}; use super::super::Changed; @@ -190,6 +191,14 @@ impl Handle for Surface { self.inner.invalidate_rect(rect) } + fn open_file(&self, options: FileDialogOptions) -> Option { + self.inner.open_file(options) + } + + fn save_as(&self, options: FileDialogOptions) -> Option { + self.inner.save_as(options) + } + fn run_idle(&self) { self.inner.run_idle(); } @@ -469,6 +478,26 @@ impl Data { self.schedule_deferred_task(DeferredTask::Paint); } + fn open_file(&self, options: FileDialogOptions) -> Option { + // FIXME: current ashpd has issues with wayland versions + //let id = ashpd::WindowIdentifier::from_wayland(&self.wl_surface.borrow()); + Some(zbus::open_file( + Default::default(), + crate::IdleHandle(self.get_idle_handle()), + options, + )) + } + + fn save_as(&self, options: FileDialogOptions) -> Option { + // FIXME: current ashpd has issues with wayland versions + //let id = ashpd::WindowIdentifier::from_wayland(&self.wl_surface.borrow()); + Some(zbus::save_file( + Default::default(), + crate::IdleHandle(self.get_idle_handle()), + options, + )) + } + pub fn schedule_deferred_task(&self, task: DeferredTask) { tracing::trace!("scedule_deferred_task initiated"); self.deferred_tasks.borrow_mut().push_back(task); @@ -625,6 +654,16 @@ impl Handle for Dead { tracing::warn!("invalidate_rect invoked on a dead surface") } + fn open_file(&self, _options: FileDialogOptions) -> Option { + tracing::warn!("open_file invoked on a dead surface"); + None + } + + fn save_as(&self, _options: FileDialogOptions) -> Option { + tracing::warn!("save_as invoked on a dead surface"); + None + } + fn run_idle(&self) { tracing::warn!("run_idle invoked on a dead surface") } diff --git a/druid-shell/src/backend/wayland/window.rs b/druid-shell/src/backend/wayland/window.rs index bc15a50805..99f5a1b450 100644 --- a/druid-shell/src/backend/wayland/window.rs +++ b/druid-shell/src/backend/wayland/window.rs @@ -243,14 +243,12 @@ impl WindowHandle { None } - pub fn open_file(&mut self, _options: FileDialogOptions) -> Option { - tracing::warn!("unimplemented open_file"); - None + pub fn open_file(&mut self, options: FileDialogOptions) -> Option { + self.inner.surface.open_file(options) } - pub fn save_as(&mut self, _options: FileDialogOptions) -> Option { - tracing::warn!("unimplemented save_as"); - None + pub fn save_as(&mut self, options: FileDialogOptions) -> Option { + self.inner.surface.save_as(options) } /// Get a handle that can be used to schedule an idle task. diff --git a/druid-shell/src/backend/x11/mod.rs b/druid-shell/src/backend/x11/mod.rs index ae2e9b0927..6451f6ac52 100644 --- a/druid-shell/src/backend/x11/mod.rs +++ b/druid-shell/src/backend/x11/mod.rs @@ -35,7 +35,6 @@ mod util; pub mod application; pub mod clipboard; -pub mod dialog; pub mod error; pub mod menu; pub mod screen; diff --git a/druid-shell/src/backend/x11/window.rs b/druid-shell/src/backend/x11/window.rs index a57c87539c..31031b803d 100644 --- a/druid-shell/src/backend/x11/window.rs +++ b/druid-shell/src/backend/x11/window.rs @@ -42,8 +42,9 @@ use x11rb::wrapper::ConnectionExt as _; use x11rb::xcb_ffi::XCBConnection; #[cfg(feature = "raw-win-handle")] -use raw_window_handle::{unix::XcbHandle, HasRawWindowHandle, RawWindowHandle}; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XcbHandle}; +use crate::backend::shared::zbus; use crate::backend::shared::Timer; use crate::common_util::IdleCallback; use crate::dialog::FileDialogOptions; @@ -61,7 +62,6 @@ use crate::window::{ use crate::{window, KeyEvent, ScaledArea}; use super::application::Application; -use super::dialog; use super::menu::Menu; /// A version of XCB's `xcb_visualtype_t` struct. This was copied from the [example] in x11rb; it @@ -1798,7 +1798,11 @@ impl WindowHandle { pub fn open_file(&mut self, options: FileDialogOptions) -> Option { if let Some(w) = self.window.upgrade() { if let Some(idle) = self.get_idle_handle() { - Some(dialog::open_file(w.id, idle, options)) + Some(zbus::open_file( + ashpd::WindowIdentifier::from_xid(w.id as u64), + window::IdleHandle(idle), + options, + )) } else { warn!("Couldn't open file because no idle handle available"); None @@ -1811,7 +1815,11 @@ impl WindowHandle { pub fn save_as(&mut self, options: FileDialogOptions) -> Option { if let Some(w) = self.window.upgrade() { if let Some(idle) = self.get_idle_handle() { - Some(dialog::save_file(w.id, idle, options)) + Some(zbus::save_file( + ashpd::WindowIdentifier::from_xid(w.id as u64), + window::IdleHandle(idle), + options, + )) } else { warn!("Couldn't save file because no idle handle available"); None diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 5cb2beed94..593fda1e7e 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -86,7 +86,7 @@ impl TextFieldToken { //NOTE: this has a From impl for construction /// A handle that can enqueue tasks on the window loop. #[derive(Clone)] -pub struct IdleHandle(backend::IdleHandle); +pub struct IdleHandle(pub(crate) backend::IdleHandle); impl IdleHandle { /// Add an idle handler, which is called (once) when the message loop