Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements PlatformExt::set_modal for Wayland #1237

Merged
merged 1 commit into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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