diff --git a/gui/src/ui/backend/mod.rs b/gui/src/ui/backend/mod.rs index de42b8def..6e70cc2ae 100644 --- a/gui/src/ui/backend/mod.rs +++ b/gui/src/ui/backend/mod.rs @@ -194,6 +194,10 @@ pub enum BackendError { #[error("couldn't bind xdg_wm_dialog_v1")] BindXdgWmDialogV1(#[source] wayland_client::globals::BindError), + #[cfg(target_os = "linux")] + #[error("couldn't bind zxdg_exporter_v2")] + BindZxdgExporterV2(#[source] wayland_client::globals::BindError), + #[cfg(target_os = "linux")] #[error("couldn't dispatch Wayland request")] DispatchWayland(#[source] wayland_client::DispatchError), diff --git a/gui/src/ui/backend/wayland.rs b/gui/src/ui/backend/wayland.rs index 0fbc584b1..dfc1508a3 100644 --- a/gui/src/ui/backend/wayland.rs +++ b/gui/src/ui/backend/wayland.rs @@ -3,12 +3,15 @@ use std::cell::RefCell; use std::future::Future; use std::hint::unreachable_unchecked; use std::pin::Pin; +use std::sync::{Arc, OnceLock}; use std::task::{ready, Context, Poll}; use wayland_backend::sys::client::Backend; use wayland_client::globals::{registry_queue_init, GlobalListContents}; use wayland_client::protocol::wl_registry::WlRegistry; use wayland_client::{Connection, Dispatch, EventQueue, Proxy, QueueHandle}; use wayland_protocols::xdg::dialog::v1::client::xdg_wm_dialog_v1::XdgWmDialogV1; +use wayland_protocols::xdg::foreign::zv2::client::zxdg_exported_v2::ZxdgExportedV2; +use wayland_protocols::xdg::foreign::zv2::client::zxdg_exporter_v2::ZxdgExporterV2; use wayland_protocols::xdg::shell::client::xdg_wm_base::XdgWmBase; /// Contains global objects for Wayland. @@ -47,8 +50,20 @@ impl Wayland { } }; + // Get zxdg_exporter_v2. + let v = ZxdgExporterV2::interface().version; + let xdg_exporter: ZxdgExporterV2 = match globals.bind(&qh, v..=v, ()) { + Ok(v) => v, + Err(e) => { + xdg_dialog.destroy(); + xdg_base.destroy(); + return Err(BackendError::BindZxdgExporterV2(e)); + } + }; + // Dispatch initial requests. let mut state = WaylandState { + xdg_exporter, xdg_dialog, xdg_base, }; @@ -83,12 +98,20 @@ impl Wayland { /// Provides [`Dispatch`] implementation to handle Wayland events. pub struct WaylandState { + xdg_exporter: ZxdgExporterV2, xdg_dialog: XdgWmDialogV1, xdg_base: XdgWmBase, } +impl WaylandState { + pub fn xdg_exporter(&self) -> &ZxdgExporterV2 { + &self.xdg_exporter + } +} + impl Drop for WaylandState { fn drop(&mut self) { + self.xdg_exporter.destroy(); self.xdg_dialog.destroy(); self.xdg_base.destroy(); } @@ -136,6 +159,36 @@ impl Dispatch for WaylandState { } } +impl Dispatch for WaylandState { + fn event( + _: &mut Self, + _: &ZxdgExporterV2, + _: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + } +} + +impl Dispatch>> for WaylandState { + fn event( + _: &mut Self, + _: &ZxdgExportedV2, + event: ::Event, + data: &Arc>, + _: &Connection, + _: &QueueHandle, + ) { + use wayland_protocols::xdg::foreign::zv2::client::zxdg_exported_v2::Event; + + match event { + Event::Handle { handle } => data.set(handle).unwrap(), + _ => (), + } + } +} + /// Implementation of [`Future`] to dispatch pending events for our queue. struct Run<'a>(&'a Wayland); diff --git a/gui/src/ui/linux/dialogs.rs b/gui/src/ui/linux/dialogs.rs index bc6cf3f48..273a0a43b 100644 --- a/gui/src/ui/linux/dialogs.rs +++ b/gui/src/ui/linux/dialogs.rs @@ -1,85 +1,132 @@ -use crate::ui::FileType; +use crate::rt::{global, WinitWindow}; +use crate::ui::{FileType, SlintBackend}; use ashpd::desktop::file_chooser::{FileFilter, SelectedFiles}; use ashpd::desktop::ResponseError; use ashpd::WindowIdentifier; -use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; -use slint::ComponentHandle; -use std::future::Future; +use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use std::path::PathBuf; +use std::sync::{Arc, OnceLock}; +use wayland_backend::sys::client::ObjectId; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::Proxy; +use wayland_protocols::xdg::foreign::zv2::client::zxdg_exported_v2::ZxdgExportedV2; -pub async fn open_file( +pub async fn open_file( parent: &T, title: impl AsRef, ty: FileType, ) -> Option { - with_window_id(parent, move |parent| async move { - // Build filter. - let filter = match ty { - FileType::Firmware => FileFilter::new("Firmware Dump").glob("*.obf"), - }; - - // Send the request - let resp = match SelectedFiles::open_file() - .identifier(parent) - .title(title.as_ref()) - .modal(true) - .filter(filter) - .send() - .await - .unwrap() - .response() - { - Ok(v) => v, - Err(ashpd::Error::Response(ResponseError::Cancelled)) => return None, - Err(_) => unimplemented!(), - }; - - // Get file path. - Some(resp.uris().first().unwrap().to_file_path().unwrap()) - }) - .await + // Build filter. + let filter = match ty { + FileType::Firmware => FileFilter::new("Firmware Dump").glob("*.obf"), + }; + + // Send the request. + let parent = get_parent_id(parent); + let req = SelectedFiles::open_file() + .identifier(parent.id) + .title(title.as_ref()) + .modal(true) + .filter(filter) + .send() + .await; + + if let Some(v) = parent.surface { + v.destroy(); + } + + // Get response. + let resp = match req.unwrap().response() { + Ok(v) => v, + Err(ashpd::Error::Response(ResponseError::Cancelled)) => return None, + Err(_) => unimplemented!(), + }; + + // Get file path. + Some(resp.uris().first().unwrap().to_file_path().unwrap()) } -pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { - with_window_id(parent, move |parent| async move { - // Send the request - let resp = match SelectedFiles::open_file() - .identifier(parent) - .title(title.as_ref()) - .modal(true) - .directory(true) - .send() - .await - .unwrap() - .response() - { - Ok(v) => v, - Err(ashpd::Error::Response(ResponseError::Cancelled)) => return None, - Err(_) => unimplemented!(), - }; - - // Get directory path. - Some(resp.uris().first().unwrap().to_file_path().unwrap()) - }) - .await +pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { + // Send the request + let parent = get_parent_id(parent); + let req = SelectedFiles::open_file() + .identifier(parent.id) + .title(title.as_ref()) + .modal(true) + .directory(true) + .send() + .await; + + if let Some(v) = parent.surface { + v.destroy(); + } + + // Get response. + let resp = match req.unwrap().response() { + Ok(v) => v, + Err(ashpd::Error::Response(ResponseError::Cancelled)) => return None, + Err(_) => unimplemented!(), + }; + + // Get directory path. + Some(resp.uris().first().unwrap().to_file_path().unwrap()) } -async fn with_window_id(parent: &P, f: F) -> R::Output +fn get_parent_id

(parent: &P) -> Parent where - R: Future, - P: ComponentHandle, - F: FnOnce(Option) -> R, + P: WinitWindow, { - // Get display handle. All local variable here must not get dropped until the operation is - // complete. - let parent = parent.window().window_handle(); - let display = parent.display_handle(); - let display = display.as_ref().map(|v| v.as_ref()).ok(); - - // Get parent handle. - let parent = parent.window_handle(); - let parent = parent.as_ref().map(|v| v.as_ref()).unwrap(); - let parent = WindowIdentifier::from_raw_handle(parent, display).await; - - f(Some(parent)).await + // Check window type. + let parent = parent.handle(); + let parent = parent.window_handle().unwrap(); + let surface = match parent.as_ref() { + RawWindowHandle::Xlib(v) => { + return Parent { + id: WindowIdentifier::from_xid(v.window), + surface: None, + } + } + RawWindowHandle::Xcb(v) => { + return Parent { + id: WindowIdentifier::from_xid(v.window.get().into()), + surface: None, + } + } + RawWindowHandle::Wayland(v) => v.surface.as_ptr(), + RawWindowHandle::Drm(_) | RawWindowHandle::Gbm(_) => unimplemented!(), + _ => unreachable!(), + }; + + // Get WlSurface. + let backend = global::().unwrap(); + let wayland = backend.wayland().unwrap(); + let surface = unsafe { ObjectId::from_ptr(WlSurface::interface(), surface.cast()).unwrap() }; + let surface = WlSurface::from_id(wayland.connection(), surface).unwrap(); + + // Export surface. + let mut queue = wayland.queue().borrow_mut(); + let mut state = wayland.state().borrow_mut(); + let qh = queue.handle(); + let handle = Arc::new(OnceLock::::new()); + let surface = state + .xdg_exporter() + .export_toplevel(&surface, &qh, handle.clone()); + + queue.roundtrip(&mut state).unwrap(); + + // Construct WindowIdentifier. We need some hack here since we can't construct + // WindowIdentifier::Wayland. + let id = format!("wayland:{}", handle.get().unwrap()); + let id = WindowIdentifier::X11(id.parse().unwrap()); + + Parent { + id, + surface: Some(surface), + } +} + +/// Encapsulates [`WindowIdentifier`] for parent window. +struct Parent { + id: WindowIdentifier, + surface: Option, } diff --git a/gui/src/ui/macos/dialogs.rs b/gui/src/ui/macos/dialogs.rs index a135d81d2..21af9f2ba 100644 --- a/gui/src/ui/macos/dialogs.rs +++ b/gui/src/ui/macos/dialogs.rs @@ -1,8 +1,8 @@ +use crate::rt::WinitWindow; use crate::ui::FileType; -use slint::ComponentHandle; use std::path::PathBuf; -pub async fn open_file( +pub async fn open_file( parent: &T, title: impl AsRef, ty: FileType, @@ -10,6 +10,6 @@ pub async fn open_file( todo!(); } -pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { +pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { todo!() } diff --git a/gui/src/ui/windows/dialogs.rs b/gui/src/ui/windows/dialogs.rs index a135d81d2..21af9f2ba 100644 --- a/gui/src/ui/windows/dialogs.rs +++ b/gui/src/ui/windows/dialogs.rs @@ -1,8 +1,8 @@ +use crate::rt::WinitWindow; use crate::ui::FileType; -use slint::ComponentHandle; use std::path::PathBuf; -pub async fn open_file( +pub async fn open_file( parent: &T, title: impl AsRef, ty: FileType, @@ -10,6 +10,6 @@ pub async fn open_file( todo!(); } -pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { +pub async fn open_dir(parent: &T, title: impl AsRef) -> Option { todo!() }