Skip to content

Commit

Permalink
Implements PlatformExt::set_modal for Wayland (#1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
ultimaweapon authored Jan 11, 2025
1 parent c7fce0b commit e4852da
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 42 deletions.
12 changes: 5 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ panic = "abort"
[profile.release]
panic = "abort"
lto = true

[patch.crates-io]
winit = { git = "https://github.com/obhq/winit.git" }
2 changes: 2 additions & 0 deletions gui/src/rt/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ pub trait WindowHandler {
pub trait WinitWindow {
fn id(&self) -> WindowId;
fn handle(&self) -> impl HasWindowHandle + '_;
#[cfg(target_os = "linux")]
fn xdg_toplevel(&self) -> *mut std::ffi::c_void;
}
17 changes: 17 additions & 0 deletions gui/src/ui/backend/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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_dialog_v1::XdgDialogV1;
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;
Expand Down Expand Up @@ -107,6 +108,10 @@ impl WaylandState {
pub fn xdg_exporter(&self) -> &ZxdgExporterV2 {
&self.xdg_exporter
}

pub fn xdg_dialog(&self) -> &XdgWmDialogV1 {
&self.xdg_dialog
}
}

impl Drop for WaylandState {
Expand Down Expand Up @@ -159,6 +164,18 @@ impl Dispatch<XdgWmDialogV1, ()> for WaylandState {
}
}

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

impl Dispatch<ZxdgExporterV2, ()> for WaylandState {
fn event(
_: &mut Self,
Expand Down
29 changes: 21 additions & 8 deletions gui/src/ui/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
pub use self::dialogs::*;

use super::{Modal, PlatformExt, SlintBackend};
use self::modal::Modal;
use super::{PlatformExt, SlintBackend};
use crate::rt::{global, WinitWindow};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use thiserror::Error;

mod dialogs;
mod modal;
mod wayland;

impl<T: WinitWindow> PlatformExt for T {
type Modal<'a, P>
= Modal<'a, Self, P>
where
P: WinitWindow + 'a;

fn set_center(&self) -> Result<(), PlatformError> {
let win = self.handle();
let win = win.window_handle().unwrap();
Expand All @@ -29,19 +36,25 @@ impl<T: WinitWindow> PlatformExt for T {
P: WinitWindow,
Self: Sized,
{
let win = self.handle();
let back = global::<SlintBackend>().unwrap();

if let Some(v) = back.wayland() {
self::wayland::set_modal(v, win, parent.handle())?;
let wayland = if let Some(v) = back.wayland() {
// SAFETY: The Modal struct we construct below force the parent to outlive the modal
// window.
unsafe { self::wayland::set_modal(v, &self, parent).map(Some)? }
} else {
todo!()
}
};

Ok(Modal::new(self, parent))
Ok(Modal::new(self, parent, wayland))
}
}

/// Linux-specific error for [`PlatformExt`].
#[derive(Debug, Error)]
pub enum PlatformError {}
pub enum PlatformError {
#[error("couldn't create xdg_dialog_v1")]
CreateXdgDialogV1(#[source] wayland_client::DispatchError),

#[error("couldn't set window modal")]
SetModal(#[source] wayland_client::DispatchError),
}
39 changes: 39 additions & 0 deletions gui/src/ui/linux/modal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::rt::{block, Blocker, WinitWindow};
use std::ops::Deref;
use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;

/// Encapsulates a modal window and its parent.
///
/// This struct forces the modal window to be dropped before its parent.
pub struct Modal<'a, W, P: WinitWindow> {
window: W,
wayland: Option<XdgDialogV1>,
#[allow(dead_code)]
blocker: Blocker<'a, P>,
}

impl<'a, W, P: WinitWindow> Modal<'a, W, P> {
pub fn new(window: W, parent: &'a P, wayland: Option<XdgDialogV1>) -> Self {
Self {
window,
wayland,
blocker: block(parent),
}
}
}

impl<'a, W, P: WinitWindow> Drop for Modal<'a, W, P> {
fn drop(&mut self) {
if let Some(v) = self.wayland.take() {
v.destroy();
}
}
}

impl<'a, W, P: WinitWindow> Deref for Modal<'a, W, P> {
type Target = W;

fn deref(&self) -> &Self::Target {
&self.window
}
}
58 changes: 45 additions & 13 deletions gui/src/ui/linux/wayland.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
use super::PlatformError;
use crate::rt::WinitWindow;
use crate::ui::Wayland;
use raw_window_handle::HasWindowHandle;

pub fn set_modal(
_: &Wayland,
_: impl HasWindowHandle,
_: impl HasWindowHandle,
) -> Result<(), PlatformError> {
// TODO: We need xdg_toplevel from the target window to use xdg_wm_dialog_v1::get_xdg_dialog.
// AFAIK the only way to get it is using xdg_surface::get_toplevel. The problem is
// xdg_wm_base::get_xdg_surface that return xdg_surface can be called only once per wl_surface
// and this call already done by winit. So we need winit to expose either xdg_surface or
// xdg_toplevel in order for us to implement this.
Ok(())
use wayland_backend::sys::client::ObjectId;
use wayland_client::Proxy;
use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1;
use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel;

/// # Safety
/// `parent` must outlive `target`.
pub unsafe fn set_modal(
wayland: &Wayland,
target: &impl WinitWindow,
parent: &impl WinitWindow,
) -> Result<XdgDialogV1, PlatformError> {
// Get xdg_toplevel for parent.
let mut queue = wayland.queue().borrow_mut();
let mut state = wayland.state().borrow_mut();
let qh = queue.handle();
let parent = get_xdg_toplevel(wayland, parent);

// Get xdg_dialog_v1.
let target = get_xdg_toplevel(wayland, target);
let dialog = state.xdg_dialog().get_xdg_dialog(&target, &qh, ());

queue
.roundtrip(&mut state)
.map_err(PlatformError::CreateXdgDialogV1)?;

// Set modal.
target.set_parent(Some(&parent));
dialog.set_modal();

queue
.roundtrip(&mut state)
.map_err(PlatformError::SetModal)?;

Ok(dialog)
}

/// # Safety
/// `win` must outlive the returned [`XdgToplevel`].
unsafe fn get_xdg_toplevel(wayland: &Wayland, win: &impl WinitWindow) -> XdgToplevel {
let obj = win.xdg_toplevel();
let obj = ObjectId::from_ptr(XdgToplevel::interface(), obj.cast()).unwrap();

XdgToplevel::from_id(wayland.connection(), obj).unwrap()
}
9 changes: 8 additions & 1 deletion gui/src/ui/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub use self::dialogs::*;

use self::modal::Modal;
use self::view::with_window;
use super::{Modal, PlatformExt};
use super::PlatformExt;
use crate::rt::WinitWindow;
use block::ConcreteBlock;
use objc::{msg_send, sel, sel_impl};
Expand All @@ -10,9 +11,15 @@ use std::ops::Deref;
use thiserror::Error;

mod dialogs;
mod modal;
mod view;

impl<T: WinitWindow> PlatformExt for T {
type Modal<'a, P>
= Modal<'a, Self, P>
where
P: WinitWindow + 'a;

fn set_center(&self) -> Result<(), PlatformError> {
with_window::<()>(self.handle(), |win| unsafe { msg_send![win, center] });

Expand Down
File renamed without changes.
18 changes: 15 additions & 3 deletions gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub use self::backend::*;
pub use self::modal::*;
pub use self::os::*;
pub use self::profile::*;

Expand All @@ -8,10 +7,10 @@ use i_slint_core::window::WindowInner;
use raw_window_handle::HasWindowHandle;
use slint::{ComponentHandle, SharedString, Weak};
use std::future::Future;
use std::ops::Deref;
use winit::window::WindowId;

mod backend;
mod modal;
#[cfg_attr(target_os = "linux", path = "linux/mod.rs")]
#[cfg_attr(target_os = "macos", path = "macos/mod.rs")]
#[cfg_attr(target_os = "windows", path = "windows/mod.rs")]
Expand Down Expand Up @@ -63,16 +62,29 @@ impl<T: ComponentHandle> WinitWindow for T {
fn handle(&self) -> impl HasWindowHandle + '_ {
self.window().window_handle()
}

#[cfg(target_os = "linux")]
fn xdg_toplevel(&self) -> *mut std::ffi::c_void {
use winit::platform::wayland::WindowExtWayland;

let win = WindowInner::from_pub(self.window()).window_adapter();

Window::from_adapter(win.as_ref()).winit().xdg_toplevel()
}
}

/// Provides platform-specific methods to operate on [`WinitWindow`].
pub trait PlatformExt: WinitWindow {
type Modal<'a, P>: Deref<Target = Self>
where
P: WinitWindow + 'a;

/// Center window on the screen.
///
/// For [`slint::Window`] this need to call after [`slint::Window::show()`] otherwise it won't
/// work on macOS.
fn set_center(&self) -> Result<(), PlatformError>;
fn set_modal<P>(self, parent: &P) -> Result<Modal<Self, P>, PlatformError>
fn set_modal<P>(self, parent: &P) -> Result<Self::Modal<'_, P>, PlatformError>
where
P: WinitWindow,
Self: Sized;
Expand Down
28 changes: 18 additions & 10 deletions gui/src/ui/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub use self::dialogs::*;

use super::{Modal, PlatformExt};
use self::modal::Modal;
use super::PlatformExt;
use crate::rt::WinitWindow;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use std::io::Error;
use std::mem::zeroed;
use thiserror::Error;
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::UI::WindowsAndMessaging::{
Expand All @@ -12,8 +14,14 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
};

mod dialogs;
mod modal;

impl<T: WinitWindow> PlatformExt for T {
type Modal<'a, P>
= Modal<'a, Self, P>
where
P: WinitWindow + 'a;

fn set_center(&self) -> Result<(), PlatformError> {
// Get HWND.
let win = self.handle();
Expand All @@ -22,16 +30,16 @@ impl<T: WinitWindow> PlatformExt for T {
unreachable!();
};

unsafe {
let hwnd = win.hwnd.get() as HWND;
let mut rect = std::mem::zeroed();

let ret = GetWindowRect(hwnd, &mut rect);
// Get window rectangle.
let win = win.hwnd.get() as HWND;
let mut rect = unsafe { zeroed() };
let ret = unsafe { GetWindowRect(win, &mut rect) };

if ret == 0 {
return Err(PlatformError::GetWindowRect(Error::last_os_error()));
}
if ret == 0 {
return Err(PlatformError::GetWindowRect(Error::last_os_error()));
}

unsafe {
let win_width = rect.right - rect.left;
let win_height = rect.bottom - rect.top;

Expand All @@ -48,7 +56,7 @@ impl<T: WinitWindow> PlatformExt for T {
}

let ret = SetWindowPos(
hwnd,
win,
HWND_TOP,
(screen_width - win_width) / 2,
(screen_height - win_height) / 2,
Expand Down
Loading

0 comments on commit e4852da

Please sign in to comment.