Merge pull request #358 from PolyMeilex/xdg-activate

XDG activation support
This commit is contained in:
Victor Berger 2021-08-01 11:42:42 +02:00 committed by GitHub
commit dbd03567ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 463 additions and 14 deletions

View File

@ -6,6 +6,7 @@
#### Clients & Protocols
- `xdg_activation_v1` support
- `wlr-layer-shell-unstable-v1` support
- Added public api constants for the roles of `wl_shell_surface`, `zxdg_toplevel` and `xdg_toplevel`. See the
`shell::legacy` and `shell::xdg` modules for these constants.

View File

@ -42,7 +42,7 @@ thiserror = "1.0.2"
udev = { version = "0.6", optional = true }
wayland-commons = { 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-sys = { version = "0.29.0", optional = true }
winit = { version = "0.25.0", optional = true }

View File

@ -19,6 +19,7 @@ use smithay::{
seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig},
shm::init_shm_global,
tablet_manager::{init_tablet_manager_global, TabletSeatTrait},
xdg_activation::{init_xdg_activation_global, XdgActivationEvent},
},
};
@ -85,6 +86,29 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
let shell_handles = init_shell::<BackendData>(display.clone(), log.clone());
init_xdg_output_manager(&mut display.borrow_mut(), log.clone());
init_xdg_activation_global(
&mut display.borrow_mut(),
|state, req, mut ddata| {
let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap();
match req {
XdgActivationEvent::RequestActivation {
token,
token_data,
surface,
} => {
if token_data.timestamp.elapsed().as_secs() < 10 {
// Just grant the wish
anvil_state.window_map.borrow_mut().bring_surface_to_top(&surface);
} else {
// Discard the request
state.lock().unwrap().remove_request(&token);
}
}
XdgActivationEvent::DestroyActivationRequest { .. } => {}
}
},
log.clone(),
);
let socket_name = if listen_on_socket {
let socket_name = display

View File

@ -2,7 +2,10 @@ use std::cell::RefCell;
use std::sync::Mutex;
use smithay::{
reexports::{wayland_protocols::xdg_shell::server::xdg_toplevel, wayland_server::protocol::wl_surface},
reexports::{
wayland_protocols::xdg_shell::server::xdg_toplevel,
wayland_server::protocol::wl_surface::{self, WlSurface},
},
utils::{Logical, Point, Rectangle},
wayland::{
compositor::{with_states, with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
@ -297,6 +300,32 @@ impl WindowMap {
None
}
fn bring_nth_window_to_top(&mut self, id: usize) {
let winner = self.windows.remove(id);
// Take activation away from all the windows
for window in self.windows.iter() {
window.toplevel.set_activated(false);
}
// Give activation to our winner
winner.toplevel.set_activated(true);
self.windows.insert(0, winner);
}
pub fn bring_surface_to_top(&mut self, surface: &WlSurface) {
let found = self.windows.iter().enumerate().find(|(_, w)| {
w.toplevel
.get_surface()
.map(|s| s.as_ref().equals(surface.as_ref()))
.unwrap_or(false)
});
if let Some((id, _)) = found {
self.bring_nth_window_to_top(id);
}
}
pub fn get_surface_and_bring_to_top(
&mut self,
point: Point<f64, Logical>,
@ -308,18 +337,8 @@ impl WindowMap {
break;
}
}
if let Some((i, surface)) = found {
let winner = self.windows.remove(i);
// Take activation away from all the windows
for window in self.windows.iter() {
window.toplevel.set_activated(false);
}
// Give activation to our winner
winner.toplevel.set_activated(true);
self.windows.insert(0, winner);
if let Some((id, surface)) = found {
self.bring_nth_window_to_top(id);
Some(surface)
} else {
None

View File

@ -63,6 +63,7 @@ pub mod seat;
pub mod shell;
pub mod shm;
pub mod tablet_manager;
pub mod xdg_activation;
pub mod xdg_foreign;
/// A global [`SerialCounter`] for use in your compositor.

View File

@ -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,
);
}
}

View File

@ -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_requests<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,
},
}