1use 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
80pub const XWAYLAND_SHELL_ROLE: &str = "xwayland_shell";
82
83const VERSION: u32 = 1;
84
85#[derive(Debug, Clone)]
87pub struct XWaylandShellState {
88 global: GlobalId,
89 by_serial: HashMap<u64, WlSurface>,
90}
91
92impl XWaylandShellState {
93 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 pub fn global(&self) -> GlobalId {
111 self.global.clone()
112 }
113
114 pub fn surface_for_serial(&self, serial: u64) -> Option<WlSurface> {
116 self.by_serial.get(&serial).cloned()
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct XWaylandSurfaceUserData {
123 pub(crate) wl_surface: wl_surface::WlSurface,
124}
125
126pub trait XWaylandShellHandler {
128 fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState;
130
131 fn surface_associated(&mut self, xwm: XwmId, wl_surface: wl_surface::WlSurface, surface: X11Surface) {
134 let _ = (xwm, wl_surface, surface);
135 }
136}
137
138#[derive(Debug, Default, Clone, Copy)]
141pub struct XWaylandShellCachedState {
142 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 }
206 xwayland_shell_v1::Request::Destroy => {
207 }
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 xwayland_surface_v1::Request::SetSerial { serial_lo, serial_hi } => {
234 let serial = u64::from(serial_lo) | (u64::from(serial_hi) << 32);
235
236 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 }
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 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 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 XWaylandShellHandler::xwayland_shell_state(state)
302 .by_serial
303 .insert(serial, surface.clone());
304 }
305 }
306 }
307 }
308}
309
310#[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}