1use std::collections::HashMap;
20
21use smithay::desktop::{Window, layer_map_for_output};
22use smithay::input::pointer::Focus;
23use smithay::output::Output;
24use smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::{
25 zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},
26 zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
27};
28use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
29use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
30use smithay::reexports::wayland_server::{
31 Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
32 backend::{ClientId, GlobalId},
33};
34use smithay::utils::Rectangle;
35
36use crate::wayland::frontend::{AppState, FocusTarget, next_serial, window_meta, window_output_id};
37use crate::wayland::wm;
38use crate::wm::{Rect, Request, WindowId};
39
40#[derive(Clone, Copy, Debug)]
42pub struct HandleData {
43 pub window: WindowId,
44}
45
46#[derive(Clone, Debug, Default, PartialEq)]
48struct Published {
49 title: String,
50 app_id: String,
51 maximized: bool,
52 minimized: bool,
53 activated: bool,
54 fullscreen: bool,
55 output: Option<u32>,
56}
57
58#[derive(Default)]
60pub struct ForeignToplevelState {
61 managers: Vec<ZwlrForeignToplevelManagerV1>,
62 handles: HashMap<WindowId, Vec<ZwlrForeignToplevelHandleV1>>,
63 published: HashMap<WindowId, Published>,
64 global: Option<GlobalId>,
65}
66
67impl ForeignToplevelState {
68 pub fn new(dh: &DisplayHandle) -> ForeignToplevelState {
70 let global = dh.create_global::<AppState, ZwlrForeignToplevelManagerV1, _>(3, ());
71 ForeignToplevelState {
72 global: Some(global),
73 ..ForeignToplevelState::default()
74 }
75 }
76
77 pub fn bound(&self) -> bool {
79 !self.managers.is_empty()
80 }
81}
82
83fn mapped(state: &AppState, window: &Window, id: WindowId) -> bool {
88 state.space.elements().any(|w| w == window)
89 || state
90 .wm
91 .as_ref()
92 .is_some_and(|wm| wm.hidden.contains_key(&id))
93}
94
95fn listed(window: &Window) -> bool {
96 window_meta(window).is_some_and(|m| !m.parked.load(std::sync::atomic::Ordering::Relaxed))
97}
98
99fn published_of(state: &AppState, window: &Window, id: WindowId) -> Published {
101 let info = wm::window_info(state, window);
102 let states = wm::states_of(state, id);
103 let focused = match state.wm.as_ref() {
104 Some(w) if wm::active(state) => w.focus == Some(id),
105 _ => state.keyboard_focus == Some(id),
106 };
107 let (maximized, minimized, fullscreen, hidden) = match states {
108 Some(s) => (
109 s.maximized,
110 s.minimized && !s.hidden,
111 s.fullscreen,
112 s.hidden,
113 ),
114 None => (false, false, true, false),
115 };
116 Published {
117 title: info.title,
118 app_id: info.app_id,
119 maximized,
120 minimized,
121 activated: focused,
122 fullscreen,
123 output: (!hidden).then(|| window_output_id(window)),
124 }
125}
126
127fn send_all(
128 state: &AppState,
129 handle: &ZwlrForeignToplevelHandleV1,
130 published: &Published,
131 previous: Option<&Published>,
132) {
133 if previous.is_none_or(|p| p.title != published.title) {
134 handle.title(published.title.clone());
135 }
136 if previous.is_none_or(|p| p.app_id != published.app_id) {
137 handle.app_id(published.app_id.clone());
138 }
139 let states_changed = previous.is_none_or(|p| {
140 (p.maximized, p.minimized, p.activated, p.fullscreen)
141 != (
142 published.maximized,
143 published.minimized,
144 published.activated,
145 published.fullscreen,
146 )
147 });
148 if states_changed {
149 let mut states: Vec<u8> = Vec::new();
150 let mut push = |s: zwlr_foreign_toplevel_handle_v1::State| {
151 states.extend_from_slice(&(s as u32).to_ne_bytes());
152 };
153 if published.maximized {
154 push(zwlr_foreign_toplevel_handle_v1::State::Maximized);
155 }
156 if published.minimized {
157 push(zwlr_foreign_toplevel_handle_v1::State::Minimized);
158 }
159 if published.activated {
160 push(zwlr_foreign_toplevel_handle_v1::State::Activated);
161 }
162 if published.fullscreen && handle.version() >= 2 {
163 push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen);
164 }
165 handle.state(states);
166 }
167 if previous.is_none_or(|p| p.output != published.output) {
168 if let Some(old) = previous.and_then(|p| p.output)
169 && let Some(idx) = state.node_idx_for_id(old)
170 && let Some(client) = handle.client()
171 {
172 for wl_output in state.output_nodes[idx].output.client_outputs(&client) {
173 handle.output_leave(&wl_output);
174 }
175 }
176 if let Some(new) = published.output
177 && let Some(idx) = state.node_idx_for_id(new)
178 && let Some(client) = handle.client()
179 {
180 for wl_output in state.output_nodes[idx].output.client_outputs(&client) {
181 handle.output_enter(&wl_output);
182 }
183 }
184 }
185 handle.done();
186}
187
188pub fn add(state: &mut AppState, window: &Window) {
190 let Some(id) = window_meta(window).map(|m| m.id) else {
191 return;
192 };
193 if !listed(window) || state.foreign_toplevel.handles.contains_key(&id) {
194 return;
195 }
196 let published = published_of(state, window, id);
197 let managers = state.foreign_toplevel.managers.clone();
198 let mut handles = Vec::new();
199 for manager in &managers {
200 let Some(client) = manager.client() else {
201 continue;
202 };
203 let Ok(handle) = client
204 .create_resource::<ZwlrForeignToplevelHandleV1, HandleData, AppState>(
205 &state.dh,
206 manager.version(),
207 HandleData { window: id },
208 )
209 else {
210 continue;
211 };
212 manager.toplevel(&handle);
213 send_all(state, &handle, &published, None);
214 handles.push(handle);
215 }
216 state.foreign_toplevel.handles.insert(id, handles);
217 state.foreign_toplevel.published.insert(id, published);
218}
219
220pub fn update(state: &mut AppState, id: WindowId) {
222 let Some(window) = wm::find_window(state, id) else {
223 return;
224 };
225 if !state.foreign_toplevel.handles.contains_key(&id) {
226 if mapped(state, &window, id) {
227 add(state, &window);
228 }
229 return;
230 }
231 let published = published_of(state, &window, id);
232 let previous = state.foreign_toplevel.published.get(&id).cloned();
233 if previous.as_ref() == Some(&published) {
234 return;
235 }
236 let handles = state
237 .foreign_toplevel
238 .handles
239 .get(&id)
240 .cloned()
241 .unwrap_or_default();
242 for handle in &handles {
243 send_all(state, handle, &published, previous.as_ref());
244 }
245 state.foreign_toplevel.published.insert(id, published);
246}
247
248pub fn update_all(state: &mut AppState) {
250 let ids: Vec<WindowId> = state.foreign_toplevel.handles.keys().copied().collect();
251 for id in ids {
252 update(state, id);
253 }
254}
255
256pub fn output_bound(state: &mut AppState, output: &Output, wl_output: &WlOutput) {
259 let Some(node_id) = state
260 .output_nodes
261 .iter()
262 .find(|n| n.output == *output)
263 .map(|n| n.id)
264 else {
265 return;
266 };
267 let client = wl_output.client().map(|c| c.id());
268 for (id, handles) in &state.foreign_toplevel.handles {
269 if state
270 .foreign_toplevel
271 .published
272 .get(id)
273 .and_then(|p| p.output)
274 != Some(node_id)
275 {
276 continue;
277 }
278 for handle in handles
279 .iter()
280 .filter(|h| h.client().map(|c| c.id()) == client)
281 {
282 handle.output_enter(wl_output);
283 handle.done();
284 }
285 }
286}
287
288pub fn remove(state: &mut AppState, id: WindowId) {
290 if let Some(handles) = state.foreign_toplevel.handles.remove(&id) {
291 for handle in handles {
292 handle.closed();
293 }
294 }
295 state.foreign_toplevel.published.remove(&id);
296}
297
298impl GlobalDispatch<ZwlrForeignToplevelManagerV1, ()> for AppState {
299 fn bind(
300 state: &mut Self,
301 _dh: &DisplayHandle,
302 client: &Client,
303 resource: New<ZwlrForeignToplevelManagerV1>,
304 _global_data: &(),
305 data_init: &mut DataInit<'_, Self>,
306 ) {
307 let manager = data_init.init(resource, ());
308 let ids: Vec<WindowId> = state.foreign_toplevel.handles.keys().copied().collect();
309 for id in ids {
310 let Some(published) = state.foreign_toplevel.published.get(&id).cloned() else {
311 continue;
312 };
313 let Ok(handle) = client
314 .create_resource::<ZwlrForeignToplevelHandleV1, HandleData, AppState>(
315 &state.dh,
316 manager.version(),
317 HandleData { window: id },
318 )
319 else {
320 continue;
321 };
322 manager.toplevel(&handle);
323 send_all(state, &handle, &published, None);
324 if let Some(handles) = state.foreign_toplevel.handles.get_mut(&id) {
325 handles.push(handle);
326 }
327 }
328 state.foreign_toplevel.managers.push(manager);
329 }
330}
331
332impl Dispatch<ZwlrForeignToplevelManagerV1, ()> for AppState {
333 fn request(
334 state: &mut Self,
335 _client: &Client,
336 manager: &ZwlrForeignToplevelManagerV1,
337 request: zwlr_foreign_toplevel_manager_v1::Request,
338 _data: &(),
339 _dh: &DisplayHandle,
340 _data_init: &mut DataInit<'_, Self>,
341 ) {
342 if let zwlr_foreign_toplevel_manager_v1::Request::Stop = request {
343 manager.finished();
344 state.foreign_toplevel.managers.retain(|m| m != manager);
345 }
346 }
347
348 fn destroyed(
349 state: &mut Self,
350 _client: ClientId,
351 manager: &ZwlrForeignToplevelManagerV1,
352 _data: &(),
353 ) {
354 state.foreign_toplevel.managers.retain(|m| m != manager);
355 for handles in state.foreign_toplevel.handles.values_mut() {
356 handles.retain(|h| {
357 h.client().map(|c| c.id()) != manager.client().map(|c| c.id()) || h.is_alive()
358 });
359 }
360 }
361}
362
363impl Dispatch<ZwlrForeignToplevelHandleV1, HandleData> for AppState {
364 fn request(
365 state: &mut Self,
366 _client: &Client,
367 handle: &ZwlrForeignToplevelHandleV1,
368 request: zwlr_foreign_toplevel_handle_v1::Request,
369 data: &HandleData,
370 _dh: &DisplayHandle,
371 _data_init: &mut DataInit<'_, Self>,
372 ) {
373 use zwlr_foreign_toplevel_handle_v1::Request as R;
374 let id = data.window;
375 let policy =
376 |state: &mut AppState, request: Request| -> bool { wm::request(state, id, request) };
377 match request {
378 R::SetMaximized => {
379 policy(state, Request::Maximize);
380 }
381 R::UnsetMaximized => {
382 policy(state, Request::Unmaximize);
383 }
384 R::SetMinimized => {
385 policy(state, Request::Minimize);
386 }
387 R::UnsetMinimized => {
388 policy(state, Request::Unminimize);
389 }
390 R::SetFullscreen { output } => {
391 let id_of = output.and_then(|o| {
392 state
393 .output_nodes
394 .iter()
395 .find(|n| n.output.owns(&o))
396 .map(|n| n.id)
397 });
398 policy(state, Request::Fullscreen(id_of));
399 }
400 R::UnsetFullscreen => {
401 policy(state, Request::Unfullscreen);
402 }
403 R::Activate { seat: _ } => {
404 if !policy(state, Request::Activate) {
405 activate_plain(state, id);
406 }
407 }
408 R::Close => {
409 if !policy(state, Request::Close)
410 && let Some(window) = wm::find_window(state, id)
411 {
412 if let Some(toplevel) = window.toplevel() {
413 toplevel.send_close();
414 }
415 if let Some(surface) = window.x11_surface() {
416 let _ = surface.close();
417 }
418 }
419 }
420 R::SetRectangle {
421 surface,
422 x,
423 y,
424 width,
425 height,
426 } => {
427 let rect = (width > 0 && height > 0)
428 .then(|| layer_surface_origin(state, &surface))
429 .flatten()
430 .map(|(ox, oy)| Rect::new(ox + x, oy + y, width, height));
431 wm::taskbar_rect(state, id, rect);
432 }
433 R::Destroy => {
434 if let Some(handles) = state.foreign_toplevel.handles.get_mut(&id) {
435 handles.retain(|h| h != handle);
436 }
437 }
438 _ => {}
439 }
440 }
441}
442
443fn layer_surface_origin(state: &AppState, surface: &WlSurface) -> Option<(i32, i32)> {
445 for node in &state.output_nodes {
446 let map = layer_map_for_output(&node.output);
447 for layer in map.layers() {
448 if layer.wl_surface() == surface {
449 let geo: Rectangle<i32, smithay::utils::Logical> = map.layer_geometry(layer)?;
450 return Some((node.pos.0 + geo.loc.x, node.pos.1 + geo.loc.y));
451 }
452 }
453 }
454 None
455}
456
457fn activate_plain(state: &mut AppState, id: WindowId) {
459 let Some(window) = wm::find_window(state, id) else {
460 return;
461 };
462 state.space.raise_element(&window, true);
463 if let Some(keyboard) = state.seat.get_keyboard() {
464 keyboard.set_focus(state, Some(FocusTarget::Window(window)), next_serial());
465 }
466 let _ = Focus::Keep;
467 update_all(state);
468}