Skip to content

Commit

Permalink
xdg-dialog: Implement the protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex committed May 31, 2024
1 parent 12bcefc commit 674d9b6
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 0 deletions.
282 changes: 282 additions & 0 deletions src/wayland/shell/xdg/dialog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
//! XDG Dialog Windows
//!
//! This interface allows a compositor to announce support for xdg-dialog toplevel hints, eg. modal hint.
//!
//! ```no_run
//! # extern crate wayland_server;
//! #
//! use smithay::{delegate_xdg_dialog, delegate_xdg_shell};
//! use smithay::wayland::shell::xdg::{ToplevelSurface, XdgShellHandler};
//! # use smithay::utils::Serial;
//! # use smithay::wayland::shell::xdg::{XdgShellState, PopupSurface, PositionerState};
//! # use smithay::reexports::wayland_server::protocol::{wl_seat, wl_surface};
//! use smithay::wayland::shell::xdg::dialog::{XdgDialogState, XdgDialogHandler};
//!
//! # struct State { dialog_state: XdgDialogState, seat_state: SeatState<Self> }
//! # let mut display = wayland_server::Display::<State>::new().unwrap();
//!
//! // Create a dialog state
//! let dialog_state = XdgDialogState::new::<State>(
//! &display.handle(),
//! );
//!
//! // store that state inside your compositor state
//! // ...
//!
//! // implement the necessary traits
//! impl XdgShellHandler for State {
//! # fn xdg_shell_state(&mut self) -> &mut XdgShellState { unimplemented!() }
//! # fn new_toplevel(&mut self, surface: ToplevelSurface) { unimplemented!() }
//! # fn new_popup(
//! # &mut self,
//! # surface: PopupSurface,
//! # positioner: PositionerState,
//! # ) { unimplemented!() }
//! # fn grab(
//! # &mut self,
//! # surface: PopupSurface,
//! # seat: wl_seat::WlSeat,
//! # serial: Serial,
//! # ) { unimplemented!() }
//! # fn reposition_request(
//! # &mut self,
//! # surface: PopupSurface,
//! # positioner: PositionerState,
//! # token: u32,
//! # ) { unimplemented!() }
//! // ...
//! }
//! impl XdgDialogHandler for State {
//! fn modal_changed(&mut self, toplevel: ToplevelSurface, is_modal: bool) { /* ... */ }
//! }
//!
//! use smithay::input::{Seat, SeatState, SeatHandler, pointer::CursorImageStatus};
//!
//! type Target = wl_surface::WlSurface;
//! impl SeatHandler for State {
//! type KeyboardFocus = Target;
//! type PointerFocus = Target;
//! type TouchFocus = Target;
//!
//! fn seat_state(&mut self) -> &mut SeatState<Self> {
//! &mut self.seat_state
//! }
//!
//! fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Target>) {
//! // handle focus changes, if you need to ...
//! }
//! fn cursor_image(&mut self, seat: &Seat<Self>, image: CursorImageStatus) {
//! // handle new images for the cursor ...
//! }
//! }
//! delegate_xdg_shell!(State);
//! delegate_xdg_dialog!(State);
//!
//! // You are ready to go!
use wayland_protocols::xdg::dialog::v1::server::{
xdg_dialog_v1::{self, XdgDialogV1},
xdg_wm_dialog_v1::{self, XdgWmDialogV1},
};
use wayland_server::{
backend::GlobalId, protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle,
GlobalDispatch, New, Resource,
};

use super::{ToplevelSurface, XdgShellHandler};
use crate::wayland::{
compositor,
shell::xdg::{XdgShellSurfaceUserData, XdgToplevelSurfaceData},
};

/// Delegate type for handling xdg dialog events.
#[derive(Debug)]
pub struct XdgDialogState {
global: GlobalId,
}

impl XdgDialogState {
/// Creates a new delegate type for handling xdg dialog events.
///
/// A global id is also returned to allow destroying the global in the future.
pub fn new<D>(display: &DisplayHandle) -> XdgDialogState
where
D: GlobalDispatch<XdgWmDialogV1, ()> + Dispatch<XdgWmDialogV1, ()> + 'static,
{
let global = display.create_global::<D, XdgWmDialogV1, _>(1, ());
XdgDialogState { global }
}

/// Returns the xdg-wm-dialog global.
pub fn global(&self) -> GlobalId {
self.global.clone()
}
}

/// Handler trait for xdg dialog events.
pub trait XdgDialogHandler:
XdgShellHandler
+ GlobalDispatch<XdgWmDialogV1, ()>
+ Dispatch<XdgWmDialogV1, ()>
+ Dispatch<XdgDialogV1, ToplevelSurface>
{
/// Does client want to be presented as a modal dialog
fn modal_changed(&mut self, toplevel: ToplevelSurface, is_modal: bool) {
let _ = toplevel;
let _ = is_modal;
}
}

/// Macro to delegate implementation of the xdg dialog to [`XdgDialogState`].
///
/// You must also implement [`XdgDialogHandler`] to use this.
#[macro_export]
macro_rules! delegate_xdg_dialog {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
$crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
$crate::reexports::wayland_protocols::xdg::dialog::v1::server::xdg_wm_dialog_v1::XdgWmDialogV1: ()
] => $crate::wayland::shell::xdg::dialog::XdgDialogState);

$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
$crate::reexports::wayland_protocols::xdg::dialog::v1::server::xdg_wm_dialog_v1::XdgWmDialogV1: ()
] => $crate::wayland::shell::xdg::dialog::XdgDialogState);
$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
$crate::reexports::wayland_protocols::xdg::dialog::v1::server::xdg_dialog_v1::XdgDialogV1: $crate::wayland::shell::xdg::ToplevelSurface
] => $crate::wayland::shell::xdg::dialog::XdgDialogState);
};
}

// xdg_wm_dialog_v1

impl<D: XdgDialogHandler> GlobalDispatch<XdgWmDialogV1, (), D> for XdgDialogState {
fn bind(
_: &mut D,
_: &DisplayHandle,
_: &Client,
resource: New<XdgWmDialogV1>,
_: &(),
data_init: &mut DataInit<'_, D>,
) {
data_init.init(resource, ());
}
}

impl<D: XdgDialogHandler> Dispatch<XdgWmDialogV1, (), D> for XdgDialogState {
fn request(
state: &mut D,
_: &Client,
resource: &XdgWmDialogV1,
request: xdg_wm_dialog_v1::Request,
_: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
use xdg_wm_dialog_v1::Request;

match request {
Request::GetXdgDialog { id, toplevel } => {
let data = toplevel.data::<XdgShellSurfaceUserData>().unwrap();

let mut dialog_guard = data.dialog.lock().unwrap();

if dialog_guard.is_some() {
resource.post_error(
xdg_wm_dialog_v1::Error::AlreadyUsed,
"toplevel dialog is already constructed",
);
return;
}

let toplevel = state.xdg_shell_state().get_toplevel(&toplevel).unwrap();
let toplevel_dialog = data_init.init(id, toplevel.clone());

*dialog_guard = Some(toplevel_dialog);
drop(dialog_guard);
}

Request::Destroy => {}

_ => unreachable!(),
}
}
}

// xdg_dialog_v1

impl<D: XdgDialogHandler> Dispatch<XdgDialogV1, ToplevelSurface, D> for XdgDialogState {
fn request(
state: &mut D,
_: &Client,
_: &XdgDialogV1,
request: xdg_dialog_v1::Request,
toplevel: &ToplevelSurface,
_dh: &DisplayHandle,
_: &mut DataInit<'_, D>,
) {
use xdg_dialog_v1::Request;

match request {
Request::SetModal => {
if set_modal(toplevel.wl_surface()) {
state.modal_changed(toplevel.clone(), true);
}
}

Request::UnsetModal => {
if unset_modal(toplevel.wl_surface()) {
state.modal_changed(toplevel.clone(), false);
}
}

Request::Destroy => {
if unset_modal(toplevel.wl_surface()) {
state.modal_changed(toplevel.clone(), false);
}

if let Some(data) = toplevel.xdg_toplevel().data::<XdgShellSurfaceUserData>() {
data.dialog.lock().unwrap().take();
}
}

_ => unreachable!(),
}
}
}

/// Returns true if changed
fn set_modal(wl_surface: &WlSurface) -> bool {
compositor::with_states(wl_surface, |states| {
let role = &mut states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();

if role.modal {
false
} else {
role.modal = true;
true
}
})
}

/// Returns true if changed
fn unset_modal(wl_surface: &WlSurface) -> bool {
compositor::with_states(wl_surface, |states| {
let role = &mut states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap();

if role.modal {
role.modal = false;
true
} else {
false
}
})
}
4 changes: 4 additions & 0 deletions src/wayland/shell/xdg/handlers/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{

use wayland_protocols::{
xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1,
xdg::dialog::v1::server::xdg_dialog_v1,
xdg::shell::server::{
xdg_popup::XdgPopup, xdg_surface, xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel, xdg_wm_base,
},
Expand Down Expand Up @@ -133,6 +134,7 @@ where
xdg_surface: xdg_surface.clone(),
wm_base: data.wm_base.clone(),
decoration: Default::default(),
dialog: Default::default(),
alive_tracker: Default::default(),
},
);
Expand Down Expand Up @@ -207,6 +209,7 @@ where
xdg_surface: xdg_surface.clone(),
wm_base: data.wm_base.clone(),
decoration: Default::default(),
dialog: Default::default(),
alive_tracker: Default::default(),
},
);
Expand Down Expand Up @@ -329,6 +332,7 @@ pub struct XdgShellSurfaceUserData {
pub(crate) wm_base: xdg_wm_base::XdgWmBase,
pub(crate) xdg_surface: xdg_surface::XdgSurface,
pub(crate) decoration: Mutex<Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>>,
pub(crate) dialog: Mutex<Option<xdg_dialog_v1::XdgDialogV1>>,

pub(crate) alive_tracker: AliveTracker,
}
Expand Down
7 changes: 7 additions & 0 deletions src/wayland/shell/xdg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ use wayland_server::{
use super::PingError;

pub mod decoration;
pub mod dialog;

// handlers for the xdg_shell protocol
pub(super) mod handlers;
Expand Down Expand Up @@ -337,6 +338,12 @@ xdg_role!(
/// should be brought to front. If the parent is focused
/// all of it's child should be brought to front.
pub parent: Option<wl_surface::WlSurface>,
/// Hints that the dialog has "modal" behavior.
/// Modal dialogs typically require to be fully addressed by the user (i.e. closed)
/// before resuming interaction with the parent toplevel, and may require a distinct presentation.
///
/// This interface should have no effect on toplevels that are not attached to a parent toplevel.
pub modal: bool,
/// Holds the optional title the client has set for
/// this toplevel. For example a web-browser will most likely
/// set this to include the current uri.
Expand Down

0 comments on commit 674d9b6

Please sign in to comment.