chore: refresco desde el monorepo (pin tawasuyu.git v0.2.0)
1//! Helpers for handling the xwayland shell protocol
2//!
3//! # Example
4//!
5//! ```
6//! use smithay::wayland::xwayland_shell::{
7//! XWaylandShellHandler,
8//! XWaylandShellState,
9//! };
10//! use smithay::delegate_xwayland_shell;
11//! use smithay::xwayland::xwm::{XwmId, X11Surface};
12//!
13//! # struct State;
14//! # let mut display = wayland_server::Display::<State>::new().unwrap();
15//! // Create the global:
16//! XWaylandShellState::new::<State>(
17//! &display.handle(),
18//! );
19//! #
20//! # use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
21//!
22//! impl XWaylandShellHandler for State {
23//! fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
24//! // Return a reference to the state we created earlier.
25//! todo!()
26//! }
27//!
28//! fn surface_associated(&mut self, _xwm_id: XwmId, _surface: WlSurface, _window: X11Surface) {
29//! // Called when XWayland has associated an X11 window with a wl_surface.
30//! todo!()
31//! }
32//! }
33//!
34//! # use smithay::wayland::selection::SelectionTarget;
35//! # use smithay::xwayland::{XWayland, XWaylandEvent, X11Wm, XwmHandler, xwm::{ResizeEdge, Reorder}};
36//! # use smithay::utils::{Rectangle, Logical};
37//! # use std::os::unix::io::OwnedFd;
38//! # use std::process::Stdio;
39//!
40//! impl XwmHandler for State {
41//! fn xwm_state(&mut self, xwm: XwmId) -> &mut X11Wm {
42//! // ...
43//! # unreachable!()
44//! }
45//! fn new_window(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
46//! fn new_override_redirect_window(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
47//! fn map_window_request(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
48//! fn mapped_override_redirect_window(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
49//! fn unmapped_window(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
50//! fn destroyed_window(&mut self, xwm: XwmId, window: X11Surface) { /* ... */ }
51//! fn configure_request(&mut self, xwm: XwmId, window: X11Surface, x: Option<i32>, y: Option<i32>, w: Option<u32>, h: Option<u32>, reorder: Option<Reorder>) { /* ... */ }
52//! fn configure_notify(&mut self, xwm: XwmId, window: X11Surface, geometry: Rectangle<i32, Logical>, above: Option<u32>) { /* ... */ }
53//! fn resize_request(&mut self, xwm: XwmId, window: X11Surface, button: u32, resize_edge: ResizeEdge) { /* ... */ }
54//! fn move_request(&mut self, xwm: XwmId, window: X11Surface, button: u32) { /* ... */ }
55//! fn send_selection(&mut self, xwm: XwmId, selection: SelectionTarget, mime_type: String, fd: OwnedFd) { /* ... */ }
56//! }
57//!
58//! // implement Dispatch for your state.
59//! delegate_xwayland_shell!(State);
60//! ```
61
62use std::collections::HashMap;
63
64use tracing::{debug, warn};
65use wayland_protocols::xwayland::shell::v1::server::{
66 xwayland_shell_v1::{self, XwaylandShellV1},
67 xwayland_surface_v1::{self, XwaylandSurfaceV1},
68};
69use wayland_server::{
70 backend::GlobalId,
71 protocol::wl_surface::{self, WlSurface},
72 Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
73};
74
75use crate::{
76 wayland::compositor,
77 xwayland::{xwm::XwmId, X11Surface, XWaylandClientData, XwmHandler},
78};
79
80/// The role for an xwayland-associated surface.
81pub const XWAYLAND_SHELL_ROLE: &str = "xwayland_shell";
82
83const VERSION: u32 = 1;
84
85/// The global for the xwayland shell protocol.
86#[derive(Debug, Clone)]
87pub struct XWaylandShellState {
88 global: GlobalId,
89 by_serial: HashMap<u64, WlSurface>,
90}
91
92impl XWaylandShellState {
93 /// Registers a new [XwaylandShellV1] global. Only XWayland clients will be
94 /// able to bind it.
95 pub fn new<D>(display: &DisplayHandle) -> Self
96 where
97 D: GlobalDispatch<XwaylandShellV1, ()>,
98 D: Dispatch<XwaylandShellV1, ()>,
99 D: Dispatch<XwaylandSurfaceV1, XWaylandSurfaceUserData>,
100 D: 'static,
101 {
102 let global = display.create_global::<D, XwaylandShellV1, _>(VERSION, ());
103 Self {
104 global,
105 by_serial: HashMap::new(),
106 }
107 }
108
109 /// Retrieve a handle for the [XwaylandShellV1] global.
110 pub fn global(&self) -> GlobalId {
111 self.global.clone()
112 }
113
114 /// Retrieves the surface for a given serial.
115 pub fn surface_for_serial(&self, serial: u64) -> Option<WlSurface> {
116 self.by_serial.get(&serial).cloned()
117 }
118}
119
120/// Userdata for an xwayland shell surface.
121#[derive(Debug, Clone)]
122pub struct XWaylandSurfaceUserData {
123 pub(crate) wl_surface: wl_surface::WlSurface,
124}
125
126/// Handler for the xwayland shell protocol.
127pub trait XWaylandShellHandler {
128 /// Retrieves the global state.
129 fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState;
130
131 /// An X11 window has been associated with a wayland surface. This doesn't
132 /// take effect until the wl_surface is committed.
133 fn surface_associated(&mut self, xwm: XwmId, wl_surface: wl_surface::WlSurface, surface: X11Surface) {
134 let _ = (xwm, wl_surface, surface);
135 }
136}
137
138/// Represents a pending X11 serial, used to associate X11 windows with wayland
139/// surfaces.
140#[derive(Debug, Default, Clone, Copy)]
141pub struct XWaylandShellCachedState {
142 /// The serial of the matching X11 window.
143 pub serial: Option<u64>,
144}
145
146impl compositor::Cacheable for XWaylandShellCachedState {
147 fn commit(&mut self, _dh: &DisplayHandle) -> Self {
148 *self
149 }
150
151 fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
152 *into = self;
153 }
154}
155
156impl<D> GlobalDispatch<XwaylandShellV1, (), D> for XWaylandShellState
157where
158 D: GlobalDispatch<XwaylandShellV1, ()>,
159 D: Dispatch<XwaylandShellV1, ()>,
160 D: 'static,
161{
162 fn bind(
163 _state: &mut D,
164 _handle: &DisplayHandle,
165 _client: &Client,
166 resource: New<XwaylandShellV1>,
167 _global_data: &(),
168 data_init: &mut DataInit<'_, D>,
169 ) {
170 data_init.init(resource, ());
171 }
172
173 fn can_view(client: Client, _global_data: &()) -> bool {
174 client.get_data::<XWaylandClientData>().is_some()
175 }
176}
177
178impl<D> Dispatch<XwaylandShellV1, (), D> for XWaylandShellState
179where
180 D: Dispatch<XwaylandShellV1, ()>,
181 D: Dispatch<XwaylandSurfaceV1, XWaylandSurfaceUserData>,
182 D: XWaylandShellHandler + XwmHandler,
183 D: 'static,
184{
185 fn request(
186 _state: &mut D,
187 _client: &Client,
188 resource: &XwaylandShellV1,
189 request: <XwaylandShellV1 as Resource>::Request,
190 _data: &(),
191 _dhandle: &DisplayHandle,
192 data_init: &mut DataInit<'_, D>,
193 ) {
194 match request {
195 xwayland_shell_v1::Request::GetXwaylandSurface { id, surface } => {
196 if compositor::give_role(&surface, XWAYLAND_SHELL_ROLE).is_err() {
197 resource.post_error(xwayland_shell_v1::Error::Role, "Surface already has a role.");
198 return;
199 }
200
201 compositor::add_pre_commit_hook::<D, _>(&surface, serial_commit_hook);
202
203 data_init.init(id, XWaylandSurfaceUserData { wl_surface: surface });
204 // We call the handler callback once the serial is set.
205 }
206 xwayland_shell_v1::Request::Destroy => {
207 // The child objects created via this interface are unaffected.
208 }
209 _ => unreachable!(),
210 }
211 }
212}
213
214impl<D> Dispatch<XwaylandSurfaceV1, XWaylandSurfaceUserData, D> for XWaylandShellState
215where
216 D: Dispatch<XwaylandSurfaceV1, XWaylandSurfaceUserData>,
217 D: XWaylandShellHandler,
218 D: 'static,
219 D: XwmHandler,
220{
221 fn request(
222 _state: &mut D,
223 _client: &Client,
224 _resource: &XwaylandSurfaceV1,
225 request: <XwaylandSurfaceV1 as Resource>::Request,
226 data: &XWaylandSurfaceUserData,
227 _dhandle: &DisplayHandle,
228 _data_init: &mut DataInit<'_, D>,
229 ) {
230 match request {
231 // In order to match a WlSurface to an X11 window, we need to match a Serial sent both to the X11 window and the WlSurface.
232 // This can happen in any order so we need to store the Serial until we have both.
233 xwayland_surface_v1::Request::SetSerial { serial_lo, serial_hi } => {
234 let serial = u64::from(serial_lo) | (u64::from(serial_hi) << 32);
235
236 // Set the serial on the pending state of surface
237 compositor::with_states(&data.wl_surface, |states| {
238 states
239 .cached_state
240 .get::<XWaylandShellCachedState>()
241 .pending()
242 .serial = Some(serial);
243 });
244 }
245 xwayland_surface_v1::Request::Destroy => {
246 // Any already existing associations are unaffected.
247 }
248 _ => unreachable!(),
249 }
250 }
251}
252
253fn serial_commit_hook<D: XWaylandShellHandler + XwmHandler + 'static>(
254 state: &mut D,
255 _dh: &DisplayHandle,
256 surface: &WlSurface,
257) {
258 if let Some(serial) = compositor::with_states(surface, |states| {
259 states
260 .cached_state
261 .get::<XWaylandShellCachedState>()
262 .pending()
263 .serial
264 }) {
265 if let Some(client) = surface.client() {
266 // We only care about surfaces created by XWayland.
267 if let Some(xwm_id) = client
268 .get_data::<XWaylandClientData>()
269 .and_then(|data| data.user_data().get::<XwmId>())
270 {
271 let xwm = XwmHandler::xwm_state(state, *xwm_id);
272
273 // This handles the case that the serial was set on the X11
274 // window before surface. To handle the other case, we look for
275 // a matching surface when the WL_SURFACE_SERIAL atom is sent.
276 if let Some(window) = xwm.unpaired_surfaces.remove(&serial) {
277 if let Some(xsurface) = xwm
278 .windows
279 .iter()
280 .find(|x| x.window_id() == window || x.mapped_window_id() == Some(window))
281 .cloned()
282 {
283 debug!(
284 window = xsurface.window_id(),
285 wl_surface = ?surface.id().protocol_id(),
286 "associated X11 window to wl_surface in commit hook",
287 );
288
289 xsurface.state.lock().unwrap().wl_surface = Some(surface.clone());
290
291 XWaylandShellHandler::surface_associated(state, *xwm_id, surface.clone(), xsurface);
292 } else {
293 warn!(
294 window,
295 wl_surface = ?surface.id().protocol_id(),
296 "Unknown X11 window associated to wl_surface in commit hook"
297 )
298 }
299 } else {
300 // this is necessary for the atom-handler to look up the matching surface
301 XWaylandShellHandler::xwayland_shell_state(state)
302 .by_serial
303 .insert(serial, surface.clone());
304 }
305 }
306 }
307 }
308}
309
310/// Macro to delegate implementation of the xwayland keyboard grab protocol
311#[macro_export]
312macro_rules! delegate_xwayland_shell {
313 ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
314 $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
315 $crate::reexports::wayland_protocols::xwayland::shell::v1::server::xwayland_shell_v1::XwaylandShellV1: ()
316 ] => $crate::wayland::xwayland_shell::XWaylandShellState);
317 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
318 $crate::reexports::wayland_protocols::xwayland::shell::v1::server::xwayland_shell_v1::XwaylandShellV1: ()
319 ] => $crate::wayland::xwayland_shell::XWaylandShellState);
320 $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
321 $crate::reexports::wayland_protocols::xwayland::shell::v1::server::xwayland_surface_v1::XwaylandSurfaceV1: $crate::wayland::xwayland_shell::XWaylandSurfaceUserData
322 ] => $crate::wayland::xwayland_shell::XWaylandShellState);
323 };
324}