| 1 | use wayland_client::{ |
| 2 | globals::{BindError, GlobalList}, |
| 3 | protocol::{wl_seat, wl_surface}, |
| 4 | Dispatch, Proxy, QueueHandle, |
| 5 | }; |
| 6 | use wayland_protocols::xdg::activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1}; |
| 7 | |
| 8 | use 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)] |
| 18 | pub 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 |
| 34 | pub 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 | |
| 49 | impl 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 |
| 64 | pub 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)] |
| 73 | pub struct ActivationState { |
| 74 | xdg_activation: xdg_activation_v1::XdgActivationV1, |
| 75 | } |
| 76 | |
| 77 | impl 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 | |
| 132 | impl<D> Dispatch<xdg_activation_v1::XdgActivationV1, GlobalData, D> for ActivationState |
| 133 | where |
| 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 | |
| 148 | impl 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 | |
| 154 | impl<D, R> Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, R, D> for ActivationState |
| 155 | where |
| 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 ] |
| 175 | macro_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 | |