1use wayland_client::{
2 globals::{BindError, GlobalList},
3 protocol::{wl_seat, wl_surface},
4 Dispatch, Proxy, QueueHandle,
5};
6use wayland_protocols::xdg::activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1};
7
8use crate::{
9 error::GlobalError,
10 globals::{GlobalData, ProvidesBoundGlobal},
11};
12
13/// Minimal implementation of [`RequestDataExt`].
14///
15/// Use a custom type implementing [`RequestDataExt`] to store more data with a token request
16/// e.g. to identify which request produced which token.
17#[derive(Debug, Clone)]
18pub struct RequestData {
19 /// App_id of the application requesting the token, if applicable
20 pub app_id: Option<String>,
21 /// Seat and serial of the window requesting the token, if applicable.
22 ///
23 /// *Warning*: Many compositors will issue invalid tokens for requests without
24 /// recent serials. There is no way to detect this from the client-side.
25 pub seat_and_serial: Option<(wl_seat::WlSeat, u32)>,
26 /// Surface of the window requesting the token, if applicable.
27 ///
28 /// *Warning*: Many compositors will issue invalid tokens for requests from
29 /// unfocused surfaces. There is no way to detect this from the client-side.
30 pub surface: Option<wl_surface::WlSurface>,
31}
32
33/// Data attached to a token request
34pub trait RequestDataExt: Send + Sync {
35 /// App_id of the application requesting the token, if applicable
36 fn app_id(&self) -> Option<&str>;
37 /// Seat and serial of the window requesting the token, if applicable.
38 ///
39 /// *Warning*: Many compositors will issue invalid tokens for requests without
40 /// recent serials. There is no way to detect this from the client-side.
41 fn seat_and_serial(&self) -> Option<(&wl_seat::WlSeat, u32)>;
42 /// Surface of the window requesting the token, if applicable.
43 ///
44 /// *Warning*: Many compositors will issue invalid tokens for requests from
45 /// unfocused surfaces. There is no way to detect this from the client-side.
46 fn surface(&self) -> Option<&wl_surface::WlSurface>;
47}
48
49impl RequestDataExt for RequestData {
50 fn app_id(&self) -> Option<&str> {
51 self.app_id.as_deref()
52 }
53
54 fn seat_and_serial(&self) -> Option<(&wl_seat::WlSeat, u32)> {
55 self.seat_and_serial.as_ref().map(|(seat: &{unknown}, serial: &u32)| (seat, *serial))
56 }
57
58 fn surface(&self) -> Option<&wl_surface::WlSurface> {
59 self.surface.as_ref()
60 }
61}
62
63/// Handler for xdg-activation
64pub trait ActivationHandler: Sized {
65 /// Data type used for requesting activation tokens
66 type RequestData: RequestDataExt;
67 /// A token was issued for a previous request with `data`.
68 fn new_token(&mut self, token: String, data: &Self::RequestData);
69}
70
71/// State for xdg-activation
72#[derive(Debug)]
73pub struct ActivationState {
74 xdg_activation: xdg_activation_v1::XdgActivationV1,
75}
76
77impl ActivationState {
78 /// Bind the `xdg-activation` global
79 pub fn bind<State>(
80 globals: &GlobalList,
81 qh: &QueueHandle<State>,
82 ) -> Result<ActivationState, BindError>
83 where
84 State: Dispatch<xdg_activation_v1::XdgActivationV1, GlobalData, State> + 'static,
85 {
86 let xdg_activation = globals.bind(qh, 1..=1, GlobalData)?;
87 Ok(ActivationState { xdg_activation })
88 }
89
90 /// Activate a surface with the provided token.
91 pub fn activate<D>(&self, surface: &wl_surface::WlSurface, token: String) {
92 self.xdg_activation.activate(token, surface)
93 }
94
95 /// Request a token for surface activation.
96 ///
97 /// To attach custom data to the request implement [`RequestDataExt`] on a custom type
98 /// and use [`Self::request_token_with_data`] instead.
99 pub fn request_token<D>(&self, qh: &QueueHandle<D>, request_data: RequestData)
100 where
101 D: ActivationHandler<RequestData = RequestData>,
102 D: Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, RequestData> + 'static,
103 {
104 Self::request_token_with_data::<D, RequestData>(self, qh, request_data)
105 }
106
107 /// Request a token for surface activation with user data.
108 ///
109 /// To use this method you need to provide [`delegate_activation`] with your custom type.
110 /// E.g. `delegate_activation!(SimpleWindow, MyRequestData);`
111 pub fn request_token_with_data<D, R>(&self, qh: &QueueHandle<D>, request_data: R)
112 where
113 D: ActivationHandler<RequestData = R>,
114 D: Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, R> + 'static,
115 R: RequestDataExt + 'static,
116 {
117 let token = self.xdg_activation.get_activation_token(qh, request_data);
118 let data = token.data::<R>().unwrap();
119 if let Some(app_id) = data.app_id() {
120 token.set_app_id(String::from(app_id));
121 }
122 if let Some((seat, serial)) = data.seat_and_serial() {
123 token.set_serial(serial, seat);
124 }
125 if let Some(surface) = data.surface() {
126 token.set_surface(surface);
127 }
128 token.commit();
129 }
130}
131
132impl<D> Dispatch<xdg_activation_v1::XdgActivationV1, GlobalData, D> for ActivationState
133where
134 D: Dispatch<xdg_activation_v1::XdgActivationV1, GlobalData> + ActivationHandler,
135{
136 fn event(
137 _: &mut D,
138 _: &xdg_activation_v1::XdgActivationV1,
139 _: <xdg_activation_v1::XdgActivationV1 as Proxy>::Event,
140 _: &GlobalData,
141 _: &wayland_client::Connection,
142 _: &QueueHandle<D>,
143 ) {
144 unreachable!("xdg_activation_v1 has no events");
145 }
146}
147
148impl ProvidesBoundGlobal<xdg_activation_v1::XdgActivationV1, 1> for ActivationState {
149 fn bound_global(&self) -> Result<xdg_activation_v1::XdgActivationV1, GlobalError> {
150 Ok(self.xdg_activation.clone())
151 }
152}
153
154impl<D, R> Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, R, D> for ActivationState
155where
156 D: Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, R>
157 + ActivationHandler<RequestData = R>,
158 R: RequestDataExt,
159{
160 fn event(
161 state: &mut D,
162 _proxy: &xdg_activation_token_v1::XdgActivationTokenV1,
163 event: <xdg_activation_token_v1::XdgActivationTokenV1 as Proxy>::Event,
164 data: &R,
165 _conn: &wayland_client::Connection,
166 _qhandle: &QueueHandle<D>,
167 ) {
168 if let xdg_activation_token_v1::Event::Done { token: String } = event {
169 state.new_token(token, data);
170 }
171 }
172}
173
174#[macro_export]
175macro_rules! delegate_activation {
176 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
177 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
178 [
179 $crate::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1: $crate::globals::GlobalData
180 ] => $crate::activation::ActivationState
181 );
182 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
183 [
184 $crate::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::XdgActivationTokenV1: $crate::activation::RequestData
185 ] => $crate::activation::ActivationState
186 );
187 };
188 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, $data: ty) => {
189 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
190 [
191 $crate::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1: $crate::globals::GlobalData
192 ] => $crate::activation::ActivationState
193 );
194 $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
195 [
196 $crate::reexports::protocols::xdg::activation::v1::client::xdg_activation_token_v1::XdgActivationTokenV1: $data
197 ] => $crate::activation::ActivationState
198 );
199 };
200}
201