Skip to content

Commit

Permalink
Uses our Wayland event queue to construct WindowIdentifier (#1228)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Jan 5, 2025
1 parent 99b38cf commit b6d26c9
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 75 deletions.
4 changes: 4 additions & 0 deletions gui/src/ui/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
53 changes: 53 additions & 0 deletions gui/src/ui/backend/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -136,6 +159,36 @@ impl Dispatch<XdgWmDialogV1, ()> for WaylandState {
}
}

impl Dispatch<ZxdgExporterV2, ()> for WaylandState {
fn event(
_: &mut Self,
_: &ZxdgExporterV2,
_: <ZxdgExporterV2 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}

impl Dispatch<ZxdgExportedV2, Arc<OnceLock<String>>> for WaylandState {
fn event(
_: &mut Self,
_: &ZxdgExportedV2,
event: <ZxdgExportedV2 as Proxy>::Event,
data: &Arc<OnceLock<String>>,
_: &Connection,
_: &QueueHandle<Self>,
) {
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);

Expand Down
185 changes: 116 additions & 69 deletions gui/src/ui/linux/dialogs.rs
Original file line number Diff line number Diff line change
@@ -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<T: ComponentHandle>(
pub async fn open_file<T: WinitWindow>(
parent: &T,
title: impl AsRef<str>,
ty: FileType,
) -> Option<PathBuf> {
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<T: ComponentHandle>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
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<T: WinitWindow>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
// 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<R, P, F>(parent: &P, f: F) -> R::Output
fn get_parent_id<P>(parent: &P) -> Parent
where
R: Future,
P: ComponentHandle,
F: FnOnce(Option<WindowIdentifier>) -> 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::<SlintBackend>().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::<String>::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<ZxdgExportedV2>,
}
6 changes: 3 additions & 3 deletions gui/src/ui/macos/dialogs.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::rt::WinitWindow;
use crate::ui::FileType;
use slint::ComponentHandle;
use std::path::PathBuf;

pub async fn open_file<T: ComponentHandle>(
pub async fn open_file<T: WinitWindow>(
parent: &T,
title: impl AsRef<str>,
ty: FileType,
) -> Option<PathBuf> {
todo!();
}

pub async fn open_dir<T: ComponentHandle>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
pub async fn open_dir<T: WinitWindow>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
todo!()
}
6 changes: 3 additions & 3 deletions gui/src/ui/windows/dialogs.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::rt::WinitWindow;
use crate::ui::FileType;
use slint::ComponentHandle;
use std::path::PathBuf;

pub async fn open_file<T: ComponentHandle>(
pub async fn open_file<T: WinitWindow>(
parent: &T,
title: impl AsRef<str>,
ty: FileType,
) -> Option<PathBuf> {
todo!();
}

pub async fn open_dir<T: ComponentHandle>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
pub async fn open_dir<T: WinitWindow>(parent: &T, title: impl AsRef<str>) -> Option<PathBuf> {
todo!()
}

0 comments on commit b6d26c9

Please sign in to comment.