wayland.xdg-activation: Initial implementaion
This commit is contained in:
parent
01ac0f135b
commit
f24332f4b4
|
@ -42,7 +42,7 @@ thiserror = "1.0.2"
|
||||||
udev = { version = "0.6", optional = true }
|
udev = { version = "0.6", optional = true }
|
||||||
wayland-commons = { version = "0.29.0", optional = true }
|
wayland-commons = { version = "0.29.0", optional = true }
|
||||||
wayland-egl = { version = "0.29.0", optional = true }
|
wayland-egl = { version = "0.29.0", optional = true }
|
||||||
wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "server"], optional = true }
|
wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "staging_protocols", "server"], optional = true }
|
||||||
wayland-server = { version = "0.29.0", optional = true }
|
wayland-server = { version = "0.29.0", optional = true }
|
||||||
wayland-sys = { version = "0.29.0", optional = true }
|
wayland-sys = { version = "0.29.0", optional = true }
|
||||||
winit = { version = "0.25.0", optional = true }
|
winit = { version = "0.25.0", optional = true }
|
||||||
|
|
|
@ -63,6 +63,7 @@ pub mod seat;
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
pub mod shm;
|
pub mod shm;
|
||||||
pub mod tablet_manager;
|
pub mod tablet_manager;
|
||||||
|
pub mod xdg_activation;
|
||||||
pub mod xdg_foreign;
|
pub mod xdg_foreign;
|
||||||
|
|
||||||
/// A global [`SerialCounter`] for use in your compositor.
|
/// A global [`SerialCounter`] for use in your compositor.
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
rc::Rc,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use wayland_protocols::staging::xdg_activation::v1::server::{xdg_activation_token_v1, xdg_activation_v1};
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
|
||||||
|
DispatchData, Filter, Main,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::wayland::Serial;
|
||||||
|
|
||||||
|
use super::{XdgActivationEvent, XdgActivationState, XdgActivationToken, XdgActivationTokenData};
|
||||||
|
|
||||||
|
type Impl = dyn FnMut(&Mutex<XdgActivationState>, XdgActivationEvent, DispatchData<'_>);
|
||||||
|
|
||||||
|
/// New xdg activation global
|
||||||
|
pub(super) fn implement_activation_global(
|
||||||
|
global: Main<xdg_activation_v1::XdgActivationV1>,
|
||||||
|
state: Arc<Mutex<XdgActivationState>>,
|
||||||
|
implementation: Rc<RefCell<Impl>>,
|
||||||
|
) {
|
||||||
|
global.quick_assign(move |_, req, ddata| match req {
|
||||||
|
xdg_activation_v1::Request::GetActivationToken { id } => {
|
||||||
|
get_activation_token(id, state.clone(), implementation.clone());
|
||||||
|
}
|
||||||
|
xdg_activation_v1::Request::Activate { token, surface } => {
|
||||||
|
activate(
|
||||||
|
token.into(),
|
||||||
|
surface,
|
||||||
|
state.as_ref(),
|
||||||
|
implementation.as_ref(),
|
||||||
|
ddata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New xdg activation token
|
||||||
|
fn get_activation_token(
|
||||||
|
id: Main<xdg_activation_token_v1::XdgActivationTokenV1>,
|
||||||
|
state: Arc<Mutex<XdgActivationState>>,
|
||||||
|
implementation: Rc<RefCell<Impl>>,
|
||||||
|
) {
|
||||||
|
id.quick_assign({
|
||||||
|
let state = state.clone();
|
||||||
|
|
||||||
|
let mut token_serial: Option<(Serial, WlSeat)> = None;
|
||||||
|
let mut token_app_id: Option<String> = None;
|
||||||
|
let mut token_surface: Option<WlSurface> = None;
|
||||||
|
let mut token_constructed = false;
|
||||||
|
|
||||||
|
move |id, req, _| {
|
||||||
|
if !token_constructed {
|
||||||
|
match req {
|
||||||
|
xdg_activation_token_v1::Request::SetSerial { serial, seat } => {
|
||||||
|
token_serial = Some((serial.into(), seat));
|
||||||
|
}
|
||||||
|
xdg_activation_token_v1::Request::SetAppId { app_id } => {
|
||||||
|
token_app_id = Some(app_id);
|
||||||
|
}
|
||||||
|
xdg_activation_token_v1::Request::SetSurface { surface } => {
|
||||||
|
token_surface = Some(surface);
|
||||||
|
}
|
||||||
|
xdg_activation_token_v1::Request::Commit => {
|
||||||
|
let (token, token_data) = XdgActivationTokenData::new(
|
||||||
|
token_serial.take(),
|
||||||
|
token_app_id.take(),
|
||||||
|
token_surface.take(),
|
||||||
|
);
|
||||||
|
|
||||||
|
state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.pending_tokens
|
||||||
|
.insert(token.clone(), token_data);
|
||||||
|
id.as_ref().user_data().set_threadsafe(|| token.clone());
|
||||||
|
|
||||||
|
id.done(token.to_string());
|
||||||
|
|
||||||
|
token_constructed = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
id.as_ref().post_error(
|
||||||
|
xdg_activation_token_v1::Error::AlreadyUsed as u32,
|
||||||
|
"The activation token has already been constructed".into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
id.assign_destructor(Filter::new(
|
||||||
|
move |token: xdg_activation_token_v1::XdgActivationTokenV1, _, ddata| {
|
||||||
|
if let Some(token) = token.as_ref().user_data().get::<XdgActivationToken>() {
|
||||||
|
state.lock().unwrap().pending_tokens.remove(token);
|
||||||
|
|
||||||
|
if let Some((token_data, surface)) = state.lock().unwrap().activation_requests.remove(token) {
|
||||||
|
let mut cb = implementation.borrow_mut();
|
||||||
|
cb(
|
||||||
|
&state,
|
||||||
|
XdgActivationEvent::DestroyActivationRequest {
|
||||||
|
token: token.clone(),
|
||||||
|
token_data,
|
||||||
|
surface,
|
||||||
|
},
|
||||||
|
ddata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Xdg activation request
|
||||||
|
fn activate(
|
||||||
|
token: XdgActivationToken,
|
||||||
|
surface: WlSurface,
|
||||||
|
state: &Mutex<XdgActivationState>,
|
||||||
|
implementation: &RefCell<Impl>,
|
||||||
|
ddata: DispatchData<'_>,
|
||||||
|
) {
|
||||||
|
let mut guard = state.lock().unwrap();
|
||||||
|
if let Some(token_data) = guard.pending_tokens.remove(&token) {
|
||||||
|
guard
|
||||||
|
.activation_requests
|
||||||
|
.insert(token.clone(), (token_data.clone(), surface.clone()));
|
||||||
|
|
||||||
|
// The user may want to use state, so we need to unlock it
|
||||||
|
drop(guard);
|
||||||
|
|
||||||
|
let mut cb = implementation.borrow_mut();
|
||||||
|
cb(
|
||||||
|
state,
|
||||||
|
XdgActivationEvent::RequestActivation {
|
||||||
|
token: token.clone(),
|
||||||
|
token_data,
|
||||||
|
surface,
|
||||||
|
},
|
||||||
|
ddata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
//! Utilities for handling activation requests with the `xdg_activation` protocol
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ### Example
|
||||||
|
//! ```no_run
|
||||||
|
//! # extern crate wayland_server;
|
||||||
|
//! #
|
||||||
|
//! use smithay::wayland::xdg_activation::{init_xdg_activation_global, XdgActivationEvent};
|
||||||
|
//!
|
||||||
|
//! # let mut display = wayland_server::Display::new();
|
||||||
|
//! let (state, _) = init_xdg_activation_global(
|
||||||
|
//! &mut display,
|
||||||
|
//! // your implementation
|
||||||
|
//! |state, req, dispatch_data| {
|
||||||
|
//! match req{
|
||||||
|
//! XdgActivationEvent::RequestActivation { token, token_data, surface } => {
|
||||||
|
//! if token_data.timestamp.elapsed().as_secs() < 10 {
|
||||||
|
//! // Request surface activation
|
||||||
|
//! } else{
|
||||||
|
//! // Discard the request
|
||||||
|
//! state.lock().unwrap().remove_request(&token);
|
||||||
|
//! }
|
||||||
|
//! },
|
||||||
|
//! XdgActivationEvent::DestroyActivationRequest {..} => {
|
||||||
|
//! // The request is canceled
|
||||||
|
//! },
|
||||||
|
//! }
|
||||||
|
//! },
|
||||||
|
//! None // put a logger if you want
|
||||||
|
//! );
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::HashMap,
|
||||||
|
ops,
|
||||||
|
rc::Rc,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use wayland_protocols::staging::xdg_activation::v1::server::xdg_activation_v1;
|
||||||
|
use wayland_server::{
|
||||||
|
protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
|
||||||
|
DispatchData, Display, Filter, Global, Main, UserDataMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
|
|
||||||
|
use crate::wayland::Serial;
|
||||||
|
|
||||||
|
mod handlers;
|
||||||
|
|
||||||
|
/// Contains the unique string token of activation request
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct XdgActivationToken(String);
|
||||||
|
|
||||||
|
impl XdgActivationToken {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(Alphanumeric.sample_string(&mut rand::thread_rng(), 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a string slice containing the entire token.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for XdgActivationToken {
|
||||||
|
type Target = str;
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for XdgActivationToken {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<XdgActivationToken> for String {
|
||||||
|
fn from(s: XdgActivationToken) -> Self {
|
||||||
|
s.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Activation data asosiated with the [`XdgActivationToken`]
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct XdgActivationTokenData {
|
||||||
|
/// Provides information about the seat and serial event that requested the token.
|
||||||
|
///
|
||||||
|
/// The serial can come from an input or focus event.
|
||||||
|
/// For instance, if a click triggers the launch of a third-party client,
|
||||||
|
/// this field should contain serial and seat from the wl_pointer.button event.
|
||||||
|
///
|
||||||
|
/// Some compositors might refuse to activate toplevels
|
||||||
|
/// when the token doesn't have a valid and recent enough event serial.
|
||||||
|
pub serial: Option<(Serial, WlSeat)>,
|
||||||
|
/// The requesting client can specify an app_id to associate the token being created with it.
|
||||||
|
pub app_id: Option<String>,
|
||||||
|
/// The surface requesting the activation.
|
||||||
|
///
|
||||||
|
/// Note, this is different from the surface that will be activated.
|
||||||
|
pub surface: Option<WlSurface>,
|
||||||
|
/// Timestamp of the token
|
||||||
|
///
|
||||||
|
/// You can use this do ignore tokens based on time.
|
||||||
|
/// For example you coould ignore all tokens older that 5s.
|
||||||
|
pub timestamp: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XdgActivationTokenData {
|
||||||
|
fn new(
|
||||||
|
serial: Option<(Serial, WlSeat)>,
|
||||||
|
app_id: Option<String>,
|
||||||
|
surface: Option<WlSurface>,
|
||||||
|
) -> (XdgActivationToken, XdgActivationTokenData) {
|
||||||
|
(
|
||||||
|
XdgActivationToken::new(),
|
||||||
|
XdgActivationTokenData {
|
||||||
|
serial,
|
||||||
|
app_id,
|
||||||
|
surface,
|
||||||
|
timestamp: Instant::now(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks the list of pending and current activation requests
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct XdgActivationState {
|
||||||
|
log: ::slog::Logger,
|
||||||
|
user_data: UserDataMap,
|
||||||
|
|
||||||
|
pending_tokens: HashMap<XdgActivationToken, XdgActivationTokenData>,
|
||||||
|
|
||||||
|
activation_requests: HashMap<XdgActivationToken, (XdgActivationTokenData, WlSurface)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XdgActivationState {
|
||||||
|
/// Get current activation requests
|
||||||
|
///
|
||||||
|
/// HashMap contains token data and target surface
|
||||||
|
pub fn requests(&self) -> &HashMap<XdgActivationToken, (XdgActivationTokenData, WlSurface)> {
|
||||||
|
&self.activation_requests
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove and return the activation request
|
||||||
|
///
|
||||||
|
/// If you consider a request to be unwanted you can use this method to
|
||||||
|
/// discard it and don't track it any futher.
|
||||||
|
pub fn remove_request(
|
||||||
|
&mut self,
|
||||||
|
token: &XdgActivationToken,
|
||||||
|
) -> Option<(XdgActivationTokenData, WlSurface)> {
|
||||||
|
self.activation_requests.remove(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retain activation requests
|
||||||
|
pub fn retain_reqests<F>(&mut self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&XdgActivationToken, &(XdgActivationTokenData, WlSurface)) -> bool,
|
||||||
|
{
|
||||||
|
self.activation_requests.retain(|k, v| f(k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retain pending tokens
|
||||||
|
///
|
||||||
|
/// You may want to remove super old tokens
|
||||||
|
/// that were never turned into activation request for some reason
|
||||||
|
pub fn retain_pending_tokens<F>(&mut self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&XdgActivationToken, &XdgActivationTokenData) -> bool,
|
||||||
|
{
|
||||||
|
self.pending_tokens.retain(|k, v| f(k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the `UserDataMap` associated with this `XdgActivationState `
|
||||||
|
pub fn user_data(&self) -> &UserDataMap {
|
||||||
|
&self.user_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new `xdg-activation` global.
|
||||||
|
pub fn init_xdg_activation_global<L, Impl>(
|
||||||
|
display: &mut Display,
|
||||||
|
implementation: Impl,
|
||||||
|
logger: L,
|
||||||
|
) -> (
|
||||||
|
Arc<Mutex<XdgActivationState>>,
|
||||||
|
Global<xdg_activation_v1::XdgActivationV1>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
L: Into<Option<::slog::Logger>>,
|
||||||
|
Impl: FnMut(&Mutex<XdgActivationState>, XdgActivationEvent, DispatchData<'_>) + 'static,
|
||||||
|
{
|
||||||
|
let log = crate::slog_or_fallback(logger);
|
||||||
|
|
||||||
|
let implementation = Rc::new(RefCell::new(implementation));
|
||||||
|
|
||||||
|
let activation_state = Arc::new(Mutex::new(XdgActivationState {
|
||||||
|
log: log.new(slog::o!("smithay_module" => "xdg_activation_handler")),
|
||||||
|
user_data: UserDataMap::new(),
|
||||||
|
pending_tokens: HashMap::new(),
|
||||||
|
activation_requests: HashMap::new(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let state = activation_state.clone();
|
||||||
|
let global = display.create_global(
|
||||||
|
1,
|
||||||
|
Filter::new(
|
||||||
|
move |(global, _version): (Main<xdg_activation_v1::XdgActivationV1>, _), _, _| {
|
||||||
|
handlers::implement_activation_global(global, state.clone(), implementation.clone());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
(activation_state, global)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Xdg activation related events
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum XdgActivationEvent {
|
||||||
|
/// Requests surface activation.
|
||||||
|
///
|
||||||
|
/// The compositor may know who requested this by checking the token data
|
||||||
|
/// and might decide not to follow through with the activation if it's considered unwanted.
|
||||||
|
///
|
||||||
|
/// If you consider a request to be unwanted you can use [`XdgActivationState::remove_request`]
|
||||||
|
/// to discard it and don't track it any futher.
|
||||||
|
RequestActivation {
|
||||||
|
/// Token of the request
|
||||||
|
token: XdgActivationToken,
|
||||||
|
/// Data asosiated with the token
|
||||||
|
token_data: XdgActivationTokenData,
|
||||||
|
/// Target surface
|
||||||
|
surface: WlSurface,
|
||||||
|
},
|
||||||
|
/// The activation token just got destroyed
|
||||||
|
///
|
||||||
|
/// In response to that activation request should be canceled.
|
||||||
|
///
|
||||||
|
/// For example if your compostior blinks a window when it requests activation,
|
||||||
|
/// after this request the animation should stop.
|
||||||
|
DestroyActivationRequest {
|
||||||
|
/// Token of the request that just died
|
||||||
|
token: XdgActivationToken,
|
||||||
|
/// Data asosiated with the token
|
||||||
|
token_data: XdgActivationTokenData,
|
||||||
|
/// Target surface
|
||||||
|
surface: WlSurface,
|
||||||
|
},
|
||||||
|
}
|
Loading…
Reference in New Issue