wayland.shell: split wl and xdg & port xdg
This commit is contained in:
parent
422e8b33e0
commit
b8d4b17a8a
|
@ -38,7 +38,7 @@ slog-async = "2.2"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium", "backend_session_logind"]
|
default = ["backend_winit", "backend_drm", "backend_libinput", "backend_udev", "renderer_glium"]
|
||||||
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"]
|
backend_winit = ["winit", "wayland-server/dlopen", "wayland-client/dlopen"]
|
||||||
backend_drm = ["drm", "gbm"]
|
backend_drm = ["drm", "gbm"]
|
||||||
backend_libinput = ["input"]
|
backend_libinput = ["input"]
|
||||||
|
|
|
@ -22,4 +22,4 @@ pub mod compositor;
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod seat;
|
pub mod seat;
|
||||||
pub mod shm;
|
pub mod shm;
|
||||||
//pub mod shell;
|
pub mod shell;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -1,940 +1,2 @@
|
||||||
//! Utilities for handling shell surfaces, toplevel and popups
|
pub mod legacy;
|
||||||
//!
|
pub mod xdg;
|
||||||
//! This module provides automatic handling of shell surfaces objects, by being registered
|
|
||||||
//! as a global handler for `wl_shell` and `xdg_shell`.
|
|
||||||
//!
|
|
||||||
//! ## Why use this implementation
|
|
||||||
//!
|
|
||||||
//! This implementation can track for you the various shell surfaces defined by the
|
|
||||||
//! clients by handling the `xdg_shell` protocol. It also includes a compatibility
|
|
||||||
//! layer for the deprecated `wl_shell` global.
|
|
||||||
//!
|
|
||||||
//! It allows you to easily access a list of all shell surfaces defined by your clients
|
|
||||||
//! access their associated metadata and underlying `wl_surface`s.
|
|
||||||
//!
|
|
||||||
//! This handler only handles the protocol exchanges with the client to present you the
|
|
||||||
//! information in a coherent and relatively easy to use maneer. All the actual drawing
|
|
||||||
//! and positioning logic of windows is out of its scope.
|
|
||||||
//!
|
|
||||||
//! ## How to use it
|
|
||||||
//!
|
|
||||||
//! ### Initialization
|
|
||||||
//!
|
|
||||||
//! To initialize this handler, simple use the `shell_init` function provided in this
|
|
||||||
//! module. You will need to provide it the `CompositorToken` you retrieved from an
|
|
||||||
//! instanciation of the `CompositorHandler` provided by smithay.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! # extern crate wayland_server;
|
|
||||||
//! # #[macro_use] extern crate smithay;
|
|
||||||
//! # extern crate wayland_protocols;
|
|
||||||
//! #
|
|
||||||
//! use smithay::wayland::compositor::roles::*;
|
|
||||||
//! use smithay::wayland::compositor::CompositorToken;
|
|
||||||
//! use smithay::wayland::shell::{shell_init, ShellSurfaceRole, ShellSurfaceUserImplementation};
|
|
||||||
//! use wayland_server::protocol::wl_shell::WlShell;
|
|
||||||
//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6;
|
|
||||||
//! use wayland_server::{EventLoop, EventLoopHandle};
|
|
||||||
//! # use wayland_server::protocol::{wl_seat, wl_output};
|
|
||||||
//! # use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_toplevel_v6;
|
|
||||||
//! # #[derive(Default)] struct MySurfaceData;
|
|
||||||
//!
|
|
||||||
//! // define the roles type. You need to integrate the ShellSurface role:
|
|
||||||
//! define_roles!(MyRoles =>
|
|
||||||
//! [ShellSurface, ShellSurfaceRole]
|
|
||||||
//! );
|
|
||||||
//!
|
|
||||||
//! // define the metadata you want associated with the shell clients
|
|
||||||
//! #[derive(Default)]
|
|
||||||
//! struct MyShellData {
|
|
||||||
//! /* ... */
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! # fn main() {
|
|
||||||
//! # let (_display, mut event_loop) = wayland_server::create_display();
|
|
||||||
//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<(), MyRoles, _, _>(
|
|
||||||
//! # &mut event_loop,
|
|
||||||
//! # unimplemented!(),
|
|
||||||
//! # (),
|
|
||||||
//! # None
|
|
||||||
//! # );
|
|
||||||
//! // define your implementation for shell
|
|
||||||
//! let my_shell_implementation = ShellSurfaceUserImplementation {
|
|
||||||
//! new_client: |evlh, idata, client| { unimplemented!() },
|
|
||||||
//! client_pong: |evlh, idata, client| { unimplemented!() },
|
|
||||||
//! new_toplevel: |evlh, idata, toplevel| { unimplemented!() },
|
|
||||||
//! new_popup: |evlh, idata, popup| { unimplemented!() },
|
|
||||||
//! move_: |evlh, idata, toplevel, seat, serial| { unimplemented!() },
|
|
||||||
//! resize: |evlh, idata, toplevel, seat, serial, edges| { unimplemented!() },
|
|
||||||
//! grab: |evlh, idata, popup, seat, serial| { unimplemented!() },
|
|
||||||
//! change_display_state: |evlh, idata, toplevel, maximized, minimized, fullscreen, output| {
|
|
||||||
//! unimplemented!()
|
|
||||||
//! },
|
|
||||||
//! show_window_menu: |evlh, idata, toplevel, seat, serial, x, y| { unimplemented!() },
|
|
||||||
//! };
|
|
||||||
//!
|
|
||||||
//! // define your implementation data
|
|
||||||
//! let my_shell_implementation_data = ();
|
|
||||||
//!
|
|
||||||
//! let (shell_state_token, _, _) = shell_init::<_, _, _, _, MyShellData, _>(
|
|
||||||
//! &mut event_loop,
|
|
||||||
//! compositor_token, // token from the compositor implementation
|
|
||||||
//! my_shell_implementation, // instance of shell::ShellSurfaceUserImplementation
|
|
||||||
//! my_shell_implementation_data, // whatever data you need here
|
|
||||||
//! None // put a logger if you want
|
|
||||||
//! );
|
|
||||||
//!
|
|
||||||
//! // You're now ready to go!
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### Access to shell surface and clients data
|
|
||||||
//!
|
|
||||||
//! There are mainly 3 kind of objects that you'll manipulate from this implementation:
|
|
||||||
//!
|
|
||||||
//! - `ShellClient`: This is a handle representing an isntanciation of a shell global
|
|
||||||
//! you can associate client-wise metadata to it (this is the `MyShellData` type in
|
|
||||||
//! the example above).
|
|
||||||
//! - `ToplevelSurface`: This is a handle representing a toplevel surface, you can
|
|
||||||
//! retrive a list of all currently alive toplevel surface from the `ShellState`.
|
|
||||||
//! - `PopupSurface`: This is a handle representing a popup/tooltip surface. Similarly,
|
|
||||||
//! you can get a list of all currently alive popup surface from the `ShellState`.
|
|
||||||
//!
|
|
||||||
//! You'll obtain these objects though two means: either via the callback methods of
|
|
||||||
//! the subhandler you provided, or via methods on the `ShellState` that you can
|
|
||||||
//! access from the `state()` of the event loop and the token returned by the init
|
|
||||||
//! function.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use utils::Rectangle;
|
|
||||||
use wayland::compositor::CompositorToken;
|
|
||||||
use wayland::compositor::roles::Role;
|
|
||||||
use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6,
|
|
||||||
zxdg_positioner_v6 as xdg_positioner,
|
|
||||||
zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6};
|
|
||||||
use wayland_server::{EventLoopHandle, EventResult, Global, Liveness, Resource, StateToken};
|
|
||||||
use wayland_server::protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface};
|
|
||||||
|
|
||||||
mod wl_handlers;
|
|
||||||
mod xdg_handlers;
|
|
||||||
|
|
||||||
/// Metadata associated with the `shell_surface` role
|
|
||||||
pub struct ShellSurfaceRole {
|
|
||||||
/// Pending state as requested by the client
|
|
||||||
///
|
|
||||||
/// The data in this field are double-buffered, you should
|
|
||||||
/// apply them on a surface commit.
|
|
||||||
pub pending_state: ShellSurfacePendingState,
|
|
||||||
/// Geometry of the surface
|
|
||||||
///
|
|
||||||
/// Defines, in surface relative coordinates, what should
|
|
||||||
/// be considered as "the surface itself", regarding focus,
|
|
||||||
/// window alignment, etc...
|
|
||||||
///
|
|
||||||
/// By default, you should consider the full contents of the
|
|
||||||
/// buffers of this surface and its subsurfaces.
|
|
||||||
pub window_geometry: Option<Rectangle>,
|
|
||||||
/// List of non-acked configures pending
|
|
||||||
///
|
|
||||||
/// Whenever a configure is acked by the client, all configure
|
|
||||||
/// older than it are discarded as well. As such, this vec contains
|
|
||||||
/// the serials of all the configure send to this surface that are
|
|
||||||
/// newer than the last ack received.
|
|
||||||
pub pending_configures: Vec<u32>,
|
|
||||||
/// Has this surface acked at least one configure?
|
|
||||||
///
|
|
||||||
/// xdg_shell defines it as illegal to commit on a surface that has
|
|
||||||
/// not yet acked a configure.
|
|
||||||
pub configured: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
/// The state of a positioner, as set by the client
|
|
||||||
pub struct PositionerState {
|
|
||||||
/// Size of the rectangle that needs to be positioned
|
|
||||||
pub rect_size: (i32, i32),
|
|
||||||
/// Anchor rectangle in the parent surface coordinates
|
|
||||||
/// relative to which the surface must be positioned
|
|
||||||
pub anchor_rect: Rectangle,
|
|
||||||
/// Edges defining the anchor point
|
|
||||||
pub anchor_edges: xdg_positioner::Anchor,
|
|
||||||
/// Gravity direction for positioning the child surface
|
|
||||||
/// relative to its anchor point
|
|
||||||
pub gravity: xdg_positioner::Gravity,
|
|
||||||
/// Adjustments to do if previous criterias constraint the
|
|
||||||
/// surface
|
|
||||||
pub constraint_adjustment: xdg_positioner::ConstraintAdjustment,
|
|
||||||
/// Offset placement relative to the anchor point
|
|
||||||
pub offset: (i32, i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contents of the pending state of a shell surface, depending on its role
|
|
||||||
pub enum ShellSurfacePendingState {
|
|
||||||
/// This a regular, toplevel surface
|
|
||||||
///
|
|
||||||
/// This corresponds to either the `xdg_toplevel` role from the
|
|
||||||
/// `xdg_shell` protocol, or the result of `set_toplevel` using the
|
|
||||||
/// `wl_shell` protocol.
|
|
||||||
///
|
|
||||||
/// This is what you'll generaly interpret as "a window".
|
|
||||||
Toplevel(ToplevelState),
|
|
||||||
/// This is a popup surface
|
|
||||||
///
|
|
||||||
/// This corresponds to either the `xdg_popup` role from the
|
|
||||||
/// `xdg_shell` protocol, or the result of `set_popup` using the
|
|
||||||
/// `wl_shell` protocol.
|
|
||||||
///
|
|
||||||
/// This are mostly for small tooltips and similar short-lived
|
|
||||||
/// surfaces.
|
|
||||||
Popup(PopupState),
|
|
||||||
/// This surface was not yet assigned a kind
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State of a regular toplevel surface
|
|
||||||
pub struct ToplevelState {
|
|
||||||
/// Parent of this surface
|
|
||||||
///
|
|
||||||
/// If this surface has a parent, it should be hidden
|
|
||||||
/// or displayed, brought up at the same time as it.
|
|
||||||
pub parent: Option<wl_surface::WlSurface>,
|
|
||||||
/// Title of this shell surface
|
|
||||||
pub title: String,
|
|
||||||
/// App id for this shell surface
|
|
||||||
///
|
|
||||||
/// This identifier can be used to group surface together
|
|
||||||
/// as being several instance of the same app. This can
|
|
||||||
/// also be used as the D-Bus name for the app.
|
|
||||||
pub app_id: String,
|
|
||||||
/// Minimum size requested for this surface
|
|
||||||
///
|
|
||||||
/// A value of 0 on an axis means this axis is not constrained
|
|
||||||
pub min_size: (i32, i32),
|
|
||||||
/// Maximum size requested for this surface
|
|
||||||
///
|
|
||||||
/// A value of 0 on an axis means this axis is not constrained
|
|
||||||
pub max_size: (i32, i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToplevelState {
|
|
||||||
/// Clone this ToplevelState
|
|
||||||
///
|
|
||||||
/// If the parent surface refers to a surface that no longer
|
|
||||||
/// exists, it is replaced by `None` in the process.
|
|
||||||
pub fn clone(&self) -> ToplevelState {
|
|
||||||
ToplevelState {
|
|
||||||
parent: self.parent.as_ref().and_then(|p| p.clone()),
|
|
||||||
title: self.title.clone(),
|
|
||||||
app_id: self.app_id.clone(),
|
|
||||||
min_size: self.min_size,
|
|
||||||
max_size: self.max_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The pending state of a popup surface
|
|
||||||
pub struct PopupState {
|
|
||||||
/// Parent of this popup surface
|
|
||||||
pub parent: wl_surface::WlSurface,
|
|
||||||
/// The positioner specifying how this tooltip should
|
|
||||||
/// be placed relative to its parent.
|
|
||||||
pub positioner: PositionerState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PopupState {
|
|
||||||
/// Clone this PopupState
|
|
||||||
///
|
|
||||||
/// If the parent surface refers to a surface that no longer
|
|
||||||
/// exists, this will return `None`, as the popup can no
|
|
||||||
/// longer be meaningfully displayed.
|
|
||||||
pub fn clone(&self) -> Option<PopupState> {
|
|
||||||
if let Some(p) = self.parent.clone() {
|
|
||||||
Some(PopupState {
|
|
||||||
parent: p,
|
|
||||||
positioner: self.positioner,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// the parent surface does no exist any longer,
|
|
||||||
// this popup does not make any sense now
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ShellSurfacePendingState {
|
|
||||||
fn default() -> ShellSurfacePendingState {
|
|
||||||
ShellSurfacePendingState::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal implementation data of shell surfaces
|
|
||||||
///
|
|
||||||
/// This type is only visible as type parameter of
|
|
||||||
/// the `Global` handle you are provided.
|
|
||||||
pub struct ShellSurfaceIData<U, R, CID, SID, SD> {
|
|
||||||
log: ::slog::Logger,
|
|
||||||
compositor_token: CompositorToken<U, R, CID>,
|
|
||||||
implementation: ShellSurfaceUserImplementation<U, R, CID, SID, SD>,
|
|
||||||
idata: Rc<RefCell<SID>>,
|
|
||||||
state_token: StateToken<ShellState<U, R, CID, SD>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R, CID, SID, SD> Clone for ShellSurfaceIData<U, R, CID, SID, SD> {
|
|
||||||
fn clone(&self) -> ShellSurfaceIData<U, R, CID, SID, SD> {
|
|
||||||
ShellSurfaceIData {
|
|
||||||
log: self.log.clone(),
|
|
||||||
compositor_token: self.compositor_token,
|
|
||||||
implementation: self.implementation,
|
|
||||||
idata: self.idata.clone(),
|
|
||||||
state_token: self.state_token.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new `xdg_shell` and `wl_shell` globals.
|
|
||||||
///
|
|
||||||
/// The globals are directly registered into the eventloop, and this function
|
|
||||||
/// returns a `StateToken<_>` which you'll need access the list of shell
|
|
||||||
/// surfaces created by your clients.
|
|
||||||
///
|
|
||||||
/// It also returns the two global handles, in case you whish to remove these
|
|
||||||
/// globals from the event loop in the future.
|
|
||||||
pub fn shell_init<U, R, CID, SID, SD, L>(
|
|
||||||
evlh: &mut EventLoopHandle, token: CompositorToken<U, R, CID>,
|
|
||||||
implementation: ShellSurfaceUserImplementation<U, R, CID, SID, SD>, idata: SID, logger: L,
|
|
||||||
) -> (
|
|
||||||
StateToken<ShellState<U, R, CID, SD>>,
|
|
||||||
Global<wl_shell::WlShell, ShellSurfaceIData<U, R, CID, SID, SD>>,
|
|
||||||
Global<zxdg_shell_v6::ZxdgShellV6, ShellSurfaceIData<U, R, CID, SID, SD>>,
|
|
||||||
)
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: Default + 'static,
|
|
||||||
L: Into<Option<::slog::Logger>>,
|
|
||||||
{
|
|
||||||
let log = ::slog_or_stdlog(logger);
|
|
||||||
let shell_state = ShellState {
|
|
||||||
known_toplevels: Vec::new(),
|
|
||||||
known_popups: Vec::new(),
|
|
||||||
};
|
|
||||||
let shell_state_token = evlh.state().insert(shell_state);
|
|
||||||
|
|
||||||
let shell_surface_idata = ShellSurfaceIData {
|
|
||||||
log: log.new(o!("smithay_module" => "shell_handler")),
|
|
||||||
compositor_token: token,
|
|
||||||
implementation: implementation,
|
|
||||||
idata: Rc::new(RefCell::new(idata)),
|
|
||||||
state_token: shell_state_token.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: init globals
|
|
||||||
let wl_shell_global = evlh.register_global(
|
|
||||||
1,
|
|
||||||
self::wl_handlers::wl_shell_bind::<U, R, CID, SID, SD>,
|
|
||||||
shell_surface_idata.clone(),
|
|
||||||
);
|
|
||||||
let xdg_shell_global = evlh.register_global(
|
|
||||||
1,
|
|
||||||
self::xdg_handlers::xdg_shell_bind::<U, R, CID, SID, SD>,
|
|
||||||
shell_surface_idata.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
(shell_state_token, wl_shell_global, xdg_shell_global)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shell global state
|
|
||||||
///
|
|
||||||
/// This state allows you to retrieve a list of surfaces
|
|
||||||
/// currently known to the shell global.
|
|
||||||
pub struct ShellState<U, R, CID, SD> {
|
|
||||||
known_toplevels: Vec<ToplevelSurface<U, R, CID, SD>>,
|
|
||||||
known_popups: Vec<PopupSurface<U, R, CID, SD>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R, CID, SD> ShellState<U, R, CID, SD>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
/// Cleans the internal surface storage by removing all dead surfaces
|
|
||||||
pub fn cleanup_surfaces(&mut self) {
|
|
||||||
self.known_toplevels.retain(|s| s.alive());
|
|
||||||
self.known_popups.retain(|s| s.alive());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access all the shell surfaces known by this handler
|
|
||||||
pub fn toplevel_surfaces(&self) -> &[ToplevelSurface<U, R, CID, SD>] {
|
|
||||||
&self.known_toplevels[..]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access all the popup surfaces known by this handler
|
|
||||||
pub fn popup_surfaces(&self) -> &[PopupSurface<U, R, CID, SD>] {
|
|
||||||
&self.known_popups[..]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* User interaction
|
|
||||||
*/
|
|
||||||
|
|
||||||
enum ShellClientKind {
|
|
||||||
Wl(wl_shell::WlShell),
|
|
||||||
Xdg(zxdg_shell_v6::ZxdgShellV6),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct ShellClientData<SD> {
|
|
||||||
pending_ping: u32,
|
|
||||||
data: SD,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_shell_client_data<SD: Default>() -> ShellClientData<SD> {
|
|
||||||
ShellClientData {
|
|
||||||
pending_ping: 0,
|
|
||||||
data: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shell client
|
|
||||||
///
|
|
||||||
/// This represents an instanciation of a shell
|
|
||||||
/// global (be it `wl_shell` or `xdg_shell`).
|
|
||||||
///
|
|
||||||
/// Most of the time, you can consider that a
|
|
||||||
/// wayland client will be a single shell client.
|
|
||||||
///
|
|
||||||
/// You can use this handle to access a storage for any
|
|
||||||
/// client-specific data you wish to associate with it.
|
|
||||||
pub struct ShellClient<SD> {
|
|
||||||
kind: ShellClientKind,
|
|
||||||
_data: ::std::marker::PhantomData<*mut SD>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<SD> ShellClient<SD> {
|
|
||||||
/// Is the shell client represented by this handle still connected?
|
|
||||||
pub fn alive(&self) -> bool {
|
|
||||||
match self.kind {
|
|
||||||
ShellClientKind::Wl(ref s) => s.status() == Liveness::Alive,
|
|
||||||
ShellClientKind::Xdg(ref s) => s.status() == Liveness::Alive,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if this handle and the other one actually refer to the
|
|
||||||
/// same shell client
|
|
||||||
pub fn equals(&self, other: &Self) -> bool {
|
|
||||||
match (&self.kind, &other.kind) {
|
|
||||||
(&ShellClientKind::Wl(ref s1), &ShellClientKind::Wl(ref s2)) => s1.equals(s2),
|
|
||||||
(&ShellClientKind::Xdg(ref s1), &ShellClientKind::Xdg(ref s2)) => s1.equals(s2),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a ping request to this shell client
|
|
||||||
///
|
|
||||||
/// You'll receive the reply in the `Handler::cient_pong()` method.
|
|
||||||
///
|
|
||||||
/// A typical use is to start a timer at the same time you send this ping
|
|
||||||
/// request, and cancel it when you receive the pong. If the timer runs
|
|
||||||
/// down to 0 before a pong is received, mark the client as unresponsive.
|
|
||||||
///
|
|
||||||
/// Fails if this shell client already has a pending ping or is already dead.
|
|
||||||
pub fn send_ping(&self, serial: u32) -> Result<(), ()> {
|
|
||||||
if !self.alive() {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
match self.kind {
|
|
||||||
ShellClientKind::Wl(ref shell) => {
|
|
||||||
let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData<SD>) };
|
|
||||||
let mut guard = mutex.lock().unwrap();
|
|
||||||
if guard.0.pending_ping == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
guard.0.pending_ping = serial;
|
|
||||||
if let Some(surface) = guard.1.first() {
|
|
||||||
// there is at least one surface, send the ping
|
|
||||||
// if there is no surface, the ping will remain pending
|
|
||||||
// and will be sent when the client creates a surface
|
|
||||||
surface.ping(serial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ShellClientKind::Xdg(ref shell) => {
|
|
||||||
let mutex =
|
|
||||||
unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData<SD>) };
|
|
||||||
let mut guard = mutex.lock().unwrap();
|
|
||||||
if guard.pending_ping == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
guard.pending_ping = serial;
|
|
||||||
shell.ping(serial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the user data associated with this shell client
|
|
||||||
pub fn with_data<F, T>(&self, f: F) -> Result<T, ()>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut SD) -> T,
|
|
||||||
{
|
|
||||||
if !self.alive() {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
match self.kind {
|
|
||||||
ShellClientKind::Wl(ref shell) => {
|
|
||||||
let mutex = unsafe { &*(shell.get_user_data() as *mut self::wl_handlers::ShellUserData<SD>) };
|
|
||||||
let mut guard = mutex.lock().unwrap();
|
|
||||||
Ok(f(&mut guard.0.data))
|
|
||||||
}
|
|
||||||
ShellClientKind::Xdg(ref shell) => {
|
|
||||||
let mutex =
|
|
||||||
unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData<SD>) };
|
|
||||||
let mut guard = mutex.lock().unwrap();
|
|
||||||
Ok(f(&mut guard.data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SurfaceKind {
|
|
||||||
Wl(wl_shell_surface::WlShellSurface),
|
|
||||||
XdgToplevel(zxdg_toplevel_v6::ZxdgToplevelV6),
|
|
||||||
XdgPopup(zxdg_popup_v6::ZxdgPopupV6),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to a toplevel surface
|
|
||||||
///
|
|
||||||
/// This is an unified abstraction over the toplevel surfaces
|
|
||||||
/// of both `wl_shell` and `xdg_shell`.
|
|
||||||
pub struct ToplevelSurface<U, R, CID, SD> {
|
|
||||||
wl_surface: wl_surface::WlSurface,
|
|
||||||
shell_surface: SurfaceKind,
|
|
||||||
token: CompositorToken<U, R, CID>,
|
|
||||||
_shell_data: ::std::marker::PhantomData<SD>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R, CID, SD> ToplevelSurface<U, R, CID, SD>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
/// Is the toplevel surface refered by this handle still alive?
|
|
||||||
pub fn alive(&self) -> bool {
|
|
||||||
let shell_surface_alive = match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive,
|
|
||||||
SurfaceKind::XdgToplevel(ref s) => s.status() == Liveness::Alive,
|
|
||||||
SurfaceKind::XdgPopup(_) => unreachable!(),
|
|
||||||
};
|
|
||||||
shell_surface_alive && self.wl_surface.status() == Liveness::Alive
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do this handle and the other one actually refer to the same toplevel surface?
|
|
||||||
pub fn equals(&self, other: &Self) -> bool {
|
|
||||||
self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the shell client owning this toplevel surface
|
|
||||||
///
|
|
||||||
/// Returns `None` if the surface does actually no longer exist.
|
|
||||||
pub fn client(&self) -> Option<ShellClient<SD>> {
|
|
||||||
if !self.alive() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => {
|
|
||||||
let &(_, ref shell) =
|
|
||||||
unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) };
|
|
||||||
Some(ShellClient {
|
|
||||||
kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }),
|
|
||||||
_data: ::std::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SurfaceKind::XdgToplevel(ref s) => {
|
|
||||||
let &(_, ref shell, _) =
|
|
||||||
unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
|
||||||
Some(ShellClient {
|
|
||||||
kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }),
|
|
||||||
_data: ::std::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SurfaceKind::XdgPopup(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a configure event to this toplevel surface to suggest it a new configuration
|
|
||||||
///
|
|
||||||
/// The serial of this configure will be tracked waiting for the client to ACK it.
|
|
||||||
pub fn send_configure(&self, cfg: ToplevelConfigure) -> EventResult<()> {
|
|
||||||
if !self.alive() {
|
|
||||||
return EventResult::Destroyed;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => self::wl_handlers::send_toplevel_configure(s, cfg),
|
|
||||||
SurfaceKind::XdgToplevel(ref s) => {
|
|
||||||
self::xdg_handlers::send_toplevel_configure(self.token, s, cfg)
|
|
||||||
}
|
|
||||||
SurfaceKind::XdgPopup(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
EventResult::Sent(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make sure this surface was configured
|
|
||||||
///
|
|
||||||
/// Returns `true` if it was, if not, returns `false` and raise
|
|
||||||
/// a protocol error to the associated client. Also returns `false`
|
|
||||||
/// if the surface is already destroyed.
|
|
||||||
///
|
|
||||||
/// xdg_shell mandates that a client acks a configure before commiting
|
|
||||||
/// anything.
|
|
||||||
pub fn ensure_configured(&self) -> bool {
|
|
||||||
if !self.alive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let configured = self.token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| data.configured)
|
|
||||||
.expect("A shell surface object exists but the surface does not have the shell_surface role ?!");
|
|
||||||
if !configured {
|
|
||||||
if let SurfaceKind::XdgToplevel(ref s) = self.shell_surface {
|
|
||||||
let ptr = s.get_user_data();
|
|
||||||
let &(_, _, ref xdg_surface) =
|
|
||||||
unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
|
||||||
xdg_surface.post_error(
|
|
||||||
zxdg_surface_v6::Error::NotConstructed as u32,
|
|
||||||
"Surface has not been confgured yet.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configured
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a "close" event to the client
|
|
||||||
pub fn send_close(&self) -> EventResult<()> {
|
|
||||||
if !self.alive() {
|
|
||||||
return EventResult::Destroyed;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(_) => EventResult::Sent(()),
|
|
||||||
SurfaceKind::XdgToplevel(ref s) => s.close(),
|
|
||||||
SurfaceKind::XdgPopup(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the underlying `wl_surface` of this toplevel surface
|
|
||||||
///
|
|
||||||
/// Returns `None` if the toplevel surface actually no longer exists.
|
|
||||||
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
|
|
||||||
if self.alive() {
|
|
||||||
Some(&self.wl_surface)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a copy of the pending state of this toplevel surface
|
|
||||||
///
|
|
||||||
/// Returns `None` of the toplevel surface actually no longer exists.
|
|
||||||
pub fn get_pending_state(&self) -> Option<ToplevelState> {
|
|
||||||
if !self.alive() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
|
|
||||||
ShellSurfacePendingState::Toplevel(ref state) => Some(state.clone()),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to a popup surface
|
|
||||||
///
|
|
||||||
/// This is an unified abstraction over the popup surfaces
|
|
||||||
/// of both `wl_shell` and `xdg_shell`.
|
|
||||||
pub struct PopupSurface<U, R, CID, SD> {
|
|
||||||
wl_surface: wl_surface::WlSurface,
|
|
||||||
shell_surface: SurfaceKind,
|
|
||||||
token: CompositorToken<U, R, CID>,
|
|
||||||
_shell_data: ::std::marker::PhantomData<SD>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R, CID, SD> PopupSurface<U, R, CID, SD>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
/// Is the popup surface refered by this handle still alive?
|
|
||||||
pub fn alive(&self) -> bool {
|
|
||||||
let shell_surface_alive = match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => s.status() == Liveness::Alive,
|
|
||||||
SurfaceKind::XdgPopup(ref s) => s.status() == Liveness::Alive,
|
|
||||||
SurfaceKind::XdgToplevel(_) => unreachable!(),
|
|
||||||
};
|
|
||||||
shell_surface_alive && self.wl_surface.status() == Liveness::Alive
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do this handle and the other one actually refer to the same popup surface?
|
|
||||||
pub fn equals(&self, other: &Self) -> bool {
|
|
||||||
self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the shell client owning this popup surface
|
|
||||||
///
|
|
||||||
/// Returns `None` if the surface does actually no longer exist.
|
|
||||||
pub fn client(&self) -> Option<ShellClient<SD>> {
|
|
||||||
if !self.alive() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => {
|
|
||||||
let &(_, ref shell) =
|
|
||||||
unsafe { &*(s.get_user_data() as *mut self::wl_handlers::ShellSurfaceUserData) };
|
|
||||||
Some(ShellClient {
|
|
||||||
kind: ShellClientKind::Wl(unsafe { shell.clone_unchecked() }),
|
|
||||||
_data: ::std::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SurfaceKind::XdgPopup(ref s) => {
|
|
||||||
let &(_, ref shell, _) =
|
|
||||||
unsafe { &*(s.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
|
||||||
Some(ShellClient {
|
|
||||||
kind: ShellClientKind::Xdg(unsafe { shell.clone_unchecked() }),
|
|
||||||
_data: ::std::marker::PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
SurfaceKind::XdgToplevel(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a configure event to this toplevel surface to suggest it a new configuration
|
|
||||||
///
|
|
||||||
/// The serial of this configure will be tracked waiting for the client to ACK it.
|
|
||||||
pub fn send_configure(&self, cfg: PopupConfigure) -> EventResult<()> {
|
|
||||||
if !self.alive() {
|
|
||||||
return EventResult::Destroyed;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => self::wl_handlers::send_popup_configure(s, cfg),
|
|
||||||
SurfaceKind::XdgPopup(ref s) => self::xdg_handlers::send_popup_configure(self.token, s, cfg),
|
|
||||||
SurfaceKind::XdgToplevel(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
EventResult::Sent(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make sure this surface was configured
|
|
||||||
///
|
|
||||||
/// Returns `true` if it was, if not, returns `false` and raise
|
|
||||||
/// a protocol error to the associated client. Also returns `false`
|
|
||||||
/// if the surface is already destroyed.
|
|
||||||
///
|
|
||||||
/// xdg_shell mandates that a client acks a configure before commiting
|
|
||||||
/// anything.
|
|
||||||
pub fn ensure_configured(&self) -> bool {
|
|
||||||
if !self.alive() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let configured = self.token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| data.configured)
|
|
||||||
.expect("A shell surface object exists but the surface does not have the shell_surface role ?!");
|
|
||||||
if !configured {
|
|
||||||
if let SurfaceKind::XdgPopup(ref s) = self.shell_surface {
|
|
||||||
let ptr = s.get_user_data();
|
|
||||||
let &(_, _, ref xdg_surface) =
|
|
||||||
unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
|
||||||
xdg_surface.post_error(
|
|
||||||
zxdg_surface_v6::Error::NotConstructed as u32,
|
|
||||||
"Surface has not been confgured yet.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configured
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a 'popup_done' event to the popup surface
|
|
||||||
///
|
|
||||||
/// It means that the use has dismissed the popup surface, or that
|
|
||||||
/// the pointer has left the area of popup grab if there was a grab.
|
|
||||||
pub fn send_popup_done(&self) -> EventResult<()> {
|
|
||||||
if !self.alive() {
|
|
||||||
return EventResult::Destroyed;
|
|
||||||
}
|
|
||||||
match self.shell_surface {
|
|
||||||
SurfaceKind::Wl(ref s) => s.popup_done(),
|
|
||||||
SurfaceKind::XdgPopup(ref s) => s.popup_done(),
|
|
||||||
SurfaceKind::XdgToplevel(_) => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the underlying `wl_surface` of this toplevel surface
|
|
||||||
///
|
|
||||||
/// Returns `None` if the toplevel surface actually no longer exists.
|
|
||||||
pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> {
|
|
||||||
if self.alive() {
|
|
||||||
Some(&self.wl_surface)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a copy of the pending state of this popup surface
|
|
||||||
///
|
|
||||||
/// Returns `None` of the popup surface actually no longer exists.
|
|
||||||
pub fn get_pending_state(&self) -> Option<PopupState> {
|
|
||||||
if !self.alive() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
|
|
||||||
ShellSurfacePendingState::Popup(ref state) => state.clone(),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.and_then(|x| x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A configure message for toplevel surfaces
|
|
||||||
pub struct ToplevelConfigure {
|
|
||||||
/// A suggestion for a new size for the surface
|
|
||||||
pub size: Option<(i32, i32)>,
|
|
||||||
/// A notification of what are the current states of this surface
|
|
||||||
///
|
|
||||||
/// A surface can be any combination of these possible states
|
|
||||||
/// at the same time.
|
|
||||||
pub states: Vec<zxdg_toplevel_v6::State>,
|
|
||||||
/// A serial number to track ACK from the client
|
|
||||||
///
|
|
||||||
/// This should be an ever increasing number, as the ACK-ing
|
|
||||||
/// from a client for a serial will validate all pending lower
|
|
||||||
/// serials.
|
|
||||||
pub serial: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A configure message for popup surface
|
|
||||||
pub struct PopupConfigure {
|
|
||||||
/// The position chosen for this popup relative to
|
|
||||||
/// its parent
|
|
||||||
pub position: (i32, i32),
|
|
||||||
/// A suggested size for the popup
|
|
||||||
pub size: (i32, i32),
|
|
||||||
/// A serial number to track ACK from the client
|
|
||||||
///
|
|
||||||
/// This should be an ever increasing number, as the ACK-ing
|
|
||||||
/// from a client for a serial will validate all pending lower
|
|
||||||
/// serials.
|
|
||||||
pub serial: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sub-implementation for the shell
|
|
||||||
///
|
|
||||||
/// You need to provide this to handle events that the provided implementation
|
|
||||||
/// cannot process for you directly.
|
|
||||||
///
|
|
||||||
/// Depending on what you want to do, you might implement some of these functions
|
|
||||||
/// as doing nothing.
|
|
||||||
pub struct ShellSurfaceUserImplementation<U, R, CID, SID, SD> {
|
|
||||||
/// A new shell client was instanciated
|
|
||||||
pub new_client: fn(evlh: &mut EventLoopHandle, idata: &mut SID, client: ShellClient<SD>),
|
|
||||||
/// The pong for a pending ping of this shell client was received
|
|
||||||
///
|
|
||||||
/// The ShellHandler already checked for you that the serial matches the one
|
|
||||||
/// from the pending ping.
|
|
||||||
pub client_pong: fn(evlh: &mut EventLoopHandle, idata: &mut SID, client: ShellClient<SD>),
|
|
||||||
/// A new toplevel surface was created
|
|
||||||
///
|
|
||||||
/// You need to return a `ToplevelConfigure` from this function, which will be sent
|
|
||||||
/// to the client to configure this surface
|
|
||||||
pub new_toplevel:
|
|
||||||
fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: ToplevelSurface<U, R, CID, SD>)
|
|
||||||
-> ToplevelConfigure,
|
|
||||||
/// A new popup surface was created
|
|
||||||
///
|
|
||||||
/// You need to return a `PopupConfigure` from this function, which will be sent
|
|
||||||
/// to the client to configure this surface
|
|
||||||
pub new_popup: fn(evlh: &mut EventLoopHandle, idata: &mut SID, surface: PopupSurface<U, R, CID, SD>)
|
|
||||||
-> PopupConfigure,
|
|
||||||
/// The client requested the start of an interactive move for this surface
|
|
||||||
pub move_: fn(
|
|
||||||
evlh: &mut EventLoopHandle,
|
|
||||||
idata: &mut SID,
|
|
||||||
surface: ToplevelSurface<U, R, CID, SD>,
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
serial: u32,
|
|
||||||
),
|
|
||||||
/// The client requested the start of an interactive resize for this surface
|
|
||||||
///
|
|
||||||
/// The `edges` argument specifies which part of the window's border is being dragged.
|
|
||||||
pub resize: fn(
|
|
||||||
evlh: &mut EventLoopHandle,
|
|
||||||
idata: &mut SID,
|
|
||||||
surface: ToplevelSurface<U, R, CID, SD>,
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
serial: u32,
|
|
||||||
edges: zxdg_toplevel_v6::ResizeEdge,
|
|
||||||
),
|
|
||||||
/// This popup requests a grab of the pointer
|
|
||||||
///
|
|
||||||
/// This means it requests to be sent a `popup_done` event when the pointer leaves
|
|
||||||
/// the grab area.
|
|
||||||
pub grab: fn(
|
|
||||||
evlh: &mut EventLoopHandle,
|
|
||||||
idata: &mut SID,
|
|
||||||
surface: PopupSurface<U, R, CID, SD>,
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
serial: u32,
|
|
||||||
),
|
|
||||||
/// A toplevel surface requested its display state to be changed
|
|
||||||
///
|
|
||||||
/// Each field represents the request of the client for a specific property:
|
|
||||||
///
|
|
||||||
/// - `None`: no request is made to change this property
|
|
||||||
/// - `Some(true)`: this property should be enabled
|
|
||||||
/// - `Some(false)`: this property should be disabled
|
|
||||||
///
|
|
||||||
/// For fullscreen/maximization, the client can also optionnaly request a specific
|
|
||||||
/// output.
|
|
||||||
///
|
|
||||||
/// You are to answer with a `ToplevelConfigure` that will be sent to the client in
|
|
||||||
/// response.
|
|
||||||
pub change_display_state: fn(
|
|
||||||
evlh: &mut EventLoopHandle,
|
|
||||||
idata: &mut SID,
|
|
||||||
surface: ToplevelSurface<U, R, CID, SD>,
|
|
||||||
maximized: Option<bool>,
|
|
||||||
minimized: Option<bool>,
|
|
||||||
fullscreen: Option<bool>,
|
|
||||||
output: Option<&wl_output::WlOutput>,
|
|
||||||
) -> ToplevelConfigure,
|
|
||||||
/// The client requests the window menu to be displayed on this surface at this location
|
|
||||||
///
|
|
||||||
/// This menu belongs to the compositor. It is typically expected to contain options for
|
|
||||||
/// control of the window (maximize/minimize/close/move/etc...).
|
|
||||||
pub show_window_menu: fn(
|
|
||||||
evlh: &mut EventLoopHandle,
|
|
||||||
idata: &mut SID,
|
|
||||||
surface: ToplevelSurface<U, R, CID, SD>,
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
serial: u32,
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R, CID, SID, SD> Copy for ShellSurfaceUserImplementation<U, R, CID, SID, SD> {}
|
|
||||||
impl<U, R, CID, SID, SD> Clone for ShellSurfaceUserImplementation<U, R, CID, SID, SD> {
|
|
||||||
fn clone(&self) -> ShellSurfaceUserImplementation<U, R, CID, SID, SD> {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,808 @@
|
||||||
|
//! Utilities for handling shell surfaces, toplevel and popups using the `xdg_shell` protocol
|
||||||
|
//!
|
||||||
|
//! This module provides automatic handling of shell surfaces objects, by being registered
|
||||||
|
//! as a global handler for `xdg_shell`.
|
||||||
|
//!
|
||||||
|
//! ## Why use this implementation
|
||||||
|
//!
|
||||||
|
//! This implementation can track for you the various shell surfaces defined by the
|
||||||
|
//! clients by handling the `xdg_shell` protocol.
|
||||||
|
//!
|
||||||
|
//! It allows you to easily access a list of all shell surfaces defined by your clients
|
||||||
|
//! access their associated metadata and underlying `wl_surface`s.
|
||||||
|
//!
|
||||||
|
//! This handler only handles the protocol exchanges with the client to present you the
|
||||||
|
//! information in a coherent and relatively easy to use maneer. All the actual drawing
|
||||||
|
//! and positioning logic of windows is out of its scope.
|
||||||
|
//!
|
||||||
|
//! ## How to use it
|
||||||
|
//!
|
||||||
|
//! ### Initialization
|
||||||
|
//!
|
||||||
|
//! To initialize this handler, simple use the `shell_init` function provided in this
|
||||||
|
//! module. You will need to provide it the `CompositorToken` you retrieved from an
|
||||||
|
//! instanciation of the `CompositorHandler` provided by smithay.
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # extern crate wayland_server;
|
||||||
|
//! # #[macro_use] extern crate smithay;
|
||||||
|
//! # extern crate wayland_protocols;
|
||||||
|
//! #
|
||||||
|
//! use smithay::wayland::compositor::roles::*;
|
||||||
|
//! use smithay::wayland::compositor::CompositorToken;
|
||||||
|
//! use smithay::wayland::shell::{shell_init, ShellSurfaceRole, ShellSurfaceUserImplementation};
|
||||||
|
//! use wayland_server::protocol::wl_shell::WlShell;
|
||||||
|
//! use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_shell_v6::ZxdgShellV6;
|
||||||
|
//! use wayland_server::{EventLoop, EventLoopHandle};
|
||||||
|
//! # use wayland_server::protocol::{wl_seat, wl_output};
|
||||||
|
//! # use wayland_protocols::unstable::xdg_shell::v6::server::zxdg_toplevel_v6;
|
||||||
|
//! # #[derive(Default)] struct MySurfaceData;
|
||||||
|
//!
|
||||||
|
//! // define the roles type. You need to integrate the ShellSurface role:
|
||||||
|
//! define_roles!(MyRoles =>
|
||||||
|
//! [ShellSurface, ShellSurfaceRole]
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // define the metadata you want associated with the shell clients
|
||||||
|
//! #[derive(Default)]
|
||||||
|
//! struct MyShellData {
|
||||||
|
//! /* ... */
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! # fn main() {
|
||||||
|
//! # let (_display, mut event_loop) = wayland_server::create_display();
|
||||||
|
//! # let (compositor_token, _, _) = smithay::wayland::compositor::compositor_init::<(), MyRoles, _, _>(
|
||||||
|
//! # &mut event_loop,
|
||||||
|
//! # unimplemented!(),
|
||||||
|
//! # (),
|
||||||
|
//! # None
|
||||||
|
//! # );
|
||||||
|
//! // define your implementation for shell
|
||||||
|
//! let my_shell_implementation = ShellSurfaceUserImplementation {
|
||||||
|
//! new_client: |evlh, idata, client| { unimplemented!() },
|
||||||
|
//! client_pong: |evlh, idata, client| { unimplemented!() },
|
||||||
|
//! new_toplevel: |evlh, idata, toplevel| { unimplemented!() },
|
||||||
|
//! new_popup: |evlh, idata, popup| { unimplemented!() },
|
||||||
|
//! move_: |evlh, idata, toplevel, seat, serial| { unimplemented!() },
|
||||||
|
//! resize: |evlh, idata, toplevel, seat, serial, edges| { unimplemented!() },
|
||||||
|
//! grab: |evlh, idata, popup, seat, serial| { unimplemented!() },
|
||||||
|
//! change_display_state: |evlh, idata, toplevel, maximized, minimized, fullscreen, output| {
|
||||||
|
//! unimplemented!()
|
||||||
|
//! },
|
||||||
|
//! show_window_menu: |evlh, idata, toplevel, seat, serial, x, y| { unimplemented!() },
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // define your implementation data
|
||||||
|
//! let my_shell_implementation_data = ();
|
||||||
|
//!
|
||||||
|
//! let (shell_state_token, _, _) = shell_init::<_, _, _, _, MyShellData, _>(
|
||||||
|
//! &mut event_loop,
|
||||||
|
//! compositor_token, // token from the compositor implementation
|
||||||
|
//! my_shell_implementation, // instance of shell::ShellSurfaceUserImplementation
|
||||||
|
//! my_shell_implementation_data, // whatever data you need here
|
||||||
|
//! None // put a logger if you want
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! // You're now ready to go!
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Access to shell surface and clients data
|
||||||
|
//!
|
||||||
|
//! There are mainly 3 kind of objects that you'll manipulate from this implementation:
|
||||||
|
//!
|
||||||
|
//! - `ShellClient`: This is a handle representing an isntanciation of a shell global
|
||||||
|
//! you can associate client-wise metadata to it (this is the `MyShellData` type in
|
||||||
|
//! the example above).
|
||||||
|
//! - `ToplevelSurface`: This is a handle representing a toplevel surface, you can
|
||||||
|
//! retrive a list of all currently alive toplevel surface from the `ShellState`.
|
||||||
|
//! - `PopupSurface`: This is a handle representing a popup/tooltip surface. Similarly,
|
||||||
|
//! you can get a list of all currently alive popup surface from the `ShellState`.
|
||||||
|
//!
|
||||||
|
//! You'll obtain these objects though two means: either via the callback methods of
|
||||||
|
//! the subhandler you provided, or via methods on the `ShellState` that you can
|
||||||
|
//! access from the `state()` of the event loop and the token returned by the init
|
||||||
|
//! function.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use utils::Rectangle;
|
||||||
|
use wayland::compositor::CompositorToken;
|
||||||
|
use wayland::compositor::roles::Role;
|
||||||
|
use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6,
|
||||||
|
zxdg_positioner_v6 as xdg_positioner,
|
||||||
|
zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6};
|
||||||
|
use wayland_server::{Display, Global, LoopToken, Resource};
|
||||||
|
use wayland_server::commons::Implementation;
|
||||||
|
use wayland_server::protocol::{wl_output, wl_seat, wl_surface};
|
||||||
|
|
||||||
|
mod xdg_handlers;
|
||||||
|
|
||||||
|
/// Metadata associated with the `shell_surface` role
|
||||||
|
pub struct ShellSurfaceRole {
|
||||||
|
/// Pending state as requested by the client
|
||||||
|
///
|
||||||
|
/// The data in this field are double-buffered, you should
|
||||||
|
/// apply them on a surface commit.
|
||||||
|
pub pending_state: ShellSurfacePendingState,
|
||||||
|
/// Geometry of the surface
|
||||||
|
///
|
||||||
|
/// Defines, in surface relative coordinates, what should
|
||||||
|
/// be considered as "the surface itself", regarding focus,
|
||||||
|
/// window alignment, etc...
|
||||||
|
///
|
||||||
|
/// By default, you should consider the full contents of the
|
||||||
|
/// buffers of this surface and its subsurfaces.
|
||||||
|
pub window_geometry: Option<Rectangle>,
|
||||||
|
/// List of non-acked configures pending
|
||||||
|
///
|
||||||
|
/// Whenever a configure is acked by the client, all configure
|
||||||
|
/// older than it are discarded as well. As such, this vec contains
|
||||||
|
/// the serials of all the configure send to this surface that are
|
||||||
|
/// newer than the last ack received.
|
||||||
|
pub pending_configures: Vec<u32>,
|
||||||
|
/// Has this surface acked at least one configure?
|
||||||
|
///
|
||||||
|
/// xdg_shell defines it as illegal to commit on a surface that has
|
||||||
|
/// not yet acked a configure.
|
||||||
|
pub configured: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
/// The state of a positioner, as set by the client
|
||||||
|
pub struct PositionerState {
|
||||||
|
/// Size of the rectangle that needs to be positioned
|
||||||
|
pub rect_size: (i32, i32),
|
||||||
|
/// Anchor rectangle in the parent surface coordinates
|
||||||
|
/// relative to which the surface must be positioned
|
||||||
|
pub anchor_rect: Rectangle,
|
||||||
|
/// Edges defining the anchor point
|
||||||
|
pub anchor_edges: xdg_positioner::Anchor,
|
||||||
|
/// Gravity direction for positioning the child surface
|
||||||
|
/// relative to its anchor point
|
||||||
|
pub gravity: xdg_positioner::Gravity,
|
||||||
|
/// Adjustments to do if previous criterias constraint the
|
||||||
|
/// surface
|
||||||
|
pub constraint_adjustment: xdg_positioner::ConstraintAdjustment,
|
||||||
|
/// Offset placement relative to the anchor point
|
||||||
|
pub offset: (i32, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contents of the pending state of a shell surface, depending on its role
|
||||||
|
pub enum ShellSurfacePendingState {
|
||||||
|
/// This a regular, toplevel surface
|
||||||
|
///
|
||||||
|
/// This corresponds to the `xdg_toplevel` role
|
||||||
|
///
|
||||||
|
/// This is what you'll generaly interpret as "a window".
|
||||||
|
Toplevel(ToplevelState),
|
||||||
|
/// This is a popup surface
|
||||||
|
///
|
||||||
|
/// This corresponds to the `xdg_popup` role
|
||||||
|
///
|
||||||
|
/// This are mostly for small tooltips and similar short-lived
|
||||||
|
/// surfaces.
|
||||||
|
Popup(PopupState),
|
||||||
|
/// This surface was not yet assigned a kind
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State of a regular toplevel surface
|
||||||
|
pub struct ToplevelState {
|
||||||
|
/// Parent of this surface
|
||||||
|
///
|
||||||
|
/// If this surface has a parent, it should be hidden
|
||||||
|
/// or displayed, brought up at the same time as it.
|
||||||
|
pub parent: Option<Resource<wl_surface::WlSurface>>,
|
||||||
|
/// Title of this shell surface
|
||||||
|
pub title: String,
|
||||||
|
/// App id for this shell surface
|
||||||
|
///
|
||||||
|
/// This identifier can be used to group surface together
|
||||||
|
/// as being several instance of the same app. This can
|
||||||
|
/// also be used as the D-Bus name for the app.
|
||||||
|
pub app_id: String,
|
||||||
|
/// Minimum size requested for this surface
|
||||||
|
///
|
||||||
|
/// A value of 0 on an axis means this axis is not constrained
|
||||||
|
pub min_size: (i32, i32),
|
||||||
|
/// Maximum size requested for this surface
|
||||||
|
///
|
||||||
|
/// A value of 0 on an axis means this axis is not constrained
|
||||||
|
pub max_size: (i32, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for ToplevelState {
|
||||||
|
fn clone(&self) -> ToplevelState {
|
||||||
|
ToplevelState {
|
||||||
|
parent: self.parent.as_ref().map(|p| p.clone()),
|
||||||
|
title: self.title.clone(),
|
||||||
|
app_id: self.app_id.clone(),
|
||||||
|
min_size: self.min_size,
|
||||||
|
max_size: self.max_size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pending state of a popup surface
|
||||||
|
pub struct PopupState {
|
||||||
|
/// Parent of this popup surface
|
||||||
|
pub parent: Resource<wl_surface::WlSurface>,
|
||||||
|
/// The positioner specifying how this tooltip should
|
||||||
|
/// be placed relative to its parent.
|
||||||
|
pub positioner: PositionerState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for PopupState {
|
||||||
|
fn clone(&self) -> PopupState {
|
||||||
|
PopupState {
|
||||||
|
parent: self.parent.clone(),
|
||||||
|
positioner: self.positioner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ShellSurfacePendingState {
|
||||||
|
fn default() -> ShellSurfacePendingState {
|
||||||
|
ShellSurfacePendingState::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ShellImplementation<U, R, SD> {
|
||||||
|
log: ::slog::Logger,
|
||||||
|
compositor_token: CompositorToken<U, R>,
|
||||||
|
loop_token: LoopToken,
|
||||||
|
user_impl: Rc<RefCell<Implementation<(), ShellEvent<U, R, SD>>>>,
|
||||||
|
shell_state: Arc<Mutex<ShellState<U, R, SD>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> Clone for ShellImplementation<U, R, SD> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ShellImplementation {
|
||||||
|
log: self.log.clone(),
|
||||||
|
compositor_token: self.compositor_token.clone(),
|
||||||
|
loop_token: self.loop_token.clone(),
|
||||||
|
user_impl: self.user_impl.clone(),
|
||||||
|
shell_state: self.shell_state.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `xdg_shell` globals
|
||||||
|
pub fn shell_init<U, R, SD, L, Impl>(
|
||||||
|
display: &mut Display,
|
||||||
|
ltoken: LoopToken,
|
||||||
|
ctoken: CompositorToken<U, R>,
|
||||||
|
implementation: Impl,
|
||||||
|
logger: L,
|
||||||
|
) -> (
|
||||||
|
Arc<Mutex<ShellState<U, R, SD>>>,
|
||||||
|
Global<zxdg_shell_v6::ZxdgShellV6>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: Default + 'static,
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
Impl: Implementation<(), ShellEvent<U, R, SD>>,
|
||||||
|
{
|
||||||
|
let log = ::slog_or_stdlog(logger);
|
||||||
|
let shell_state = Arc::new(Mutex::new(ShellState {
|
||||||
|
known_toplevels: Vec::new(),
|
||||||
|
known_popups: Vec::new(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let shell_impl = ShellImplementation {
|
||||||
|
log: log.new(o!("smithay_module" => "shell_handler")),
|
||||||
|
loop_token: ltoken.clone(),
|
||||||
|
compositor_token: ctoken,
|
||||||
|
user_impl: Rc::new(RefCell::new(implementation)),
|
||||||
|
shell_state: shell_state.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let xdg_shell_global = display.create_global(<oken, 1, move |_version, shell| {
|
||||||
|
self::xdg_handlers::implement_shell(shell, &shell_impl);
|
||||||
|
});
|
||||||
|
|
||||||
|
(shell_state, xdg_shell_global)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shell global state
|
||||||
|
///
|
||||||
|
/// This state allows you to retrieve a list of surfaces
|
||||||
|
/// currently known to the shell global.
|
||||||
|
pub struct ShellState<U, R, SD> {
|
||||||
|
known_toplevels: Vec<ToplevelSurface<U, R, SD>>,
|
||||||
|
known_popups: Vec<PopupSurface<U, R, SD>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> ShellState<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
/// Cleans the internal surface storage by removing all dead surfaces
|
||||||
|
pub fn cleanup_surfaces(&mut self) {
|
||||||
|
self.known_toplevels.retain(|s| s.alive());
|
||||||
|
self.known_popups.retain(|s| s.alive());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access all the shell surfaces known by this handler
|
||||||
|
pub fn toplevel_surfaces(&self) -> &[ToplevelSurface<U, R, SD>] {
|
||||||
|
&self.known_toplevels[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access all the popup surfaces known by this handler
|
||||||
|
pub fn popup_surfaces(&self) -> &[PopupSurface<U, R, SD>] {
|
||||||
|
&self.known_popups[..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* User interaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum ShellClientKind {
|
||||||
|
Xdg(Resource<zxdg_shell_v6::ZxdgShellV6>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ShellClientData<SD> {
|
||||||
|
pending_ping: u32,
|
||||||
|
data: SD,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_shell_client_data<SD: Default>() -> ShellClientData<SD> {
|
||||||
|
ShellClientData {
|
||||||
|
pending_ping: 0,
|
||||||
|
data: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shell client
|
||||||
|
///
|
||||||
|
/// This represents an instanciation of a shell
|
||||||
|
/// global (be it `wl_shell` or `xdg_shell`).
|
||||||
|
///
|
||||||
|
/// Most of the time, you can consider that a
|
||||||
|
/// wayland client will be a single shell client.
|
||||||
|
///
|
||||||
|
/// You can use this handle to access a storage for any
|
||||||
|
/// client-specific data you wish to associate with it.
|
||||||
|
pub struct ShellClient<SD> {
|
||||||
|
kind: ShellClientKind,
|
||||||
|
_data: ::std::marker::PhantomData<*mut SD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SD> ShellClient<SD> {
|
||||||
|
/// Is the shell client represented by this handle still connected?
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
ShellClientKind::Xdg(ref s) => s.is_alive(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if this handle and the other one actually refer to the
|
||||||
|
/// same shell client
|
||||||
|
pub fn equals(&self, other: &Self) -> bool {
|
||||||
|
match (&self.kind, &other.kind) {
|
||||||
|
(&ShellClientKind::Xdg(ref s1), &ShellClientKind::Xdg(ref s2)) => s1.equals(s2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a ping request to this shell client
|
||||||
|
///
|
||||||
|
/// You'll receive the reply in the `Handler::cient_pong()` method.
|
||||||
|
///
|
||||||
|
/// A typical use is to start a timer at the same time you send this ping
|
||||||
|
/// request, and cancel it when you receive the pong. If the timer runs
|
||||||
|
/// down to 0 before a pong is received, mark the client as unresponsive.
|
||||||
|
///
|
||||||
|
/// Fails if this shell client already has a pending ping or is already dead.
|
||||||
|
pub fn send_ping(&self, serial: u32) -> Result<(), ()> {
|
||||||
|
if !self.alive() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
ShellClientKind::Xdg(ref shell) => {
|
||||||
|
let mutex =
|
||||||
|
unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData<SD>) };
|
||||||
|
let mut guard = mutex.lock().unwrap();
|
||||||
|
if guard.pending_ping == 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
guard.pending_ping = serial;
|
||||||
|
shell.send(zxdg_shell_v6::Event::Ping { serial });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the user data associated with this shell client
|
||||||
|
pub fn with_data<F, T>(&self, f: F) -> Result<T, ()>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut SD) -> T,
|
||||||
|
{
|
||||||
|
if !self.alive() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
ShellClientKind::Xdg(ref shell) => {
|
||||||
|
let mutex =
|
||||||
|
unsafe { &*(shell.get_user_data() as *mut self::xdg_handlers::ShellUserData<SD>) };
|
||||||
|
let mut guard = mutex.lock().unwrap();
|
||||||
|
Ok(f(&mut guard.data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to a toplevel surface
|
||||||
|
pub struct ToplevelSurface<U, R, SD> {
|
||||||
|
wl_surface: Resource<wl_surface::WlSurface>,
|
||||||
|
shell_surface: Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
_shell_data: ::std::marker::PhantomData<SD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> ToplevelSurface<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
/// Is the toplevel surface refered by this handle still alive?
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
self.shell_surface.is_alive() && self.wl_surface.is_alive()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do this handle and the other one actually refer to the same toplevel surface?
|
||||||
|
pub fn equals(&self, other: &Self) -> bool {
|
||||||
|
self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the shell client owning this toplevel surface
|
||||||
|
///
|
||||||
|
/// Returns `None` if the surface does actually no longer exist.
|
||||||
|
pub fn client(&self) -> Option<ShellClient<SD>> {
|
||||||
|
if !self.alive() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let &(_, ref shell, _) = unsafe {
|
||||||
|
&*(self.shell_surface.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData)
|
||||||
|
};
|
||||||
|
Some(ShellClient {
|
||||||
|
kind: ShellClientKind::Xdg(shell.clone()),
|
||||||
|
_data: ::std::marker::PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a configure event to this toplevel surface to suggest it a new configuration
|
||||||
|
///
|
||||||
|
/// The serial of this configure will be tracked waiting for the client to ACK it.
|
||||||
|
pub fn send_configure(&self, cfg: ToplevelConfigure) {
|
||||||
|
if !self.alive() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self::xdg_handlers::send_toplevel_configure(self.token, &self.shell_surface, cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure this surface was configured
|
||||||
|
///
|
||||||
|
/// Returns `true` if it was, if not, returns `false` and raise
|
||||||
|
/// a protocol error to the associated client. Also returns `false`
|
||||||
|
/// if the surface is already destroyed.
|
||||||
|
///
|
||||||
|
/// xdg_shell mandates that a client acks a configure before commiting
|
||||||
|
/// anything.
|
||||||
|
pub fn ensure_configured(&self) -> bool {
|
||||||
|
if !self.alive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let configured = self.token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| data.configured)
|
||||||
|
.expect("A shell surface object exists but the surface does not have the shell_surface role ?!");
|
||||||
|
if !configured {
|
||||||
|
let ptr = self.shell_surface.get_user_data();
|
||||||
|
let &(_, _, ref xdg_surface) =
|
||||||
|
unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
||||||
|
xdg_surface.post_error(
|
||||||
|
zxdg_surface_v6::Error::NotConstructed as u32,
|
||||||
|
"Surface has not been confgured yet.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
configured
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a "close" event to the client
|
||||||
|
pub fn send_close(&self) {
|
||||||
|
self.shell_surface.send(zxdg_toplevel_v6::Event::Close);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the underlying `wl_surface` of this toplevel surface
|
||||||
|
///
|
||||||
|
/// Returns `None` if the toplevel surface actually no longer exists.
|
||||||
|
pub fn get_surface(&self) -> Option<&Resource<wl_surface::WlSurface>> {
|
||||||
|
if self.alive() {
|
||||||
|
Some(&self.wl_surface)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a copy of the pending state of this toplevel surface
|
||||||
|
///
|
||||||
|
/// Returns `None` of the toplevel surface actually no longer exists.
|
||||||
|
pub fn get_pending_state(&self) -> Option<ToplevelState> {
|
||||||
|
if !self.alive() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
|
||||||
|
ShellSurfacePendingState::Toplevel(ref state) => Some(state.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.and_then(|x| x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to a popup surface
|
||||||
|
///
|
||||||
|
/// This is an unified abstraction over the popup surfaces
|
||||||
|
/// of both `wl_shell` and `xdg_shell`.
|
||||||
|
pub struct PopupSurface<U, R, SD> {
|
||||||
|
wl_surface: Resource<wl_surface::WlSurface>,
|
||||||
|
shell_surface: Resource<zxdg_popup_v6::ZxdgPopupV6>,
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
_shell_data: ::std::marker::PhantomData<SD>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> PopupSurface<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
/// Is the popup surface refered by this handle still alive?
|
||||||
|
pub fn alive(&self) -> bool {
|
||||||
|
self.shell_surface.is_alive() && self.wl_surface.is_alive()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do this handle and the other one actually refer to the same popup surface?
|
||||||
|
pub fn equals(&self, other: &Self) -> bool {
|
||||||
|
self.alive() && other.alive() && self.wl_surface.equals(&other.wl_surface)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the shell client owning this popup surface
|
||||||
|
///
|
||||||
|
/// Returns `None` if the surface does actually no longer exist.
|
||||||
|
pub fn client(&self) -> Option<ShellClient<SD>> {
|
||||||
|
if !self.alive() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let &(_, ref shell, _) = unsafe {
|
||||||
|
&*(self.shell_surface.get_user_data() as *mut self::xdg_handlers::ShellSurfaceUserData)
|
||||||
|
};
|
||||||
|
Some(ShellClient {
|
||||||
|
kind: ShellClientKind::Xdg(shell.clone()),
|
||||||
|
_data: ::std::marker::PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a configure event to this toplevel surface to suggest it a new configuration
|
||||||
|
///
|
||||||
|
/// The serial of this configure will be tracked waiting for the client to ACK it.
|
||||||
|
pub fn send_configure(&self, cfg: PopupConfigure) {
|
||||||
|
if !self.alive() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self::xdg_handlers::send_popup_configure(self.token, &self.shell_surface, cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure this surface was configured
|
||||||
|
///
|
||||||
|
/// Returns `true` if it was, if not, returns `false` and raise
|
||||||
|
/// a protocol error to the associated client. Also returns `false`
|
||||||
|
/// if the surface is already destroyed.
|
||||||
|
///
|
||||||
|
/// xdg_shell mandates that a client acks a configure before commiting
|
||||||
|
/// anything.
|
||||||
|
pub fn ensure_configured(&self) -> bool {
|
||||||
|
if !self.alive() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let configured = self.token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| data.configured)
|
||||||
|
.expect("A shell surface object exists but the surface does not have the shell_surface role ?!");
|
||||||
|
if !configured {
|
||||||
|
let ptr = self.shell_surface.get_user_data();
|
||||||
|
let &(_, _, ref xdg_surface) =
|
||||||
|
unsafe { &*(ptr as *mut self::xdg_handlers::ShellSurfaceUserData) };
|
||||||
|
xdg_surface.post_error(
|
||||||
|
zxdg_surface_v6::Error::NotConstructed as u32,
|
||||||
|
"Surface has not been confgured yet.".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
configured
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a 'popup_done' event to the popup surface
|
||||||
|
///
|
||||||
|
/// It means that the use has dismissed the popup surface, or that
|
||||||
|
/// the pointer has left the area of popup grab if there was a grab.
|
||||||
|
pub fn send_popup_done(&self) {
|
||||||
|
self.shell_surface.send(zxdg_popup_v6::Event::PopupDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the underlying `wl_surface` of this toplevel surface
|
||||||
|
///
|
||||||
|
/// Returns `None` if the toplevel surface actually no longer exists.
|
||||||
|
pub fn get_surface(&self) -> Option<&Resource<wl_surface::WlSurface>> {
|
||||||
|
if self.alive() {
|
||||||
|
Some(&self.wl_surface)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a copy of the pending state of this popup surface
|
||||||
|
///
|
||||||
|
/// Returns `None` of the popup surface actually no longer exists.
|
||||||
|
pub fn get_pending_state(&self) -> Option<PopupState> {
|
||||||
|
if !self.alive() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&self.wl_surface, |data| match data.pending_state {
|
||||||
|
ShellSurfacePendingState::Popup(ref state) => Some(state.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
.and_then(|x| x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A configure message for toplevel surfaces
|
||||||
|
pub struct ToplevelConfigure {
|
||||||
|
/// A suggestion for a new size for the surface
|
||||||
|
pub size: Option<(i32, i32)>,
|
||||||
|
/// A notification of what are the current states of this surface
|
||||||
|
///
|
||||||
|
/// A surface can be any combination of these possible states
|
||||||
|
/// at the same time.
|
||||||
|
pub states: Vec<zxdg_toplevel_v6::State>,
|
||||||
|
/// A serial number to track ACK from the client
|
||||||
|
///
|
||||||
|
/// This should be an ever increasing number, as the ACK-ing
|
||||||
|
/// from a client for a serial will validate all pending lower
|
||||||
|
/// serials.
|
||||||
|
pub serial: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A configure message for popup surface
|
||||||
|
pub struct PopupConfigure {
|
||||||
|
/// The position chosen for this popup relative to
|
||||||
|
/// its parent
|
||||||
|
pub position: (i32, i32),
|
||||||
|
/// A suggested size for the popup
|
||||||
|
pub size: (i32, i32),
|
||||||
|
/// A serial number to track ACK from the client
|
||||||
|
///
|
||||||
|
/// This should be an ever increasing number, as the ACK-ing
|
||||||
|
/// from a client for a serial will validate all pending lower
|
||||||
|
/// serials.
|
||||||
|
pub serial: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Events generated by xdg shell surfaces
|
||||||
|
///
|
||||||
|
/// These are events that the provided implementation cannot process
|
||||||
|
/// for you directly.
|
||||||
|
///
|
||||||
|
/// Depending on what you want to do, you might ignore some of them
|
||||||
|
pub enum ShellEvent<U, R, SD> {
|
||||||
|
/// A new shell client was instanciated
|
||||||
|
NewClient {
|
||||||
|
/// the client
|
||||||
|
client: ShellClient<SD>,
|
||||||
|
},
|
||||||
|
/// The pong for a pending ping of this shell client was received
|
||||||
|
///
|
||||||
|
/// The ShellHandler already checked for you that the serial matches the one
|
||||||
|
/// from the pending ping.
|
||||||
|
ClientPong {
|
||||||
|
/// the client
|
||||||
|
client: ShellClient<SD>,
|
||||||
|
},
|
||||||
|
/// A new toplevel surface was created
|
||||||
|
///
|
||||||
|
/// You likely need to send a `ToplevelConfigure` to the surface, to hint the
|
||||||
|
/// client as to how its window should be
|
||||||
|
NewToplevel {
|
||||||
|
/// the surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// A new popup surface was created
|
||||||
|
///
|
||||||
|
/// You likely need to send a `PopupConfigure` to the surface, to hint the
|
||||||
|
/// client as to how its popup should be
|
||||||
|
NewPopup {
|
||||||
|
/// the surface
|
||||||
|
surface: PopupSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// The client requested the start of an interactive move for this surface
|
||||||
|
Move {
|
||||||
|
/// the surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
/// the seat associated to this move
|
||||||
|
seat: Resource<wl_seat::WlSeat>,
|
||||||
|
/// the grab serial
|
||||||
|
serial: u32,
|
||||||
|
},
|
||||||
|
/// The client requested the start of an interactive resize for this surface
|
||||||
|
Resize {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
/// The seat associated with this resize
|
||||||
|
seat: Resource<wl_seat::WlSeat>,
|
||||||
|
/// The grab serial
|
||||||
|
serial: u32,
|
||||||
|
/// Specification of which part of the window's border is being dragged
|
||||||
|
edges: zxdg_toplevel_v6::ResizeEdge,
|
||||||
|
},
|
||||||
|
/// This popup requests a grab of the pointer
|
||||||
|
///
|
||||||
|
/// This means it requests to be sent a `popup_done` event when the pointer leaves
|
||||||
|
/// the grab area.
|
||||||
|
Grab {
|
||||||
|
/// The surface
|
||||||
|
surface: PopupSurface<U, R, SD>,
|
||||||
|
/// The seat to grab
|
||||||
|
seat: Resource<wl_seat::WlSeat>,
|
||||||
|
/// The grab serial
|
||||||
|
serial: u32,
|
||||||
|
},
|
||||||
|
/// A toplevel surface requested to be maximized
|
||||||
|
Maximize {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// A toplevel surface requested to stop being maximized
|
||||||
|
UnMaximize {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// A toplevel surface requested to be set fullscreen
|
||||||
|
Fullscreen {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
/// The output (if any) on which the fullscreen is requested
|
||||||
|
output: Option<Resource<wl_output::WlOutput>>,
|
||||||
|
},
|
||||||
|
/// A toplevel surface request to stop being fullscreen
|
||||||
|
UnFullscreen {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// A toplevel surface requested to be minimized
|
||||||
|
Minimize {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
},
|
||||||
|
/// The client requests the window menu to be displayed on this surface at this location
|
||||||
|
///
|
||||||
|
/// This menu belongs to the compositor. It is typically expected to contain options for
|
||||||
|
/// control of the window (maximize/minimize/close/move/etc...).
|
||||||
|
ShowWindowMenu {
|
||||||
|
/// The surface
|
||||||
|
surface: ToplevelSurface<U, R, SD>,
|
||||||
|
/// The seat associated with this input grab
|
||||||
|
seat: Resource<wl_seat::WlSeat>,
|
||||||
|
/// the grab serial
|
||||||
|
serial: u32,
|
||||||
|
/// location of the menu request
|
||||||
|
location: (i32, i32),
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,748 @@
|
||||||
|
use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient,
|
||||||
|
ShellClientData, ShellEvent, ShellImplementation, ShellSurfacePendingState, ShellSurfaceRole,
|
||||||
|
ToplevelConfigure, ToplevelState};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use utils::Rectangle;
|
||||||
|
use wayland::compositor::CompositorToken;
|
||||||
|
use wayland::compositor::roles::*;
|
||||||
|
use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6,
|
||||||
|
zxdg_surface_v6, zxdg_toplevel_v6};
|
||||||
|
use wayland_server::{LoopToken, NewResource, Resource};
|
||||||
|
use wayland_server::commons::{downcast_impl, Implementation};
|
||||||
|
use wayland_server::protocol::wl_surface;
|
||||||
|
|
||||||
|
pub(crate) fn implement_shell<U, R, SD>(
|
||||||
|
shell: NewResource<zxdg_shell_v6::ZxdgShellV6>,
|
||||||
|
implem: &ShellImplementation<U, R, SD>,
|
||||||
|
) -> Resource<zxdg_shell_v6::ZxdgShellV6>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: Default + 'static,
|
||||||
|
{
|
||||||
|
let shell = shell.implement_nonsend(
|
||||||
|
implem.clone(),
|
||||||
|
Some(|shell, _| destroy_shell::<SD>(&shell)),
|
||||||
|
&implem.loop_token,
|
||||||
|
);
|
||||||
|
shell.set_user_data(Box::into_raw(Box::new(Mutex::new(make_shell_client_data::<SD>()))) as *mut _);
|
||||||
|
let mut user_impl = implem.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::NewClient {
|
||||||
|
client: make_shell_client(&shell),
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
shell
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xdg_shell
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub(crate) type ShellUserData<SD> = Mutex<ShellClientData<SD>>;
|
||||||
|
|
||||||
|
fn destroy_shell<SD>(shell: &Resource<zxdg_shell_v6::ZxdgShellV6>) {
|
||||||
|
let ptr = shell.get_user_data();
|
||||||
|
shell.set_user_data(::std::ptr::null_mut());
|
||||||
|
let data = unsafe { Box::from_raw(ptr as *mut ShellUserData<SD>) };
|
||||||
|
// explicit call to drop to not forget what we're doing here
|
||||||
|
::std::mem::drop(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn make_shell_client<SD>(resource: &Resource<zxdg_shell_v6::ZxdgShellV6>) -> ShellClient<SD> {
|
||||||
|
ShellClient {
|
||||||
|
kind: super::ShellClientKind::Xdg(resource.clone()),
|
||||||
|
_data: ::std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> Implementation<Resource<zxdg_shell_v6::ZxdgShellV6>, zxdg_shell_v6::Request>
|
||||||
|
for ShellImplementation<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
fn receive(&mut self, request: zxdg_shell_v6::Request, shell: Resource<zxdg_shell_v6::ZxdgShellV6>) {
|
||||||
|
match request {
|
||||||
|
zxdg_shell_v6::Request::Destroy => {
|
||||||
|
// all is handled by destructor
|
||||||
|
}
|
||||||
|
zxdg_shell_v6::Request::CreatePositioner { id } => {
|
||||||
|
implement_positioner(id, &self.loop_token);
|
||||||
|
}
|
||||||
|
zxdg_shell_v6::Request::GetXdgSurface { id, surface } => {
|
||||||
|
let role_data = ShellSurfaceRole {
|
||||||
|
pending_state: ShellSurfacePendingState::None,
|
||||||
|
window_geometry: None,
|
||||||
|
pending_configures: Vec::new(),
|
||||||
|
configured: false,
|
||||||
|
};
|
||||||
|
if self.compositor_token
|
||||||
|
.give_role_with(&surface, role_data)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
shell.post_error(
|
||||||
|
zxdg_shell_v6::Error::Role as u32,
|
||||||
|
"Surface already has a role.".into(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let xdg_surface = id.implement_nonsend(
|
||||||
|
self.clone(),
|
||||||
|
Some(destroy_surface::<U, R, SD>),
|
||||||
|
&self.loop_token,
|
||||||
|
);
|
||||||
|
xdg_surface
|
||||||
|
.set_user_data(Box::into_raw(Box::new((surface.clone(), shell.clone()))) as *mut _);
|
||||||
|
}
|
||||||
|
zxdg_shell_v6::Request::Pong { serial } => {
|
||||||
|
let valid = {
|
||||||
|
let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData<SD>) };
|
||||||
|
let mut guard = mutex.lock().unwrap();
|
||||||
|
if guard.pending_ping == serial {
|
||||||
|
guard.pending_ping = 0;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if valid {
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::ClientPong {
|
||||||
|
client: make_shell_client(&shell),
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xdg_positioner
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn destroy_positioner(positioner: &Resource<zxdg_positioner_v6::ZxdgPositionerV6>) {
|
||||||
|
let ptr = positioner.get_user_data();
|
||||||
|
positioner.set_user_data(::std::ptr::null_mut());
|
||||||
|
// drop the PositionerState
|
||||||
|
let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) };
|
||||||
|
// explicit call to drop to not forget what we're doing here
|
||||||
|
::std::mem::drop(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn implement_positioner(
|
||||||
|
positioner: NewResource<zxdg_positioner_v6::ZxdgPositionerV6>,
|
||||||
|
token: &LoopToken,
|
||||||
|
) -> Resource<zxdg_positioner_v6::ZxdgPositionerV6> {
|
||||||
|
let positioner = positioner.implement_nonsend(
|
||||||
|
|request, positioner: Resource<_>| {
|
||||||
|
let ptr = positioner.get_user_data();
|
||||||
|
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
||||||
|
match request {
|
||||||
|
zxdg_positioner_v6::Request::Destroy => {
|
||||||
|
// handled by destructor
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetSize { width, height } => {
|
||||||
|
if width < 1 || height < 1 {
|
||||||
|
positioner.post_error(
|
||||||
|
zxdg_positioner_v6::Error::InvalidInput as u32,
|
||||||
|
"Invalid size for positioner.".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.rect_size = (width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetAnchorRect {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} => {
|
||||||
|
if width < 1 || height < 1 {
|
||||||
|
positioner.post_error(
|
||||||
|
zxdg_positioner_v6::Error::InvalidInput as u32,
|
||||||
|
"Invalid size for positioner's anchor rectangle.".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.anchor_rect = Rectangle {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetAnchor { anchor } => {
|
||||||
|
use self::zxdg_positioner_v6::Anchor;
|
||||||
|
if anchor.contains(Anchor::Left | Anchor::Right)
|
||||||
|
|| anchor.contains(Anchor::Top | Anchor::Bottom)
|
||||||
|
{
|
||||||
|
positioner.post_error(
|
||||||
|
zxdg_positioner_v6::Error::InvalidInput as u32,
|
||||||
|
"Invalid anchor for positioner.".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.anchor_edges = anchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetGravity { gravity } => {
|
||||||
|
use self::zxdg_positioner_v6::Gravity;
|
||||||
|
if gravity.contains(Gravity::Left | Gravity::Right)
|
||||||
|
|| gravity.contains(Gravity::Top | Gravity::Bottom)
|
||||||
|
{
|
||||||
|
positioner.post_error(
|
||||||
|
zxdg_positioner_v6::Error::InvalidInput as u32,
|
||||||
|
"Invalid gravity for positioner.".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.gravity = gravity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetConstraintAdjustment {
|
||||||
|
constraint_adjustment,
|
||||||
|
} => {
|
||||||
|
let constraint_adjustment =
|
||||||
|
zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment);
|
||||||
|
state.constraint_adjustment = constraint_adjustment;
|
||||||
|
}
|
||||||
|
zxdg_positioner_v6::Request::SetOffset { x, y } => {
|
||||||
|
state.offset = (x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(|positioner, _| destroy_positioner(&positioner)),
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
let data = PositionerState {
|
||||||
|
rect_size: (0, 0),
|
||||||
|
anchor_rect: Rectangle {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
anchor_edges: zxdg_positioner_v6::Anchor::empty(),
|
||||||
|
gravity: zxdg_positioner_v6::Gravity::empty(),
|
||||||
|
constraint_adjustment: zxdg_positioner_v6::ConstraintAdjustment::empty(),
|
||||||
|
offset: (0, 0),
|
||||||
|
};
|
||||||
|
positioner.set_user_data(Box::into_raw(Box::new(data)) as *mut _);
|
||||||
|
positioner
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xdg_surface
|
||||||
|
*/
|
||||||
|
|
||||||
|
type XdgSurfaceUserData = (
|
||||||
|
Resource<wl_surface::WlSurface>,
|
||||||
|
Resource<zxdg_shell_v6::ZxdgShellV6>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn destroy_surface<U, R, SD>(
|
||||||
|
surface: Resource<zxdg_surface_v6::ZxdgSurfaceV6>,
|
||||||
|
implem: Box<Implementation<Resource<zxdg_surface_v6::ZxdgSurfaceV6>, zxdg_surface_v6::Request>>,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
let implem: ShellImplementation<U, R, SD> = *downcast_impl(implem).unwrap_or_else(|_| unreachable!());
|
||||||
|
let ptr = surface.get_user_data();
|
||||||
|
surface.set_user_data(::std::ptr::null_mut());
|
||||||
|
// take back ownership of the userdata
|
||||||
|
let data = unsafe { Box::from_raw(ptr as *mut XdgSurfaceUserData) };
|
||||||
|
implem
|
||||||
|
.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&data.0, |rdata| {
|
||||||
|
if let ShellSurfacePendingState::None = rdata.pending_state {
|
||||||
|
// all is good
|
||||||
|
} else {
|
||||||
|
data.1.post_error(
|
||||||
|
zxdg_shell_v6::Error::Role as u32,
|
||||||
|
"xdg_surface was destroyed before its role object".into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> Implementation<Resource<zxdg_surface_v6::ZxdgSurfaceV6>, zxdg_surface_v6::Request>
|
||||||
|
for ShellImplementation<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
fn receive(
|
||||||
|
&mut self,
|
||||||
|
request: zxdg_surface_v6::Request,
|
||||||
|
xdg_surface: Resource<zxdg_surface_v6::ZxdgSurfaceV6>,
|
||||||
|
) {
|
||||||
|
let ptr = xdg_surface.get_user_data();
|
||||||
|
let &(ref surface, ref shell) = unsafe { &*(ptr as *mut XdgSurfaceUserData) };
|
||||||
|
match request {
|
||||||
|
zxdg_surface_v6::Request::Destroy => {
|
||||||
|
// all is handled by our destructor
|
||||||
|
}
|
||||||
|
zxdg_surface_v6::Request::GetToplevel { id } => {
|
||||||
|
self.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
||||||
|
data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState {
|
||||||
|
parent: None,
|
||||||
|
title: String::new(),
|
||||||
|
app_id: String::new(),
|
||||||
|
min_size: (0, 0),
|
||||||
|
max_size: (0, 0),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
||||||
|
let toplevel = id.implement_nonsend(
|
||||||
|
self.clone(),
|
||||||
|
Some(destroy_toplevel::<U, R, SD>),
|
||||||
|
&self.loop_token,
|
||||||
|
);
|
||||||
|
toplevel.set_user_data(Box::into_raw(Box::new((
|
||||||
|
surface.clone(),
|
||||||
|
shell.clone(),
|
||||||
|
xdg_surface.clone(),
|
||||||
|
))) as *mut _);
|
||||||
|
|
||||||
|
self.shell_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.known_toplevels
|
||||||
|
.push(make_toplevel_handle(self.compositor_token, &toplevel));
|
||||||
|
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::NewToplevel { surface: handle }, ());
|
||||||
|
}
|
||||||
|
zxdg_surface_v6::Request::GetPopup {
|
||||||
|
id,
|
||||||
|
parent,
|
||||||
|
positioner,
|
||||||
|
} => {
|
||||||
|
let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) };
|
||||||
|
|
||||||
|
let parent_ptr = parent.get_user_data();
|
||||||
|
let &(ref parent_surface, _) = unsafe { &*(parent_ptr as *mut XdgSurfaceUserData) };
|
||||||
|
self.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
||||||
|
data.pending_state = ShellSurfacePendingState::Popup(PopupState {
|
||||||
|
parent: parent_surface.clone(),
|
||||||
|
positioner: *positioner_data,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
||||||
|
let popup = id.implement_nonsend(
|
||||||
|
self.clone(),
|
||||||
|
Some(destroy_popup::<U, R, SD>),
|
||||||
|
&self.loop_token,
|
||||||
|
);
|
||||||
|
popup.set_user_data(Box::into_raw(Box::new((
|
||||||
|
surface.clone(),
|
||||||
|
shell.clone(),
|
||||||
|
xdg_surface.clone(),
|
||||||
|
))) as *mut _);
|
||||||
|
|
||||||
|
self.shell_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.known_popups
|
||||||
|
.push(make_popup_handle(self.compositor_token, &popup));
|
||||||
|
|
||||||
|
let handle = make_popup_handle(self.compositor_token, &popup);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::NewPopup { surface: handle }, ());
|
||||||
|
}
|
||||||
|
zxdg_surface_v6::Request::SetWindowGeometry {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} => {
|
||||||
|
self.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
||||||
|
data.window_geometry = Some(Rectangle {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
zxdg_surface_v6::Request::AckConfigure { serial } => {
|
||||||
|
self.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
||||||
|
let mut found = false;
|
||||||
|
data.pending_configures.retain(|&s| {
|
||||||
|
if s == serial {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
s > serial
|
||||||
|
});
|
||||||
|
if !found {
|
||||||
|
// client responded to a non-existing configure
|
||||||
|
shell.post_error(
|
||||||
|
zxdg_shell_v6::Error::InvalidSurfaceState as u32,
|
||||||
|
format!("Wrong configure serial: {}", serial),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
data.configured = true;
|
||||||
|
})
|
||||||
|
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xdg_toplevel
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub type ShellSurfaceUserData = (
|
||||||
|
Resource<wl_surface::WlSurface>,
|
||||||
|
Resource<zxdg_shell_v6::ZxdgShellV6>,
|
||||||
|
Resource<zxdg_surface_v6::ZxdgSurfaceV6>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Utility functions allowing to factor out a lot of the upcoming logic
|
||||||
|
fn with_surface_toplevel_data<U, R, SD, F>(
|
||||||
|
implem: &ShellImplementation<U, R, SD>,
|
||||||
|
toplevel: &Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
f: F,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
F: FnOnce(&mut ToplevelState),
|
||||||
|
{
|
||||||
|
let ptr = toplevel.get_user_data();
|
||||||
|
let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
||||||
|
implem
|
||||||
|
.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| match data.pending_state {
|
||||||
|
ShellSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_toplevel_configure<U, R>(
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
resource: &Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
configure: ToplevelConfigure,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
{
|
||||||
|
let &(ref surface, _, ref shell_surface) =
|
||||||
|
unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) };
|
||||||
|
let (width, height) = configure.size.unwrap_or((0, 0));
|
||||||
|
// convert the Vec<State> (which is really a Vec<u32>) into Vec<u8>
|
||||||
|
let states = {
|
||||||
|
let mut states = configure.states;
|
||||||
|
let ptr = states.as_mut_ptr();
|
||||||
|
let len = states.len();
|
||||||
|
let cap = states.capacity();
|
||||||
|
::std::mem::forget(states);
|
||||||
|
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
|
||||||
|
};
|
||||||
|
let serial = configure.serial;
|
||||||
|
resource.send(zxdg_toplevel_v6::Event::Configure {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
states,
|
||||||
|
});
|
||||||
|
shell_surface.send(zxdg_surface_v6::Event::Configure { serial });
|
||||||
|
// Add the configure as pending
|
||||||
|
token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| data.pending_configures.push(serial))
|
||||||
|
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_toplevel_handle<U, R, SD>(
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
resource: &Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
) -> super::ToplevelSurface<U, R, SD> {
|
||||||
|
let ptr = resource.get_user_data();
|
||||||
|
let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
||||||
|
super::ToplevelSurface {
|
||||||
|
wl_surface: wl_surface.clone(),
|
||||||
|
shell_surface: resource.clone(),
|
||||||
|
token: token,
|
||||||
|
_shell_data: ::std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> Implementation<Resource<zxdg_toplevel_v6::ZxdgToplevelV6>, zxdg_toplevel_v6::Request>
|
||||||
|
for ShellImplementation<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
fn receive(
|
||||||
|
&mut self,
|
||||||
|
request: zxdg_toplevel_v6::Request,
|
||||||
|
toplevel: Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
) {
|
||||||
|
match request {
|
||||||
|
zxdg_toplevel_v6::Request::Destroy => {
|
||||||
|
// all it done by the destructor
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetParent { parent } => {
|
||||||
|
with_surface_toplevel_data(self, &toplevel, |toplevel_data| {
|
||||||
|
toplevel_data.parent = parent.map(|toplevel_surface_parent| {
|
||||||
|
let parent_ptr = toplevel_surface_parent.get_user_data();
|
||||||
|
let &(ref parent_surface, _, _) =
|
||||||
|
unsafe { &*(parent_ptr as *mut ShellSurfaceUserData) };
|
||||||
|
parent_surface.clone()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetTitle { title } => {
|
||||||
|
with_surface_toplevel_data(self, &toplevel, |toplevel_data| {
|
||||||
|
toplevel_data.title = title;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetAppId { app_id } => {
|
||||||
|
with_surface_toplevel_data(self, &toplevel, |toplevel_data| {
|
||||||
|
toplevel_data.app_id = app_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::ShowWindowMenu { seat, serial, x, y } => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::ShowWindowMenu {
|
||||||
|
surface: handle,
|
||||||
|
seat,
|
||||||
|
serial,
|
||||||
|
location: (x, y),
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::Move { seat, serial } => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::Move {
|
||||||
|
surface: handle,
|
||||||
|
seat,
|
||||||
|
serial,
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::Resize {
|
||||||
|
seat,
|
||||||
|
serial,
|
||||||
|
edges,
|
||||||
|
} => {
|
||||||
|
let edges = zxdg_toplevel_v6::ResizeEdge::from_raw(edges)
|
||||||
|
.unwrap_or(zxdg_toplevel_v6::ResizeEdge::None);
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::Resize {
|
||||||
|
surface: handle,
|
||||||
|
seat,
|
||||||
|
serial,
|
||||||
|
edges,
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetMaxSize { width, height } => {
|
||||||
|
with_surface_toplevel_data(self, &toplevel, |toplevel_data| {
|
||||||
|
toplevel_data.max_size = (width, height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetMinSize { width, height } => {
|
||||||
|
with_surface_toplevel_data(self, &toplevel, |toplevel_data| {
|
||||||
|
toplevel_data.max_size = (width, height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetMaximized => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::Maximize { surface: handle }, ());
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::UnsetMaximized => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::UnMaximize { surface: handle }, ());
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetFullscreen { output } => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::Fullscreen {
|
||||||
|
surface: handle,
|
||||||
|
output,
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::UnsetFullscreen => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::UnFullscreen { surface: handle }, ());
|
||||||
|
}
|
||||||
|
zxdg_toplevel_v6::Request::SetMinimized => {
|
||||||
|
let handle = make_toplevel_handle(self.compositor_token, &toplevel);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(ShellEvent::Minimize { surface: handle }, ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_toplevel<U, R, SD>(
|
||||||
|
toplevel: Resource<zxdg_toplevel_v6::ZxdgToplevelV6>,
|
||||||
|
implem: Box<Implementation<Resource<zxdg_toplevel_v6::ZxdgToplevelV6>, zxdg_toplevel_v6::Request>>,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
let implem: ShellImplementation<U, R, SD> = *downcast_impl(implem).unwrap_or_else(|_| unreachable!());
|
||||||
|
let ptr = toplevel.get_user_data();
|
||||||
|
toplevel.set_user_data(::std::ptr::null_mut());
|
||||||
|
// take back ownership of the userdata
|
||||||
|
let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) };
|
||||||
|
implem
|
||||||
|
.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&data.0, |data| {
|
||||||
|
data.pending_state = ShellSurfacePendingState::None;
|
||||||
|
data.configured = false;
|
||||||
|
})
|
||||||
|
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
||||||
|
// remove this surface from the known ones (as well as any leftover dead surface)
|
||||||
|
implem
|
||||||
|
.shell_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.known_toplevels
|
||||||
|
.retain(|other| {
|
||||||
|
other
|
||||||
|
.get_surface()
|
||||||
|
.map(|s| !s.equals(&data.0))
|
||||||
|
.unwrap_or(false)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xdg_popup
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub(crate) fn send_popup_configure<U, R>(
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
resource: &Resource<zxdg_popup_v6::ZxdgPopupV6>,
|
||||||
|
configure: PopupConfigure,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
{
|
||||||
|
let &(ref surface, _, ref shell_surface) =
|
||||||
|
unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) };
|
||||||
|
let (x, y) = configure.position;
|
||||||
|
let (width, height) = configure.size;
|
||||||
|
let serial = configure.serial;
|
||||||
|
resource.send(zxdg_popup_v6::Event::Configure {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
shell_surface.send(zxdg_surface_v6::Event::Configure { serial });
|
||||||
|
// Add the configure as pending
|
||||||
|
token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| data.pending_configures.push(serial))
|
||||||
|
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_popup_handle<U, R, SD>(
|
||||||
|
token: CompositorToken<U, R>,
|
||||||
|
resource: &Resource<zxdg_popup_v6::ZxdgPopupV6>,
|
||||||
|
) -> super::PopupSurface<U, R, SD> {
|
||||||
|
let ptr = resource.get_user_data();
|
||||||
|
let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
||||||
|
super::PopupSurface {
|
||||||
|
wl_surface: wl_surface.clone(),
|
||||||
|
shell_surface: resource.clone(),
|
||||||
|
token: token,
|
||||||
|
_shell_data: ::std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<U, R, SD> Implementation<Resource<zxdg_popup_v6::ZxdgPopupV6>, zxdg_popup_v6::Request>
|
||||||
|
for ShellImplementation<U, R, SD>
|
||||||
|
where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
fn receive(&mut self, request: zxdg_popup_v6::Request, popup: Resource<zxdg_popup_v6::ZxdgPopupV6>) {
|
||||||
|
match request {
|
||||||
|
zxdg_popup_v6::Request::Destroy => {
|
||||||
|
// all is handled by our destructor
|
||||||
|
}
|
||||||
|
zxdg_popup_v6::Request::Grab { seat, serial } => {
|
||||||
|
let handle = make_popup_handle(self.compositor_token, &popup);
|
||||||
|
let mut user_impl = self.user_impl.borrow_mut();
|
||||||
|
user_impl.receive(
|
||||||
|
ShellEvent::Grab {
|
||||||
|
surface: handle,
|
||||||
|
seat,
|
||||||
|
serial,
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_popup<U, R, SD>(
|
||||||
|
popup: Resource<zxdg_popup_v6::ZxdgPopupV6>,
|
||||||
|
implem: Box<Implementation<Resource<zxdg_popup_v6::ZxdgPopupV6>, zxdg_popup_v6::Request>>,
|
||||||
|
) where
|
||||||
|
U: 'static,
|
||||||
|
R: Role<ShellSurfaceRole> + 'static,
|
||||||
|
SD: 'static,
|
||||||
|
{
|
||||||
|
let implem: ShellImplementation<U, R, SD> = *downcast_impl(implem).unwrap_or_else(|_| unreachable!());
|
||||||
|
let ptr = popup.get_user_data();
|
||||||
|
popup.set_user_data(::std::ptr::null_mut());
|
||||||
|
// take back ownership of the userdata
|
||||||
|
let data = *unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) };
|
||||||
|
implem
|
||||||
|
.compositor_token
|
||||||
|
.with_role_data::<ShellSurfaceRole, _, _>(&data.0, |data| {
|
||||||
|
data.pending_state = ShellSurfacePendingState::None;
|
||||||
|
data.configured = false;
|
||||||
|
})
|
||||||
|
.expect("xdg_popup exists but surface has not shell_surface role?!");
|
||||||
|
// remove this surface from the known ones (as well as any leftover dead surface)
|
||||||
|
implem
|
||||||
|
.shell_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.known_popups
|
||||||
|
.retain(|other| {
|
||||||
|
other
|
||||||
|
.get_surface()
|
||||||
|
.map(|s| !s.equals(&data.0))
|
||||||
|
.unwrap_or(false)
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,697 +0,0 @@
|
||||||
use super::{make_shell_client_data, PopupConfigure, PopupState, PositionerState, ShellClient,
|
|
||||||
ShellClientData, ShellSurfaceIData, ShellSurfacePendingState, ShellSurfaceRole,
|
|
||||||
ToplevelConfigure, ToplevelState};
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use utils::Rectangle;
|
|
||||||
use wayland::compositor::CompositorToken;
|
|
||||||
use wayland::compositor::roles::*;
|
|
||||||
use wayland_protocols::unstable::xdg_shell::v6::server::{zxdg_popup_v6, zxdg_positioner_v6, zxdg_shell_v6,
|
|
||||||
zxdg_surface_v6, zxdg_toplevel_v6};
|
|
||||||
use wayland_server::{Client, EventLoopHandle, Resource};
|
|
||||||
use wayland_server::protocol::{wl_output, wl_surface};
|
|
||||||
|
|
||||||
pub(crate) fn xdg_shell_bind<U, R, CID, SID, SD>(
|
|
||||||
evlh: &mut EventLoopHandle, idata: &mut ShellSurfaceIData<U, R, CID, SID, SD>, _: &Client,
|
|
||||||
shell: zxdg_shell_v6::ZxdgShellV6,
|
|
||||||
) where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: Default + 'static,
|
|
||||||
{
|
|
||||||
shell.set_user_data(Box::into_raw(Box::new(Mutex::new(make_shell_client_data::<SD>()))) as *mut _);
|
|
||||||
evlh.register(
|
|
||||||
&shell,
|
|
||||||
shell_implementation(),
|
|
||||||
idata.clone(),
|
|
||||||
Some(destroy_shell::<SD>),
|
|
||||||
);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.new_client)(evlh, &mut *user_idata, make_shell_client(&shell));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* xdg_shell
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub(crate) type ShellUserData<SD> = Mutex<ShellClientData<SD>>;
|
|
||||||
|
|
||||||
fn destroy_shell<SD>(shell: &zxdg_shell_v6::ZxdgShellV6) {
|
|
||||||
let ptr = shell.get_user_data();
|
|
||||||
shell.set_user_data(::std::ptr::null_mut());
|
|
||||||
let data = unsafe { Box::from_raw(ptr as *mut ShellUserData<SD>) };
|
|
||||||
// explicit call to drop to not forget what we're doing here
|
|
||||||
::std::mem::drop(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn make_shell_client<SD>(resource: &zxdg_shell_v6::ZxdgShellV6) -> ShellClient<SD> {
|
|
||||||
ShellClient {
|
|
||||||
kind: super::ShellClientKind::Xdg(unsafe { resource.clone_unchecked() }),
|
|
||||||
_data: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shell_implementation<U, R, CID, SID, SD>(
|
|
||||||
) -> zxdg_shell_v6::Implementation<ShellSurfaceIData<U, R, CID, SID, SD>>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
zxdg_shell_v6::Implementation {
|
|
||||||
destroy: |_, _, _, _| {},
|
|
||||||
create_positioner: |evlh, _, _, _, positioner| {
|
|
||||||
let data = PositionerState {
|
|
||||||
rect_size: (0, 0),
|
|
||||||
anchor_rect: Rectangle {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
},
|
|
||||||
anchor_edges: zxdg_positioner_v6::Anchor::empty(),
|
|
||||||
gravity: zxdg_positioner_v6::Gravity::empty(),
|
|
||||||
constraint_adjustment: zxdg_positioner_v6::ConstraintAdjustment::empty(),
|
|
||||||
offset: (0, 0),
|
|
||||||
};
|
|
||||||
positioner.set_user_data(Box::into_raw(Box::new(data)) as *mut _);
|
|
||||||
evlh.register(
|
|
||||||
&positioner,
|
|
||||||
positioner_implementation(),
|
|
||||||
(),
|
|
||||||
Some(destroy_positioner),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
get_xdg_surface: |evlh, idata, _, shell, xdg_surface, wl_surface| {
|
|
||||||
let role_data = ShellSurfaceRole {
|
|
||||||
pending_state: ShellSurfacePendingState::None,
|
|
||||||
window_geometry: None,
|
|
||||||
pending_configures: Vec::new(),
|
|
||||||
configured: false,
|
|
||||||
};
|
|
||||||
if idata
|
|
||||||
.compositor_token
|
|
||||||
.give_role_with(wl_surface, role_data)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
shell.post_error(
|
|
||||||
zxdg_shell_v6::Error::Role as u32,
|
|
||||||
"Surface already has a role.".into(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xdg_surface.set_user_data(Box::into_raw(Box::new((
|
|
||||||
unsafe { wl_surface.clone_unchecked() },
|
|
||||||
unsafe { shell.clone_unchecked() },
|
|
||||||
))) as *mut _);
|
|
||||||
evlh.register(
|
|
||||||
&xdg_surface,
|
|
||||||
surface_implementation(),
|
|
||||||
idata.clone(),
|
|
||||||
Some(destroy_surface),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
pong: |evlh, idata, _, shell, serial| {
|
|
||||||
let valid = {
|
|
||||||
let mutex = unsafe { &*(shell.get_user_data() as *mut ShellUserData<SD>) };
|
|
||||||
let mut guard = mutex.lock().unwrap();
|
|
||||||
if guard.pending_ping == serial {
|
|
||||||
guard.pending_ping = 0;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if valid {
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.client_pong)(evlh, &mut *user_idata, make_shell_client(shell));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* xdg_positioner
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn destroy_positioner(positioner: &zxdg_positioner_v6::ZxdgPositionerV6) {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
positioner.set_user_data(::std::ptr::null_mut());
|
|
||||||
// drop the PositionerState
|
|
||||||
let surface = unsafe { Box::from_raw(ptr as *mut PositionerState) };
|
|
||||||
// explicit call to drop to not forget what we're doing here
|
|
||||||
::std::mem::drop(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn positioner_implementation() -> zxdg_positioner_v6::Implementation<()> {
|
|
||||||
zxdg_positioner_v6::Implementation {
|
|
||||||
destroy: |_, _, _, _| {},
|
|
||||||
set_size: |_, _, _, positioner, width, height| {
|
|
||||||
if width < 1 || height < 1 {
|
|
||||||
positioner.post_error(
|
|
||||||
zxdg_positioner_v6::Error::InvalidInput as u32,
|
|
||||||
"Invalid size for positioner.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.rect_size = (width, height);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_anchor_rect: |_, _, _, positioner, x, y, width, height| {
|
|
||||||
if width < 1 || height < 1 {
|
|
||||||
positioner.post_error(
|
|
||||||
zxdg_positioner_v6::Error::InvalidInput as u32,
|
|
||||||
"Invalid size for positioner's anchor rectangle.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.anchor_rect = Rectangle {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_anchor: |_, _, _, positioner, anchor| {
|
|
||||||
use self::zxdg_positioner_v6::Anchor;
|
|
||||||
if anchor.contains(Anchor::Left | Anchor::Right) || anchor.contains(Anchor::Top | Anchor::Bottom)
|
|
||||||
{
|
|
||||||
positioner.post_error(
|
|
||||||
zxdg_positioner_v6::Error::InvalidInput as u32,
|
|
||||||
"Invalid anchor for positioner.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.anchor_edges = anchor;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_gravity: |_, _, _, positioner, gravity| {
|
|
||||||
use self::zxdg_positioner_v6::Gravity;
|
|
||||||
if gravity.contains(Gravity::Left | Gravity::Right)
|
|
||||||
|| gravity.contains(Gravity::Top | Gravity::Bottom)
|
|
||||||
{
|
|
||||||
positioner.post_error(
|
|
||||||
zxdg_positioner_v6::Error::InvalidInput as u32,
|
|
||||||
"Invalid gravity for positioner.".into(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.gravity = gravity;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set_constraint_adjustment: |_, _, _, positioner, constraint_adjustment| {
|
|
||||||
let constraint_adjustment =
|
|
||||||
zxdg_positioner_v6::ConstraintAdjustment::from_bits_truncate(constraint_adjustment);
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.constraint_adjustment = constraint_adjustment;
|
|
||||||
},
|
|
||||||
set_offset: |_, _, _, positioner, x, y| {
|
|
||||||
let ptr = positioner.get_user_data();
|
|
||||||
let state = unsafe { &mut *(ptr as *mut PositionerState) };
|
|
||||||
state.offset = (x, y);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* xdg_surface
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn destroy_surface(surface: &zxdg_surface_v6::ZxdgSurfaceV6) {
|
|
||||||
let ptr = surface.get_user_data();
|
|
||||||
surface.set_user_data(::std::ptr::null_mut());
|
|
||||||
// drop the state
|
|
||||||
let data =
|
|
||||||
unsafe { Box::from_raw(ptr as *mut (zxdg_surface_v6::ZxdgSurfaceV6, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
// explicit call to drop to not forget what we're doing here
|
|
||||||
::std::mem::drop(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn surface_implementation<U, R, CID, SID, SD>(
|
|
||||||
) -> zxdg_surface_v6::Implementation<ShellSurfaceIData<U, R, CID, SID, SD>>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
zxdg_surface_v6::Implementation {
|
|
||||||
destroy: |_, idata, _, xdg_surface| {
|
|
||||||
let ptr = xdg_surface.get_user_data();
|
|
||||||
let &(ref surface, ref shell) =
|
|
||||||
unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
if let ShellSurfacePendingState::None = data.pending_state {
|
|
||||||
// all is good
|
|
||||||
} else {
|
|
||||||
shell.post_error(
|
|
||||||
zxdg_shell_v6::Error::Role as u32,
|
|
||||||
"xdg_surface was destroyed before its role object".into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
|
||||||
},
|
|
||||||
get_toplevel: |evlh, idata, _, xdg_surface, toplevel| {
|
|
||||||
let ptr = xdg_surface.get_user_data();
|
|
||||||
let &(ref surface, ref shell) =
|
|
||||||
unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
data.pending_state = ShellSurfacePendingState::Toplevel(ToplevelState {
|
|
||||||
parent: None,
|
|
||||||
title: String::new(),
|
|
||||||
app_id: String::new(),
|
|
||||||
min_size: (0, 0),
|
|
||||||
max_size: (0, 0),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
|
||||||
|
|
||||||
toplevel.set_user_data(Box::into_raw(Box::new(unsafe {
|
|
||||||
(
|
|
||||||
surface.clone_unchecked(),
|
|
||||||
shell.clone_unchecked(),
|
|
||||||
xdg_surface.clone_unchecked(),
|
|
||||||
)
|
|
||||||
})) as *mut _);
|
|
||||||
evlh.register(
|
|
||||||
&toplevel,
|
|
||||||
toplevel_implementation(),
|
|
||||||
idata.clone(),
|
|
||||||
Some(destroy_toplevel),
|
|
||||||
);
|
|
||||||
|
|
||||||
// register to self
|
|
||||||
evlh.state()
|
|
||||||
.get_mut(&idata.state_token)
|
|
||||||
.known_toplevels
|
|
||||||
.push(make_toplevel_handle(idata.compositor_token, &toplevel));
|
|
||||||
|
|
||||||
// intial configure event
|
|
||||||
let handle = make_toplevel_handle(idata.compositor_token, &toplevel);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
let configure = (idata.implementation.new_toplevel)(evlh, &mut *user_idata, handle);
|
|
||||||
send_toplevel_configure(idata.compositor_token, &toplevel, configure);
|
|
||||||
},
|
|
||||||
get_popup: |evlh, idata, _, xdg_surface, popup, parent, positioner| {
|
|
||||||
let ptr = xdg_surface.get_user_data();
|
|
||||||
let &(ref surface, ref shell) =
|
|
||||||
unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
|
|
||||||
let positioner_data = unsafe { &*(positioner.get_user_data() as *const PositionerState) };
|
|
||||||
|
|
||||||
let parent_ptr = parent.get_user_data();
|
|
||||||
let &(ref parent_surface, _) =
|
|
||||||
unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
data.pending_state = ShellSurfacePendingState::Popup(PopupState {
|
|
||||||
parent: unsafe { parent_surface.clone_unchecked() },
|
|
||||||
positioner: *positioner_data,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
|
||||||
|
|
||||||
popup.set_user_data(Box::into_raw(Box::new(unsafe {
|
|
||||||
(
|
|
||||||
surface.clone_unchecked(),
|
|
||||||
shell.clone_unchecked(),
|
|
||||||
xdg_surface.clone_unchecked(),
|
|
||||||
)
|
|
||||||
})) as *mut _);
|
|
||||||
evlh.register(
|
|
||||||
&popup,
|
|
||||||
popup_implementation(),
|
|
||||||
idata.clone(),
|
|
||||||
Some(destroy_popup),
|
|
||||||
);
|
|
||||||
|
|
||||||
// register to self
|
|
||||||
evlh.state()
|
|
||||||
.get_mut(&idata.state_token)
|
|
||||||
.known_popups
|
|
||||||
.push(make_popup_handle(idata.compositor_token, &popup));
|
|
||||||
|
|
||||||
// intial configure event
|
|
||||||
let handle = make_popup_handle(idata.compositor_token, &popup);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
let configure = (idata.implementation.new_popup)(evlh, &mut *user_idata, handle);
|
|
||||||
send_popup_configure(idata.compositor_token, &popup, configure);
|
|
||||||
},
|
|
||||||
set_window_geometry: |_, idata, _, surface, x, y, width, height| {
|
|
||||||
let ptr = surface.get_user_data();
|
|
||||||
let &(ref surface, _) =
|
|
||||||
unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
data.window_geometry = Some(Rectangle {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
|
||||||
},
|
|
||||||
ack_configure: |_, idata, _, surface, serial| {
|
|
||||||
let ptr = surface.get_user_data();
|
|
||||||
let &(ref surface, ref shell) =
|
|
||||||
unsafe { &*(ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
let mut found = false;
|
|
||||||
data.pending_configures.retain(|&s| {
|
|
||||||
if s == serial {
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
s > serial
|
|
||||||
});
|
|
||||||
if !found {
|
|
||||||
// client responded to a non-existing configure
|
|
||||||
shell.post_error(
|
|
||||||
zxdg_shell_v6::Error::InvalidSurfaceState as u32,
|
|
||||||
format!("Wrong configure serial: {}", serial),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
data.configured = true;
|
|
||||||
})
|
|
||||||
.expect("xdg_surface exists but surface has not shell_surface role?!");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* xdg_toplevel
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub type ShellSurfaceUserData = (
|
|
||||||
wl_surface::WlSurface,
|
|
||||||
zxdg_shell_v6::ZxdgShellV6,
|
|
||||||
zxdg_surface_v6::ZxdgSurfaceV6,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn destroy_toplevel(surface: &zxdg_toplevel_v6::ZxdgToplevelV6) {
|
|
||||||
let ptr = surface.get_user_data();
|
|
||||||
surface.set_user_data(::std::ptr::null_mut());
|
|
||||||
// drop the PositionerState
|
|
||||||
let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
// explicit call to drop to not forget what we're doing there
|
|
||||||
::std::mem::drop(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility functions allowing to factor out a lot of the upcoming logic
|
|
||||||
fn with_surface_toplevel_data<U, R, CID, SID, SD, F>(
|
|
||||||
idata: &ShellSurfaceIData<U, R, CID, SID, SD>, toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, f: F
|
|
||||||
) where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
F: FnOnce(&mut ToplevelState),
|
|
||||||
{
|
|
||||||
let ptr = toplevel.get_user_data();
|
|
||||||
let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| match data.pending_state {
|
|
||||||
ShellSurfacePendingState::Toplevel(ref mut toplevel_data) => f(toplevel_data),
|
|
||||||
_ => unreachable!(),
|
|
||||||
})
|
|
||||||
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn xdg_handle_display_state_change<U, R, CID, SID, SD>(
|
|
||||||
evlh: &mut EventLoopHandle, idata: &ShellSurfaceIData<U, R, CID, SID, SD>,
|
|
||||||
toplevel: &zxdg_toplevel_v6::ZxdgToplevelV6, maximized: Option<bool>, minimized: Option<bool>,
|
|
||||||
fullscreen: Option<bool>, output: Option<&wl_output::WlOutput>,
|
|
||||||
) where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
let handle = make_toplevel_handle(idata.compositor_token, toplevel);
|
|
||||||
// handler callback
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
let configure = (idata.implementation.change_display_state)(
|
|
||||||
evlh,
|
|
||||||
&mut *user_idata,
|
|
||||||
handle,
|
|
||||||
maximized,
|
|
||||||
minimized,
|
|
||||||
fullscreen,
|
|
||||||
output,
|
|
||||||
);
|
|
||||||
// send the configure response to client
|
|
||||||
send_toplevel_configure(idata.compositor_token, toplevel, configure);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_toplevel_configure<U, R, ID>(
|
|
||||||
token: CompositorToken<U, R, ID>, resource: &zxdg_toplevel_v6::ZxdgToplevelV6,
|
|
||||||
configure: ToplevelConfigure,
|
|
||||||
) where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
ID: 'static,
|
|
||||||
{
|
|
||||||
let &(ref surface, _, ref shell_surface) =
|
|
||||||
unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) };
|
|
||||||
let (w, h) = configure.size.unwrap_or((0, 0));
|
|
||||||
// convert the Vec<State> (which is really a Vec<u32>) into Vec<u8>
|
|
||||||
let states = {
|
|
||||||
let mut states = configure.states;
|
|
||||||
let ptr = states.as_mut_ptr();
|
|
||||||
let len = states.len();
|
|
||||||
let cap = states.capacity();
|
|
||||||
::std::mem::forget(states);
|
|
||||||
unsafe { Vec::from_raw_parts(ptr as *mut u8, len * 4, cap * 4) }
|
|
||||||
};
|
|
||||||
let serial = configure.serial;
|
|
||||||
resource.configure(w, h, states);
|
|
||||||
shell_surface.configure(serial);
|
|
||||||
// Add the configure as pending
|
|
||||||
token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| data.pending_configures.push(serial))
|
|
||||||
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_toplevel_handle<U, R, H, SD>(
|
|
||||||
token: CompositorToken<U, R, H>, resource: &zxdg_toplevel_v6::ZxdgToplevelV6
|
|
||||||
) -> super::ToplevelSurface<U, R, H, SD> {
|
|
||||||
let ptr = resource.get_user_data();
|
|
||||||
let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
super::ToplevelSurface {
|
|
||||||
wl_surface: unsafe { wl_surface.clone_unchecked() },
|
|
||||||
shell_surface: super::SurfaceKind::XdgToplevel(unsafe { resource.clone_unchecked() }),
|
|
||||||
token: token,
|
|
||||||
_shell_data: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toplevel_implementation<U, R, CID, SID, SD>(
|
|
||||||
) -> zxdg_toplevel_v6::Implementation<ShellSurfaceIData<U, R, CID, SID, SD>>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
zxdg_toplevel_v6::Implementation {
|
|
||||||
destroy: |evlh, idata, _, toplevel| {
|
|
||||||
let ptr = toplevel.get_user_data();
|
|
||||||
let &(ref surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
data.pending_state = ShellSurfacePendingState::None;
|
|
||||||
data.configured = false;
|
|
||||||
})
|
|
||||||
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
|
||||||
// remove this surface from the known ones (as well as any leftover dead surface)
|
|
||||||
evlh.state()
|
|
||||||
.get_mut(&idata.state_token)
|
|
||||||
.known_toplevels
|
|
||||||
.retain(|other| {
|
|
||||||
other
|
|
||||||
.get_surface()
|
|
||||||
.map(|s| !s.equals(surface))
|
|
||||||
.unwrap_or(false)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set_parent: |_, idata, _, toplevel, parent| {
|
|
||||||
with_surface_toplevel_data(idata, toplevel, |toplevel_data| {
|
|
||||||
toplevel_data.parent = parent.map(|toplevel_surface_parent| {
|
|
||||||
let parent_ptr = toplevel_surface_parent.get_user_data();
|
|
||||||
let &(ref parent_surface, _) =
|
|
||||||
unsafe { &*(parent_ptr as *mut (wl_surface::WlSurface, zxdg_shell_v6::ZxdgShellV6)) };
|
|
||||||
unsafe { parent_surface.clone_unchecked() }
|
|
||||||
})
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set_title: |_, idata, _, toplevel, title| {
|
|
||||||
with_surface_toplevel_data(idata, toplevel, |toplevel_data| {
|
|
||||||
toplevel_data.title = title;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set_app_id: |_, idata, _, toplevel, app_id| {
|
|
||||||
with_surface_toplevel_data(idata, toplevel, |toplevel_data| {
|
|
||||||
toplevel_data.app_id = app_id;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
show_window_menu: |evlh, idata, _, toplevel, seat, serial, x, y| {
|
|
||||||
let handle = make_toplevel_handle(idata.compositor_token, toplevel);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.show_window_menu)(evlh, &mut *user_idata, handle, seat, serial, x, y)
|
|
||||||
},
|
|
||||||
move_: |evlh, idata, _, toplevel, seat, serial| {
|
|
||||||
let handle = make_toplevel_handle(idata.compositor_token, toplevel);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.move_)(evlh, &mut *user_idata, handle, seat, serial)
|
|
||||||
},
|
|
||||||
resize: |evlh, idata, _, toplevel, seat, serial, edges| {
|
|
||||||
let edges =
|
|
||||||
zxdg_toplevel_v6::ResizeEdge::from_raw(edges).unwrap_or(zxdg_toplevel_v6::ResizeEdge::None);
|
|
||||||
let handle = make_toplevel_handle(idata.compositor_token, toplevel);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.resize)(evlh, &mut *user_idata, handle, seat, serial, edges)
|
|
||||||
},
|
|
||||||
set_max_size: |_, idata, _, toplevel, width, height| {
|
|
||||||
with_surface_toplevel_data(idata, toplevel, |toplevel_data| {
|
|
||||||
toplevel_data.max_size = (width, height);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
set_min_size: |_, idata, _, toplevel, width, height| {
|
|
||||||
with_surface_toplevel_data(idata, toplevel, |toplevel_data| {
|
|
||||||
toplevel_data.min_size = (width, height);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
set_maximized: |evlh, idata, _, toplevel| {
|
|
||||||
xdg_handle_display_state_change(evlh, idata, toplevel, Some(true), None, None, None);
|
|
||||||
},
|
|
||||||
unset_maximized: |evlh, idata, _, toplevel| {
|
|
||||||
xdg_handle_display_state_change(evlh, idata, toplevel, Some(false), None, None, None);
|
|
||||||
},
|
|
||||||
set_fullscreen: |evlh, idata, _, toplevel, seat| {
|
|
||||||
xdg_handle_display_state_change(evlh, idata, toplevel, None, None, Some(true), seat);
|
|
||||||
},
|
|
||||||
unset_fullscreen: |evlh, idata, _, toplevel| {
|
|
||||||
xdg_handle_display_state_change(evlh, idata, toplevel, None, None, Some(false), None);
|
|
||||||
},
|
|
||||||
set_minimized: |evlh, idata, _, toplevel| {
|
|
||||||
xdg_handle_display_state_change(evlh, idata, toplevel, None, Some(true), None, None);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* xdg_popup
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn destroy_popup(surface: &zxdg_popup_v6::ZxdgPopupV6) {
|
|
||||||
let ptr = surface.get_user_data();
|
|
||||||
surface.set_user_data(::std::ptr::null_mut());
|
|
||||||
// drop the PositionerState
|
|
||||||
let data = unsafe { Box::from_raw(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
// explicit call to drop to not forget what we're doing
|
|
||||||
::std::mem::drop(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn send_popup_configure<U, R, ID>(
|
|
||||||
token: CompositorToken<U, R, ID>, resource: &zxdg_popup_v6::ZxdgPopupV6, configure: PopupConfigure
|
|
||||||
) where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
ID: 'static,
|
|
||||||
{
|
|
||||||
let &(ref surface, _, ref shell_surface) =
|
|
||||||
unsafe { &*(resource.get_user_data() as *mut ShellSurfaceUserData) };
|
|
||||||
let (x, y) = configure.position;
|
|
||||||
let (w, h) = configure.size;
|
|
||||||
let serial = configure.serial;
|
|
||||||
resource.configure(x, y, w, h);
|
|
||||||
shell_surface.configure(serial);
|
|
||||||
// Add the configure as pending
|
|
||||||
token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| data.pending_configures.push(serial))
|
|
||||||
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_popup_handle<U, R, H, SD>(
|
|
||||||
token: CompositorToken<U, R, H>, resource: &zxdg_popup_v6::ZxdgPopupV6
|
|
||||||
) -> super::PopupSurface<U, R, H, SD> {
|
|
||||||
let ptr = resource.get_user_data();
|
|
||||||
let &(ref wl_surface, _, _) = unsafe { &*(ptr as *mut ShellSurfaceUserData) };
|
|
||||||
super::PopupSurface {
|
|
||||||
wl_surface: unsafe { wl_surface.clone_unchecked() },
|
|
||||||
shell_surface: super::SurfaceKind::XdgPopup(unsafe { resource.clone_unchecked() }),
|
|
||||||
token: token,
|
|
||||||
_shell_data: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn popup_implementation<U, R, CID, SID, SD>(
|
|
||||||
) -> zxdg_popup_v6::Implementation<ShellSurfaceIData<U, R, CID, SID, SD>>
|
|
||||||
where
|
|
||||||
U: 'static,
|
|
||||||
R: Role<ShellSurfaceRole> + 'static,
|
|
||||||
CID: 'static,
|
|
||||||
SID: 'static,
|
|
||||||
SD: 'static,
|
|
||||||
{
|
|
||||||
zxdg_popup_v6::Implementation {
|
|
||||||
destroy: |evlh, idata, _, popup| {
|
|
||||||
let ptr = popup.get_user_data();
|
|
||||||
let &(ref surface, _, _) = unsafe {
|
|
||||||
&*(ptr
|
|
||||||
as *mut (
|
|
||||||
wl_surface::WlSurface,
|
|
||||||
zxdg_shell_v6::ZxdgShellV6,
|
|
||||||
zxdg_surface_v6::ZxdgSurfaceV6,
|
|
||||||
))
|
|
||||||
};
|
|
||||||
idata
|
|
||||||
.compositor_token
|
|
||||||
.with_role_data::<ShellSurfaceRole, _, _>(surface, |data| {
|
|
||||||
data.pending_state = ShellSurfacePendingState::None;
|
|
||||||
data.configured = false;
|
|
||||||
})
|
|
||||||
.expect("xdg_toplevel exists but surface has not shell_surface role?!");
|
|
||||||
// remove this surface from the known ones (as well as any leftover dead surface)
|
|
||||||
evlh.state()
|
|
||||||
.get_mut(&idata.state_token)
|
|
||||||
.known_popups
|
|
||||||
.retain(|other| {
|
|
||||||
other
|
|
||||||
.get_surface()
|
|
||||||
.map(|s| !s.equals(surface))
|
|
||||||
.unwrap_or(false)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
grab: |evlh, idata, _, popup, seat, serial| {
|
|
||||||
let handle = make_popup_handle(idata.compositor_token, popup);
|
|
||||||
let mut user_idata = idata.idata.borrow_mut();
|
|
||||||
(idata.implementation.grab)(evlh, &mut *user_idata, handle, seat, serial)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue