Merge pull request #358 from PolyMeilex/xdg-activate
XDG activation support
This commit is contained in:
commit
dbd03567ff
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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_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,
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue